Compare commits
95 Commits
search
...
7fb3c26633
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fb3c26633 | |||
| 21ca8e5e90 | |||
| f719cedc37 | |||
| 22e70f7164 | |||
| a573a0b765 | |||
| ca1abf951f | |||
| 2efa3c4a42 | |||
| f3fd177235 | |||
| 092f98c17a | |||
| eec6174562 | |||
| 81efcea0e5 | |||
| 7ac85b5b1e | |||
| 1b35ca32ac | |||
| 97c989e465 | |||
| b43b81cc02 | |||
| c061e3e1b2 | |||
| cd61c47e35 | |||
| 4f9b3669a2 | |||
| 7164140c15 | |||
| d41973f1a8 | |||
| 8806f2862d | |||
| 36989c76ee | |||
| 13aef5b3c0 | |||
| b8f9f93537 | |||
| 1c93d28441 | |||
| 2ce351f7ef | |||
| 826dde759f | |||
| d1aa966737 | |||
| 4d24e7095b | |||
| 6c1940f5d2 | |||
| 30c395151d | |||
| d72e64c7f9 | |||
| abdc8e5056 | |||
| bc754c7a7d | |||
| 84ad8d43b5 | |||
| e440630497 | |||
| 71689fce79 | |||
| e7185ff460 | |||
| 18f493675a | |||
| 0c004b2e85 | |||
| c214d9ee37 | |||
| 72259c16a9 | |||
| 66b656ada5 | |||
| 46e4ca3948 | |||
| f2bf2fb025 | |||
| 50d48deec1 | |||
| 3c905aa1d7 | |||
| d5f478b3c6 | |||
| 0f96b93532 | |||
| 5449affbc8 | |||
| 2cf19900db | |||
| efe5d08430 | |||
| 994e9ed8d2 | |||
| 72af5cb7f0 | |||
| 308ee34025 | |||
| 9839befdf1 | |||
| d688df6c92 | |||
| 24eef25984 | |||
| 77ae0be899 | |||
| ca939da28e | |||
| 5d0920cb6d | |||
| d1ea7b5364 | |||
| ebdb986e2a | |||
| 4bb6695c2e | |||
| a6c5a42c1d | |||
| c44c718d06 | |||
| 5e4097453b | |||
| bfeae89ab5 | |||
| 755364c0df | |||
| dcb1e9a736 | |||
| c8543961af | |||
| cbad3b76eb | |||
| b3ff2fe135 | |||
| 6a6f25547e | |||
| 43dfee56cc | |||
| 6f9a2ce092 | |||
| 06014eade9 | |||
| 6f92a50c83 | |||
| 60eb50737d | |||
| 250746e686 | |||
| 3bac151b08 | |||
| c61d9ccb99 | |||
| 56ad03b833 | |||
| 2f9e6278ba | |||
| 17e0fbc6fb | |||
| 7ee7feadf3 | |||
| b36ea558a3 | |||
| 17d6a75465 | |||
| d5541bc985 | |||
| 98a46e9fd4 | |||
| 2e3074df00 | |||
| b3dc3e690b | |||
| b1943ede2f | |||
| 0467e4e12f | |||
| 8164624cee |
12
.gitmodules
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
[submodule "code/aoc-2020"]
|
||||
path = code/aoc-2020
|
||||
url = https://dev.danilafe.com/Advent-of-Code/AdventOfCode-2020.git
|
||||
[submodule "code/libabacus"]
|
||||
path = code/libabacus
|
||||
url = https://dev.danilafe.com/Experiments/libabacus
|
||||
[submodule "themes/vanilla"]
|
||||
path = themes/vanilla
|
||||
url = https://dev.danilafe.com/Web-Projects/vanilla-hugo.git
|
||||
[submodule "code/server-config"]
|
||||
path = code/server-config
|
||||
url = https://dev.danilafe.com/Nix-Configs/server-config
|
||||
82
analyze.rb
Normal file
@@ -0,0 +1,82 @@
|
||||
require "pathname"
|
||||
require "set"
|
||||
require "json"
|
||||
|
||||
def resolve_path(bp, p)
|
||||
path = nil
|
||||
if bp.start_with? "."
|
||||
path = Pathname.new(File.join(bp, p)).cleanpath.to_s
|
||||
elsif p.start_with? "blog/"
|
||||
path = File.join("content", p)
|
||||
else
|
||||
path = File.join("content", "blog", p)
|
||||
end
|
||||
if File.directory? path
|
||||
path = File.join(path, "index.md")
|
||||
elsif !path.end_with? ".md"
|
||||
path += ".md"
|
||||
end
|
||||
path.gsub("blog/blog/", "blog/")
|
||||
end
|
||||
|
||||
files = Set.new
|
||||
refs = {}
|
||||
ARGF.each do |file|
|
||||
file = file.chomp
|
||||
files << file
|
||||
arr = refs[file] || (refs[file] = [])
|
||||
File.open(file).read.scan(/< relref "([^"]+)" >/) do |ref|
|
||||
ref = resolve_path(File.dirname(file), ref[0])
|
||||
arr << ref
|
||||
files << ref
|
||||
end
|
||||
arr.uniq!
|
||||
end
|
||||
|
||||
data = {}
|
||||
id = 0
|
||||
files.each do |file|
|
||||
id += 1
|
||||
name = file
|
||||
tags = []
|
||||
group = 1
|
||||
value = File.size(file)
|
||||
url = file.gsub(/^content/, "https://danilafe.com").delete_suffix("/index.md").delete_suffix(".md")
|
||||
File.readlines(file).each do |l|
|
||||
if l =~ /^title: (.+)$/
|
||||
name = $~[1].delete_prefix('"').delete_suffix('"')
|
||||
elsif l =~ /^tags: (.+)$/
|
||||
tags = $~[1].delete_prefix("[").delete_suffix("]").split(/,\s?/).map { |it| it.gsub('"', '') }
|
||||
if tags.include? "Compilers"
|
||||
group = 2
|
||||
elsif tags.include? "Coq"
|
||||
group = 3
|
||||
elsif tags.include? "Programming Languages"
|
||||
group = 4
|
||||
elsif tags.include? "Haskell"
|
||||
group = 5
|
||||
elsif tags.include? "Crystal"
|
||||
group = 6
|
||||
end
|
||||
end
|
||||
end
|
||||
data[file] = { :id => id, :label => name, :group => group, :tags => tags, :url => url, :value => value }
|
||||
end
|
||||
|
||||
edges = []
|
||||
files.each do |file1|
|
||||
# files.each do |file2|
|
||||
# next if file1 == file2
|
||||
# next unless data[file1][:tags].any? { |t| data[file2][:tags].include? t }
|
||||
# edges << { :from => data[file1][:id], :to => data[file2][:id] }
|
||||
# end
|
||||
next unless frefs = refs[file1]
|
||||
frefs.each do |ref|
|
||||
edges << { :from => data[file1][:id], :to => data[ref][:id] }
|
||||
end
|
||||
end
|
||||
edges.uniq
|
||||
# edges.filter! { |e| e[:from] < e[:to] }
|
||||
|
||||
puts ("const nodes = " + JSON.pretty_unparse(data.values) + ";")
|
||||
puts ("const edges = " + JSON.pretty_unparse(edges) + ";")
|
||||
56
assets/scss/donate.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
@import "../../themes/vanilla/assets/scss/mixins.scss";
|
||||
|
||||
.donation-methods {
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-spacing: 0 0.5rem;
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&:first-child {
|
||||
@include bordered-block;
|
||||
text-align: right;
|
||||
border-right: none;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5rem;
|
||||
|
||||
@include below-container-width {
|
||||
@include bordered-block;
|
||||
text-align: center;
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
@include bordered-block;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
@include below-container-width {
|
||||
@include bordered-block;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
@include below-container-width {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
}
|
||||
1
code/aoc-2020
Submodule
@@ -1,102 +0,0 @@
|
||||
Require Import Coq.Lists.List.
|
||||
Require Import Omega.
|
||||
|
||||
Definition has_pair (t : nat) (is : list nat) : Prop :=
|
||||
exists n1 n2 : nat, n1 <> n2 /\ In n1 is /\ In n2 is /\ n1 + n2 = t.
|
||||
|
||||
Fixpoint find_matching (is : list nat) (total : nat) (x : nat) : option nat :=
|
||||
match is with
|
||||
| nil => None
|
||||
| cons y ys =>
|
||||
if Nat.eqb (x + y) total
|
||||
then Some y
|
||||
else find_matching ys total x
|
||||
end.
|
||||
|
||||
Fixpoint find_sum (is : list nat) (total : nat) : option (nat * nat) :=
|
||||
match is with
|
||||
| nil => None
|
||||
| cons x xs =>
|
||||
match find_matching xs total x with
|
||||
| None => find_sum xs total (* Was buggy! *)
|
||||
| Some y => Some (x, y)
|
||||
end
|
||||
end.
|
||||
|
||||
Lemma find_matching_correct : forall is k x y,
|
||||
find_matching is k x = Some y -> x + y = k.
|
||||
Proof.
|
||||
intros is. induction is;
|
||||
intros k x y Hev.
|
||||
- simpl in Hev. inversion Hev.
|
||||
- simpl in Hev. destruct (Nat.eqb (x+a) k) eqn:Heq.
|
||||
+ injection Hev as H; subst.
|
||||
apply EqNat.beq_nat_eq. auto.
|
||||
+ apply IHis. assumption.
|
||||
Qed.
|
||||
|
||||
Lemma find_matching_skip : forall k x y i is,
|
||||
find_matching is k x = Some y -> find_matching (cons i is) k x = Some y.
|
||||
Proof.
|
||||
intros k x y i is Hsmall.
|
||||
simpl. destruct (Nat.eqb (x+i) k) eqn:Heq.
|
||||
- apply find_matching_correct in Hsmall.
|
||||
symmetry in Heq. apply EqNat.beq_nat_eq in Heq.
|
||||
assert (i = y). { omega. } rewrite H. reflexivity.
|
||||
- assumption.
|
||||
Qed.
|
||||
|
||||
Lemma find_matching_works : forall is k x y, In y is /\ x + y = k ->
|
||||
find_matching is k x = Some y.
|
||||
Proof.
|
||||
intros is. induction is;
|
||||
intros k x y [Hin Heq].
|
||||
- inversion Hin.
|
||||
- inversion Hin.
|
||||
+ subst a. simpl. Search Nat.eqb.
|
||||
destruct (Nat.eqb_spec (x+y) k).
|
||||
* reflexivity.
|
||||
* exfalso. apply n. assumption.
|
||||
+ apply find_matching_skip. apply IHis.
|
||||
split; assumption.
|
||||
Qed.
|
||||
|
||||
Theorem find_sum_works :
|
||||
forall k is, has_pair k is ->
|
||||
exists x y, (find_sum is k = Some (x, y) /\ x + y = k).
|
||||
Proof.
|
||||
intros k is. generalize dependent k.
|
||||
induction is; intros k [x' [y' [Hneq [Hinx [Hiny Hsum]]]]].
|
||||
- (* is is empty. But x is in is! *)
|
||||
inversion Hinx.
|
||||
- (* is is not empty. *)
|
||||
inversion Hinx.
|
||||
+ (* x is the first element. *)
|
||||
subst a. inversion Hiny.
|
||||
* (* y is also the first element; but this is impossible! *)
|
||||
exfalso. apply Hneq. apply H.
|
||||
* (* y is somewhere in the rest of the list.
|
||||
We've proven that we will find it! *)
|
||||
exists x'. simpl.
|
||||
erewrite find_matching_works.
|
||||
{ exists y'. split. reflexivity. assumption. }
|
||||
{ split; assumption. }
|
||||
+ (* x is not the first element. *)
|
||||
inversion Hiny.
|
||||
* (* y is the first element,
|
||||
so x is somewhere in the rest of the list.
|
||||
Again, we've proven that we can find it. *)
|
||||
subst a. exists y'. simpl.
|
||||
erewrite find_matching_works.
|
||||
{ exists x'. split. reflexivity. rewrite plus_comm. assumption. }
|
||||
{ split. assumption. rewrite plus_comm. assumption. }
|
||||
* (* y is not the first element, either.
|
||||
Of course, there could be another matching pair
|
||||
starting with a. Otherwise, the inductive hypothesis applies. *)
|
||||
simpl. destruct (find_matching is k a) eqn:Hf.
|
||||
{ exists a. exists n. split.
|
||||
reflexivity.
|
||||
apply find_matching_correct with is. assumption. }
|
||||
{ apply IHis. unfold has_pair. exists x'. exists y'.
|
||||
repeat split; assumption. }
|
||||
Qed.
|
||||
179
code/dawn/Dawn.v
Normal file
@@ -0,0 +1,179 @@
|
||||
Require Import Coq.Lists.List.
|
||||
From Ltac2 Require Import Ltac2.
|
||||
|
||||
Inductive intrinsic :=
|
||||
| swap
|
||||
| clone
|
||||
| drop
|
||||
| quote
|
||||
| compose
|
||||
| apply.
|
||||
|
||||
Inductive expr :=
|
||||
| e_int (i : intrinsic)
|
||||
| e_quote (e : expr)
|
||||
| e_comp (e1 e2 : expr).
|
||||
|
||||
Definition e_compose (e : expr) (es : list expr) := fold_left e_comp es e.
|
||||
|
||||
Inductive IsValue : expr -> Prop :=
|
||||
| Val_quote : forall {e : expr}, IsValue (e_quote e).
|
||||
|
||||
Definition value := { v : expr & IsValue v }.
|
||||
Definition value_stack := list value.
|
||||
|
||||
Definition v_quote (e : expr) := existT IsValue (e_quote e) Val_quote.
|
||||
|
||||
Inductive Sem_int : value_stack -> intrinsic -> value_stack -> Prop :=
|
||||
| Sem_swap : forall (v v' : value) (vs : value_stack), Sem_int (v' :: v :: vs) swap (v :: v' :: vs)
|
||||
| Sem_clone : forall (v : value) (vs : value_stack), Sem_int (v :: vs) clone (v :: v :: vs)
|
||||
| Sem_drop : forall (v : value) (vs : value_stack), Sem_int (v :: vs) drop vs
|
||||
| Sem_quote : forall (v : value) (vs : value_stack), Sem_int (v :: vs) quote ((v_quote (projT1 v)) :: vs)
|
||||
| Sem_compose : forall (e e' : expr) (vs : value_stack), Sem_int (v_quote e' :: v_quote e :: vs) compose (v_quote (e_comp e e') :: vs)
|
||||
| Sem_apply : forall (e : expr) (vs vs': value_stack), Sem_expr vs e vs' -> Sem_int (v_quote e :: vs) apply vs'
|
||||
|
||||
with Sem_expr : value_stack -> expr -> value_stack -> Prop :=
|
||||
| Sem_e_int : forall (i : intrinsic) (vs vs' : value_stack), Sem_int vs i vs' -> Sem_expr vs (e_int i) vs'
|
||||
| Sem_e_quote : forall (e : expr) (vs : value_stack), Sem_expr vs (e_quote e) (v_quote e :: vs)
|
||||
| Sem_e_comp : forall (e1 e2 : expr) (vs1 vs2 vs3 : value_stack),
|
||||
Sem_expr vs1 e1 vs2 -> Sem_expr vs2 e2 vs3 -> Sem_expr vs1 (e_comp e1 e2) vs3.
|
||||
|
||||
Definition false : expr := e_quote (e_int drop).
|
||||
Definition false_v : value := v_quote (e_int drop).
|
||||
|
||||
Definition true : expr := e_quote (e_comp (e_int swap) (e_int drop)).
|
||||
Definition true_v : value := v_quote (e_comp (e_int swap) (e_int drop)).
|
||||
|
||||
Theorem false_correct : forall (v v' : value) (vs : value_stack), Sem_expr (v' :: v :: vs) (e_comp false (e_int apply)) (v :: vs).
|
||||
Proof.
|
||||
intros v v' vs.
|
||||
eapply Sem_e_comp.
|
||||
- apply Sem_e_quote.
|
||||
- apply Sem_e_int. apply Sem_apply. apply Sem_e_int. apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Theorem true_correct : forall (v v' : value) (vs : value_stack), Sem_expr (v' :: v :: vs) (e_comp true (e_int apply)) (v' :: vs).
|
||||
Proof.
|
||||
intros v v' vs.
|
||||
eapply Sem_e_comp.
|
||||
- apply Sem_e_quote.
|
||||
- apply Sem_e_int. apply Sem_apply. eapply Sem_e_comp.
|
||||
* apply Sem_e_int. apply Sem_swap.
|
||||
* apply Sem_e_int. apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Definition or : expr := e_comp (e_int clone) (e_int apply).
|
||||
|
||||
Theorem or_false_v : forall (v : value) (vs : value_stack), Sem_expr (false_v :: v :: vs) or (v :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v vs.
|
||||
eapply Sem_e_comp...
|
||||
- apply Sem_clone.
|
||||
- apply Sem_apply... apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Theorem or_true : forall (v : value) (vs : value_stack), Sem_expr (true_v :: v :: vs) or (true_v :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v vs.
|
||||
eapply Sem_e_comp...
|
||||
- apply Sem_clone...
|
||||
- apply Sem_apply. eapply Sem_e_comp...
|
||||
* apply Sem_swap.
|
||||
* apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Definition or_false_false := or_false_v false_v.
|
||||
Definition or_false_true := or_false_v true_v.
|
||||
Definition or_true_false := or_true false_v.
|
||||
Definition or_true_true := or_true true_v.
|
||||
|
||||
Fixpoint quote_n (n : nat) :=
|
||||
match n with
|
||||
| O => e_int quote
|
||||
| S n' => e_compose (quote_n n') (e_int swap :: e_int quote :: e_int swap :: e_int compose :: nil)
|
||||
end.
|
||||
|
||||
Theorem quote_2_correct : forall (v1 v2 : value) (vs : value_stack),
|
||||
Sem_expr (v2 :: v1 :: vs) (quote_n 1) (v_quote (e_comp (projT1 v1) (projT1 v2)) :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v1 v2 vs. simpl.
|
||||
repeat (eapply Sem_e_comp)...
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_compose.
|
||||
Qed.
|
||||
|
||||
Theorem quote_3_correct : forall (v1 v2 v3 : value) (vs : value_stack),
|
||||
Sem_expr (v3 :: v2 :: v1 :: vs) (quote_n 2) (v_quote (e_comp (projT1 v1) (e_comp (projT1 v2) (projT1 v3))) :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v1 v2 v3 vs. simpl.
|
||||
repeat (eapply Sem_e_comp)...
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_compose.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_compose.
|
||||
Qed.
|
||||
|
||||
Ltac2 rec solve_basic () := Control.enter (fun _ =>
|
||||
match! goal with
|
||||
| [|- Sem_int ?vs1 swap ?vs2] => apply Sem_swap
|
||||
| [|- Sem_int ?vs1 clone ?vs2] => apply Sem_clone
|
||||
| [|- Sem_int ?vs1 drop ?vs2] => apply Sem_drop
|
||||
| [|- Sem_int ?vs1 quote ?vs2] => apply Sem_quote
|
||||
| [|- Sem_int ?vs1 compose ?vs2] => apply Sem_compose
|
||||
| [|- Sem_int ?vs1 apply ?vs2] => apply Sem_apply
|
||||
| [|- Sem_expr ?vs1 (e_comp ?e1 ?e2) ?vs2] => eapply Sem_e_comp; solve_basic ()
|
||||
| [|- Sem_expr ?vs1 (e_int ?e) ?vs2] => apply Sem_e_int; solve_basic ()
|
||||
| [|- Sem_expr ?vs1 (e_quote ?e) ?vs2] => apply Sem_e_quote
|
||||
| [_ : _ |- _] => ()
|
||||
end).
|
||||
|
||||
Theorem quote_2_correct' : forall (v1 v2 : value) (vs : value_stack),
|
||||
Sem_expr (v2 :: v1 :: vs) (quote_n 1) (v_quote (e_comp (projT1 v1) (projT1 v2)) :: vs).
|
||||
Proof. intros. simpl. solve_basic (). Qed.
|
||||
|
||||
Theorem quote_3_correct' : forall (v1 v2 v3 : value) (vs : value_stack),
|
||||
Sem_expr (v3 :: v2 :: v1 :: vs) (quote_n 2) (v_quote (e_comp (projT1 v1) (e_comp (projT1 v2) (projT1 v3))) :: vs).
|
||||
Proof. intros. simpl. solve_basic (). Qed.
|
||||
|
||||
Definition rotate_n (n : nat) := e_compose (quote_n n) (e_int swap :: e_int quote :: e_int compose :: e_int apply :: nil).
|
||||
|
||||
Lemma eval_value : forall (v : value) (vs : value_stack),
|
||||
Sem_expr vs (projT1 v) (v :: vs).
|
||||
Proof.
|
||||
intros v vs.
|
||||
destruct v. destruct i.
|
||||
simpl. apply Sem_e_quote.
|
||||
Qed.
|
||||
|
||||
Theorem rotate_3_correct : forall (v1 v2 v3 : value) (vs : value_stack),
|
||||
Sem_expr (v3 :: v2 :: v1 :: vs) (rotate_n 1) (v1 :: v3 :: v2 :: vs).
|
||||
Proof.
|
||||
intros. unfold rotate_n. simpl. solve_basic ().
|
||||
repeat (eapply Sem_e_comp); apply eval_value.
|
||||
Qed.
|
||||
|
||||
Theorem rotate_4_correct : forall (v1 v2 v3 v4 : value) (vs : value_stack),
|
||||
Sem_expr (v4 :: v3 :: v2 :: v1 :: vs) (rotate_n 2) (v1 :: v4 :: v3 :: v2 :: vs).
|
||||
Proof.
|
||||
intros. unfold rotate_n. simpl. solve_basic ().
|
||||
repeat (eapply Sem_e_comp); apply eval_value.
|
||||
Qed.
|
||||
|
||||
Theorem e_comp_assoc : forall (e1 e2 e3 : expr) (vs vs' : value_stack),
|
||||
Sem_expr vs (e_comp e1 (e_comp e2 e3)) vs' <-> Sem_expr vs (e_comp (e_comp e1 e2) e3) vs'.
|
||||
Proof.
|
||||
intros e1 e2 e3 vs vs'.
|
||||
split; intros Heval.
|
||||
- inversion Heval; subst. inversion H4; subst.
|
||||
eapply Sem_e_comp. eapply Sem_e_comp. apply H2. apply H3. apply H6.
|
||||
- inversion Heval; subst. inversion H2; subst.
|
||||
eapply Sem_e_comp. apply H3. eapply Sem_e_comp. apply H6. apply H4.
|
||||
Qed.
|
||||
254
code/dawn/DawnEval.v
Normal file
@@ -0,0 +1,254 @@
|
||||
Require Import Coq.Lists.List.
|
||||
Require Import DawnV2.
|
||||
Require Import Coq.Program.Equality.
|
||||
From Ltac2 Require Import Ltac2.
|
||||
|
||||
Inductive step_result :=
|
||||
| err
|
||||
| middle (e : expr) (s : value_stack)
|
||||
| final (s : value_stack).
|
||||
|
||||
Fixpoint eval_step (s : value_stack) (e : expr) : step_result :=
|
||||
match e, s with
|
||||
| e_int swap, v' :: v :: vs => final (v :: v' :: vs)
|
||||
| e_int clone, v :: vs => final (v :: v :: vs)
|
||||
| e_int drop, v :: vs => final vs
|
||||
| e_int quote, v :: vs => final (v_quote (value_to_expr v) :: vs)
|
||||
| e_int compose, (v_quote v2) :: (v_quote v1) :: vs => final (v_quote (e_comp v1 v2) :: vs)
|
||||
| e_int apply, (v_quote v1) :: vs => middle v1 vs
|
||||
| e_quote e', vs => final (v_quote e' :: vs)
|
||||
| e_comp e1 e2, vs =>
|
||||
match eval_step vs e1 with
|
||||
| final vs' => middle e2 vs'
|
||||
| middle e1' vs' => middle (e_comp e1' e2) vs'
|
||||
| err => err
|
||||
end
|
||||
| _, _ => err
|
||||
end.
|
||||
|
||||
Theorem eval_step_correct : forall (e : expr) (vs vs' : value_stack), Sem_expr vs e vs' ->
|
||||
(eval_step vs e = final vs') \/
|
||||
(exists (ei : expr) (vsi : value_stack),
|
||||
eval_step vs e = middle ei vsi /\
|
||||
Sem_expr vsi ei vs').
|
||||
Proof.
|
||||
intros e vs vs' Hsem.
|
||||
(* Proceed by induction on the semantics. *)
|
||||
induction Hsem.
|
||||
- inversion H; (* The expression is just an intrnsic. *)
|
||||
(* Dismiss all the straightforward "final" cases,
|
||||
of which most intrinsics are. *)
|
||||
try (left; reflexivity).
|
||||
(* Only apply remains; We are in an intermediate / middle case. *)
|
||||
right.
|
||||
(* The semantics guarantee that the expression in the
|
||||
quote evaluates to the final state. *)
|
||||
exists e, vs0. auto.
|
||||
- (* The expression is a quote. This is yet another final case. *)
|
||||
left; reflexivity.
|
||||
- (* The composition is never a final step, since we have to evaluate both
|
||||
branches to "finish up". *)
|
||||
destruct IHHsem1; right.
|
||||
+ (* If the left branch finihed, only the right branch needs to be evaluted. *)
|
||||
simpl. rewrite H. exists e2, vs2. auto.
|
||||
+ (* Otherwise, the left branch has an intermediate evaluation, guaranteed
|
||||
by induction to be consitent. *)
|
||||
destruct H as [ei [vsi [Heval Hsem']]].
|
||||
(* We compose the remaining part of the left branch with the right branch. *)
|
||||
exists (e_comp ei e2), vsi. simpl.
|
||||
(* The evaluation is trivially to a "middle" state. *)
|
||||
rewrite Heval. split. auto.
|
||||
eapply Sem_e_comp. apply Hsem'. apply Hsem2.
|
||||
Qed.
|
||||
|
||||
Inductive eval_chain (vs : value_stack) (e : expr) (vs' : value_stack) : Prop :=
|
||||
| chain_final (P : eval_step vs e = final vs')
|
||||
| chain_middle (ei : expr) (vsi : value_stack)
|
||||
(P : eval_step vs e = middle ei vsi) (rest : eval_chain vsi ei vs').
|
||||
|
||||
Lemma eval_chain_merge : forall (e1 e2 : expr) (vs vs' vs'' : value_stack),
|
||||
eval_chain vs e1 vs' -> eval_chain vs' e2 vs'' -> eval_chain vs (e_comp e1 e2) vs''.
|
||||
Proof.
|
||||
intros e1 e2 vs vs' vs'' ch1 ch2.
|
||||
induction ch1;
|
||||
eapply chain_middle; simpl; try (rewrite P); auto.
|
||||
Qed.
|
||||
|
||||
Lemma eval_chain_split : forall (e1 e2 : expr) (vs vs'' : value_stack),
|
||||
eval_chain vs (e_comp e1 e2) vs'' -> exists vs', (eval_chain vs e1 vs') /\ (eval_chain vs' e2 vs'').
|
||||
Proof.
|
||||
intros e1 e2 vs vss'' ch.
|
||||
ltac1:(dependent induction ch).
|
||||
- simpl in P. destruct (eval_step vs e1); inversion P.
|
||||
- simpl in P. destruct (eval_step vs e1) eqn:Hval; try (inversion P).
|
||||
+ injection P as Hinj; subst. specialize (IHch e e2 H0) as [s'0 [ch1 ch2]].
|
||||
eexists. split.
|
||||
* eapply chain_middle. apply Hval. apply ch1.
|
||||
* apply ch2.
|
||||
+ subst. eexists. split.
|
||||
* eapply chain_final. apply Hval.
|
||||
* apply ch.
|
||||
Qed.
|
||||
|
||||
Theorem val_step_sem : forall (e : expr) (vs vs' : value_stack),
|
||||
Sem_expr vs e vs' -> eval_chain vs e vs'
|
||||
with eval_step_int : forall (i : intrinsic) (vs vs' : value_stack),
|
||||
Sem_int vs i vs' -> eval_chain vs (e_int i) vs'.
|
||||
Proof.
|
||||
- intros e vs vs' Hsem.
|
||||
induction Hsem.
|
||||
+ (* This is an intrinsic, which is handled by the second
|
||||
theorem, eval_step_int. This lemma is used here. *)
|
||||
auto.
|
||||
+ (* A quote doesn't have a next step, and so is final. *)
|
||||
apply chain_final. auto.
|
||||
+ (* In composition, by induction, we know that the two sub-expressions produce
|
||||
proper evaluation chains. Chains can be composed (via eval_chain_merge). *)
|
||||
eapply eval_chain_merge; eauto.
|
||||
- intros i vs vs' Hsem.
|
||||
(* The evaluation chain depends on the specific intrinsic in use. *)
|
||||
inversion Hsem; subst;
|
||||
(* Most intrinsics produce a final value, and the evaluation chain is trivial. *)
|
||||
try (apply chain_final; auto; fail).
|
||||
(* Only apply is non-final. The first step is popping the quote from the stack,
|
||||
and the rest of the steps are given by the evaluation of the code in the quote. *)
|
||||
apply chain_middle with e vs0; auto.
|
||||
Qed.
|
||||
|
||||
Ltac2 Type exn ::= [ | Not_intrinsic ].
|
||||
|
||||
Ltac2 rec destruct_n (n : int) (vs : constr) : unit :=
|
||||
if Int.le n 0 then () else
|
||||
let v := Fresh.in_goal @v in
|
||||
let vs' := Fresh.in_goal @vs in
|
||||
destruct $vs as [|$v $vs']; Control.enter (fun () =>
|
||||
try (destruct_n (Int.sub n 1) (Control.hyp vs'))
|
||||
).
|
||||
|
||||
Ltac2 int_arity (int : constr) : int :=
|
||||
match! int with
|
||||
| swap => 2
|
||||
| clone => 1
|
||||
| drop => 1
|
||||
| quote => 1
|
||||
| compose => 2
|
||||
| apply => 1
|
||||
| _ => Control.throw Not_intrinsic
|
||||
end.
|
||||
|
||||
Ltac2 destruct_int_stack (int : constr) (va: constr) := destruct_n (int_arity int) va.
|
||||
|
||||
Ltac2 ensure_valid_stack () := Control.enter (fun () =>
|
||||
match! goal with
|
||||
| [h : eval_step ?a (e_int ?b) = ?c |- _] =>
|
||||
let h := Control.hyp h in
|
||||
destruct_int_stack b a;
|
||||
try (inversion $h; fail)
|
||||
| [|- _ ] => ()
|
||||
end).
|
||||
|
||||
Theorem test : forall (vs vs': value_stack), eval_step vs (e_int swap) = final vs' ->
|
||||
exists v1 v2 vs'', vs = v1 :: v2 :: vs'' /\ vs' = v2 :: v1 :: vs''.
|
||||
Proof.
|
||||
intros s s' Heq.
|
||||
ensure_valid_stack ().
|
||||
simpl in Heq. injection Heq as Hinj. subst. eauto.
|
||||
Qed.
|
||||
|
||||
Theorem eval_step_final_sem : forall (e : expr) (vs vs' : value_stack),
|
||||
eval_step vs e = final vs' -> Sem_expr vs e vs'.
|
||||
Proof.
|
||||
intros e vs vs' Hev. destruct e.
|
||||
- destruct i; ensure_valid_stack ();
|
||||
(* Get rid of trivial cases that match one-to-one. *)
|
||||
simpl in Hev; try (injection Hev as Hinj; subst; solve_basic ()).
|
||||
+ (* compose with one quoted value is not final, but an error. *)
|
||||
destruct v. inversion Hev.
|
||||
+ (* compose with two quoted values. *)
|
||||
destruct v; destruct v0.
|
||||
injection Hev as Hinj; subst; solve_basic ().
|
||||
+ (* Apply is not final. *) destruct v. inversion Hev.
|
||||
- (* Quote is always final, trivially, and the semantics match easily. *)
|
||||
simpl in Hev. injection Hev as Hinj; subst. solve_basic ().
|
||||
- (* Compose is never final, so we don't need to handle it here. *)
|
||||
simpl in Hev. destruct (eval_step vs e1); inversion Hev.
|
||||
Qed.
|
||||
|
||||
Theorem eval_step_middle_sem : forall (e ei: expr) (vs vsi vs' : value_stack),
|
||||
eval_step vs e = middle ei vsi ->
|
||||
Sem_expr vsi ei vs' ->
|
||||
Sem_expr vs e vs'.
|
||||
Proof.
|
||||
intros e. induction e; intros ei vs vsi vs' Hev Hsem.
|
||||
- destruct i; ensure_valid_stack ().
|
||||
+ (* compose with one quoted value; invalid. *)
|
||||
destruct v. inversion Hev.
|
||||
+ (* compose with two quoted values; not a middle step. *)
|
||||
destruct v; destruct v0. inversion Hev.
|
||||
+ (* Apply *)
|
||||
destruct v. injection Hev as Hinj; subst.
|
||||
solve_basic (). auto.
|
||||
- (* quoting an expression is not middle. *)
|
||||
inversion Hev.
|
||||
- simpl in Hev.
|
||||
destruct (eval_step vs e1) eqn:Hev1.
|
||||
+ (* Step led to an error, which can't happen in a chain. *)
|
||||
inversion Hev.
|
||||
+ (* Left expression makes a non-final step. Milk this for equalities first. *)
|
||||
injection Hev as Hinj; subst.
|
||||
(* The rest of the program (e_comp e e2) evaluates using our semantics,
|
||||
which means that both e and e2 evaluate using our semantics. *)
|
||||
inversion Hsem; subst.
|
||||
(* By induction, e1 evaluates using our semantics if e does, which we just confirmed. *)
|
||||
specialize (IHe1 e vs vsi vs2 Hev1 H2).
|
||||
(* The composition rule can now be applied. *)
|
||||
eapply Sem_e_comp; eauto.
|
||||
+ (* Left expression makes a final step. Milk this for equalities first. *)
|
||||
injection Hev as Hinj; subst.
|
||||
(* Using eval_step_final, we know that e1 evaluates to the intermediate
|
||||
state given our semantics. *)
|
||||
specialize (eval_step_final_sem e1 vs vsi Hev1) as Hsem1.
|
||||
(* The composition rule can now be applied. *)
|
||||
eapply Sem_e_comp; eauto.
|
||||
Qed.
|
||||
|
||||
Theorem eval_step_sem_back : forall (e : expr) (vs vs' : value_stack),
|
||||
eval_chain vs e vs' -> Sem_expr vs e vs'.
|
||||
Proof.
|
||||
intros e vs vs' ch.
|
||||
ltac1:(dependent induction ch).
|
||||
- apply eval_step_final_sem. auto.
|
||||
- specialize (eval_step_middle_sem e ei vs vsi vs' P IHch). auto.
|
||||
Qed.
|
||||
|
||||
Corollary eval_step_no_sem : forall (e : expr) (vs vs' : value_stack),
|
||||
~(Sem_expr vs e vs') -> ~(eval_chain vs e vs').
|
||||
Proof.
|
||||
intros e vs vs' Hnsem Hch.
|
||||
specialize (eval_step_sem_back _ _ _ Hch). auto.
|
||||
Qed.
|
||||
|
||||
Require Extraction.
|
||||
Require Import ExtrHaskellBasic.
|
||||
Extraction Language Haskell.
|
||||
Set Extraction KeepSingleton.
|
||||
Extraction "UccGen.hs" expr eval_step true false or.
|
||||
|
||||
Remark eval_swap_two_values : forall (vs vs' : value_stack),
|
||||
eval_step vs (e_int swap) = final vs' -> exists v1 v2 vst, vs = v1 :: v2 :: vst /\ vs' = v2 :: v1 :: vst.
|
||||
Proof.
|
||||
intros vs vs' Hev.
|
||||
(* Can't proceed until we know more about the stack. *)
|
||||
destruct vs as [|v1 [|v2 vs]].
|
||||
- (* Invalid case; empty stack. *) inversion Hev.
|
||||
- (* Invalid case; stack only has one value. *) inversion Hev.
|
||||
- (* Valid case: the stack has two values. *) injection Hev. eauto.
|
||||
Qed.
|
||||
|
||||
Remark eval_swap_two_values' : forall (vs vs' : value_stack),
|
||||
eval_step vs (e_int swap) = final vs' -> exists v1 v2 vst, vs = v1 :: v2 :: vst /\ vs' = v2 :: v1 :: vst.
|
||||
Proof.
|
||||
intros vs vs' Hev.
|
||||
ensure_valid_stack ().
|
||||
injection Hev. eauto.
|
||||
Qed.
|
||||
179
code/dawn/DawnV2.v
Normal file
@@ -0,0 +1,179 @@
|
||||
Require Import Coq.Lists.List.
|
||||
From Ltac2 Require Import Ltac2.
|
||||
|
||||
Inductive intrinsic :=
|
||||
| swap
|
||||
| clone
|
||||
| drop
|
||||
| quote
|
||||
| compose
|
||||
| apply.
|
||||
|
||||
Inductive expr :=
|
||||
| e_int (i : intrinsic)
|
||||
| e_quote (e : expr)
|
||||
| e_comp (e1 e2 : expr).
|
||||
|
||||
Definition e_compose (e : expr) (es : list expr) := fold_left e_comp es e.
|
||||
|
||||
Inductive value := v_quote (e : expr).
|
||||
Definition value_stack := list value.
|
||||
|
||||
Definition value_to_expr (v : value) : expr :=
|
||||
match v with
|
||||
| v_quote e => e_quote e
|
||||
end.
|
||||
|
||||
Inductive Sem_int : value_stack -> intrinsic -> value_stack -> Prop :=
|
||||
| Sem_swap : forall (v v' : value) (vs : value_stack), Sem_int (v' :: v :: vs) swap (v :: v' :: vs)
|
||||
| Sem_clone : forall (v : value) (vs : value_stack), Sem_int (v :: vs) clone (v :: v :: vs)
|
||||
| Sem_drop : forall (v : value) (vs : value_stack), Sem_int (v :: vs) drop vs
|
||||
| Sem_quote : forall (v : value) (vs : value_stack), Sem_int (v :: vs) quote ((v_quote (value_to_expr v)) :: vs)
|
||||
| Sem_compose : forall (e e' : expr) (vs : value_stack), Sem_int (v_quote e' :: v_quote e :: vs) compose (v_quote (e_comp e e') :: vs)
|
||||
| Sem_apply : forall (e : expr) (vs vs': value_stack), Sem_expr vs e vs' -> Sem_int (v_quote e :: vs) apply vs'
|
||||
|
||||
with Sem_expr : value_stack -> expr -> value_stack -> Prop :=
|
||||
| Sem_e_int : forall (i : intrinsic) (vs vs' : value_stack), Sem_int vs i vs' -> Sem_expr vs (e_int i) vs'
|
||||
| Sem_e_quote : forall (e : expr) (vs : value_stack), Sem_expr vs (e_quote e) (v_quote e :: vs)
|
||||
| Sem_e_comp : forall (e1 e2 : expr) (vs1 vs2 vs3 : value_stack),
|
||||
Sem_expr vs1 e1 vs2 -> Sem_expr vs2 e2 vs3 -> Sem_expr vs1 (e_comp e1 e2) vs3.
|
||||
|
||||
Definition false : expr := e_quote (e_int drop).
|
||||
Definition false_v : value := v_quote (e_int drop).
|
||||
|
||||
Definition true : expr := e_quote (e_comp (e_int swap) (e_int drop)).
|
||||
Definition true_v : value := v_quote (e_comp (e_int swap) (e_int drop)).
|
||||
|
||||
Theorem false_correct : forall (v v' : value) (vs : value_stack), Sem_expr (v' :: v :: vs) (e_comp false (e_int apply)) (v :: vs).
|
||||
Proof.
|
||||
intros v v' vs.
|
||||
eapply Sem_e_comp.
|
||||
- apply Sem_e_quote.
|
||||
- apply Sem_e_int. apply Sem_apply. apply Sem_e_int. apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Theorem true_correct : forall (v v' : value) (vs : value_stack), Sem_expr (v' :: v :: vs) (e_comp true (e_int apply)) (v' :: vs).
|
||||
Proof.
|
||||
intros v v' vs.
|
||||
eapply Sem_e_comp.
|
||||
- apply Sem_e_quote.
|
||||
- apply Sem_e_int. apply Sem_apply. eapply Sem_e_comp.
|
||||
* apply Sem_e_int. apply Sem_swap.
|
||||
* apply Sem_e_int. apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Definition or : expr := e_comp (e_int clone) (e_int apply).
|
||||
|
||||
Theorem or_false_v : forall (v : value) (vs : value_stack), Sem_expr (false_v :: v :: vs) or (v :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v vs.
|
||||
eapply Sem_e_comp...
|
||||
- apply Sem_clone.
|
||||
- apply Sem_apply... apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Theorem or_true : forall (v : value) (vs : value_stack), Sem_expr (true_v :: v :: vs) or (true_v :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v vs.
|
||||
eapply Sem_e_comp...
|
||||
- apply Sem_clone...
|
||||
- apply Sem_apply. eapply Sem_e_comp...
|
||||
* apply Sem_swap.
|
||||
* apply Sem_drop.
|
||||
Qed.
|
||||
|
||||
Definition or_false_false := or_false_v false_v.
|
||||
Definition or_false_true := or_false_v true_v.
|
||||
Definition or_true_false := or_true false_v.
|
||||
Definition or_true_true := or_true true_v.
|
||||
|
||||
Fixpoint quote_n (n : nat) :=
|
||||
match n with
|
||||
| O => e_int quote
|
||||
| S n' => e_compose (quote_n n') (e_int swap :: e_int quote :: e_int swap :: e_int compose :: nil)
|
||||
end.
|
||||
|
||||
Theorem quote_2_correct : forall (v1 v2 : value) (vs : value_stack),
|
||||
Sem_expr (v2 :: v1 :: vs) (quote_n 1) (v_quote (e_comp (value_to_expr v1) (value_to_expr v2)) :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v1 v2 vs. simpl.
|
||||
repeat (eapply Sem_e_comp)...
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_compose.
|
||||
Qed.
|
||||
|
||||
Theorem quote_3_correct : forall (v1 v2 v3 : value) (vs : value_stack),
|
||||
Sem_expr (v3 :: v2 :: v1 :: vs) (quote_n 2) (v_quote (e_comp (value_to_expr v1) (e_comp (value_to_expr v2) (value_to_expr v3))) :: vs).
|
||||
Proof with apply Sem_e_int.
|
||||
intros v1 v2 v3 vs. simpl.
|
||||
repeat (eapply Sem_e_comp)...
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_compose.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_quote.
|
||||
- apply Sem_swap.
|
||||
- apply Sem_compose.
|
||||
Qed.
|
||||
|
||||
Ltac2 rec solve_basic () := Control.enter (fun _ =>
|
||||
match! goal with
|
||||
| [|- Sem_int ?vs1 swap ?vs2] => apply Sem_swap
|
||||
| [|- Sem_int ?vs1 clone ?vs2] => apply Sem_clone
|
||||
| [|- Sem_int ?vs1 drop ?vs2] => apply Sem_drop
|
||||
| [|- Sem_int ?vs1 quote ?vs2] => apply Sem_quote
|
||||
| [|- Sem_int ?vs1 compose ?vs2] => apply Sem_compose
|
||||
| [|- Sem_int ?vs1 apply ?vs2] => apply Sem_apply
|
||||
| [|- Sem_expr ?vs1 (e_comp ?e1 ?e2) ?vs2] => eapply Sem_e_comp; solve_basic ()
|
||||
| [|- Sem_expr ?vs1 (e_int ?e) ?vs2] => apply Sem_e_int; solve_basic ()
|
||||
| [|- Sem_expr ?vs1 (e_quote ?e) ?vs2] => apply Sem_e_quote
|
||||
| [_ : _ |- _] => ()
|
||||
end).
|
||||
|
||||
Theorem quote_2_correct' : forall (v1 v2 : value) (vs : value_stack),
|
||||
Sem_expr (v2 :: v1 :: vs) (quote_n 1) (v_quote (e_comp (value_to_expr v1) (value_to_expr v2)) :: vs).
|
||||
Proof. intros. simpl. solve_basic (). Qed.
|
||||
|
||||
Theorem quote_3_correct' : forall (v1 v2 v3 : value) (vs : value_stack),
|
||||
Sem_expr (v3 :: v2 :: v1 :: vs) (quote_n 2) (v_quote (e_comp (value_to_expr v1) (e_comp (value_to_expr v2) (value_to_expr v3))) :: vs).
|
||||
Proof. intros. simpl. solve_basic (). Qed.
|
||||
|
||||
Definition rotate_n (n : nat) := e_compose (quote_n n) (e_int swap :: e_int quote :: e_int compose :: e_int apply :: nil).
|
||||
|
||||
Lemma eval_value : forall (v : value) (vs : value_stack),
|
||||
Sem_expr vs (value_to_expr v) (v :: vs).
|
||||
Proof.
|
||||
intros v vs.
|
||||
destruct v.
|
||||
simpl. apply Sem_e_quote.
|
||||
Qed.
|
||||
|
||||
Theorem rotate_3_correct : forall (v1 v2 v3 : value) (vs : value_stack),
|
||||
Sem_expr (v3 :: v2 :: v1 :: vs) (rotate_n 1) (v1 :: v3 :: v2 :: vs).
|
||||
Proof.
|
||||
intros. unfold rotate_n. simpl. solve_basic ().
|
||||
repeat (eapply Sem_e_comp); apply eval_value.
|
||||
Qed.
|
||||
|
||||
Theorem rotate_4_correct : forall (v1 v2 v3 v4 : value) (vs : value_stack),
|
||||
Sem_expr (v4 :: v3 :: v2 :: v1 :: vs) (rotate_n 2) (v1 :: v4 :: v3 :: v2 :: vs).
|
||||
Proof.
|
||||
intros. unfold rotate_n. simpl. solve_basic ().
|
||||
repeat (eapply Sem_e_comp); apply eval_value.
|
||||
Qed.
|
||||
|
||||
Theorem e_comp_assoc : forall (e1 e2 e3 : expr) (vs vs' : value_stack),
|
||||
Sem_expr vs (e_comp e1 (e_comp e2 e3)) vs' <-> Sem_expr vs (e_comp (e_comp e1 e2) e3) vs'.
|
||||
Proof.
|
||||
intros e1 e2 e3 vs vs'.
|
||||
split; intros Heval.
|
||||
- inversion Heval; subst. inversion H4; subst.
|
||||
eapply Sem_e_comp. eapply Sem_e_comp. apply H2. apply H3. apply H6.
|
||||
- inversion Heval; subst. inversion H2; subst.
|
||||
eapply Sem_e_comp. apply H3. eapply Sem_e_comp. apply H6. apply H4.
|
||||
Qed.
|
||||
64
code/dawn/Ucc.hs
Normal file
@@ -0,0 +1,64 @@
|
||||
module Ucc where
|
||||
import UccGen
|
||||
import Text.Parsec
|
||||
import Data.Functor.Identity
|
||||
import Control.Applicative hiding ((<|>))
|
||||
import System.IO
|
||||
|
||||
instance Show Intrinsic where
|
||||
show Swap = "swap"
|
||||
show Clone = "clone"
|
||||
show Drop = "drop"
|
||||
show Quote = "quote"
|
||||
show Compose = "compose"
|
||||
show Apply = "apply"
|
||||
|
||||
instance Show Expr where
|
||||
show (E_int i) = show i
|
||||
show (E_quote e) = "[" ++ show e ++ "]"
|
||||
show (E_comp e1 e2) = show e1 ++ " " ++ show e2
|
||||
|
||||
instance Show Value where
|
||||
show (V_quote e) = show (E_quote e)
|
||||
|
||||
type Parser a = ParsecT String () Identity a
|
||||
|
||||
intrinsic :: Parser Intrinsic
|
||||
intrinsic = (<* spaces) $ foldl1 (<|>) $ map (\(s, i) -> try (string s >> return i))
|
||||
[ ("swap", Swap)
|
||||
, ("clone", Clone)
|
||||
, ("drop", Drop)
|
||||
, ("quote", Quote)
|
||||
, ("compose", Compose)
|
||||
, ("apply", Apply)
|
||||
]
|
||||
|
||||
expression :: Parser Expr
|
||||
expression = foldl1 E_comp <$> many1 single
|
||||
where
|
||||
single
|
||||
= (E_int <$> intrinsic)
|
||||
<|> (fmap E_quote $ char '[' *> spaces *> expression <* char ']' <* spaces)
|
||||
|
||||
parseExpression :: String -> Either ParseError Expr
|
||||
parseExpression = runParser expression () "<inline>"
|
||||
|
||||
eval :: [Value] -> Expr -> Maybe [Value]
|
||||
eval s e =
|
||||
case eval_step s e of
|
||||
Err -> Nothing
|
||||
Final s' -> Just s'
|
||||
Middle e' s' -> eval s' e'
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
putStr "> "
|
||||
hFlush stdout
|
||||
str <- getLine
|
||||
case parseExpression str of
|
||||
Right e ->
|
||||
case eval [] e of
|
||||
Just st -> putStrLn $ show st
|
||||
_ -> putStrLn "Evaluation error"
|
||||
_ -> putStrLn "Parse error"
|
||||
main
|
||||
68
code/patterns/patterns.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
require 'victor'
|
||||
|
||||
def sum_digits(n)
|
||||
while n > 9
|
||||
n = n.to_s.chars.map(&:to_i).sum
|
||||
end
|
||||
n
|
||||
end
|
||||
|
||||
def step(x, y, n, dir)
|
||||
case dir
|
||||
when :top
|
||||
return [x,y+n,:right]
|
||||
when :right
|
||||
return [x+n,y,:bottom]
|
||||
when :bottom
|
||||
return [x,y-n,:left]
|
||||
when :left
|
||||
return [x-n,y,:top]
|
||||
end
|
||||
end
|
||||
|
||||
def run_number(number)
|
||||
counter = 1
|
||||
x, y, dir = 0, 0, :top
|
||||
line_stack = [[0,0]]
|
||||
|
||||
loop do
|
||||
x, y, dir = step(x,y, sum_digits(counter*number), dir)
|
||||
line_stack << [x,y]
|
||||
counter += 1
|
||||
break if x == 0 && y == 0
|
||||
end
|
||||
return make_svg(line_stack)
|
||||
end
|
||||
|
||||
def make_svg(line_stack)
|
||||
line_length = 20
|
||||
xs = line_stack.map { |c| c[0] }
|
||||
ys = line_stack.map { |c| c[1] }
|
||||
|
||||
x_offset = -xs.min
|
||||
y_offset = -ys.min
|
||||
svg_coords = ->(p) {
|
||||
nx, ny = p
|
||||
[(nx+x_offset)*line_length + line_length/2, (ny+y_offset)*line_length + line_length/2]
|
||||
}
|
||||
|
||||
max_width = (xs.max - xs.min).abs * line_length + line_length
|
||||
max_height = (ys.max - ys.min).abs * line_length + line_length
|
||||
svg = Victor::SVG.new width: max_width, height: max_height
|
||||
|
||||
style = { stroke: 'black', stroke_width: 5 }
|
||||
svg.build do
|
||||
line_stack.each_cons(2) do |pair|
|
||||
p1, p2 = pair
|
||||
x1, y1 = svg_coords.call(p1)
|
||||
x2, y2 = svg_coords.call(p2)
|
||||
line x1: x1, y1: y1, x2: x2, y2: y2, style: style
|
||||
circle cx: x2, cy: y2, r: line_length/6, style: style, fill: 'black'
|
||||
end
|
||||
end
|
||||
return svg
|
||||
end
|
||||
|
||||
(1..9).each do |i|
|
||||
run_number(i).save "pattern_#{i}"
|
||||
end
|
||||
1
code/server-config
Submodule
23
code/typescript-emitter/js1.js
Normal file
@@ -0,0 +1,23 @@
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this.handlers = {}
|
||||
}
|
||||
|
||||
emit(event) {
|
||||
this.handlers[event]?.forEach(h => h());
|
||||
}
|
||||
|
||||
addHandler(event, handler) {
|
||||
if(!this.handlers[event]) {
|
||||
this.handlers[event] = [handler];
|
||||
} else {
|
||||
this.handlers[event].push(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
emitter.addHandler("start", () => console.log("Started!"));
|
||||
emitter.addHandler("end", () => console.log("Ended!"));
|
||||
emitter.emit("end");
|
||||
emitter.emit("start");
|
||||
23
code/typescript-emitter/js2.js
Normal file
@@ -0,0 +1,23 @@
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this.handlers = {}
|
||||
}
|
||||
|
||||
emit(event, value) {
|
||||
this.handlers[event]?.forEach(h => h(value));
|
||||
}
|
||||
|
||||
addHandler(event, handler) {
|
||||
if(!this.handlers[event]) {
|
||||
this.handlers[event] = [handler];
|
||||
} else {
|
||||
this.handlers[event].push(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
emitter.addHandler("numberChange", n => console.log("New number value is: ", n));
|
||||
emitter.addHandler("stringChange", s => console.log("New string value is: ", s));
|
||||
emitter.emit("numberChange", 1);
|
||||
emitter.emit("stringChange", "3");
|
||||
27
code/typescript-emitter/ts.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
class EventEmitter<T> {
|
||||
private handlers: { [eventName in keyof T]?: ((value: T[eventName]) => void)[] }
|
||||
|
||||
constructor() {
|
||||
this.handlers = {}
|
||||
}
|
||||
|
||||
emit<K extends keyof T>(event: K, value: T[K]): void {
|
||||
this.handlers[event]?.forEach(h => h(value));
|
||||
}
|
||||
|
||||
addHandler<K extends keyof T>(event: K, handler: (value: T[K]) => void): void {
|
||||
if(!this.handlers[event]) {
|
||||
this.handlers[event] = [handler];
|
||||
} else {
|
||||
this.handlers[event].push(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const emitter = new EventEmitter<{ numberChange: number, stringChange: string }>();
|
||||
emitter.addHandler("numberChange", n => console.log("New number value is: ", n));
|
||||
emitter.addHandler("stringChange", s => console.log("New string value is: ", s));
|
||||
emitter.emit("numberChange", 1);
|
||||
emitter.emit("stringChange", "3");
|
||||
emitter.emit("numberChange", "1");
|
||||
emitter.emit("stringChange", 3);
|
||||
8
config-gen.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[params]
|
||||
[params.submoduleLinks]
|
||||
[params.submoduleLinks.aoc2020]
|
||||
url = "https://dev.danilafe.com/Advent-of-Code/AdventOfCode-2020/src/commit/7a8503c3fe1aa7e624e4d8672aa9b56d24b4ba82"
|
||||
path = "aoc-2020"
|
||||
[params.submoduleLinks.serverconfig]
|
||||
url = "https://dev.danilafe.com/Nix-Configs/server-config/src/commit/98cffe09546aee1678f7baebdea5eb5fef288935"
|
||||
path = "server-config"
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: About
|
||||
---
|
||||
{{< donate_css >}}
|
||||
|
||||
I'm Daniel, a Computer Science student currently working towards my Master's Degree at Oregon State University.
|
||||
Due to my initial interest in calculators and compilers, I got involved in the Programming Language Theory research
|
||||
group, gaining same experience in formal verification, domain specific language, and explainable computing.
|
||||
@@ -8,3 +10,34 @@ group, gaining same experience in formal verification, domain specific language,
|
||||
For work, school, and hobby projects, I use a variety of programming languages, most commonly C/C++,
|
||||
Haskell, [Crystal](https://crystal-lang.org/), and [Elm](https://elm-lang.org/). I also have experience
|
||||
with Java, Python, Haxe, and JavaScript.
|
||||
|
||||
A few notes about me or this site:
|
||||
* __Correctness__: I mostly write technical content. Even though I proofread my articles, there's
|
||||
always a fairly good chance that I'm wrong. You should always use your best judgement when reading
|
||||
anything on this site -- if something seems wrong, it may very well be. I'm far from an expert.
|
||||
* __Schedule__: I do not have a set post schedule. There are many reasons for this:
|
||||
schoolwork, personal life, lack of inspiration. It also takes a _very_ long time for
|
||||
me to write a single article. My article on [polymorphic type checking]({{< relref "/blog/10_compiler_polymorphism.md" >}})
|
||||
is around 8,000 words long; besides writing it, I have to edit it, link up all the code
|
||||
references, and proofread the final result. And of course, I need to write the code and
|
||||
occasionally do some research.
|
||||
* __Design__: I am doing my best to keep this website accessible and easy on the eyes.
|
||||
I'm also doing my best to avoid any and all uses of JavaScript. I used to use a lot of
|
||||
uMatrix, and most of the websites I browsed during this time were broken. Similarly,
|
||||
a lot of websites were unusable on my weaker machines. So, I'm doing my part and
|
||||
making this site usable without any JavaScript, and, as it seems to me, even
|
||||
without any CSS.
|
||||
* __Source code__: This blog is open source, but not on GitHub. Instead,
|
||||
you can find the code on my [Gitea instance](https://dev.danilafe.com/Web-Projects/blog-static).
|
||||
If you use this code for your own site, I would prefer that you don't copy the theme.
|
||||
|
||||
### Donate
|
||||
I don't run ads, nor do I profit from writing anything on here. I have no trouble paying for hosting,
|
||||
and I write my articles voluntarily, for my own enjoyment. However, if you found something particularly
|
||||
helpful on here, and would like to buy me a cup of coffee or help host the site, you can donate using
|
||||
the method(s) below.
|
||||
|
||||
{{< donation_methods >}}
|
||||
{{< donation_method "Bitcoin" "1BbXPZhdzv4xHq5LYhme3xBiUsHw5fmafd" >}}
|
||||
{{< donation_method "Ethereum" "0xd111E49344bEC80570e68EE0A00b87B1EFcb5D56" >}}
|
||||
{{< /donation_methods >}}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
title: "Advent of Code in Coq - Day 1"
|
||||
date: 2020-12-02T18:44:56-08:00
|
||||
tags: ["Advent of Code", "Coq"]
|
||||
favorite: true
|
||||
---
|
||||
|
||||
The first puzzle of this year's [Advent of Code](https://adventofcode.com) was quite
|
||||
@@ -25,7 +26,7 @@ let's write a helper function that, given a number `x`, tries to find another nu
|
||||
`y` such that `x + y = 2020`. In fact, rather than hardcoding the desired
|
||||
sum to `2020`, let's just use another argument called `total`. The code is quite simple:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 7 14 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 11 18 >}}
|
||||
|
||||
Here, `is` is the list of numbers that we want to search.
|
||||
We proceed by case analysis: if the list is empty, we can't
|
||||
@@ -42,7 +43,7 @@ for our purposes when the argument being case analyzed is given first.
|
||||
We can now use `find_matching` to define our `find_sum` function, which solves part 1.
|
||||
Here's the code:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 16 24 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 20 28 >}}
|
||||
|
||||
For every `x` that we encounter in our input list `is`, we want to check if there's
|
||||
a matching number in the rest of the list. We only search the remainder of the list
|
||||
@@ -71,13 +72,13 @@ formally as follows:
|
||||
|
||||
And this is how we write it in Coq:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 26 27 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 30 31 >}}
|
||||
|
||||
The arrow, `->`, reads "implies". Other than that, I think this
|
||||
property reads pretty well. The proof, unfortunately, is a little bit more involved.
|
||||
Here are the first few lines:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 28 31 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 32 35 >}}
|
||||
|
||||
We start with the `intros is` tactic, which is akin to saying
|
||||
"consider a particular list of integers `is`". We do this without losing
|
||||
@@ -156,7 +157,7 @@ is zero. This means we're done with the base case!
|
||||
The inductive case is the meat of this proof. Here's the corresponding part
|
||||
of the Coq source file:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 32 36 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 36 40 >}}
|
||||
|
||||
This time, the proof state is more complicated:
|
||||
|
||||
@@ -283,14 +284,14 @@ Coq proofs is to actually step through them in the IDE!
|
||||
|
||||
First on the list is `find_matching_skip`. Here's the type:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 38 39 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 42 43 >}}
|
||||
|
||||
It reads: if we correctly find a number in a small list `is`, we can find that same number
|
||||
even if another number is prepended to `is`. That makes sense: _adding_ a number to
|
||||
a list doesn't remove whatever we found in it! I used this lemma to prove another,
|
||||
`find_matching_works`:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 49 50 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 53 54 >}}
|
||||
|
||||
This reads, if there _is_ an element `y` in `is` that adds up to `k` with `x`, then
|
||||
`find_matching` will find it. This is an important property. After all, if it didn't
|
||||
@@ -309,7 +310,7 @@ that all lists from this Advent of Code puzzle are guaranteed to have two number
|
||||
add up to our goal, and that these numbers are not equal to each other. In Coq,
|
||||
we state this as follows:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 4 5 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 8 9 >}}
|
||||
|
||||
This defines a new property, `has_pair t is` (read "`is` has a pair of numbers that add to `t`"),
|
||||
which means:
|
||||
@@ -322,7 +323,7 @@ which means:
|
||||
When making claims about the correctness of our algorithm, we will assume that this
|
||||
property holds. Finally, here's the theorem we want to prove:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 64 66 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 68 70 >}}
|
||||
|
||||
It reads, "for any total `k` and list `is`, if `is` has a pair of numbers that add to `k`,
|
||||
then `find_sum` will return a pair of numbers `x` and `y` that add to `k`".
|
||||
@@ -334,7 +335,7 @@ we want to confirm that `find_sum` will find one of them. Finally, here is the p
|
||||
I will not be able to go through it in detail in this post, but I did comment it to
|
||||
make it easier to read:
|
||||
|
||||
{{< codelines "Coq" "aoc-coq/day1.v" 67 102 >}}
|
||||
{{< codelines "Coq" "aoc-2020/day1.v" 71 106 >}}
|
||||
|
||||
Coq seems happy with it, and so am I! The bug I mentioned earlier popped up on line 96.
|
||||
I had accidentally made `find_sum` return `None` if it couldn't find a complement
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: A Language for an Assignment - Homework 1
|
||||
date: 2019-12-27T23:27:09-08:00
|
||||
tags: ["Haskell", "Python", "Algorithms"]
|
||||
tags: ["Haskell", "Python", "Algorithms", "Programming Languages"]
|
||||
---
|
||||
|
||||
On a rainy Oregon day, I was walking between classes with a group of friends.
|
||||
|
||||
940
content/blog/01_aoc_coq.md
Normal file
@@ -0,0 +1,940 @@
|
||||
---
|
||||
title: "Advent of Code in Coq - Day 8"
|
||||
date: 2021-01-10T22:48:39-08:00
|
||||
tags: ["Advent of Code", "Coq"]
|
||||
---
|
||||
|
||||
Huh? We're on day 8? What happened to days 2 through 7?
|
||||
|
||||
Well, for the most part, I didn't think they were that interesting from the Coq point of view.
|
||||
Day 7 got close, but not close enough to inspire me to create a formalization. Day 8, on the other
|
||||
hand, is
|
||||
{{< sidenote "right" "pl-note" "quite interesting," >}}
|
||||
Especially to someone like me who's interested in programming languages!
|
||||
{{< /sidenote >}} and took quite some time to formalize.
|
||||
|
||||
As before, here's an (abridged) description of the problem:
|
||||
|
||||
> Given a tiny assembly-like language, determine the state of its accumulator
|
||||
> when the same instruction is executed twice.
|
||||
|
||||
Before we start on the Coq formalization, let's talk about an idea from
|
||||
Programming Language Theory (PLT), _big step operational semantics_.
|
||||
|
||||
### Big Step Operational Semantics
|
||||
What we have in Advent of Code's Day 8 is, undeniably, a small programming language.
|
||||
We are tasked with executing this language, or, in PLT lingo, defining its _semantics_.
|
||||
There are many ways of doing this - at university, I've been taught of [denotational](https://en.wikipedia.org/wiki/Denotational_semantics), [axiomatic](https://en.wikipedia.org/wiki/Axiomatic_semantics),
|
||||
and [operational](https://en.wikipedia.org/wiki/Operational_semantics) semantics.
|
||||
I believe that Coq's mechanism of inductive definitions lends itself very well
|
||||
to operational semantics, so we'll take that route. But even "operational semantics"
|
||||
doesn't refer to a concrete technique - we have a choice between small-step (structural) and
|
||||
big-step (natural) operational semantics. The former describe the minimal "steps" a program
|
||||
takes as it's being evaluated, while the latter define the final results of evaluating a program.
|
||||
I decided to go with big-step operational semantics, since they're more intutive (natural!).
|
||||
|
||||
So, how does one go about "[defining] the final results of evaluating a program?" Most commonly,
|
||||
we go about using _inference rules_. Let's talk about those next.
|
||||
|
||||
#### Inference Rules
|
||||
Inference rules are a very general notion. The describe how we can determine (infer) a conclusion
|
||||
from a set of assumptions. It helps to look at an example. Here's a silly little inference rule:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{\text{I'm allergic to cats} \quad \text{My friend has a cat}}
|
||||
{\text{I will not visit my friend very much}}
|
||||
{{< /latex >}}
|
||||
|
||||
It reads, "if I'm allergic to cats, and if my friend has a cat, then I will not visit my friend very much".
|
||||
Here, "I'm allergic to cats" and "my friend has a cat" are _premises_, and "I will not visit my friend very much" is
|
||||
a _conclusion_. An inference rule states that if all its premises are true, then its conclusion must be true.
|
||||
Here's another inference rule, this time with some mathematical notation instead of words:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{n < m}
|
||||
{n + 1 < m + 1}
|
||||
{{< /latex >}}
|
||||
|
||||
This one reads, "if \\(n\\) is less than \\(m\\), then \\(n+1\\) is less than \\(m+1\\)". We can use inference
|
||||
rules to define various constructs. As an example, let's define what it means for a natural number to be even.
|
||||
It takes two rules:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{}
|
||||
{0 \; \text{is even}}
|
||||
\quad
|
||||
\frac
|
||||
{n \; \text{is even}}
|
||||
{n+2 \; \text{is even}}
|
||||
{{< /latex >}}
|
||||
|
||||
First of all, zero is even. We take this as fact - there are no premises for the first rule, so they
|
||||
are all trivially true. Next, if a number is even, then adding 2 to that number results in another
|
||||
even number. Using the two of these rules together, we can correctly determine whether any number
|
||||
is or isn't even. We start knowing that 0 is even. Adding 2 we learn that 2 is even, and adding 2
|
||||
again we see that 4 is even, as well. We can continue this to determine that 6, 8, 10, and so on
|
||||
are even too. Never in this process will we visit the numbers 1 or 3 or 5, and that's good - they're not even!
|
||||
|
||||
Let's now extend this notion to programming languages, starting with a simple arithmetic language.
|
||||
This language is made up of natural numbers and the \\(\square\\) operation, which represents the addition
|
||||
of two numbers. Again, we need two rules:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{n \in \mathbb{N}}
|
||||
{n \; \text{evaluates to} \; n}
|
||||
\quad
|
||||
\frac
|
||||
{e_1 \; \text{evaluates to} \; n_1 \quad e_2 \; \text{evaluates to} \; n_2}
|
||||
{e_1 \square e_2 \; \text{evaluates to} \; n_1 + n_2}
|
||||
{{< /latex >}}
|
||||
|
||||
First, let me explain myself. I used \\(\square\\) to demonstrate two important points. First, languages can be made of
|
||||
any kind of characters we want; it's the rules that we define that give these languages meaning.
|
||||
Second, while \\(\square\\) is the addition operation _in our language_, \\(+\\) is the _mathematical addition operator_.
|
||||
They are not the same - we use the latter to define how the former works.
|
||||
|
||||
Finally, writing "evaluates to" gets quite tedious, especially for complex languages. Instead,
|
||||
PLT people use notation to make their semantics more concise. The symbol \\(\Downarrow\\) is commonly
|
||||
used to mean "evaluates to"; thus, \\(e \Downarrow v\\) reads "the expression \\(e\\) evaluates to the value \\(v\\).
|
||||
Using this notation, our rules start to look like the following:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{n \in \mathbb{N}}
|
||||
{n \Downarrow n}
|
||||
\quad
|
||||
\frac
|
||||
{e_1 \Downarrow n_1 \quad e_2 \Downarrow n_2}
|
||||
{e_1 \square e_2 \Downarrow n_1 + n_2}
|
||||
{{< /latex >}}
|
||||
|
||||
If nothing else, these are way more compact! Though these may look intimidating at first, it helps to
|
||||
simply read each symbol as its English meaning.
|
||||
|
||||
#### Encoding Inference Rules in Coq
|
||||
Now that we've seen what inference rules are, we can take a look at how they can be represented in Coq.
|
||||
We can use Coq's `Inductive` mechanism to define the rules. Let's start with our "is even" property.
|
||||
|
||||
```Coq
|
||||
Inductive is_even : nat -> Prop :=
|
||||
| zero_even : is_even 0
|
||||
| plustwo_even : is_even n -> is_even (n+2).
|
||||
```
|
||||
|
||||
The first line declares the property `is_even`, which, given a natural number, returns proposition.
|
||||
This means that `is_even` is not a proposition itself, but `is_even 0`, `is_even 1`, and `is_even 2`
|
||||
are all propositions.
|
||||
|
||||
The following two lines each encode one of our aforementioned inference rules. The first rule, `zero_even`,
|
||||
is of type `is_even 0`. The `zero_even` rule doesn't require any arguments, and we can use it to create
|
||||
a proof that 0 is even. On the other hand, the `plustwo_even` rule _does_ require an argument, `is_even n`.
|
||||
To construct a proof that a number `n+2` is even using `plustwo_even`, we need to provide a proof
|
||||
that `n` itself is even. From this definition we can see a general principle: we encode each inference
|
||||
rule as constructor of an inductive Coq type. Each rule encoded in this manner takes as arguments
|
||||
the proofs of its premises, and returns a proof of its conclusion.
|
||||
|
||||
For another example, let's encode our simple addition language. First, we have to define the language
|
||||
itself:
|
||||
|
||||
```Coq
|
||||
Inductive tinylang : Type :=
|
||||
| number (n : nat) : tinylang
|
||||
| box (e1 e2 : tinylang) : tinylang.
|
||||
```
|
||||
|
||||
This defines the two elements of our example language: `number n` corresponds to \\(n\\), and `box e1 e2` corresponds
|
||||
to \\(e_1 \square e_2\\). Finally, we define the inference rules:
|
||||
|
||||
```Coq {linenos=true}
|
||||
Inductive tinylang_sem : tinylang -> nat -> Prop :=
|
||||
| number_sem : forall (n : nat), tinylang_sem (number n) n
|
||||
| box_sem : forall (e1 e2 : tinylang) (n1 n2 : nat),
|
||||
tinylang_sem e1 n1 -> tinylang_sem e2 n2 ->
|
||||
tinylang_sem (box e1 e2) (n1 + n2).
|
||||
```
|
||||
|
||||
When we wrote our rules earlier, by using arbitrary variables like \\(e_1\\) and \\(n_1\\), we implicitly meant
|
||||
that our rules work for _any_ number or expression. When writing Coq we have to make this assumption explicit
|
||||
by using `forall`. For instance, the rule on line 2 reads, "for any number `n`, the expression `n` evaluates to `n`".
|
||||
|
||||
#### Semantics of Our Language
|
||||
|
||||
We've now written some example big-step operational semantics, both "on paper" and in Coq. Now, it's time to take a look at
|
||||
the specific semantics of the language from Day 8! Our language consists of a few parts.
|
||||
|
||||
First, there are three opcodes: \\(\texttt{jmp}\\), \\(\\texttt{nop}\\), and \\(\\texttt{add}\\). Opcodes, combined
|
||||
with an integer, make up an instruction. For example, the instruction \\(\\texttt{add} \\; 3\\) will increase the
|
||||
content of the accumulator by three. Finally, a program consists of a sequence of instructions; They're separated
|
||||
by newlines in the puzzle input, but we'll instead separate them by semicolons. For example, here's a complete program.
|
||||
|
||||
{{< latex >}}
|
||||
\texttt{add} \; 0; \; \texttt{nop} \; 2; \; \texttt{jmp} \; -2
|
||||
{{< /latex >}}
|
||||
|
||||
Now, let's try evaluating this program. Starting at the beginning and with 0 in the accumulator,
|
||||
it will add 0 to the accumulator (keeping it the same),
|
||||
do nothing, and finally jump back to the beginning. At this point, it will try to run the addition instruction again,
|
||||
which is not allowed; thus, the program will terminate.
|
||||
|
||||
Did you catch that? The semantics of this language will require more information than just our program itself (which we'll denote by \\(p\\)).
|
||||
* First, to evaluate the program we will need a program counter, \\(\\textit{c}\\). This program counter
|
||||
will tell us the position of the instruction to be executed next. It can also point past the last instruction,
|
||||
which means our program terminated successfully.
|
||||
* Next, we'll need the accumulator \\(a\\). Addition instructions can change the accumulator, and we will be interested
|
||||
in the number that ends up in the accumulator when our program finishes executing.
|
||||
* Finally, and more subtly, we'll need to keep track of the states we visited. For instance,
|
||||
in the course of evaluating our program above, we encounter the \\((c, a)\\) pair of \\((0, 0)\\) twice: once
|
||||
at the beginning, and once at the end. However, whereas at the beginning we have not yet encountered the addition
|
||||
instruction, at the end we have, so the evaluation behaves differently. To make the proofs work better in Coq,
|
||||
we'll use a set \\(v\\) of
|
||||
{{< sidenote "right" "allowed-note" "allowed (valid) program counters (as opposed to visited program counters)." >}}
|
||||
Whereas the set of "visited" program counters keeps growing as our evaluation continues,
|
||||
the set of "allowed" program counters keeps shrinking. Because the "allowed" set never stops shrinking,
|
||||
assuming we're starting with a finite set, our execution will eventually terminate.
|
||||
{{< /sidenote >}}
|
||||
|
||||
Now we have all the elements of our evaluation. Let's define some notation. A program starts at some state,
|
||||
and terminates in another, possibly different state. In the course of a regular evaluation, the program
|
||||
never changes; only the state does. So I propose this (rather unorthodox) notation:
|
||||
|
||||
{{< latex >}}
|
||||
(c, a, v) \Rightarrow_p (c', a', v')
|
||||
{{< /latex >}}
|
||||
|
||||
This reads, "after starting at program counter \\(c\\), accumulator \\(a\\), and set of valid addresses \\(v\\),
|
||||
the program \\(p\\) terminates with program counter \\(c'\\), accumulator \\(a'\\), and set of valid addresses \\(v'\\)".
|
||||
Before creating the inference rules for this evaluation relation, let's define the effect of evaluating a single
|
||||
instruction, using notation \\((c, a) \rightarrow_i (c', a')\\). An addition instruction changes the accumulator,
|
||||
and increases the program counter by 1.
|
||||
|
||||
{{< latex >}}
|
||||
\frac{}
|
||||
{(c, a) \rightarrow_{\texttt{add} \; n} (c+1, a+n)}
|
||||
{{< /latex >}}
|
||||
|
||||
A no-op instruction does even less. All it does is increment the program counter.
|
||||
|
||||
{{< latex >}}
|
||||
\frac{}
|
||||
{(c, a) \rightarrow_{\texttt{nop} \; n} (c+1, a)}
|
||||
{{< /latex >}}
|
||||
|
||||
Finally, a jump instruction leaves the accumulator intact, but adds a number to the program counter itself!
|
||||
|
||||
{{< latex >}}
|
||||
\frac{}
|
||||
{(c, a) \rightarrow_{\texttt{jmp} \; n} (c+n, a)}
|
||||
{{< /latex >}}
|
||||
|
||||
None of these rules have any premises, and they really are quite simple. Now, let's define the rules
|
||||
for evaluating a program. First of all, a program starting in a state that is not considered "valid"
|
||||
is done evaluating, and is in a "failed" state.
|
||||
|
||||
{{< latex >}}
|
||||
\frac{c \not \in v \quad c \not= \text{length}(p)}
|
||||
{(c, a, v) \Rightarrow_{p} (c, a, v)}
|
||||
{{< /latex >}}
|
||||
|
||||
We use \\(\\text{length}(p)\\) to represent the number of instructions in \\(p\\). Note the second premise:
|
||||
even if our program counter \\(c\\) is not included in the valid set, if it's "past the end of the program",
|
||||
the program terminates in an "ok" state.
|
||||
{{< sidenote "left" "avoid-c-note" "Here's a rule for terminating in the \"ok\" state:" >}}
|
||||
In the presented rule, we don't use the variable <code>c</code> all that much, and we know its concrete
|
||||
value (from the equality premise). We could thus avoid introducing the name \(c\) by
|
||||
replacing it with said known value:
|
||||
|
||||
{{< latex >}}
|
||||
\frac{}
|
||||
{(\text{length}(p), a, v) \Rightarrow_{p} (\text{length}(p), a, v)}
|
||||
{{< /latex >}}
|
||||
|
||||
This introduces some duplication, but that is really because all "base case" evaluation rules
|
||||
start and stop in the same state. To work around this, we could define a separate proposition
|
||||
to mean "program \(p\) is done in state \(s\)", then \(s\) will really only need to occur once,
|
||||
and so will \(\text{length}(p)\). This is, in fact, what we will do later on,
|
||||
since being able to talk abut "programs being done" will help us with
|
||||
components of our proof.
|
||||
{{< /sidenote >}}
|
||||
|
||||
{{< latex >}}
|
||||
\frac{c = \text{length}(p)}
|
||||
{(c, a, v) \Rightarrow_{p} (c, a, v)}
|
||||
{{< /latex >}}
|
||||
|
||||
When our program counter reaches the end of the program, we are also done evaluating it. Even though
|
||||
both rules {{< sidenote "right" "redundant-note" "lead to the same conclusion," >}}
|
||||
In fact, if the end of the program is never included in the valid set, the second rule is completely redundant.
|
||||
{{< /sidenote >}}
|
||||
it helps to distinguish the two possible outcomes. Finally, if neither of the termination conditions are met,
|
||||
our program can take a step, and continue evaluating from there.
|
||||
|
||||
{{< latex >}}
|
||||
\frac{c \in v \quad p[c] = i \quad (c, a) \rightarrow_i (c', a') \quad (c', a', v - \{c\}) \Rightarrow_p (c'', a'', v'')}
|
||||
{(c, a, v) \Rightarrow_{p} (c'', a'', v'')}
|
||||
{{< /latex >}}
|
||||
|
||||
This is quite a rule. A lot of things need to work out for a program to evauate from a state that isn't
|
||||
currently the final state:
|
||||
|
||||
* The current program counter \\(c\\) must be valid. That is, it must be an element of \\(v\\).
|
||||
* This program counter must correspond to an instruction \\(i\\) in \\(p\\), which we write as \\(p[c] = i\\).
|
||||
* This instruction must be executed, changing our program counter from \\(c\\) to \\(c'\\) and our
|
||||
accumulator from \\(a\\) to \\(a'\\). The set of valid instructions will no longer include \\(c\\),
|
||||
and will become \\(v - \\{c\\}\\).
|
||||
* Our program must then finish executing, starting at state
|
||||
\\((c', a', v - \\{c\\})\\), and ending in some (unknown) state \\((c'', a'', v'')\\).
|
||||
|
||||
If all of these conditions are met, our program, starting at \\((c, a, v)\\), will terminate in the state \\((c'', a'', v'')\\). This third rule completes our semantics; a program being executed will keep running instructions using the third rule, until it finally
|
||||
hits an invalid program counter (terminating with the first rule) or gets to the end of the program (terminating with the second rule).
|
||||
|
||||
#### Aside: Vectors and Finite \\(\mathbb{N}\\)
|
||||
We'll be getting to the Coq implementation of our semantics soon, but before we do:
|
||||
what type should \\(c\\) be? It's entirely possible for an instruction like \\(\\texttt{jmp} \\; -10000\\)
|
||||
to throw our program counter way before the first instruction of our program, so at first, it seems
|
||||
as though we should use an integer. But the prompt doesn't even specify what should happen in this
|
||||
case - it only says an instruction shouldn't be run twice. The "valid set", although it may help resolve
|
||||
this debate, is our invention, and isn't part of the original specification.
|
||||
|
||||
There is, however, something we can infer from this problem. Since the problem of jumping "too far behind" or
|
||||
"too far ahead" is never mentioned, we can assume that _all jumps will lead either to an instruction,
|
||||
or right to the end of a program_. This means that \\(c\\) is a natural number, with
|
||||
|
||||
{{< latex >}}
|
||||
0 \leq c \leq \text{length}(p)
|
||||
{{< /latex >}}
|
||||
|
||||
In a language like Coq, it's possible to represent such a number. Since we've gotten familliar with
|
||||
inference rules, let's present two rules that define such a number:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{n \in \mathbb{N}^+}
|
||||
{Z : \text{Fin} \; n}
|
||||
\quad
|
||||
\frac
|
||||
{f : \text{Fin} \; n}
|
||||
{S f : \text{Fin} \; (n+1)}
|
||||
{{< /latex >}}
|
||||
|
||||
This is a variation of the [Peano encoding](https://wiki.haskell.org/Peano_numbers) of natural numbers.
|
||||
It reads as follows: zero (\\(Z\\)) is a finite natural number less than any positive natural number \\(n\\). Then, if a finite natural number
|
||||
\\(f\\) is less than \\(n\\), then adding one to that number (using the successor function \\(S\\))
|
||||
will create a natural number less than \\(n+1\\). We encode this in Coq as follows
|
||||
([originally from here](https://coq.inria.fr/library/Coq.Vectors.Fin.html#t)):
|
||||
|
||||
```Coq
|
||||
Inductive t : nat -> Set :=
|
||||
| F1 : forall {n}, t (S n)
|
||||
| FS : forall {n}, t n -> t (S n).
|
||||
```
|
||||
|
||||
The `F1` constructor here is equivalent to our \\(Z\\), and `FS` is equivalent to our \\(S\\).
|
||||
To represent positive natural numbers \\(\\mathbb{N}^+\\), we simply take a regular natural
|
||||
number from \\(\mathbb{N}\\) and find its successor using `S` (simply adding 1). Again, we have
|
||||
to explicitly use `forall` in our type signatures.
|
||||
|
||||
We can use a similar technique to represent a list with a known number of elements, known
|
||||
in the Idris and Coq world as a vector. Again, we only need two inference rules to define such
|
||||
a vector:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{t : \text{Type}}
|
||||
{[] : \text{Vec} \; t \; 0}
|
||||
\quad
|
||||
\frac
|
||||
{x : \text{t} \quad \textit{xs} : \text{Vec} \; t \; n}
|
||||
{(x::\textit{xs}) : \text{Vec} \; t \; (n+1)}
|
||||
{{< /latex >}}
|
||||
|
||||
These rules read: the empty list \\([]\\) is zero-length vector of any type \\(t\\). Then,
|
||||
if we take an element \\(x\\) of type \\(t\\), and an \\(n\\)-long vector \\(\textit{xs}\\) of \\(t\\),
|
||||
then we can prepend \\(x\\) to \\(\textit{xs}\\) and get an \\((n+1)\\)-long vector of \\(t\\).
|
||||
In Coq, we write this as follows ([originally from here](https://coq.inria.fr/library/Coq.Vectors.VectorDef.html#t)):
|
||||
|
||||
```Coq
|
||||
Inductive t A : nat -> Type :=
|
||||
| nil : t A 0
|
||||
| cons : forall (h:A) (n:nat), t A n -> t A (S n).
|
||||
```
|
||||
|
||||
The `nil` constructor represents the empty list \\([]\\), and `cons` represents
|
||||
the operation of prepending an element (called `h` in the code and \\(x\\) in our inference rules)
|
||||
to another vector of length \\(n\\), which remains unnamed in the code but is called \\(\\textit{xs}\\) in our rules.
|
||||
|
||||
These two definitions work together quite well. For instance, suppose we have a vector of length \\(n\\).
|
||||
If we were to access its elements by indices starting at 0, we'd be allowed to access indices 0 through \\(n-1\\).
|
||||
These are precisely the values of the finite natural numbers less than \\(n\\), \\(\\text{Fin} \\; n \\).
|
||||
Thus, given such an index \\(\\text{Fin} \\; n\\) and a vector \\(\\text{Vec} \\; t \\; n\\), we are guaranteed
|
||||
to be able to retrieve the element at the given index! In our code, we will not have to worry about bounds checking.
|
||||
|
||||
Of course, if our program has \\(n\\) elements, our program counter will be a finite number less than \\(n+1\\),
|
||||
since there's always the possibility of it pointing past the instructions, indicating that we've finished
|
||||
running the program. This leads to some minor complications: we can't safely access the program instruction
|
||||
at index \\(\\text{Fin} \\; (n+1)\\). We can solve this problem by considering two cases:
|
||||
either our index points one past the end of the program (in which case its value is exactly the finite
|
||||
representation of \\(n\\)), or it's less than \\(n\\), in which case we can "tighten" the upper bound,
|
||||
and convert that index into a \\(\\text{Fin} \\; n\\). We formalize it in a lemma:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 80 82 >}}
|
||||
|
||||
There's a little bit of a gotcha here. Instead of translating our above statement literally,
|
||||
and returning a value that's the result of "tightening" our input `f`, we return a value
|
||||
`f'` that can be "weakened" to `f`. This is because "tightening" is not a total function -
|
||||
it's not always possible to convert a \\(\\text{Fin} \\; (n+1)\\) into a \\(\\text{Fin} \\; n\\).
|
||||
However, "weakening" \\(\\text{Fin} \\; n\\) _is_ a total function, since a number less than \\(n\\)
|
||||
is, by the transitive property of a total order, also less than \\(n+1\\).
|
||||
|
||||
The Coq proof for this claim is as follows:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 88 97 >}}
|
||||
|
||||
The `Fin.rectS` function is a convenient way to perform inductive proofs over
|
||||
our finite natural numbers. Informally, our proof proceeds as follows:
|
||||
|
||||
* If the current finite natural number is zero, take a look at the "bound" (which
|
||||
we assume is nonzero, since there isn't a natural number less than zero).
|
||||
* If this "bounding number" is one, our `f` can't be tightened any further,
|
||||
since doing so would create a number less than zero. Fortunately, in this case,
|
||||
`n` must be `0`, so `f` is the finite representation of `n`.
|
||||
* Otherwise, `f` is most definitely a weakened version of another `f'`,
|
||||
since the tightest possible type for zero has a "bounding number" of one, and
|
||||
our "bounding number" is greater than that. We return a tighter version of our finite zero.
|
||||
* If our number is a successor of another finite number, we check if that other number
|
||||
can itself be tightened.
|
||||
* If it can't be tightened, then our smaller number is a finite representation of
|
||||
`n-1`. This, in turn, means that adding one to it will be the finite representation
|
||||
of `n` (if \\(x\\) is equal to \\(n-1\\), then \\(x+1\\) is equal to \\(n\\)).
|
||||
* If it _can_ be tightened, then so can the successor (if \\(x\\) is less
|
||||
than \\(n-1\\), then \\(x+1\\) is less than \\(n\\)).
|
||||
|
||||
Next, let's talk about addition, specifically the kind of addition done by the \\(\\texttt{jmp}\\) instruction.
|
||||
We can always add an integer to a natural number, but we can at best guarantee that the result
|
||||
will be an integer. For instance, we can add `-1000` to `1`, and get `-999`, which is _not_ a natural
|
||||
number. We implement this kind of addition in a function called `jump_t`:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 56 56 >}}
|
||||
|
||||
At the moment, its definition is not particularly important. What is important, though,
|
||||
is that it takes a bounded natural number `pc` (our program counter), an integer `off`
|
||||
(the offset provided by the jump instruction) and returns another integer representing
|
||||
the final offset. Why are integers of type `t`? Well, it so happens
|
||||
that Coq provides facilities for working with arbitrary implementations of integers,
|
||||
without relying on how they are implemented under the hood. This can be seen in its
|
||||
[`Coq.ZArith.Int`](https://coq.inria.fr/library/Coq.ZArith.Int.html) module,
|
||||
which describes what functions and types an implementation of integers should provide.
|
||||
Among those is `t`, the type of an integer in such an arbitrary implementation. We too
|
||||
will not make an assumption about how the integers are implemented, and simply
|
||||
use this generic `t` from now on.
|
||||
|
||||
Now, suppose we wanted to write a function that _does_ return a valid program
|
||||
counter after adding the offset to it. Since it's possible for this function to fail
|
||||
(for instance, if the offset is very negative), it has to return `option (fin (S n))`.
|
||||
That is, this function may either fail (returning `None`) or succeed, returning
|
||||
`Some f`, where `f` is of type `fin (S n)`, aka \\(\\text{Fin} \\; (n + 1)\\). Here's
|
||||
the function in Coq (again, don't worry too much about the definition):
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 61 61 >}}
|
||||
|
||||
We will make use of this function when we define and verify our semantics.
|
||||
Let's take a look at that next.
|
||||
|
||||
#### Semantics in Coq
|
||||
|
||||
Now that we've seen finite sets and vectors, it's time to use them to
|
||||
encode our semantics in Coq. Before we do anything else, we need
|
||||
to provide Coq definitions for the various components of our
|
||||
language, much like what we did with `tinylang`. We can start with opcodes:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 20 23 >}}
|
||||
|
||||
Now we can define a few other parts of our language and semantics, namely
|
||||
states, instructions and programs (which I called "inputs" since, we'll, they're
|
||||
our puzzle input). A state is simply the 3-tuple of the program counter, the set
|
||||
of valid program counters, and the accumulator. We write it as follows:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 33 33 >}}
|
||||
|
||||
The star `*` is used here to represent a [product type](https://en.wikipedia.org/wiki/Product_type)
|
||||
rather than arithmetic multiplication. Our state type accepts an argument,
|
||||
`n`, much like a finite natural number or a vector. In fact, this `n` is passed on
|
||||
to the state's program counter and set types. Rightly, a state for a program
|
||||
of length \\(n\\) will not be of the same type as a state for a program of length \\(n+1\\).
|
||||
|
||||
An instruction is also a tuple, but this time containing only two elements: the opcode and
|
||||
the number. We write this as follows:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 36 36 >}}
|
||||
|
||||
Finally, we have to define the type of a program. This type will also be
|
||||
indexed by `n`, the program's length. A program of length `n` is simply a
|
||||
vector of instructions `inst` of length `n`. This leads to the following
|
||||
definition:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 38 38 >}}
|
||||
|
||||
So far, so good! Finally, it's time to get started on the semantics themselves.
|
||||
We begin with the inductive definition of \\((\\rightarrow_i)\\).
|
||||
I think this is fairly straightforward. However, we do use
|
||||
`t` instead of \\(n\\) from the rules, and we use `FS`
|
||||
instead of \\(+1\\). Also, we make the formerly implicit
|
||||
assumption that \\(c+n\\) is valid explicit, by
|
||||
providing a proof that `valid_jump_t pc t = Some pc'`.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 103 110 >}}
|
||||
|
||||
Next, it will help us to combine the premises for
|
||||
"failed" and "ok" terminations into Coq data types.
|
||||
This will make it easier for us to formulate a lemma later on.
|
||||
Here are the definitions:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 112 117 >}}
|
||||
|
||||
Since all of out "termination" rules start and
|
||||
end in the same state, there's no reason to
|
||||
write that state twice. Thus, both `done`
|
||||
and `stuck` only take the input `inp`,
|
||||
and the state, which includes the accumulator
|
||||
`acc`, the set of allowed program counters `v`, and
|
||||
the program counter at which the program came to an end.
|
||||
When the program terminates successfully, this program
|
||||
counter will be equal to the length of the program `n`,
|
||||
so we use `nat_to_fin n`. On the other hand, if the program
|
||||
terminates in as stuck state, it must be that it terminated
|
||||
at a program counter that points to an instruction. Thus, this
|
||||
program counter is actually a \\(\\text{Fin} \\; n\\), and not
|
||||
a \\(\\text{Fin} \\ (n+1)\\), and is not in the set of allowed program counters.
|
||||
We use the same "weakening" trick we saw earlier to represent
|
||||
this.
|
||||
|
||||
Finally, we encode the three inference rules we came up with:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 119 126 >}}
|
||||
|
||||
Notice that we fused two of the premises in the last rule.
|
||||
Instead of naming the instruction at the current program
|
||||
counter (by writing \\(p[c] = i\\)) and using it in another premise, we simply use
|
||||
`nth inp pc`, which corresponds to \\(p[c]\\) in our
|
||||
"paper" semantics.
|
||||
|
||||
Before we go on writing some actual proofs, we have
|
||||
one more thing we have to address. Earlier, we said:
|
||||
|
||||
> All jumps will lead either to an instruction, or right to the end of a program.
|
||||
|
||||
To make Coq aware of this constraint, we'll have to formalize it. To
|
||||
start off, we'll define the notion of a "valid instruction", which is guaranteed
|
||||
to keep the program counter in the correct range.
|
||||
There are a couple of ways to do this, but we'll use yet another definition based
|
||||
on inference rules. First, though, observe that the same instruction may be valid
|
||||
for one program, and invalid for another. For instance, \\(\\texttt{jmp} \\; 100\\)
|
||||
is perfectly valid for a program with thousands of instructions, but if it occurs
|
||||
in a program with only 3 instructions, it will certainly lead to disaster. Specifically,
|
||||
the validity of an instruction depends on the length of the program in which it resides,
|
||||
and the program counter at which it's encountered.
|
||||
Thus, we refine our idea of validity to "being valid for a program of length \\(n\\) at program counter \\(f\\)".
|
||||
For this, we can use the following two inference rules:
|
||||
|
||||
{{< latex >}}
|
||||
\frac
|
||||
{c : \text{Fin} \; n}
|
||||
{\texttt{add} \; t \; \text{valid for} \; n, c }
|
||||
\quad
|
||||
\frac
|
||||
{c : \text{Fin} \; n \quad o \in \{\texttt{nop}, \texttt{jmp}\} \quad J_v(c, t) = \text{Some} \; c' }
|
||||
{o \; t \; \text{valid for} \; n, c }
|
||||
{{< /latex >}}
|
||||
|
||||
The first rule states that if a program has length \\(n\\), then \\(\\texttt{add}\\) is valid
|
||||
at any program counter whose value is less than \\(n\\). This is because running
|
||||
\\(\\texttt{add}\\) will increment the program counter \\(c\\) by 1,
|
||||
and thus, create a new program counter that's less than \\(n+1\\),
|
||||
which, as we discussed above, is perfectly valid.
|
||||
|
||||
The second rule works for the other two instructions. It has an extra premise:
|
||||
the result of `jump_valid_t` (written as \\(J_v\\)) has to be \\(\\text{Some} \\; c'\\),
|
||||
that is, `jump_valid_t` must succeed. Note that we require this even for no-ops,
|
||||
since it later turns out that one of the them may be a jump after all.
|
||||
|
||||
We now have our validity rules. If an instruction satisfies them for a given program
|
||||
and at a given program counter, evaluating it will always result in a program counter that has a proper value.
|
||||
We encode the rules in Coq as follows:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 152 157 >}}
|
||||
|
||||
Note that we have three rules instead of two. This is because we "unfolded"
|
||||
\\(o\\) from our second rule: rather than using set notation (or "or"), we
|
||||
just generated two rules that vary in nothing but the operation involved.
|
||||
|
||||
Of course, we must have that every instruction in a program is valid.
|
||||
We don't really need inference rules for this, as much as a "forall" quantifier.
|
||||
A program \\(p\\) of length \\(n\\) is valid if the following holds:
|
||||
|
||||
{{< latex >}}
|
||||
\forall (c : \text{Fin} \; n). p[c] \; \text{valid for} \; n, c
|
||||
{{< /latex >}}
|
||||
|
||||
That is, for every possible in-bounds program counter \\(c\\),
|
||||
the instruction at the program counter is valid. We can now
|
||||
encode this in Coq, too:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 160 161 >}}
|
||||
|
||||
In the above, `n` is made implicit where possible.
|
||||
Since \\(c\\) (called `pc` in the code) is of type \\(\\text{Fin} \\; n\\), there's no
|
||||
need to write \\(n\\) _again_. The curly braces tell Coq to infer that
|
||||
argument where possible.
|
||||
|
||||
### Proving Termination
|
||||
Here we go! It's finally time to make some claims about our
|
||||
definitions. Who knows - maybe we wrote down total garbage!
|
||||
We will be creating several related lemmas and theorems.
|
||||
All of them share two common assumptions:
|
||||
|
||||
* We have some valid program `inp` of length `n`.
|
||||
* This program is a valid input, that is, `valid_input` holds for it.
|
||||
There's no sense in arguing based on an invalid input program.
|
||||
|
||||
We represent these grouped assumptions by opening a Coq
|
||||
`Section`, which we call `ValidInput`, and listing our assumptions:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 163 166 >}}
|
||||
|
||||
We had to also explicitly mention the length `n` of our program.
|
||||
From now on, the variables `n`, `inp`, and `Hv` will be
|
||||
available to all of the proofs we write in this section.
|
||||
The first proof is rather simple. The claim is:
|
||||
|
||||
> For our valid program, at any program counter `pc`
|
||||
and accumulator `acc`, there must exist another program
|
||||
counter `pc'` and accumulator `acc'` such that the
|
||||
instruction evaluation relation \\((\rightarrow_i)\\)
|
||||
connects the two. That is, valid addresses aside,
|
||||
we can always make a step.
|
||||
|
||||
Here is this claim encoded in Coq:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 168 169 >}}
|
||||
|
||||
We start our proof by introducing all the relevant variables into
|
||||
the global context. I've mentioned this when I wrote about
|
||||
day 1, but here's the gist: the `intros` keyword takes
|
||||
variables from a `forall`, and makes them concrete.
|
||||
In short, `intros x` is very much like saying "suppose
|
||||
we have an `x`", and going on with the proof.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 170 171 >}}
|
||||
|
||||
Here, we said "take any program counter `pc` and any
|
||||
accumulator `acc`". Now what? Well, first of all,
|
||||
we want to take a look at the instruction at the current
|
||||
`pc`. We know that this instruction is a combination
|
||||
of an opcode and a number, so we use `destruct` to get
|
||||
access to both of these parts:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 172 172 >}}
|
||||
|
||||
Now, Coq reports the following proof state:
|
||||
|
||||
```
|
||||
1 subgoal
|
||||
|
||||
n : nat
|
||||
inp : input n
|
||||
Hv : valid_input inp
|
||||
pc : Fin.t n
|
||||
acc : t
|
||||
o : opcode
|
||||
t0 : t
|
||||
Hop : nth inp pc = (o, t0)
|
||||
|
||||
========================= (1 / 1)
|
||||
|
||||
exists (pc' : fin (S n)) (acc' : t),
|
||||
step_noswap (o, t0) (pc, acc) (pc', acc')
|
||||
```
|
||||
|
||||
We have some opcode `o`, and some associated number
|
||||
`t0`, and we must show that there exist a `pc'`
|
||||
and `acc'` to which we can move on. To prove
|
||||
that something exists in Coq, we must provide
|
||||
an instance of that "something". If we claim
|
||||
that there exists a dog that's not a good boy,
|
||||
we better have this elusive creature in hand.
|
||||
In other words, proofs in Coq are [constructive](https://en.wikipedia.org/wiki/Constructive_proof).
|
||||
Without knowing the kind of operation we're dealing with, we can't
|
||||
say for sure how the step will proceed. Thus, we proceed by
|
||||
case analysis on `o`.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 173 173 >}}
|
||||
|
||||
There are three possible cases we have to consider,
|
||||
one for each type of instruction.
|
||||
|
||||
* If the instruction is \\(\\texttt{add}\\), we know
|
||||
that `pc' = pc + 1` and `acc' = acc + t0`. That is,
|
||||
the program counter is simply incremented, and the accumulator
|
||||
is modified with the number part of the instruction.
|
||||
* If the instruction is \\(\\texttt{nop}\\), the program
|
||||
coutner will again be incremented (`pc' = pc + 1`),
|
||||
but the accumulator will stay the same, so `acc' = acc`.
|
||||
* If the instruction is \\(\\texttt{jmp}\\), things are
|
||||
more complicated. We must rely on the assumption
|
||||
that our input is valid, which tells us that adding
|
||||
`t0` to our `pc` will result in `Some f`, and not `None`.
|
||||
Given this, we have `pc' = f`, and `acc' = acc`.
|
||||
|
||||
This is how these three cases are translated to Coq:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 174 177 >}}
|
||||
|
||||
For the first two cases, we simply provide the
|
||||
values we expect for `pc'` and `acc'`, and
|
||||
apply the corresponding inference rule that
|
||||
is satisfied by these values. For the third case, we have
|
||||
to invoke `Hv`, the hypothesis that our input is valid.
|
||||
In particular, we care about the instruction at `pc`,
|
||||
so we use `specialize` to plug `pc` into the more general
|
||||
hypothesis. We then replace `nth inp pc` with its known
|
||||
value, `(jmp, t0)`. This tells us the following, in Coq's words:
|
||||
|
||||
```
|
||||
Hv : valid_inst (jmp, t0) pc
|
||||
```
|
||||
|
||||
That is, `(jmp, t0)` is a valid instruction at `pc`. Then, using
|
||||
Coq's `inversion` tactic, we ask: how is this possible? There is
|
||||
only one inference rule that gives us such a conclusion, and it is named `valid_inst_jmp`
|
||||
in our Coq code. Since we have a proof that our `jmp` is valid,
|
||||
it must mean that this rule was used. Furthermore, since this
|
||||
rule requires that `valid_jump_t` evaluates to `Some f'`, we know
|
||||
that this must be the case here! Coq now has adds the following
|
||||
two lines to our proof state:
|
||||
|
||||
```
|
||||
f' : fin (S n)
|
||||
H0 : valid_jump_t pc t0 = Some f'
|
||||
```
|
||||
|
||||
Finally, we specify, as mentioned earlier, that `pc' = f'` and `acc' = acc`.
|
||||
As before, we apply the corresponding step rule for `jmp`. When it asks
|
||||
for a proof that `valid_jump_t` produces a valid program counter,
|
||||
we hand it `H0` using `apply H0`. And with that, Coq is happy!
|
||||
|
||||
Next, we prove a claim that a valid program can always do _something_,
|
||||
and that something is one of three things:
|
||||
|
||||
* It can terminate in the "ok" state if the program counter
|
||||
reaches the programs' end.
|
||||
* It can terminate with an error if it's currently at a program
|
||||
counter that is not included in the valid set.
|
||||
* Otherwise, it can run the current instruction and advance
|
||||
to a "next" state.
|
||||
|
||||
Alternatively, we could say that one of the inference rules
|
||||
for \\((\\Rightarrow_p)\\) must apply. This is not the case if the input
|
||||
is not valid, since, as I said
|
||||
before, an arbitrary input program can lead us to jump
|
||||
to a negative address (or to an address _way_ past the end of the program).
|
||||
Here's the claim, translated to Coq:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 181 186 >}}
|
||||
|
||||
Informally, we can prove this as follows:
|
||||
|
||||
* If the current program counter is equal to the length
|
||||
of the program, we've reached the end. Thus, the program
|
||||
can terminate in the "ok" state.
|
||||
* Otherwise, the current program counter must be
|
||||
less than the length of the program.
|
||||
* If we've already encountered this program counter (that is,
|
||||
if it's gone from the set of valid program counters),
|
||||
then the program will terminate in the "error" state.
|
||||
* Otherwise, the program counter is in the set of
|
||||
valid instructions. By our earlier theorem, in a valid
|
||||
program, the instruction at any program counter can be correctly
|
||||
executed, taking us to the next state. Now too
|
||||
our program can move to this next state.
|
||||
|
||||
Below is the Coq translation of the above.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 187 203 >}}
|
||||
|
||||
It doesn't seem like we're that far from being done now.
|
||||
A program can always take a step, and each time it does,
|
||||
the set of valid program counters decreases in size. Eventually,
|
||||
this set will become empty, so if nothing else, our program will
|
||||
eventually terminate in an "error" state. Thus, it will stop
|
||||
running no matter what.
|
||||
|
||||
This seems like a task for induction, in this case on the size
|
||||
of the valid set. In particular, strong mathematical induction
|
||||
{{< sidenote "right" "strong-induction-note" "seem to work best." >}}
|
||||
Why strong induction? If we remove a single element from a set,
|
||||
its size should decrease strictly by 1. Thus, why would we need
|
||||
to care about sets of <em>all</em> sizes less than the current
|
||||
set's size?<br>
|
||||
<br>
|
||||
Unfortunately, we're not working with purely mathematical sets.
|
||||
Coq's default facility for sets is simply a layer on top
|
||||
of good old lists, and makes no effort to be "correct by construction".
|
||||
It is thus perfectly possible to have a "set" which inlcudes an element
|
||||
twice. Depending on the implementation of <code>set_remove</code>,
|
||||
we may end up removing the repeated element multiple times, thereby
|
||||
shrinking the length of our list by more than 1. I'd rather
|
||||
not worry about implementation details like that.
|
||||
{{< /sidenote >}}
|
||||
Someone on StackOverflow [implemented this](https://stackoverflow.com/questions/45872719/how-to-do-induction-on-the-length-of-a-list-in-coq),
|
||||
so I'll just use it. The Coq theorem corresonding to strong induction
|
||||
on the length of a list is as follows:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 205 207 >}}
|
||||
|
||||
It reads,
|
||||
|
||||
> If for some list `l`, the property `P` holding for all lists
|
||||
shorter than `l` means that it also holds for `l` itself, then
|
||||
`P` holds for all lists.
|
||||
|
||||
This is perhaps not particularly elucidating. We can alternatively
|
||||
think of this as trying to prove some property for all lists `l`.
|
||||
We start with all empty lists. Here, we have nothing else to rely
|
||||
on; there are no lists shorter than the empty list, and our property
|
||||
must hold for all empty lists. Then, we move on to proving
|
||||
the property for all lists of length 1, already knowing that it holds
|
||||
for all empty lists. Once we're done there, we move on to proving
|
||||
that `P` holds for all lists of length 2, now knowing that it holds
|
||||
for all empty lists _and_ all lists of length 1. We continue
|
||||
doing this, eventually covering lists of any length.
|
||||
|
||||
Before proving termination, there's one last thing we have to
|
||||
take care off. Coq's standard library does not come with
|
||||
a proof that removing an element from a set makes it smaller;
|
||||
we have to provide it ourselves. Here's the claim encoded
|
||||
in Coq:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 217 219 >}}
|
||||
|
||||
This reads, "if a set `s` contains a finite natural
|
||||
number `f`, removing `f` from `s` reduces the set's size".
|
||||
The details of the proof are not particularly interesting,
|
||||
and I hope that you understand intuitively why this is true.
|
||||
Finally, we make our termination claim.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 230 231 >}}
|
||||
|
||||
It's quite a strong claim - given _any_ program counter,
|
||||
set of valid addresses, and accumulator, a valid input program
|
||||
will terminate. Let's take a look at the proof.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 232 234 >}}
|
||||
|
||||
We use `intros` again. However, it brings in variables
|
||||
in order, and we really only care about the _second_ variable.
|
||||
We thus `intros` the first two, and then "put back" the first
|
||||
one using `generalize dependent`. Then, we proceed by
|
||||
induction on length, as seen above.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 235 236>}}
|
||||
|
||||
Now we're in the "inductive step". Our inductive hypothesis
|
||||
is that any set of valid addresses smaller than the current one will
|
||||
guarantee that the program will terminate. We must show
|
||||
that using our set, too, will guarantee termination. We already
|
||||
know that a valid input, given a state, can have one of three
|
||||
possible outcomes: "ok" termination, "failed" termination,
|
||||
or a "step". We use `destruct` to take a look at each of these
|
||||
in turn. The first two cases ("ok" termination and "failed" termination)
|
||||
are fairly trivial:
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 237 240 >}}
|
||||
|
||||
We basically connect the dots between the premises (in a form like `done`)
|
||||
and the corresponding inference rule (`run_noswap_ok`). The more
|
||||
interesting case is when we can take a step.
|
||||
|
||||
{{< codelines "Coq" "aoc-2020/day8.v" 241 253 >}}
|
||||
|
||||
Since we know we can take a step, we know that we'll be removing
|
||||
the current program counter from the set of valid addresses. This
|
||||
set must currently contain the present program counter (since otherwise
|
||||
we'd have "failed"), and thus will shrink when we remove it. This,
|
||||
in turn, lets us use the inductive hypothesis: it tells us that no matter the
|
||||
program counter or accumulator, if we start with this new "shrunk"
|
||||
set, we will terminate in some state. Coq's constructive
|
||||
nature helps us here: it doesn't just tells us that there is some state
|
||||
in which we terminate - it gives us that state! We use `edestruct` to get
|
||||
a handle on this final state, which Coq automatically names `x`. At this
|
||||
time Coq still isn't convinced that our new set is smaller, so we invoke
|
||||
our earlier `set_remove_length` theorem to placate it.
|
||||
|
||||
We now have all the pieces: we know that we can take a step, removing
|
||||
the current program counter from our current set. We also know that
|
||||
with that newly shrunken set, we'll terminate in some final state `x`.
|
||||
Thus, all that's left to say is to apply our "step" rule. It asks
|
||||
us for three things:
|
||||
|
||||
1. That the current program counter is in the set. We've long since
|
||||
established this, and `auto` takes care of that.
|
||||
2. That a step is possible. We've already established this, too,
|
||||
since we're in the "can take a step" case. We apply `Hst`,
|
||||
the hypothesis that confirms that we can, indeed, step.
|
||||
3. That we terminate after this. The `x` we got
|
||||
from our induction hypothesis came with a proof that
|
||||
running with the "next" program counter and accumulator
|
||||
will result in termination. We apply this proof, automatically
|
||||
named `H0` by Coq.
|
||||
|
||||
And that's it! We've proved that a program terminates no matter what.
|
||||
This has also (almost!) given us a solution to part 1. Consider the case
|
||||
in which we start with program counter 0, accumulator 0, and the "full"
|
||||
set of allowed program counters. Since our proof works for _all_ configurations,
|
||||
it will also work for this one. Furthermore, since Coq proofs are constructive,
|
||||
this proof will __return to us the final program counter and accumulator!__
|
||||
This is precisely what we'd need to solve part 1.
|
||||
|
||||
But wait, almost? What's missing? We're missing a few implementation details:
|
||||
* We've not provided a concrete impelmentation of integers. The simplest
|
||||
thing to do here would be to use [`Coq.ZArith.BinInt`](https://coq.inria.fr/library/Coq.ZArith.BinInt.html),
|
||||
for which there is a module [`Z_as_Int`](https://coq.inria.fr/library/Coq.ZArith.Int.html#Z_as_Int)
|
||||
that provides `t` and friends.
|
||||
* We assumed (reasonably, I would say) that it's possible to convert a natural
|
||||
number to an integer. If we're using the aforementioned `BinInt` module,
|
||||
we can use [`Z.of_nat`](https://coq.inria.fr/library/Coq.ZArith.BinIntDef.html#Z.of_nat).
|
||||
* We also assumed (still reasonably) that we can try convert an integer
|
||||
back to a finite natural number, failing if it's too small or too large.
|
||||
There's no built-in function for this, but `Z`, for one, distinguishes
|
||||
between the "positive", "zero", and "negative" cases, and we have
|
||||
`Pos.to_nat` for the positive case.
|
||||
|
||||
Well, I seem to have covered all the implementation details. Why not just
|
||||
go ahead and solve the problem? I tried, and ran into two issues:
|
||||
|
||||
* Although this is "given", we assumed that our input program will be
|
||||
valid. For us to use the result of our Coq proof, we need to provide it
|
||||
a constructive proof that our program is valid. Creating this proof is tedious
|
||||
in theory, and quite difficult in practice: I've run into a
|
||||
strange issue trying to pattern match on finite naturals.
|
||||
* Even supposing we _do_ have a proof of validity, I'm not certain
|
||||
if it's possible to actually extract an answer from it. It seems
|
||||
that Coq distinguishes between proofs (things of type `Prop`) and
|
||||
values (things of type `Set`). things of types `Prop` are supposed
|
||||
to be _erased_. This means that when you convert Coq code,
|
||||
to, say, Haskell, you will see no trace of any `Prop`s in that generated
|
||||
code. Unfortunately, this also means we
|
||||
[can't use our proofs to construct values](https://stackoverflow.com/questions/27322979/why-coq-doesnt-allow-inversion-destruct-etc-when-the-goal-is-a-type),
|
||||
even though our proof objects do indeed contain them.
|
||||
|
||||
So, we "theoretically" have a solution to part 1, down to the algorithm
|
||||
used to compute it and a proof that our algorithm works. In "reality", though, we
|
||||
can't actually use this solution to procure an answer. Like we did with day 1, we'll have
|
||||
to settle for only a proof.
|
||||
|
||||
Let's wrap up for this post. It would be more interesting to devise and
|
||||
formally verify an algorithm for part 2, but this post has already gotten
|
||||
quite long and contains a lot of information. Perhaps I will revisit this
|
||||
at a later time. Thanks for reading!
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: A Language for an Assignment - Homework 2
|
||||
date: 2019-12-30T20:05:10-08:00
|
||||
tags: ["Haskell", "Python", "Algorithms"]
|
||||
tags: ["Haskell", "Python", "Algorithms", "Programming Languages"]
|
||||
---
|
||||
|
||||
After the madness of the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: A Language for an Assignment - Homework 3
|
||||
date: 2020-01-02T22:17:43-08:00
|
||||
tags: ["Haskell", "Python", "Algorithms"]
|
||||
tags: ["Haskell", "Python", "Algorithms", "Programming Languages"]
|
||||
---
|
||||
|
||||
It rained in Sunriver on New Year's Eve, and it continued to rain
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Learning Emulation, Part 2
|
||||
date: 2016-11-23 23:23:18.664038
|
||||
tags: ["C and C++", "Emulation"]
|
||||
---
|
||||
_This is the second post in a series I'm writing about Chip-8 emulation. If you want to see the first one, head [here]({{< ref "/blog/01_learning_emulation.md" >}})._
|
||||
_This is the second post in a series I'm writing about Chip-8 emulation. If you want to see the first one, head [here]({{< relref "/blog/01_learning_emulation.md" >}})._
|
||||
|
||||
Now that we have an understanding of the physical capabilities of a Chip-8 system, we can write code that will represent such a system on our computer. In this post we'll start writing some basic code - be prepared.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Learning Emulation, Part 2.5 - Implementation
|
||||
date: 2016-11-23 23:23:56.633942
|
||||
tags: ["C and C++", "Emulation"]
|
||||
---
|
||||
_This is the third post in a series I'm writing about Chip-8 emulation. If you want to see the first one, head [here]({{< ref "/blog/01_learning_emulation.md" >}})._
|
||||
_This is the third post in a series I'm writing about Chip-8 emulation. If you want to see the first one, head [here]({{< relref "/blog/01_learning_emulation.md" >}})._
|
||||
|
||||
In the previous part of this tutorial, we created a type to represent a basic Chip-8 machine. However, we've done nothing to make it behave like one! Let's start working on that.
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ title: Compiling a Functional Language Using C++, Part 10 - Polymorphism
|
||||
date: 2020-03-25T17:14:20-07:00
|
||||
tags: ["C and C++", "Functional Languages", "Compilers"]
|
||||
description: "In this post, we extend our compiler's typechecking algorithm to implement the Hindley-Milner type system, allowing for polymorphic functions."
|
||||
favorite: true
|
||||
---
|
||||
|
||||
[In part 8]({{< relref "08_compiler_llvm.md" >}}), we wrote some pretty interesting programs in our little language.
|
||||
|
||||
78
content/blog/blog_with_nix.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "Declaratively Deploying Multiple Blog Versions with NixOS and Flakes"
|
||||
date: 2021-10-23T18:01:31-07:00
|
||||
expirydate: 2021-10-23T18:01:31-07:00
|
||||
draft: true
|
||||
tags: ["Hugo", "Nix"]
|
||||
---
|
||||
|
||||
### Prologue
|
||||
|
||||
You can skip this section if you'd like.
|
||||
|
||||
For the last few days, I've been stuck inside of my room due to some kind of cold or flu, which
|
||||
or
|
||||
{{< sidenote "right" "pcr-note" "may or may not be COVID™." >}}
|
||||
The results of the PCR test are pending at the time of writing.
|
||||
{{< /sidenote >}}
|
||||
In seeming correspondence with the progression of my cold, a thought occured in the back of my mind:
|
||||
"_Your blog deployment is kind of a mess_". On the first day, when I felt only a small tingling in
|
||||
my throat, I waved that thought away pretty easily. On the second day, feeling unwell and staying
|
||||
in bed, I couldn't help but start to look up Nix documentation. And finally, on the third day,
|
||||
between coughing fits and overconsumption of oral analgesic, I got to work.
|
||||
|
||||
In short, this post is the closest thing I've written to a fever dream.
|
||||
|
||||
### The Constraints
|
||||
|
||||
I run several versions of this site. The first is, of course, the "production" version, hosted
|
||||
at the time of writing on `danilafe.com` and containing the articles that I would like to share
|
||||
with the world. The second is a version of this site on which drafts are displayed - this
|
||||
way, I can share posts with my friends before they are published, get feedback, and even just
|
||||
re-read what I wrote from any device that has an internet connection. The third is the Russian
|
||||
version of my blog. It's rather empty, because translation is hard work, so it only exists
|
||||
so far as another "draft" website.
|
||||
|
||||
My build process (a derivative of what I describe in [rendering mathematics on the back end]({{< relref "./backend_math_rendering.md" >}})) is also fairly unconventional. When I developed this site, the best
|
||||
form of server-side mathematics rendering was handlded by KaTeX, and required some additional
|
||||
work to get rolling (specifically, I needed to write code to replace sections of LaTeX on
|
||||
a page with their HTML and MathML versions). There may be a better way now, but I haven't yet
|
||||
performed any kind of migration.
|
||||
|
||||
Currently, only my main site is behind HTTPS. However, I would like for it to be possible to
|
||||
adjust this, and possibly even switch my hosts without changing any of the code that actually
|
||||
builds my blog.
|
||||
|
||||
### Why Flakes
|
||||
This article is about using Nix Flakes to manage my configuration. But what is it that made
|
||||
me use flakes? Well, two things:
|
||||
|
||||
* __Adding custom packages__. The Nix code for my blog provides a package / derivation for each
|
||||
version of my website, and I want to use these packages in my `configuration.nix` so that
|
||||
I can point various Nginx virtual hosts to each of them. This is typically done using
|
||||
overlays, but I need a clean way to let my system configuration pull in my blog overlay (or blog
|
||||
packages); flakes solve this issue my letting me specify a blog flake, and pull it in as one
|
||||
of the inputs.
|
||||
* __Versioning__. My process for deploying new versions of the site prior to flakes boiled down to fetcing
|
||||
the latest commit from the `master` branch of my blog repository, and updating the `default.nix`
|
||||
file with that commit. This way, I could reliably fetch the version of my site that
|
||||
I want published. Flakes do the same thing: the `flake.lock` file
|
||||
contains the hashes of the Git-based dependencies of a flake, and thus prevents builds from
|
||||
accidentally pulling in something else. However, unlike my approach, which relies on custom
|
||||
scripts and extra tools such as `jq`, the locking mechanism used by flakes is provided with
|
||||
standard Nix tooling. Using Flakes also guarantees that my build process won't break with
|
||||
updates to Hugo or Ruby, since the `nixpkgs` version is stored in `flake.lock`, too.
|
||||
|
||||
### The Final Result
|
||||
Here's the relevant section of my configuration:
|
||||
|
||||
{{< codelines "Nix" "server-config/configuration.nix" 42 59 >}}
|
||||
|
||||
I really like how this turned out for three reasons. First,
|
||||
it's very clear from the configuration what I want from my server:
|
||||
three virtual hosts, one with HTTPS, one with drafts, and one with drafts and
|
||||
_in Russian_. Second, there's plenty of code reuse. I'm using two builder functions,
|
||||
`english` and `russian`, but under the hood, the exact same code is being
|
||||
used to run Hugo and all the necessay post-processing. Finally, all of this can be
|
||||
used pretty much immediately given my blog flake, which reduces the amount of glue
|
||||
code I have to write.
|
||||
@@ -2,6 +2,7 @@
|
||||
title: "How Many Values Does a Boolean Have?"
|
||||
date: 2020-08-21T23:05:55-07:00
|
||||
tags: ["Java", "Haskell", "C and C++"]
|
||||
favorite: true
|
||||
---
|
||||
|
||||
A friend of mine recently had an interview for a software
|
||||
|
||||
BIN
content/blog/codelines/example.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
268
content/blog/codelines/index.md
Normal file
@@ -0,0 +1,268 @@
|
||||
---
|
||||
title: "Pleasant Code Includes with Hugo"
|
||||
date: 2021-01-13T21:31:29-08:00
|
||||
tags: ["Hugo"]
|
||||
---
|
||||
|
||||
Ever since I started [the compiler series]({{< relref "00_compiler_intro.md" >}}),
|
||||
I began to include more and more fragments of code into my blog.
|
||||
I didn't want to be copy-pasting my code between my project
|
||||
and my Markdown files, so I quickly wrote up a Hugo [shortcode](https://gohugo.io/content-management/shortcodes/)
|
||||
to pull in other files in the local directory. I've since improved on this
|
||||
some more, so I thought I'd share what I created with others.
|
||||
|
||||
### Including Entire Files and Lines
|
||||
My needs for snippets were modest at first. For the most part,
|
||||
I had a single code file that I wanted to present, so it was
|
||||
acceptable to plop it in the middle of my post in one piece.
|
||||
The shortcode for that was quite simple:
|
||||
|
||||
```
|
||||
{{ highlight (readFile (printf "code/%s" (.Get 1))) (.Get 0) "" }}
|
||||
```
|
||||
|
||||
This leverages Hugo's built-in [`highlight`](https://gohugo.io/functions/highlight/)
|
||||
function to provide syntax highlighting to the included snippet. Hugo
|
||||
doesn't guess at the language of the code, so you have to manually provide
|
||||
it. Calling this shortcode looks as follows:
|
||||
|
||||
```
|
||||
{{</* codeblock "C++" "compiler/03/type.hpp" */>}}
|
||||
```
|
||||
|
||||
Note that this implicitly adds the `code/` prefix to all
|
||||
the files I include. This is a personal convention: I want
|
||||
all my code to be inside a dedicated directory.
|
||||
|
||||
Of course, including entire files only takes you so far.
|
||||
What if you only need to discuss a small part of your code?
|
||||
Alternaitvely, what if you want to present code piece-by-piece,
|
||||
in the style of literate programming? I quickly ran into the
|
||||
need to do this, for which I wrote another shortcode:
|
||||
|
||||
```
|
||||
{{ $s := (readFile (printf "code/%s" (.Get 1))) }}
|
||||
{{ $t := split $s "\n" }}
|
||||
{{ if not (eq (int (.Get 2)) 1) }}
|
||||
{{ .Scratch.Set "u" (after (sub (int (.Get 2)) 1) $t) }}
|
||||
{{ else }}
|
||||
{{ .Scratch.Set "u" $t }}
|
||||
{{ end }}
|
||||
{{ $v := first (add (sub (int (.Get 3)) (int (.Get 2))) 1) (.Scratch.Get "u") }}
|
||||
{{ if (.Get 4) }}
|
||||
{{ .Scratch.Set "opts" (printf ",%s" (.Get 4)) }}
|
||||
{{ else }}
|
||||
{{ .Scratch.Set "opts" "" }}
|
||||
{{ end }}
|
||||
{{ highlight (delimit $v "\n") (.Get 0) (printf "linenos=table,linenostart=%d%s" (.Get 2) (.Scratch.Get "opts")) }}
|
||||
```
|
||||
|
||||
This shortcode takes a language and a filename as before, but it also takes
|
||||
the numbers of the first and last lines indicating the part of the code that should be included. After
|
||||
splitting the contents of the file into lines, it throws away all lines before and
|
||||
after the window of code that you want to include. It seems to me (from my commit history)
|
||||
that Hugo's [`after`](https://gohugo.io/functions/after/) function (which should behave
|
||||
similarly to Haskell's `drop`) doesn't like to be given an argument of `0`.
|
||||
I had to add a special case for when this would occur, where I simply do not invoke `after` at all.
|
||||
The shortcode can be used as follows:
|
||||
|
||||
```
|
||||
{{</* codelines "C++" "compiler/04/ast.cpp" 19 22 */>}}
|
||||
```
|
||||
|
||||
To support a fuller range of Hugo's functionality, I also added an optional argument that
|
||||
accepts Hugo's Chroma settings. This way, I can do things like highlight certain
|
||||
lines in my code snippet, which is done as follows:
|
||||
|
||||
```
|
||||
{{</* codelines "Idris" "typesafe-interpreter/TypesafeIntrV3.idr" 31 39 "hl_lines=7 8 9" */>}}
|
||||
```
|
||||
|
||||
Note that the `hl_lines` field doesn't seem to work properly with `linenostart`, which means
|
||||
that the highlighted lines are counted from 1 no matter what. This is why in the above snippet,
|
||||
although I include lines 31 through 39, I feed lines 7, 8, and 9 to `hl_lines`. It's unusual,
|
||||
but hey, it works!
|
||||
|
||||
### Linking to Referenced Code
|
||||
Some time after implementing my initial system for including lines of code,
|
||||
I got an email from a reader who pointed out that it was hard for them to find
|
||||
the exact file I was referencing, and to view the surrounding context of the
|
||||
presented lines. To address this, I decided that I'd include the link
|
||||
to the file in question. After all, my website and all the associated
|
||||
code is on a [Git server I host](https://dev.danilafe.com/Web-Projects/blog-static),
|
||||
so any local file I'm referencing should -- assuming it was properly committed --
|
||||
show up there, too. I hardcoded the URL of the `code` directory on the web interface,
|
||||
and appended the relative path of each included file to it. The shortcode came out as follows:
|
||||
|
||||
```
|
||||
{{ $s := (readFile (printf "code/%s" (.Get 1))) }}
|
||||
{{ $t := split $s "\n" }}
|
||||
{{ if not (eq (int (.Get 2)) 1) }}
|
||||
{{ .Scratch.Set "u" (after (sub (int (.Get 2)) 1) $t) }}
|
||||
{{ else }}
|
||||
{{ .Scratch.Set "u" $t }}
|
||||
{{ end }}
|
||||
{{ $v := first (add (sub (int (.Get 3)) (int (.Get 2))) 1) (.Scratch.Get "u") }}
|
||||
{{ if (.Get 4) }}
|
||||
{{ .Scratch.Set "opts" (printf ",%s" (.Get 4)) }}
|
||||
{{ else }}
|
||||
{{ .Scratch.Set "opts" "" }}
|
||||
{{ end }}
|
||||
<div class="highlight-group">
|
||||
<div class="highlight-label">From <a href="https://dev.danilafe.com/Web-Projects/blog-static/src/branch/master/code/{{ .Get 1 }}">{{ path.Base (.Get 1) }}</a>,
|
||||
{{ if eq (.Get 2) (.Get 3) }}line {{ .Get 2 }}{{ else }} lines {{ .Get 2 }} through {{ .Get 3 }}{{ end }}</div>
|
||||
{{ highlight (delimit $v "\n") (.Get 0) (printf "linenos=table,linenostart=%d%s" (.Get 2) (.Scratch.Get "opts")) }}
|
||||
</div>
|
||||
```
|
||||
|
||||
This results in code blocks like the one in the image below. The image
|
||||
is the result of the `codelines` call for the Idris language, presented above.
|
||||
|
||||
{{< figure src="example.png" caption="An example of how the code looks." class="medium" >}}
|
||||
|
||||
I got a lot of mileage out of this setup . . . until I wanted to include code from _other_ git repositories.
|
||||
For instance, I wanted to talk about my [Advent of Code](https://adventofcode.com/) submissions,
|
||||
without having to copy-paste the code into my blog repository!
|
||||
|
||||
### Code from Submodules
|
||||
My first thought when including code from other repositories was to use submodules.
|
||||
This has the added advantage of "pinning" the version of the code I'm talking about,
|
||||
which means that even if I push significant changes to the other repository, the code
|
||||
in my blog will remain the same. This, in turn, means that all of my `codelines`
|
||||
shortcodes will work as intended.
|
||||
|
||||
The problem is, most Git web interfaces (my own included) don't display paths corresponding
|
||||
to submodules. Thus, even if all my code is checked out and Hugo correctly
|
||||
pulls the selected lines into its HTML output, the _links to the file_ remain
|
||||
broken!
|
||||
|
||||
There's no easy way to address this, particularly because _different submodules
|
||||
can be located on different hosts_! The Git URL used for a submodule is
|
||||
not known to Hugo (since, to the best of my knowledge, it can't run
|
||||
shell commands), and it could reside on `dev.danilafe.com`, or `github.com`,
|
||||
or elsewhere. Fortunately, it's fairly easy to tell when a file is part
|
||||
of a submodule, and which submodule that is. It's sufficient to find
|
||||
the longest submodule path that matches the selected file. If no
|
||||
submodule path matches, then the file is part of the blog repository,
|
||||
and no special action is needed.
|
||||
|
||||
Of course, this means that Hugo needs to be made aware of the various
|
||||
submodules in my repository. It also needs to be aware of the submodules
|
||||
_inside_ those submodules, and so on: it needs to be recursive. Git
|
||||
has a command to list all submodules recursively:
|
||||
|
||||
```Bash
|
||||
git submodule status --recursive
|
||||
```
|
||||
|
||||
However, this only prints the commit, submodule path, and the upstream branch.
|
||||
I don't think there's a way to list the remotes' URLs with this command; however,
|
||||
we do _need_ the URLs, since that's how we create links to the Git web interfaces.
|
||||
|
||||
There's another issue: how do we let Hugo know about the various submodules,
|
||||
even if we can find them? Hugo can read files, but doing any serious
|
||||
text processing is downright impractical. However, Hugo
|
||||
itself is not able to run commands, so it needs to be able to read in
|
||||
the output of another command that _can_ find submodules.
|
||||
|
||||
I settled on using Hugo's `params` configuration option. This
|
||||
allows users to communicate arbitrary properties to Hugo themes
|
||||
and templates. In my case, I want to communicate a collection
|
||||
of submodules. I didn't know about TOML's inline tables, so
|
||||
I decided to represent this collection as a map of (meaningless)
|
||||
submodule names to tables:
|
||||
|
||||
```TOML
|
||||
[params]
|
||||
[params.submoduleLinks]
|
||||
[params.submoduleLinks.aoc2020]
|
||||
url = "https://dev.danilafe.com/Advent-of-Code/AdventOfCode-2020/src/commit/7a8503c3fe1aa7e624e4d8672aa9b56d24b4ba82"
|
||||
path = "aoc-2020"
|
||||
```
|
||||
|
||||
Since it was seemingly impossible to wrangle Git into outputting
|
||||
all of this information using one command, I decided
|
||||
to write a quick Ruby script to generate a list of submodules
|
||||
as follows. I had to use `cd` in one of my calls to Git
|
||||
because Git's `--git-dir` option doesn't seem to work
|
||||
with submodules, treating them like a "bare" checkout.
|
||||
I also chose to use an allowlist of remote URLs,
|
||||
since the URL format for linking to files in a
|
||||
particular repository differs from service to service.
|
||||
For now, I only use my own Git server, so only `dev.danilafe.com`
|
||||
is allowed; however, just by adding `elsif`s to my code,
|
||||
I can add other services in the future.
|
||||
|
||||
```Ruby
|
||||
puts "[params]"
|
||||
puts " [params.submoduleLinks]"
|
||||
|
||||
def each_submodule(base_path)
|
||||
`cd #{base_path} && git submodule status`.lines do |line|
|
||||
hash, path = line[1..].split " "
|
||||
full_path = "#{base_path}/#{path}"
|
||||
url = `git config --file #{base_path}/.gitmodules --get 'submodule.#{path}.url'`.chomp.delete_suffix(".git")
|
||||
safe_name = full_path.gsub(/\/|-|_\./, "")
|
||||
|
||||
if url =~ /dev.danilafe.com/
|
||||
file_url = "#{url}/src/commit/#{hash}"
|
||||
else
|
||||
raise "Submodule URL #{url.dump} not in a known format!"
|
||||
end
|
||||
|
||||
yield ({ :path => full_path, :url => file_url, :name => safe_name })
|
||||
each_submodule(full_path) { |m| yield m }
|
||||
end
|
||||
end
|
||||
|
||||
each_submodule(".") do |m|
|
||||
next unless m[:path].start_with? "./code/"
|
||||
puts " [params.submoduleLinks.#{m[:name].delete_prefix(".code")}]"
|
||||
puts " url = #{m[:url].dump}"
|
||||
puts " path = #{m[:path].delete_prefix("./code/").dump}"
|
||||
end
|
||||
```
|
||||
|
||||
I pipe the output of this script into a separate configuration file
|
||||
called `config-gen.toml`, and then run Hugo as follows:
|
||||
|
||||
```
|
||||
hugo --config config.toml,config-gen.toml
|
||||
```
|
||||
|
||||
Finally, I had to modify my shortcode to find and handle the longest submodule prefix.
|
||||
Here's the relevant portion, and you can
|
||||
[view the entire file here](https://dev.danilafe.com/Web-Projects/blog-static/src/commit/bfeae89ab52d1696c4a56768b7f0c6682efaff82/themes/vanilla/layouts/shortcodes/codelines.html).
|
||||
|
||||
```
|
||||
{{ .Scratch.Set "bestLength" -1 }}
|
||||
{{ .Scratch.Set "bestUrl" (printf "https://dev.danilafe.com/Web-Projects/blog-static/src/branch/master/code/%s" (.Get 1)) }}
|
||||
{{ $filePath := (.Get 1) }}
|
||||
{{ $scratch := .Scratch }}
|
||||
{{ range $module, $props := .Site.Params.submoduleLinks }}
|
||||
{{ $path := index $props "path" }}
|
||||
{{ $bestLength := $scratch.Get "bestLength" }}
|
||||
{{ if and (le $bestLength (len $path)) (hasPrefix $filePath $path) }}
|
||||
{{ $scratch.Set "bestLength" (len $path) }}
|
||||
{{ $scratch.Set "bestUrl" (printf "%s%s" (index $props "url") (strings.TrimPrefix $path $filePath)) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
And that's what I'm using at the time of writing!
|
||||
|
||||
### Conclusion
|
||||
My current system for code includes allows me to do the following
|
||||
things:
|
||||
|
||||
* Include entire files or sections of files into the page. This
|
||||
saves me from having to copy and paste code manually, which
|
||||
is error prone and can cause inconsistencies.
|
||||
* Provide links to the files I reference on my Git interface.
|
||||
This allows users to easily view the entire file that I'm talking about.
|
||||
* Correctly link to files in repositories other than my blog
|
||||
repository, when they are included using submodules. This means
|
||||
I don't need to manually copy and update code from other projects.
|
||||
|
||||
I hope some of these shortcodes and script come in handy for someone else.
|
||||
Thank you for reading!
|
||||
377
content/blog/coq_dawn.md
Normal file
@@ -0,0 +1,377 @@
|
||||
---
|
||||
title: "Formalizing Dawn in Coq"
|
||||
date: 2021-11-20T19:04:57-08:00
|
||||
tags: ["Coq", "Dawn", "Programming Languages"]
|
||||
description: "In this article, we use Coq to write down machine-checked semantics for the untyped concatenative calculus."
|
||||
---
|
||||
|
||||
The [_Foundations of Dawn_](https://www.dawn-lang.org/posts/foundations-ucc/) article came up
|
||||
on [Lobsters](https://lobste.rs/s/clatuv/foundations_dawn_untyped_concatenative) recently.
|
||||
In this article, the author of Dawn defines a core calculus for the language, and provides its
|
||||
semantics. The core calculus is called the _untyped concatenative calculus_, or UCC.
|
||||
The definitions in the semantics seemed so clean and straightforward that I wanted to try my hand at
|
||||
translating them into machine-checked code. I am most familiar with [Coq](https://coq.inria.fr/),
|
||||
and that's what I reached for when making this attempt.
|
||||
|
||||
### Defining the Syntax
|
||||
#### Expressions and Intrinsics
|
||||
This is mostly the easy part. A UCC expression is one of three things:
|
||||
|
||||
* An "intrinsic", written \\(i\\), which is akin to a built-in function or command.
|
||||
* A "quote", written \\([e]\\), which takes a UCC expression \\(e\\) and moves it onto the stack (UCC is stack-based).
|
||||
* A composition of several expressions, written \\(e_1\\ e_2\\ \\ldots\\ e_n\\), which effectively evaluates them in order.
|
||||
|
||||
This is straightforward to define in Coq, but I'm going to make a little simplifying change.
|
||||
Instead of making "composition of \\(n\\) expressions" a core language feature, I'll only
|
||||
allow "composition of \\(e_1\\) and \\(e_2\\)", written \\(e_1\\ e_2\\). This change does not
|
||||
in any way reduce the power of the language; we can still
|
||||
{{< sidenote "right" "assoc-note" "write \(e_1\ e_2\ \ldots\ e_n\) as \((e_1\ e_2)\ \ldots\ e_n\)." >}}
|
||||
The same expression can, of course, be written as \(e_1\ \ldots\ (e_{n-1}\ e_n)\).
|
||||
So, which way should we <em>really</em> use when translating the many-expression composition
|
||||
from the Dawn article into the two-expression composition I am using here? Well, the answer is,
|
||||
it doesn't matter; expression composition is <em>associative</em>, so both ways effectively mean
|
||||
the same thing.<br>
|
||||
<br>
|
||||
This is quite similar to what we do in algebra: the regular old addition operator, \(+\) is formally
|
||||
only defined for pairs of numbers, like \(a+b\). However, no one really bats an eye when we
|
||||
write \(1+2+3\), because we can just insert parentheses any way we like, and get the same result:
|
||||
\((1+2)+3\) is the same as \(1+(2+3)\).
|
||||
{{< /sidenote >}}
|
||||
With that in mind, we can translate each of the three types of expressions in UCC into cases
|
||||
of an inductive data type in Coq.
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 12 15 >}}
|
||||
|
||||
Why do we need `e_int`? We do because a token like \\(\\text{swap}\\) can be viewed
|
||||
as belonging to the set of intrinsics \\(i\\), or the set of expressions, \\(e\\). While writing
|
||||
down the rules in mathematical notation, what exactly the token means is inferred from context - clearly
|
||||
\\(\\text{swap}\\ \\text{drop}\\) is an expression built from two other expressions. In statically-typed
|
||||
functional languages like Coq or Haskell, however, the same expression can't belong to two different,
|
||||
arbitrary types. Thus, to turn an intrinsic into an expression, we need to wrap it up in a constructor,
|
||||
which we called `e_int` here. Other than that, `e_quote` accepts as argument another expression, `e` (the
|
||||
thing being quoted), and `e_comp` accepts two expressions, `e1` and `e2` (the two sub-expressions being composed).
|
||||
|
||||
The definition for intrinsics themselves is even simpler:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 4 10 >}}
|
||||
|
||||
We simply define a constructor for each of the six intrinsics. Since none of the intrinsic
|
||||
names are reserved in Coq, we can just call our constructors exactly the same as their names
|
||||
in the written formalization.
|
||||
|
||||
#### Values and Value Stacks
|
||||
Values are up next. My initial thought was to define a value much like
|
||||
I defined an intrinsic expression: by wrapping an expression in a constructor for a new data
|
||||
type. Something like:
|
||||
|
||||
```Coq
|
||||
Inductive value :=
|
||||
| v_quot (e : expr).
|
||||
```
|
||||
|
||||
Then, `v_quot (e_int swap)` would be the Coq translation of the expression \\([\\text{swap}]\\).
|
||||
However, I didn't decide on this approach for two reasons:
|
||||
|
||||
* There are now two ways to write a quoted expression: either `v_quote e` to represent
|
||||
a quoted expression that is a value, or `e_quote e` to represent a quoted expression
|
||||
that is just an expression. In the extreme case, the value \\([[e]]\\) would
|
||||
be represented by `v_quote (e_quote e)` - two different constructors for the same concept,
|
||||
in the same expression!
|
||||
* When formalizing the lambda calculus,
|
||||
[Programming Language Foundations](https://softwarefoundations.cis.upenn.edu/plf-current/Stlc.html)
|
||||
uses an inductively-defined property to indicate values. In the simply typed lambda calculus,
|
||||
much like in UCC, values are a subset of expressions.
|
||||
|
||||
I took instead the approach from Programming Language Foundations: a value is merely an expression
|
||||
for which some predicate, `IsValue`, holds. We will define this such that `IsValue (e_quote e)` is provable,
|
||||
but also such that here is no way to prove `IsValue (e_int swap)`, since _that_ expression is not
|
||||
a value. But what does "provable" mean, here?
|
||||
|
||||
By the [Curry-Howard correspondence](https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence),
|
||||
a predicate is just a function that takes _something_ and returns a type. Thus, if \\(\\text{Even}\\)
|
||||
is a predicate, then \\(\\text{Even}\\ 3\\) is actually a type. Since \\(\\text{Even}\\) takes
|
||||
numbers in, it is a predicate on numbers. Our \\(\\text{IsValue}\\) predicate will be a predicate
|
||||
on expressions, instead. In Coq, we can write this as:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 19 19 >}}
|
||||
|
||||
You might be thinking,
|
||||
|
||||
> Huh, `Prop`? But you just said that predicates return types!
|
||||
|
||||
This is a good observation; In Coq, `Prop` is a special sort of type that corresponds to logical
|
||||
propositions. It's special for a few reasons, but those reasons are beyond the scope of this post;
|
||||
for our purposes, it's sufficient to think of `IsValue e` as a type.
|
||||
|
||||
Alright, so what good is this new `IsValue e` type? Well, we will define `IsValue` such that
|
||||
this type is only _inhabited_ if `e` is a value according to the UCC specification. A type
|
||||
is inhabited if and only if we can find a value of that type. For instance, the type of natural
|
||||
numbers, `nat`, is inhabited, because any number, like `0`, has this type. Uninhabited types
|
||||
are harder to come by, but take as an example the type `3 = 4`, the type of proofs that three is equal
|
||||
to four. Three is _not_ equal to four, so we can never find a proof of equality, and thus, `3 = 4` is
|
||||
uninhabited. As I said, `IsValue e` will only be inhabited if `e` is a value per the formal
|
||||
specification of UCC; specifically, this means that `e` is a quoted expression, like `e_quote e'`.
|
||||
|
||||
To this end, we define `IsValue` as follows:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 19 20 >}}
|
||||
|
||||
Now, `IsValue` is a new data type with only only constructor, `ValQuote`. For any expression `e`,
|
||||
this constructor creates a value of type `IsValue (e_quote e)`. Two things are true here:
|
||||
|
||||
* Since `Val_quote` accepts any expression `e` to be put inside `e_quote`, we can use
|
||||
`Val_quote` to create an `IsValue` instance for any quoted expression.
|
||||
* Because `Val_quote` is the _only_ constructor, and because it always returns `IsValue (e_quote e)`,
|
||||
there's no way to get `IsValue (e_int i)`, or anything else.
|
||||
|
||||
Thus, `IsValue e` is inhabited if and only if `e` is a UCC value, as we intended.
|
||||
|
||||
Just one more thing. A value is just an expression, but Coq only knows about this as long
|
||||
as there's an `IsValue` instance around to vouch for it. To be able to reason about values, then,
|
||||
we will need both the expression and its `IsValue` proof. Thus, we define the type `value` to mean
|
||||
a pair of two things: an expression `v` and a proof that it's a value, `IsValue v`:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 22 22 >}}
|
||||
|
||||
A value stack is just a list of values:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 23 23 >}}
|
||||
|
||||
### Semantics
|
||||
Remember our `IsValue` predicate? Well, it's not just any predicate, it's a _unary_ predicate.
|
||||
_Unary_ means that it's a predicate that only takes one argument, an expression in our case. However,
|
||||
this is far from the only type of predicate. Here are some examples:
|
||||
|
||||
* Equality, `=`, is a binary predicate in Coq. It takes two arguments, say `x` and `y`, and builds
|
||||
a type `x = y` that is only inhabited if `x` and `y` are equal.
|
||||
* The mathematical "less than" relation is also a binary predicate, and it's called `le` in Coq.
|
||||
It takes two numbers `n` and `m` and returns a type `le n m` that is only inhabited if `n` is less
|
||||
than or equal to `m`.
|
||||
* The evaluation relation in UCC is a ternary predicate. It takes two stacks, `vs` and `vs'`,
|
||||
and an expression, `e`, and creates a type that's inhabited if and only if evaluating
|
||||
`e` starting at a stack `vs` results in the stack `vs'`.
|
||||
|
||||
Binary predicates are just functions of two inputs that return types. For instance, here's what Coq has
|
||||
to say about the type of `eq`:
|
||||
|
||||
```
|
||||
eq : ?A -> ?A -> Prop
|
||||
```
|
||||
|
||||
By a similar logic, ternary predicates, much like UCC's evaluation relation, are functions
|
||||
of three inputs. We can thus write the type of our evaluation relation as follows:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 35 35 >}}
|
||||
|
||||
We define the constructors just like we did in our `IsValue` predicate. For each evaluation
|
||||
rule in UCC, such as:
|
||||
|
||||
{{< latex >}}
|
||||
\langle V, v, v'\rangle\ \text{swap}\ \rightarrow\ \langle V, v', v \rangle
|
||||
{{< /latex >}}
|
||||
|
||||
We introduce a constructor. For the `swap` rule mentioned above, the constructor looks like this:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 28 28 >}}
|
||||
|
||||
Although the stacks are written in reverse order (which is just a consequence of Coq's list notation),
|
||||
I hope that the correspondence is fairly clear. If it's not, try reading this rule out loud:
|
||||
|
||||
> The rule `Sem_swap` says that for every two values `v` and `v'`, and for any stack `vs`,
|
||||
evaluating `swap` in the original stack `v' :: v :: vs`, aka \\(\\langle V, v, v'\\rangle\\),
|
||||
results in a final stack `v :: v' :: vs`, aka \\(\\langle V, v', v\\rangle\\).
|
||||
|
||||
With that in mind, here's a definition of a predicate `Sem_int`, the evaluation predicate
|
||||
for intrinsics:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 27 33 >}}
|
||||
|
||||
Hey, what's all this with `v_quote` and `projT1`? It's just a little bit of bookkeeping.
|
||||
Given a value -- a pair of an expression `e` and a proof `IsValue e` -- the function `projT1`
|
||||
just returns the expression `e`. That is, it's basically a way of converting a value back into
|
||||
an expression. The function `v_quote` takes us in the other direction: given an expression \\(e\\),
|
||||
it constructs a quoted expression \\([e]\\), and combines it with a proof that the newly constructed
|
||||
quote is a value.
|
||||
|
||||
The above two function in combination help us define the `quote` intrinsic, which
|
||||
wraps a value on the stack in an additional layer of quotes. When we create a new quote, we
|
||||
need to push it onto the value stack, so it needs to be a value; we thus use `v_quote`. However,
|
||||
`v_quote` needs an expression to wrap in a quote, so we use `projT1` to extract the expression from
|
||||
the value on top of the stack.
|
||||
|
||||
In addition to intrinsics, we also define the evaluation relation for actual expressions.
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 35 39 >}}
|
||||
|
||||
Here, we may as well go through the three constructors to explain what they mean:
|
||||
|
||||
* `Sem_e_int` says that if the expression being evaluated is an intrinsic, and if the
|
||||
intrinsic has an effect on the stack as described by `Sem_int` above, then the effect
|
||||
of the expression itself is the same.
|
||||
* `Sem_e_quote` says that if the expression is a quote, then a corresponding quoted
|
||||
value is placed on top of the stack.
|
||||
* `Sem_e_comp` says that if one expression `e1` changes the stack from `vs1` to `vs2`,
|
||||
and if another expression `e2` takes this new stack `vs2` and changes it into `vs3`,
|
||||
then running the two expressions one after another (i.e. composing them) means starting
|
||||
at stack `vs1` and ending in stack `vs3`.
|
||||
|
||||
### \\(\\text{true}\\), \\(\\text{false}\\), \\(\\text{or}\\) and Proofs
|
||||
Now it's time for some fun! The UCC language specification starts by defining two values:
|
||||
true and false. Why don't we do the same thing?
|
||||
|
||||
|UCC Spec| Coq encoding |
|
||||
|---|----|
|
||||
|\\(\\text{false}\\)=\\([\\text{drop}]\\)| {{< codelines "Coq" "dawn/Dawn.v" 41 42 >}}
|
||||
|\\(\\text{true}\\)=\\([\\text{swap} \\ \\text{drop}]\\)| {{< codelines "Coq" "dawn/Dawn.v" 44 45 >}}
|
||||
|
||||
Let's try prove that these two work as intended.
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 47 53 >}}
|
||||
|
||||
This is the first real proof in this article. Rather than getting into the technical details,
|
||||
I invite you to take a look at the "shape" of the proof:
|
||||
|
||||
* After the initial use of `intros`, which brings the variables `v`, `v`, and `vs` into
|
||||
scope, we start by applying `Sem_e_comp`. Intuitively, this makes sense - at the top level,
|
||||
our expression, \\(\\text{false}\\ \\text{apply}\\),
|
||||
is a composition of two other expressions, \\(\\text{false}\\) and \\(\\text{apply}\\).
|
||||
Because of this, we need to use the rule from our semantics that corresponds to composition.
|
||||
* The composition rule requires that we describe the individual effects on the stack of the
|
||||
two constituent expressions (recall that the first expression takes us from the initial stack `v1`
|
||||
to some intermediate stack `v2`, and the second expression takes us from that stack `v2` to the
|
||||
final stack `v3`). Thus, we have two "bullet points":
|
||||
* The first expression, \\(\\text{false}\\), is just a quoted expression. Thus, the rule
|
||||
`Sem_e_quote` applies, and the contents of the quote are puhsed onto the stack.
|
||||
* The second expression, \\(\\text{apply}\\), is an intrinsic, so we need to use the rule
|
||||
`Sem_e_int`, which handles the intrinsic case. This, in turn, requires that we show
|
||||
the effect of the intrinsic itself; the `apply` intrinsic evaluates the quoted expression
|
||||
on the stack.
|
||||
The quoted expression contains the body of false, or \\(\\text{drop}\\). This is
|
||||
once again an intrinsic, so we use `Sem_e_int`; the intrinsic in question is \\(\\text{drop}\\),
|
||||
so the `Sem_drop` rule takes care of that.
|
||||
|
||||
Following these steps, we arrive at the fact that evaluating `false` on the stack simply drops the top
|
||||
element, as specified. The proof for \\(\\text{true}\\) is very similar in spirit:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 55 63 >}}
|
||||
|
||||
We can also formalize the \\(\\text{or}\\) operator:
|
||||
|
||||
|UCC Spec| Coq encoding |
|
||||
|---|----|
|
||||
|\\(\\text{or}\\)=\\(\\text{clone}\\ \\text{apply}\\)| {{< codelines "Coq" "dawn/Dawn.v" 65 65 >}}
|
||||
|
||||
We can write two top-level proofs about how this works: the first says that \\(\\text{or}\\),
|
||||
when the first argument is \\(\\text{false}\\), just returns the second argument (this is in agreement
|
||||
with the truth table, since \\(\\text{false}\\) is the identity element of \\(\\text{or}\\)).
|
||||
The proof proceeds much like before:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 67 73 >}}
|
||||
|
||||
To shorten the proof a little bit, I used the `Proof with` construct from Coq, which runs
|
||||
an additional _tactic_ (like `apply`) whenever `...` is used.
|
||||
Because of this, in this proof writing `apply Sem_apply...` is the same
|
||||
as `apply Sem_apply. apply Sem_e_int`. Since the `Sem_e_int` rule is used a lot, this makes for a
|
||||
very convenient shorthand.
|
||||
|
||||
Similarly, we prove that \\(\\text{or}\\) applied to \\(\\text{true}\\) always returns \\(\\text{true}\\).
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 75 83 >}}
|
||||
|
||||
Finally, the specific facts (like \\(\\text{false}\\ \\text{or}\\ \\text{false}\\) evaluating to \\(\\text{false}\\))
|
||||
can be expressed using our two new proofs, `or_false_v` and `or_true`.
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 85 88 >}}
|
||||
|
||||
### Derived Expressions
|
||||
#### Quotes
|
||||
The UCC specification defines \\(\\text{quote}_n\\) to make it more convenient to quote
|
||||
multiple terms. For example, \\(\\text{quote}_2\\) composes and quotes the first two values
|
||||
on the stack. This is defined in terms of other UCC expressions as follows:
|
||||
|
||||
{{< latex >}}
|
||||
\text{quote}_n = \text{quote}_{n-1}\ \text{swap}\ \text{quote}\ \text{swap}\ \text{compose}
|
||||
{{< /latex >}}
|
||||
|
||||
We can write this in Coq as follows:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 90 94 >}}
|
||||
|
||||
This definition diverges slightly from the one given in the UCC specification; particularly,
|
||||
UCC's spec mentions that \\(\\text{quote}_n\\) is only defined for \\(n \\geq 1\\).However,
|
||||
this means that in our code, we'd have to somehow handle the error that would arise if the
|
||||
term \\(\\text{quote}\_0\\) is used. Instead, I defined `quote_n n` to simply mean
|
||||
\\(\\text{quote}\_{n+1}\\); thus, in Coq, no matter what `n` we use, we will have a valid
|
||||
expression, since `quote_n 0` will simply correspond to \\(\\text{quote}_1 = \\text{quote}\\).
|
||||
|
||||
We can now attempt to prove that this definition is correct by ensuring that the examples given
|
||||
in the specification are valid. We may thus write,
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 96 106 >}}
|
||||
|
||||
We used a new tactic here, `repeat`, but overall, the structure of the proof is pretty straightforward:
|
||||
the definition of `quote_n` consists of many intrinsics, and we apply the corresponding rules
|
||||
one-by-one until we arrive at the final stack. Writing this proof was kind of boring, since
|
||||
I just had to see which intrinsic is being used in each step, and then write a line of `apply`
|
||||
code to handle that intrinsic. This gets worse for \\(\\text{quote}_3\\):
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 108 122 >}}
|
||||
|
||||
It's so long! Instead, I decided to try out Coq's `Ltac2` mechanism to teach Coq how
|
||||
to write proofs like this itself. Here's what I came up with:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 124 136 >}}
|
||||
|
||||
You don't have to understand the details, but in brief, this checks what kind of proof
|
||||
we're asking Coq to do (for instance, if we're trying to prove that a \\(\\text{swap}\\)
|
||||
instruction has a particular effect), and tries to apply a corresponding semantic rule.
|
||||
Thus, it will try `Sem_swap` if the expression is \\(\\text{swap}\\),
|
||||
`Sem_clone` if the expression is \\(\\text{clone}\\), and so on. Then, the two proofs become:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 138 144 >}}
|
||||
|
||||
#### Rotations
|
||||
There's a little trick to formalizing rotations. Values have an important property:
|
||||
when a value is run against a stack, all it does is place itself on a stack. We can state
|
||||
this as follows:
|
||||
|
||||
{{< latex >}}
|
||||
\langle V \rangle\ v = \langle V\ v \rangle
|
||||
{{< /latex >}}
|
||||
|
||||
Or, in Coq,
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 148 149 >}}
|
||||
|
||||
This is the trick to how \\(\\text{rotate}_n\\) works: it creates a quote of \\(n\\) reordered and composed
|
||||
values on the stack, and then evaluates that quote. Since evaluating each value
|
||||
just places it on the stack, these values end up back on the stack, in the same order that they
|
||||
were in the quote. When writing the proof, `solve_basic ()` gets us almost all the way to the
|
||||
end (evaluating a list of values against a stack). Then, we simply apply the composition
|
||||
rule over and over, following it up with `eval_value` to prove that the each value is just being
|
||||
placed back on the stack.
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 156 168 >}}
|
||||
|
||||
### `e_comp` is Associative
|
||||
When composing three expressions, which way of inserting parentheses is correct?
|
||||
Is it \\((e_1\\ e_2)\\ e_3\\)? Or is it \\(e_1\\ (e_2\\ e_3)\\)? Well, both!
|
||||
Expression composition is associative, which means that the order of the parentheses
|
||||
doesn't matter. We state this in the following theorem, which says that the two
|
||||
ways of writing the composition, if they evaluate to anything, evaluate to the same thing.
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 170 171 >}}
|
||||
|
||||
### Conclusion
|
||||
That's all I've got in me for today. However, we got pretty far! The UCC specification
|
||||
says:
|
||||
|
||||
> One of my long term goals for UCC is to democratize formal software verification in order to make it much more feasible and realistic to write perfect software.
|
||||
|
||||
I think that UCC is definitely getting there: formally defining the semantics outlined
|
||||
on the page was quite straightforward. We can now have complete confidence in the behavior
|
||||
of \\(\\text{true}\\), \\(\\text{false}\\), \\(\\text{or}\\), \\(\\text{quote}_n\\) and
|
||||
\\(\\text{rotate}_n\\). The proof of associativity is also enough to possibly argue for simplifying
|
||||
the core calculus' syntax even more. All of this we got from an official source, with only
|
||||
a little bit of tweaking to get from the written description of the language to code! I'm
|
||||
looking forward to reading the next post about the _multistack_ concatenative calculus.
|
||||
BIN
content/blog/coq_dawn_eval/coq_eval_chain_base.png
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
content/blog/coq_dawn_eval/coq_eval_chain_inductive.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
content/blog/coq_dawn_eval/coq_eval_chain_merge.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
content/blog/coq_dawn_eval/coq_eval_empty.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
content/blog/coq_dawn_eval/coq_eval_lists.png
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
content/blog/coq_dawn_eval/coq_eval_one.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
content/blog/coq_dawn_eval/coq_eval_two.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
638
content/blog/coq_dawn_eval/index.md
Normal file
@@ -0,0 +1,638 @@
|
||||
---
|
||||
title: "A Verified Evaluator for the Untyped Concatenative Calculus"
|
||||
date: 2021-11-27T20:24:57-08:00
|
||||
tags: ["Dawn", "Coq", "Programming Languages"]
|
||||
---
|
||||
|
||||
Earlier, I wrote [an article]({{< relref "./coq_dawn" >}}) in which I used Coq to
|
||||
encode the formal semantics of [Dawn's Untyped Concatenative Calculus](https://www.dawn-lang.org/posts/foundations-ucc/),
|
||||
and to prove a few things about the mini-language. Though I'm quite happy with how that turned out,
|
||||
my article was missing something that's present on the original Dawn page -- an evaluator. In this article, I'll define
|
||||
an evaluator function in Coq, prove that it's equivalent to Dawn's formal semantics,
|
||||
and extract all of this into usable Haskell code.
|
||||
|
||||
### Changes Since Last Time
|
||||
In trying to write and verify this evaluator, I decided to make changes to the way I formalized
|
||||
the UCC. Remember how we used a predicate, `IsValue`, to tag expressions that were values?
|
||||
It turns out that this is a very cumbersome approach. For one thing, this formalization
|
||||
allows for the case in which the exact same expression is a value for two different
|
||||
reasons. Although `IsValue` has only one constructor (`Val_quote`), it's actually
|
||||
{{< sidenote "right" "hott-note" "not provable" >}}
|
||||
Interestingly, it's also not provable that any two proofs of \(a = b\) are equal,
|
||||
even though equality only has one constructor, \(\text{refl}\ :\ a \rightarrow (a = a) \).
|
||||
Under the <a href="https://homotopytypetheory.org/book/">homotopic interpretation</a>
|
||||
of type theory, this corresponds to the fact that two paths from \(a\) to \(b\) need
|
||||
not be homotopic (continuously deformable) to each other.<br>
|
||||
<br>
|
||||
As an intuitive example, imagine wrapping a string around a pole. Holding the ends of
|
||||
the string in place, there's nothing you can do to "unwrap" the string. This string
|
||||
is thus not deformable into a string that starts and stops at the same points,
|
||||
but does not go around the pole.
|
||||
{{< /sidenote >}}
|
||||
that any two proofs of `IsValue e` are equal. I ended up getting into a lot of losing
|
||||
arguments with the Coq runtime about whether or not two stacks are actually the same.
|
||||
Also, some of the semantic rules expected a value on the stack with a particular proof
|
||||
for `IsValue`, and rejected the exact same stack with a generic value.
|
||||
|
||||
Thus, I switched from our old implementation:
|
||||
|
||||
{{< codelines "Coq" "dawn/Dawn.v" 19 22 >}}
|
||||
|
||||
To the one I originally had in mind:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnV2.v" 19 19 >}}
|
||||
|
||||
I then had the following function to convert a value back into an equivalent expression:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnV2.v" 22 25 >}}
|
||||
|
||||
I replaced instances of `projT1` with instances of `value_to_expr`.
|
||||
|
||||
### Where We Are
|
||||
At the end of my previous article, we ended up with a Coq encoding of the big-step
|
||||
[operational semantics](https://en.wikipedia.org/wiki/Operational_semantics)
|
||||
of UCC, as well as some proofs of correctness about the derived forms from
|
||||
the article (like \\(\\text{quote}_3\\) and \\(\\text{rotate}_3\\)). The trouble
|
||||
is, despite having our operational semantics, we can't make our Coq
|
||||
code run anything. This is for several reasons:
|
||||
|
||||
1. Our definitions only let us to _confirm_ that given some
|
||||
initial stack, a program ends up in some other final stack. We even have a
|
||||
little `Ltac2` tactic to help us automate this kind of proof. However, in an evaluator,
|
||||
the final stack is not known until the program finishes running. We can't
|
||||
confirm the result of evaluation, we need to _find_ it.
|
||||
2. To address the first point, we could try write a function that takes a program
|
||||
and an initial stack, and produces a final stack, as well as a proof that
|
||||
the program would evaluate to this stack under our semantics. However,
|
||||
it's quite easy to write a non-terminating UCC program, whereas functions
|
||||
in Coq _have to terminate!_ We can't write a terminating function to
|
||||
run non-terminating code.
|
||||
|
||||
So, is there anything we can do? No, there isn't. Writing an evaluator in Coq
|
||||
is just not possible. The end, thank you for reading.
|
||||
|
||||
Just kidding -- there's definitely a way to get our code evaluating, but it
|
||||
will look a little bit strange.
|
||||
|
||||
### A Step-by-Step Evaluator
|
||||
The trick is to recognize that program evaluation
|
||||
occurs in steps. There may well be an infinite number of steps, if the program
|
||||
is non-terminating, but there's always a step we can take. That is, unless
|
||||
an invalid instruction is run, like trying to clone from an empty stack, or unless
|
||||
the program finished running. You don't need a non-terminating function to just
|
||||
give you a next step, if one exists. We can write such a function in Coq.
|
||||
|
||||
Here's a new data type that encodes the three situations we mentioned in the
|
||||
previous paragraph. Its constructors (one per case) are as follows:
|
||||
|
||||
1. `err` - there are no possible evaluation steps due to an error.
|
||||
3. `middle e s` - the evaluation took a step; the stack changed to `s`, and the rest of the program is `e`.
|
||||
2. `final s` - there are no possible evaluation steps because the evaluation is complete,
|
||||
leaving a final stack `s`.
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 6 9 >}}
|
||||
|
||||
We can now write a function that tries to execute a single step given an expression.
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 11 27 >}}
|
||||
|
||||
Most intrinsics, by themselves, complete after just one step. For instance, a program
|
||||
consisting solely of \\(\\text{swap}\\) will either fail (if the stack doesn't have enough
|
||||
values), or it will swap the top two values and be done. We list only "correct" cases,
|
||||
and resort to a "catch-all" case on line 26 that returns an error. The one multi-step
|
||||
intrinsic is \\(\\text{apply}\\), which can evaluate an arbitrary expression from the stack.
|
||||
In this case, the "one step" consists of popping the quoted value from the stack; the
|
||||
"remaining program" is precisely the expression that was popped.
|
||||
|
||||
Quoting an expression also always completes in one step (it simply places the quoted
|
||||
expression on the stack). The really interesting case is composition. Expressions
|
||||
are evaluated left-to-right, so we first determine what kind of step the left expression (`e1`)
|
||||
can take. We may need more than one step to finish up with `e1`, so there's a good chance it
|
||||
returns a "rest program" `e1'` and a stack `vs'`. If this happens, to complete evaluation of
|
||||
\\(e_1\\ e_2\\), we need to first finish evaluating \\(e_1'\\), and then evaluate \\(e_2\\).
|
||||
Thus, the "rest of the program" is \\(e_1'\\ e_2\\), or `e_comp e1' e2`. On the other hand,
|
||||
if `e1` finished evaluating, we still need to evaluate `e2`, so our "rest of the program"
|
||||
is \\(e_2\\), or `e2`. If evaluating `e1` led to an error, then so did evaluating `e_comp e1 e2`,
|
||||
and we return `err`.
|
||||
|
||||
### Extracting Code
|
||||
Just knowing a single step is not enough to run the code. We need something that repeatedly
|
||||
tries to take a step, as long as it's possible. However, this part is once again
|
||||
not possible in Coq, as it brings back the possibility of non-termination. So if we can't use
|
||||
Coq, why don't we use another language? Coq's extraction mechanism allows us to do just that.
|
||||
|
||||
I added the following code to the end of my file:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 231 235 >}}
|
||||
|
||||
Coq happily produces a new file, `UccGen.hs` with a lot of code. It's not exactly the most
|
||||
aesthetic; here's a quick peek:
|
||||
|
||||
```Haskell
|
||||
data Intrinsic =
|
||||
Swap
|
||||
| Clone
|
||||
| Drop
|
||||
| Quote
|
||||
| Compose
|
||||
| Apply
|
||||
|
||||
data Expr =
|
||||
E_int Intrinsic
|
||||
| E_quote Expr
|
||||
| E_comp Expr Expr
|
||||
|
||||
data Value =
|
||||
V_quote Expr
|
||||
|
||||
-- ... more
|
||||
```
|
||||
|
||||
All that's left is to make a new file, `Ucc.hs`. I use a different file so that
|
||||
Coq doesn't overwrite my changes every time I re-run extraction. In this
|
||||
file, we place the "glue code" that tries running one step after another:
|
||||
|
||||
{{< codelines "Coq" "dawn/Ucc.hs" 46 51 >}}
|
||||
|
||||
Finally, loading up GHCi using `ghci Ucc.hs`, I can run the following commands:
|
||||
|
||||
```
|
||||
ghci> fromList = foldl1 E_comp
|
||||
ghci> test = eval [] $ fromList [true, false, UccGen.or]
|
||||
ghci> :f test
|
||||
test = Just [V_quote (E_comp (E_int Swap) (E_int Drop))]
|
||||
```
|
||||
|
||||
That is, applying `or` to `true` and `false` results a stack with only `true` on top.
|
||||
As expected, and proven by our semantics!
|
||||
|
||||
### Proving Equivalence
|
||||
Okay, so `true false or` evaluates to `true`, much like our semantics claims.
|
||||
However, does our evaluator _always_ match the semantics? So far, we have not
|
||||
claimed or verified that it does. Let's try giving it a shot.
|
||||
|
||||
#### First Steps and Evaluation Chains
|
||||
The first thing we can do is show that if we have a proof that `e` takes
|
||||
initial stack `vs` to final stack `vs'`, then each
|
||||
`eval_step` "makes progress" towards `vs'` (it doesn't simply _return_
|
||||
`vs'`, since it only takes a single step and doesn't always complete
|
||||
the evaluation). We also want to show that if the semantics dictates
|
||||
`e` finishes in stack `vs'`, then `eval_step` will never return an error.
|
||||
Thus, we have two possibilities:
|
||||
|
||||
* `eval_step` returns `final`. In this case, for it to match our semantics,
|
||||
the final stack must be the same as `vs'`. Here's the relevant section
|
||||
from the Coq file:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 30 30 >}}
|
||||
* `eval_step` returns `middle`. In this case, the "rest of the program" needs
|
||||
to evaluate to `vs'` according to our semantics (otherwise, taking a step
|
||||
has changed the program's final outcome, which should not happen).
|
||||
We need to quantify the new variables (specifically, the "rest of the
|
||||
program", which we'll call `ei`, and the "stack after one step", `vsi`),
|
||||
for which we use Coq's `exists` clause. The whole relevant statement is as
|
||||
follows:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 31 33 >}}
|
||||
|
||||
The whole theorem statement is as follows:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 29 33 >}}
|
||||
|
||||
I have the Coq proof script for this (in fact, you can click the link
|
||||
at the top of the code block to view the original source file). However,
|
||||
there's something unsatisfying about this statement. In particular,
|
||||
how do we prove that an entire sequence of steps evaluates
|
||||
to something? We'd have to examine the first step, checking if
|
||||
it's a "final" step or a "middle" step; if it's a "middle" step,
|
||||
we'd have to move on to the "rest of the program" and repeat the process.
|
||||
Each time we'd have to "retrieve" `ei` and `vsi` from `eval_step_correct`,
|
||||
and feed it back to `eval_step`.
|
||||
|
||||
I'll do you one better: how do we even _say_ that an expression "evaluates
|
||||
step-by-step to final stack `vs'`"? For one step, we can say:
|
||||
|
||||
```Coq
|
||||
eval_step vs e = final vs'
|
||||
```
|
||||
|
||||
Here's a picture so you can start visualizing what it looks like. The black line represents
|
||||
a single "step".
|
||||
|
||||
{{< figure src="coq_eval_empty.png" caption="Visual representation of a single-step evaluation." class="small" alt="Two dots connected by a line. One dot is labeled \"vs\", and the other \"vs1\"." >}}
|
||||
|
||||
For two steps, we'd have to assert the existence of an intermediate
|
||||
expression (the "rest of the program" after the first step):
|
||||
|
||||
```Coq
|
||||
exists ei vsi,
|
||||
eval_step vs e = middle ei vsi /\ (* First step to intermediate expression. *)
|
||||
eval_step vsi ei = final vs' (* Second step to final state. *)
|
||||
```
|
||||
|
||||
Once again, here's a picture. I've highlighted the intermediate state, `vsi`, in
|
||||
a brighter color.
|
||||
{{< figure src="coq_eval_one.png" caption="Visual representation of a two-step evaluation." class="small" alt="Three dots connected by lines. The first dot is labeled \"vs\", the next \"vsi\", and the last \"vs1\". The second dot is highlighted." >}}
|
||||
|
||||
For three steps:
|
||||
|
||||
```Coq
|
||||
exists ei1 ei2 vsi1 vsi2,
|
||||
eval_step vs e = middle ei1 vsi1 /\ (* First step to intermediate expression. *)
|
||||
eval_step vsi1 ei1 = middle ei2 vsi2 /\ (* Second intermediate step *)
|
||||
eval_step vsi2 ei2 = final vs' (* Second step to final state. *)
|
||||
```
|
||||
|
||||
I can imagine that you're getting the idea, but here's one last picture:
|
||||
{{< figure src="coq_eval_two.png" caption="Visual representation of a three-step evaluation." class="small" alt="Four dots connected by lines. The first dot is labeled \"vs\", the next \"vsi1\", the one after \"vsi2\", and the last \"vs1\". The second and third dots are highlighted." >}}
|
||||
|
||||
The Coq code for this is awful! Not only is this a lot of writing, but it also makes various
|
||||
sequences of steps have a different "shape". This way, we can't make
|
||||
proofs about evaluations of an _arbitrary_ number of steps. Not all is lost, though: if we squint
|
||||
a little at the last example (three steps), a pattern starts to emerge.
|
||||
First, let's re-arrange the `exists` quantifiers:
|
||||
|
||||
```Coq
|
||||
exists ei1 vsi1, eval_step vs e = middle ei1 vsi1 /\ (* Cons *)
|
||||
exists ei2 vsi2, eval_step vsi1 ei1 = middle ei2 vsi2 /\ (* Cons *)
|
||||
eval_step vsi2 ei2 = final vs' (* Nil *)
|
||||
```
|
||||
|
||||
If you squint at this, it kind of looks like a list! The "empty"
|
||||
part of a list is the final step, while the "cons" part is a middle step. The
|
||||
analogy holds up for another reason: an "empty" sequence has zero intermediate
|
||||
expressions, while each "cons" introduces a single new intermediate
|
||||
program.
|
||||
|
||||
{{< figure src="coq_eval_lists.png" caption="Evaluation sequences as lists." class="large" alt="The three previous figures are drawn together, each next to its list representation." >}}
|
||||
|
||||
Perhaps we can define a new data type that matches this? Indeed
|
||||
we can!
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 64 67 >}}
|
||||
|
||||
The new data type is parameterized by the initial and final stacks, as well
|
||||
as the expression that starts in the former and ends in the latter.
|
||||
Then, consider the following _type_:
|
||||
|
||||
```Coq
|
||||
eval_chain nil (e_comp (e_comp true false) or) (true :: nil)
|
||||
```
|
||||
|
||||
This is the type of sequences (or chains) of steps corresponding to the
|
||||
evaluation of the program \\(\\text{true}\\ \\text{false}\\ \\text{or}\\),
|
||||
starting in an empty stack and evaluating to a stack with only
|
||||
\\(\\text{true}\\) on top. Thus to say that an expression evaluates to some
|
||||
final stack `vs'`, in _some unknown number of steps_, it's sufficient to write:
|
||||
|
||||
```Coq
|
||||
eval_chain vs e vs'
|
||||
```
|
||||
|
||||
Evaluation chains have a couple of interesting properties. First and foremost,
|
||||
they can be "concatenated". This is analogous to the `Sem_e_comp` rule
|
||||
in our original semantics: if an expression `e1` starts in stack `vs`
|
||||
and finishes in stack `vs'`, and another expression starts in stack `vs'`
|
||||
and finishes in stack `vs''`, then we can compose these two expressions,
|
||||
and the result will start in `vs` and finish in `vs''`.
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 69 75 >}}
|
||||
|
||||
{{< figure src="coq_eval_chain_merge.png" caption="Merging evaluation chains." class="large" alt="Two evaluation chains, one starting in \"vs\" and ending in \"vs'\", and one starting in \"vs'\" and ending in \"vs''\", are combined into one. The new chain starts in \"vs\", ends in \"vs''\", and has \"vs'\" in the middle. " >}}
|
||||
|
||||
The proof is very short. We go
|
||||
{{< sidenote "right" "concat-note" "by induction on the left evaluation chain" >}}
|
||||
It's not a coincidence that defining something like <code>(++)</code>
|
||||
(list concatenation) in Haskell typically starts by pattern matching
|
||||
on the <em>left</em> list. In fact, proofs by <code>induction</code>
|
||||
actually correspond to recursive functions! It's tough putting code blocks
|
||||
in sidenotes, but if you're playing with the
|
||||
<a href="https://dev.danilafe.com/Web-Projects/blog-static/src/branch/master/code/dawn/DawnEval.v"><code>DawnEval.v</code></a> file, try running
|
||||
<code>Print eval_chain_ind</code>, and note the presence of <code>fix</code>,
|
||||
the <a href="https://en.wikipedia.org/wiki/Fixed-point_combinator">fixed point
|
||||
combinator</a> used to implement recursion.
|
||||
{{< /sidenote >}}
|
||||
(the one for `e1`). Coq takes care of most of the rest with `auto`.
|
||||
The key to this proof is that whatever `P` is contained within a "node"
|
||||
in the left chain determines how `eval_step (e_comp e1 e2)` behaves. Whether
|
||||
`e1` evaluates to `final` or `middle`, the composition evaluates to `middle`
|
||||
(a composition of two expressions cannot be done in one step), so we always
|
||||
{{< sidenote "left" "cons-note" "create a new \"cons\" node." >}}
|
||||
This is <em>unlike</em> list concatenation, since we typically don't
|
||||
create new nodes when appending to an empty list.
|
||||
{{< /sidenote >}} via `chain_middle`. Then, the two cases are as follows.
|
||||
|
||||
If the step was `final`, then
|
||||
the rest of the steps use only `e2`, and good news, we already have a chain
|
||||
for that!
|
||||
{{< figure src="coq_eval_chain_base.png" caption="Merging evaluation chains when the first chain only has one step." class="large" alt="A single-step chain connected to another by a line labeled \"chain_middle\"." >}}
|
||||
|
||||
Otherwise, the step was `middle`, an we still have a chain for some
|
||||
intermediate program `ei` that starts in some stack `vsi` and ends in `vs'`.
|
||||
By induction, we know that _this_ chain can be concatenated with the one
|
||||
for `e2`, resulting in a chain for `e_comp ei e2`. All that remains is to
|
||||
attach to this sub-chain the one step from `(vs, e1)` to `(vsi, ei)`, which
|
||||
is handled by `chain_middle`.
|
||||
{{< figure src="coq_eval_chain_inductive.png" caption="Merging evaluation chains when the first chain has a middle step and others." class="large" alt="Visualization of the inductive case of merging chains." >}}
|
||||
|
||||
The `merge` operation is reversible; chains can be split into two pieces,
|
||||
one for each composed expression:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 77 78 >}}
|
||||
|
||||
While interesting, this particular fact isn't used anywhere else in this
|
||||
post, and I will omit the proof here.
|
||||
|
||||
#### The Forward Direction
|
||||
Let's try rewording our very first proof, `eval_step_correct`, using
|
||||
chains. The _direction_ remains the same: given that an expression
|
||||
produces a final stack under the formal semantics, we need to
|
||||
prove that this expression _evaluates_ to the same final stack using
|
||||
a sequence of `eval_step`.
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 93 96 >}}
|
||||
|
||||
The power of this theorem is roughly the same as that of
|
||||
the original one: we can use `eval_step_correct` to build up a chain
|
||||
by applying it over and over, and we can take the "first element"
|
||||
of a chain to serve as a witness for `eval_step_correct`. However,
|
||||
the formulation is arguably significantly cleaner, and contains
|
||||
a proof for _all_ steps right away, rather than a single one.
|
||||
|
||||
Before we go through the proof, notice that there's actually _two_
|
||||
theorems being stated here, which depend on each other. This
|
||||
is not surprising given that our semantics are given using two
|
||||
data types, `Sem_expr` and `Sem_int`, each of which contains the other.
|
||||
Regular proofs by induction, which work on only one of the data types,
|
||||
break down because we can't make claims "by induction" about the _other_
|
||||
type. For example, while going by induction on `Sem_expr`, we run into
|
||||
issues in the `e_int` case while handling \\(\\text{apply}\\). We know
|
||||
a single step -- popping the value being run from the stack. But what then?
|
||||
The rule for \\(\\text{apply}\\) in `Sem_int` contains another `Sem_expr`
|
||||
which confirms that the quoted value properly evaluates. But this other
|
||||
`Sem_expr` isn't directly a child of the "bigger" `Sem_expr`, and so we
|
||||
don't get an inductive hypothesis about it. We know nothing about it; we're stuck.
|
||||
|
||||
We therefore have two theorems declared together using `with` (just like we
|
||||
used `with` to declare `Sem_expr` and `Sem_int`). We have to prove both,
|
||||
which is once again a surprisingly easy task thanks to Coq's `auto`. Let's
|
||||
start with the first theorem, the one for expression semantics.
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 98 107 >}}
|
||||
|
||||
We go by induction on the semantics data type. There are therefore three cases:
|
||||
intrinsics, quotes, and composition. The intrinsic case is handed right
|
||||
off to the second theorem (which we'll see momentarily). The quote case
|
||||
is very simple since quoted expressions are simply pushed onto the stack in
|
||||
a single step (we thus use `chain_final`). Finally, in the composition
|
||||
case, we have two sub-proofs, one for each expression being evaluated.
|
||||
By induction, we know that each of these sub-proofs can be turned into
|
||||
a chain, and we use `eval_chain_merge` to combine these two chains into one.
|
||||
That's it.
|
||||
|
||||
Now, let's try the second theorem. The code is even shorter:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 108 115 >}}
|
||||
|
||||
The first command, `inversion Hsem`, lets us go case-by-case on the various
|
||||
ways an intrinsic can be evaluated. Most intrinsics are quite boring;
|
||||
in our evaluator, they only need a single step, and their semantics
|
||||
rules guarantee that the stack has the exact kind of data that the evaluator
|
||||
expects. We dismiss such cases with `apply chain_final; auto`. The only
|
||||
time this doesn't work is when we encounter the \\(\\text{apply}\\) intrinsic;
|
||||
in that case, however, we can simply use the first theorem we proved.
|
||||
|
||||
#### A Quick Aside: Automation Using `Ltac2`
|
||||
Going in the other direction will involve lots of situations in which we _know_
|
||||
that `eval_step` evaluated to something. Here's a toy proof built around
|
||||
one such case:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 237 246 "hl_lines=5-8">}}
|
||||
|
||||
The proof claims that if the `swap` instruction was evaluated to something,
|
||||
then the initial stack must contain two values, and the final stack must
|
||||
have those two values on top but flipped. This is very easy to prove, since
|
||||
that's the exact behavior of `eval_step` for the `swap` intrinsic. However,
|
||||
notice how much boilerplate we're writing: lines 241 through 244 deal entirely
|
||||
with ensuring that our stack does, indeed, have two values. This is trivially true:
|
||||
if the stack _didn't_ have two values, it wouldn't evaluate to `final`, but to `error`.
|
||||
However, it takes some effort to convince Coq of this.
|
||||
|
||||
This was just for a single intrinsic. What if we're trying to prove something
|
||||
for _every_ intrinsic? Things will get messy very quickly. We can't even re-use
|
||||
our `destruct vs` code, because different intrinsics need stacks with different numbers
|
||||
of values (they have a different _arity_); if we try to handle all cases with at most
|
||||
2 values on the stack, we'd have to prove the same thing twice for simpler intrinsics.
|
||||
In short, proofs will look like a mess.
|
||||
|
||||
Things don't have to be this way, though! The boilerplate code is very repetitive, and
|
||||
this makes it a good candidate for automation. For this, Coq has `Ltac2`.
|
||||
|
||||
In short, `Ltac2` is another mini-language contained within Coq. We can use it to put
|
||||
into code the decision making that we'd be otherwise doing on our own. For example,
|
||||
here's a tactic that checks if the current proof has a hypothesis in which
|
||||
an intrinsic is evaluated to something:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 141 148 >}}
|
||||
|
||||
`Ltac2` has a pattern matching construct much like Coq itself does, but it can
|
||||
pattern match on Coq expressions and proof goals. When pattern matching
|
||||
on goals, we use the following notation: `[ h1 : t1, ..., hn : tn |- g ]`, which reads:
|
||||
|
||||
> Given hypotheses `h1` through `hn`, of types `t1` through `tn` respectively,
|
||||
we need to prove `g`.
|
||||
|
||||
In our pattern match, then, we're ignoring the actual thing we're supposed to prove
|
||||
(the tactic we're writing won't be that smart; its user -- ourselves -- will need
|
||||
to know when to use it). We are, however, searching for a hypothesis in the form
|
||||
`eval_step ?a (e_int ?b) = ?c`. The three variables, `?a`, `?b`, and `?c` are placeholders
|
||||
which can be matched with anything. Thus, we expect any hypothesis in which
|
||||
an intrinsic is evaluated to anything else (by the way, Coq checks all hypotheses one-by-one,
|
||||
starting at the most recent and finishing with the oldest). The code on lines 144 through 146 actually
|
||||
performs the `destruct` calls, as well as the `inversion` attempts that complete
|
||||
any proofs with an inconsistent assumption (like `err = final vs'`).
|
||||
|
||||
So how do these lines work? Well, first we need to get a handle on the hypothesis we found.
|
||||
Various tactics can manipulate the proof state, and cause hypotheses to disappear;
|
||||
by calling `Control.hyp` on `h`, we secure a more permanent hold on our evaluation assumption.
|
||||
In the next line, we call _another_ `Ltac2` function, `destruct_int_stack`, which unpacks
|
||||
the stack exactly as many times as necessary. We'll look at this function in a moment.
|
||||
Finally, we use regular old tactics. Specifically, we use `inversion` (as mentioned before).
|
||||
I use `fail` after `inversion` to avoid cluttering the proof in case there's _not_
|
||||
a contradiction.
|
||||
|
||||
On to `destruct_int_stack`. The definition is very simple:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 139 139 >}}
|
||||
|
||||
The main novelty is that this function takes two arguments, both of which are Coq expressions:
|
||||
`int`, or the intrinsic being evaluated, and `va`, the stack that's being analyzed. The `constr`
|
||||
type in Coq holds terms. Let's look at `int_arity` next:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 128 137 >}}
|
||||
|
||||
This is a pretty standard pattern match that assigns to each expression its arity. However, we're
|
||||
case analyzing over _literally any possible Coq expression_, so we need to handle the case in which
|
||||
the expression isn't actually a specific intrinsic. For this, we use `Control.throw` with
|
||||
the `Not_intrinsic` exception.
|
||||
|
||||
Wait a moment, does Coq just happen to include a `Not_intrinsic` exception? No, it does not.
|
||||
We have to register that one ourselves. In `Ltac2`, the type of exception (`exn`) is _extensible_,
|
||||
which means we can add on to it. We add just a single constructor with no arguments:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 118 118 >}}
|
||||
|
||||
Finally, unpacking the value stack. Here's the code for that:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 120 126 >}}
|
||||
|
||||
This function accepts the arity of an operation, and unpacks the stack that many times.
|
||||
`Ltac2` doesn't have a way to pattern match on numbers, so we resort to good old "less than or equal",
|
||||
and and `if`/`else`. If the arity is zero (or less than zero, since it's an integer), we don't
|
||||
need to unpack anymore. This is our base case. Otherwise, we generate two new free variables
|
||||
(we can't just hardcode `v1` and `v2`, since they may be in use elsewhere in the proof). To this
|
||||
end we use `Fresh.in_goal` and give it a "base symbol" to build on. We then use
|
||||
`destruct`, passing it our "scrutinee" (the expression being destructed) and the names
|
||||
we generated for its components. This generates multiple goals; the `Control.enter` tactic
|
||||
is used to run code for each one of these goals. In the non-empty list case, we try to break
|
||||
apart its tail as necessary by recursively calling `destruct_n`.
|
||||
|
||||
That's pretty much it! We can now use our tactic from Coq like any other. Rewriting
|
||||
our earlier proof about \\(\\text{swap}\\), we now only need to handle the valid case:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 248 254 >}}
|
||||
|
||||
We'll be using this new `ensure_valid_stack` tactic in our subsequent proofs.
|
||||
|
||||
#### The Backward Direction
|
||||
After our last proof before the `Ltac2` diversion, we know that our evaluator matches our semantics, but only when a `Sem_expr`
|
||||
object exists for our program. However, invalid programs don't have a `Sem_expr` object
|
||||
(there's no way to prove that they evaluate to anything). Does our evaluator behave
|
||||
properly on "bad" programs, too? We've not ruled out that our evaluator produces junk
|
||||
`final` or `middle` outputs whenever it encounters an error. We need another theorem:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 215 216 >}}
|
||||
|
||||
That is, if our evalutor reaches a final state, this state matches our semantics.
|
||||
This proof is most conveniently split into two pieces. The first piece says that
|
||||
if our evaluator completes in a single step, then this step matches our semantics.
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 158 175 >}}
|
||||
|
||||
The statement for a non-`final` step is more involved; the one step by itself need
|
||||
not match the semantics, since it only carries out a part of a computation. However,
|
||||
we _can_ say that if the "rest of the program" matches the semantics, then so does
|
||||
the whole expression.
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 177 213 >}}
|
||||
|
||||
Finally, we snap these two pieces together in `eval_step_sem_back`:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 215 222 >}}
|
||||
|
||||
We have now proved complete equivalence: our evaluator completes in a final state
|
||||
if and only if our semantics lead to this same final state. As a corollary of this,
|
||||
we can see that if a program is invalid (it doesn't evaluate to anything under our semantics),
|
||||
then our evaluator won't finish in a `final` state:
|
||||
|
||||
{{< codelines "Coq" "dawn/DawnEval.v" 224 229 >}}
|
||||
|
||||
### Making a Mini-REPL
|
||||
We can now be pretty confident about our evaluator. However, this whole business with using GHCi
|
||||
to run our programs is rather inconvenient; I'd rather write `[drop]` than `E_quote (E_int Drop)`.
|
||||
I'd also rather _read_ the former instead of the latter. We can do some work in Haskell to make
|
||||
playing with our interpreter more convenient.
|
||||
|
||||
#### Pretty Printing Code
|
||||
Coq generated Haskell data types that correspond precisely to the data types we defined for our
|
||||
proofs. We can use the standard type class mechanism in Haskell to define how they should be
|
||||
printed:
|
||||
|
||||
{{< codelines "Haskell" "dawn/Ucc.hs" 8 22 >}}
|
||||
|
||||
Now our expressions and values print a lot nicer:
|
||||
|
||||
```
|
||||
ghci> true
|
||||
[swap drop]
|
||||
ghci> false
|
||||
[drop]
|
||||
ghci> UccGen.or
|
||||
clone apply
|
||||
```
|
||||
|
||||
#### Reading Code In
|
||||
The Parsec library in Haskell can be used to convert text back into data structures.
|
||||
It's not too hard to create a parser for UCC:
|
||||
|
||||
{{< codelines "Haskell" "dawn/Ucc.hs" 24 44 >}}
|
||||
|
||||
Now, `parseExpression` can be used to read code:
|
||||
|
||||
```
|
||||
ghci> parseExpression "[drop] [drop]"
|
||||
Right [drop] [drop]
|
||||
```
|
||||
|
||||
#### The REPL
|
||||
We now literally have the three pieces of a read-evaluate-print loop. All that's left
|
||||
is putting them together:
|
||||
|
||||
{{< codelines "Haskell" "dawn/Ucc.hs" 53 64 >}}
|
||||
|
||||
We now have our own little verified evaluator:
|
||||
|
||||
```
|
||||
$ ghci Ucc
|
||||
$ ghc -main-is Ucc Ucc
|
||||
$ ./Ucc
|
||||
> [drop] [drop] clone apply
|
||||
[[drop]]
|
||||
> [drop] [swap drop] clone apply
|
||||
[[swap drop]]
|
||||
> [swap drop] [drop] clone apply
|
||||
[[swap drop]]
|
||||
> [swap drop] [swap drop] clone apply
|
||||
[[swap drop]]
|
||||
```
|
||||
|
||||
### Potential Future Work
|
||||
We now have a verified UCC evaluator in Haskell. What next?
|
||||
|
||||
1. We only verified the evaluation component of our REPL. In fact,
|
||||
the whole thing is technically a bit buggy due to the parser:
|
||||
```
|
||||
> [drop] drop coq is a weird name to say out loud in interviews
|
||||
[]
|
||||
```
|
||||
Shouldn't this be an error? Do you see why it's not? There's work in
|
||||
writing formally verified parsers; some time ago I saw a Galois
|
||||
talk about a [formally verified parser generator](https://galois.com/blog/2019/09/tech-talk-a-verified-ll1-parser-generator/).
|
||||
We could use this formally verified parser generator to create our parsers, and then be
|
||||
sure that our grammar is precisely followed.
|
||||
2. We could try make our evaluator a bit smarter. One thing we could definitely do
|
||||
is maker our REPL support variables. Then, we would be able to write:
|
||||
```
|
||||
> true = [swap drop]
|
||||
> false = [drop]
|
||||
> or = clone apply
|
||||
> true false or
|
||||
[swap drop]
|
||||
```
|
||||
There are different ways to go about this. One way is to extend our `expr` data type
|
||||
with a variable constructor. This would complicate the semantics (a _lot_), but it would
|
||||
allow us to prove facts about variables. Alternatively, we could implement expressions
|
||||
as syntax sugar in our parser. Using a variable would be the same as simply
|
||||
pasting in the variable's definition. This is pretty much what the Dawn article
|
||||
seems to be doing.
|
||||
3. We could prove more things. Can we confirm, once and for all, the correctness of \\(\\text{quote}_n\\),
|
||||
for _any_ \\(n\\)? Is there is a generalized way of converting inductive data types into a UCC encoding?
|
||||
Or could we perhaps formally verify the following comment from Lobsters:
|
||||
|
||||
> with the encoding of natural numbers given, n+1 contains the definition of n duplicated two times.
|
||||
This means that the encoding of n has size exponential in n, which seems extremely impractical.
|
||||
|
||||
The world's our oyster!
|
||||
|
||||
Despite all of these exciting possibilities, this is where I stop, for now. I hope you enjoyed this article,
|
||||
and thank you for reading!
|
||||
84
content/blog/coq_docs/index.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: "I Don't Like Coq's Documentation"
|
||||
date: 2021-11-24T21:48:59-08:00
|
||||
expirydate: 2021-11-24T21:48:59-08:00
|
||||
draft: true
|
||||
tags: ["Coq"]
|
||||
---
|
||||
|
||||
Recently, I wrote an article on [Formalizing Dawn's Core Calculus in Coq]({{< relref "./coq_dawn.md" >}}).
|
||||
One of the proofs (specifically, correctness of \\(\\text{quote}_3\\)) was the best candidate I've ever
|
||||
encountered for proof automation. I knew that proof automation was possible from the second book of Software
|
||||
Foundations, [Programming Language Foundations](https://softwarefoundations.cis.upenn.edu/plf-current/index.html).
|
||||
I went there to learn more, and started my little journey in picking up Coq's `Ltac2`.
|
||||
|
||||
Before I go any further, let me say that I'd self-describe as an "advanced beginner" in Coq, maybe "intermediate"
|
||||
on a good day. I am certainly far from a master. I will also say that I am quite young, and thus possibly spoiled
|
||||
by the explosion of well-documented languages, tools, and libraries. I don't frequently check `man` pages, and I don't
|
||||
often read straight up technical manuals. Maybe the fault lies with me.
|
||||
Nevertheles, I feel like I am where I am in the process of learning Coq
|
||||
in part because of the state of its learning resources.
|
||||
As a case study, let's take my attempt to automate away a pretty simple proof.
|
||||
|
||||
### Grammars instead of Examples
|
||||
I did not specifically remember the chapter of Software Foundation in which tactics were introduced.
|
||||
Instead of skimming through chapters until I found it, I tried to look up "coq custom tactic". The
|
||||
first thing that comes up is the page for `Ltac`.
|
||||
|
||||
After a brief explanation of what `Ltac` is, the documentation jumps straight into the grammar of the entire
|
||||
tactic language. Here' a screenshot of what that looks like:
|
||||
|
||||
{{< figure src="ltac_grammar.png" caption="The grammar of Ltac from the Coq page." class="large" alt="A grammar for the `Ltac` language within Coq. The grammar is in Backus–Naur form, and has 9 nonterminals." >}}
|
||||
|
||||
Good old Backus-Naur form. Now, my two main areas of interest are programming language theory and compilers, and so I'm no stranger to BNF. In fact, the first
|
||||
time I saw such a documentation page (most pages describing Coq language feature have some production rules), I didn't even consciously process that I was looking
|
||||
at grammar rules. However, and despite CompCert (a compiler) being probably the most well known example of a project in Coq, I don't think that Coq is made _just_
|
||||
for people familiar with PLT or compilers. I don't think it should be expected for the average newcomer to Coq (or the average beginner-ish person like me) to know how to read production rules, or to know
|
||||
what terminals and nonterminals are. Logical Founadtions sure managed to explain Gallina without resorting to BNFs.
|
||||
|
||||
And even if I, as a newcomer, know what BNF is, and how to read the rules, there's another layer to this specification:
|
||||
the precedence of various operators is encoded in the BNF rules. This is a pretty common pattern
|
||||
for writing down programming language grammars; for each level of operator precedence, there's another
|
||||
nonterminal. We have `ltac_expr4` for sequencing (the least binding operator), and `ltac_expr3` for "level 3 tactics",
|
||||
`ltac_expr2` for addition, logical "or", and "level 2 tactics". The creators of this documentation page clearly knew
|
||||
what they were getting at here, and I've seen this pattern enough times to recognize it right away. But if you
|
||||
_haven't_ seen this pattern before (and why should you have?), you'll need to take some time to decipher the rules.
|
||||
That's time that you'd rather spend trying to write your tactic.
|
||||
|
||||
The page could just as well have mentioned the types of constructs in `Ltac`, and given a table of their relative precedence.
|
||||
This could be an opportunity to give an example of what a program in `Ltac` looks like. Instead, despite having seen all of these nonterminals,
|
||||
I still don't have an image in my mind's eye of what the language looks like. And better yet, I think that the grammar's incorrect:
|
||||
|
||||
```
|
||||
ltac_expr4 ::= ltac_expr3 ; ltac_expr3|binder_tactic
|
||||
```
|
||||
|
||||
The way this is written, there's no way to sequence (using a semicolon) more than two things. The semicolon
|
||||
only occurs on level four, and both nonterminals in this rule are level three. However, Coq is perfectly happy
|
||||
to accept the following:
|
||||
|
||||
```Coq
|
||||
Ltac test := auto; auto; auto.
|
||||
```
|
||||
|
||||
In the `Ltac2` grammar, this is written the way I'd expect:
|
||||
|
||||
```
|
||||
ltac2_expr ::= ltac2_expr5 ; ltac2_expr
|
||||
```
|
||||
|
||||
Let's do a quick recap. We have an encoding that requires a degree of experience with grammars and
|
||||
programming languages to be useful to the reader, _and_ this encoding
|
||||
{{< sidenote "right" "errors-note" "leaves room for errors," >}}
|
||||
Here's a question to ponder: how come this error has gone unnoticed? Surely
|
||||
people used this page to learn <code>Ltac</code>. I believe that part of the reason is
|
||||
pattern matching: an experienced reader will recognize the "precedence trick", and quickly
|
||||
scan the grammar levels to estblish precedence. The people writing and proofreading this
|
||||
documentation likely read it this way, too.
|
||||
{{< /sidenote >}}
|
||||
errors that _actually appear in practice_.
|
||||
{{< sidenote "left" "achilles-note" "We have a very precise, yet incorrect definition." >}}
|
||||
Amusingly, I think this is very close to what is considered the achilles heel of formal verification:
|
||||
software that precisely adheres to an incorrect or incomplete specification.
|
||||
{{< /sidenote >}}
|
||||
Somehow, "a sequence of statements separated by a semicolon" seems like a simpler explanation.
|
||||
BIN
content/blog/coq_docs/ltac_grammar.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
@@ -21,7 +21,7 @@ GHC IDE is a Haskell-based program that uses the
|
||||
{{< sidenote "right" "lsp-note" "language server protocol" >}}
|
||||
You don't really need to know what the language server protocol (LSP) is
|
||||
to use GHC IDE. If you are nonetheless interested, I wrote a little
|
||||
bit about it <a href="{{< ref "/blog/haskell_language_server" >}}#prelude-language-server-protocol">in the previous iteration of this post.</a>
|
||||
bit about it <a href="{{< relref "/blog/haskell_language_server" >}}#prelude-language-server-protocol">in the previous iteration of this post.</a>
|
||||
If you want more information, check out the <a href="https://microsoft.github.io/language-server-protocol/">official Microsoft page on LSP.</a>
|
||||
{{< /sidenote >}} to communicate with any editor that supports it. Editors
|
||||
with support the the LSP include Atom, Visual Studio Code, Emacs, and Vim. Thus,
|
||||
@@ -61,7 +61,7 @@ export PATH=$PATH:/home/<yourusername>/.local/bin
|
||||
On Windows, this is done by
|
||||
{{< sidenote "right" "path-note" "editing your PATH variable." >}}
|
||||
If you need to know how to change your <code>PATH</code>, I wrote
|
||||
about it briefly in the <a href="{{< ref "/blog/haskell_language_server" >}}
|
||||
about it briefly in the <a href="{{< relref "/blog/haskell_language_server" >}}
|
||||
#installation-of-v0-5-0-0-windows-systems">previous iteration of this post.</a>
|
||||
{{< /sidenote >}} I don't run Windows,
|
||||
so I don't know where `cabal install` will place the executable, but I do know
|
||||
|
||||
BIN
content/blog/haskell_lazy_evaluation/lazy-fix.zip
Normal file
BIN
content/blog/haskell_lazy_evaluation/lazy.zip
Normal file
79
content/blog/hugo_functions.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: "Approximating Custom Functions in Hugo"
|
||||
date: 2021-01-17T18:44:53-08:00
|
||||
tags: ["Hugo"]
|
||||
---
|
||||
|
||||
This will be an uncharacteristically short post. Recently,
|
||||
I wrote about my experience with [including code from local files]({{< relref "codelines" >}}).
|
||||
After I wrote that post, I decided to expand upon my setup. In particular,
|
||||
I wanted to display links to the files I'm referring to, in three
|
||||
different cases: when I'm referring to an entire code file, to an entire raw (non-highlighted)
|
||||
file, or to a portion of a code file.
|
||||
|
||||
The problem was that in all three cases, I needed to determine the
|
||||
correct file URL to link to. The process for doing so is identical: it
|
||||
really only depends on the path to the file I'm including. However,
|
||||
many other aspects of each case are different. In the "entire code file"
|
||||
case, I simply need to read in a file. In the "portion of a code file"
|
||||
case, I have to perform some processing to extract the specific lines I want to include.
|
||||
Whenever I include a code file -- entirely or partially -- I need to invoke the `highlight`
|
||||
function to perform syntax highlighting; however, I don't want to do that when including a raw file.
|
||||
It would be difficult to write a single shortcode or partial to handle all of these different cases.
|
||||
|
||||
However, computing the target URL is a simple transformation
|
||||
of a path and a list of submodules into a link. More plainly,
|
||||
it is a function. Hugo doesn't really have support for
|
||||
custom functions, at least according to this [Discourse post](https://discourse.gohugo.io/t/adding-custom-functions/14164). The only approach to add a _real_ function to Hugo is to edit the Go-based
|
||||
source code, and recompile the thing. However, your own custom functions
|
||||
would then not be included in normal Hugo distributions, and any websites
|
||||
using these functions would not be portable. _Really_ adding your own functions
|
||||
is not viable.
|
||||
|
||||
However, we can approximate functions using Hugo's
|
||||
[scratchpad feature](https://gohugo.io/functions/scratch/)
|
||||
By feeding a
|
||||
{{< sidenote "right" "mutable-note" "scratchpad" >}}
|
||||
In reality, any mutable container will work. The scratchpad
|
||||
just seems like the perfect tool for this purpose.
|
||||
{{< /sidenote >}}
|
||||
to a partial, and expecting the partial to modify the
|
||||
scratchpad in some way, we can effectively recover data.
|
||||
For instance, in my `geturl` partial, I have something like
|
||||
the following:
|
||||
|
||||
```
|
||||
{{ .scratch.Set "bestUrl" theUrl }}
|
||||
```
|
||||
|
||||
Once this partial executes, and the rendering engine is back to its call site,
|
||||
the scratchpad will contain `bestUrl`. To call this partial while providing inputs
|
||||
(like the file path, for example), we can use Hugo's `dict` function. An (abridged)
|
||||
example:
|
||||
|
||||
```
|
||||
{{ partial "geturl.html" (dict "scratch" .Scratch "path" filePath) }}
|
||||
```
|
||||
|
||||
Now, from inside the partial, we'll be able to access the two variable using `.scratch` and `.path`.
|
||||
Once we've called our partial, we simply extract the returned data from the scratchpad and use it:
|
||||
|
||||
```
|
||||
{{ partial "geturl.html" (dict "scratch" .Scratch "path" filePath) }}
|
||||
{{ $bestUrl := .Scratch.Get "bestUrl" }}
|
||||
{{ ... do stuff with $bestUrl ... }}
|
||||
```
|
||||
|
||||
Thus, although it's a little bit tedious, we're able to use `geturl` like a function,
|
||||
thereby refraining from duplicating its definition everywhere the same logic is needed. A few
|
||||
closing thoughts:
|
||||
|
||||
* __Why not just use a real language?__ It's true that I wrote a Ruby script to
|
||||
do some of the work involved with linking submodules. However, generating the same
|
||||
information for all calls to `codelines` would complicate the process of rendering
|
||||
the blog, and make live preview impossible. In general, by limiting the use of external
|
||||
scripts, it's easier to make `hugo` the only "build tool" for the site.
|
||||
* __Is there an easier way?__ I _think_ that calls to `partial` return a string. If you
|
||||
simply wanted to return a string, you could probably do without using a scratchpad.
|
||||
However, if you wanted to do something more complicated (say, return a map or list),
|
||||
you'd probably want the scratchpad method after all.
|
||||
112
content/blog/introducing_highlight/index.md
Normal file
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: "Introducing Matrix Highlight"
|
||||
date: 2021-12-13T16:49:42-08:00
|
||||
tags: ["Matrix", "Project", "Matrix Highlight"]
|
||||
---
|
||||
|
||||
I wanted to briefly introduce a project that I've been working on in my spare time over the past couple of months.
|
||||
It's called __Matrix Highlight__, though this is a working title. However, it does exactly what the title claims:
|
||||
this little project is a browser extension to annotate the web, using [Matrix](https://matrix.org) as a communication and storage protocol.
|
||||
My goal with this project is a __decentralized, federated,
|
||||
collaborative annotation system for web pages and documents (that can be self-hosted).__
|
||||
See the image below for a quick sneak peek at what it looks like.
|
||||
|
||||
{{< figure src="mhl_many.png" caption="Text randomly highlighted with Matrix Highlight" class="fullwide" >}}
|
||||
|
||||
### Project Goals
|
||||
#### Decentralized
|
||||
Quite literally, the word "decentralized" lies in opposition to "centralized". In a centralized application, the data,
|
||||
or computation, or anything really, is controlled by a single entity. An example of this might be Google Docs: Google
|
||||
is in charge of Docs, and no one else. You log in through Google, and Google manages your various documents and edits
|
||||
to them authoritatively.
|
||||
|
||||
I don't think that's a good idea. Although convenient, this kind of arrangement shifts control out of your hands
|
||||
as a user, and into the hands of the entity running your software. Google has the power, if they wanted, to ban you from
|
||||
using Google Docs, or to manage your content in ways you don't like. They can (and do) impose storage limits. You are,
|
||||
in a sense, at their mercy. Furthermore, in the (admittedly unlikely) case that Google goes down, Google Docs goes down
|
||||
for everyone. There's a single point of failure. Whereas it's hard to imagine Google itself having any real trouble (although
|
||||
the recent AWS outage proves that such a thing is possible), most services aren't Google.
|
||||
|
||||
A decentralized application does not suffer from these problems.
|
||||
The failure of a single server in a decentralized system does not bring it down for everyone. Furthermore, users have
|
||||
the ability to switch between different servers or providers if one becomes abusive (or simply stops existing). Users
|
||||
have more choices, and more control.
|
||||
|
||||
__For Matrix Highlight specifically__, this means not having to rely on one specific group or company
|
||||
for storing and managing your annotations or notes.
|
||||
|
||||
#### Federated
|
||||
Decentralization by itself does not make for useful software. There might very well be multiple servers providing access
|
||||
to a particular piece of software. However, there's no guarantee that users of one such server can meaningfully interact
|
||||
with users of another server. Microsoft's Office 365 has collaborative document editing, and so does Google Docs. However,
|
||||
users of the two services cannot collaborate with _each other_.
|
||||
|
||||
In a federated system, the various providers establish a way of working together. The [Fediverse](https://en.wikipedia.org/wiki/Fediverse)
|
||||
is a big example of this. Users of various [Mastodon](https://joinmastodon.org/) servers can see each other's messages and posts,
|
||||
despite residing on servers with differing rules and administration. Users of Matrix can send messages between servers, with only
|
||||
one account.
|
||||
|
||||
__For Matrix Highlight__, this means that users who choose to use different servers or providers are still able to collaboratively highlight
|
||||
and annotate pages together.
|
||||
|
||||
#### Self-Hosted
|
||||
Self-hosting is the practice of running the various software you use yourself. This allows you yourself to be in charge of your data,
|
||||
instead of _any_ other entity, however trustworthy. A popular self-hosted solution is [Nextcloud](https://nextcloud.com/), which
|
||||
may be used, among other things, as a Google Drive replacement that you run on your own server. With Nextcloud, your files
|
||||
are completely under your own management, rather than that of some other person or company elsewhere.
|
||||
|
||||
__For Matrix Highlight__, this means that users can choose to run all the necessary software themselves, and thus remain in complete
|
||||
control of their annotation and other data.
|
||||
|
||||
### What it Looks Like
|
||||
First of all, you can watch a little demo video I recorded here:
|
||||
|
||||
{{< youtube Q3h5A0DsE1s >}}
|
||||
|
||||
You already got a little taste of Matrix Highlight in the opening screenshot. However, I'd like to show you some more of what I have
|
||||
so far. The most important aspect of the tool is the ability to annotate web pages. The tool can be brought up on any page;
|
||||
I typically test it on my blog, but that's just because it's convenient. Selecting some text brings up a little highlighting tooltip:
|
||||
|
||||
{{< figure src="mhl_tooltip.png" caption="A matrix highlighting tooltip appearing over one of the sidenotes in a different article." >}}
|
||||
|
||||
Selecting one of the colors in the tooltip creates a new highlight of the text you had selected:
|
||||
|
||||
{{< figure src="mhl_highlight.png" caption="The result of clicking a color in the previous screenshot." >}}
|
||||
|
||||
Annotations applied in this way are shared across all active instances of a Matrix Highlight page, including those shared with other users.
|
||||
|
||||
{{< figure src="mhl_multi.png" caption="Two chrome windows with the same annotations." class="fullwide" >}}
|
||||
|
||||
Highlights created by users can also be browsed as a list:
|
||||
|
||||
{{< figure src="mhl_quotelist.png" caption="A list of highlights from another page." class="medium" >}}
|
||||
|
||||
Highlights are stored in Matrix rooms. Since Matrix rooms are effectively chat rooms, they are built for being shared with other users.
|
||||
Thus, it is very simple to give another user access to the current list of highlights.
|
||||
|
||||
{{< figure src="mhl_userlist.png" caption="A list of users for a particular page." class="medium" >}}
|
||||
|
||||
This also means that a single page can have multiple
|
||||
independent sets of highlights, allowing you to organize them however you like. For instance, if you're proofreading a page of your own,
|
||||
you may have a highlight set (Matrix room) for every editing pass. The rooms can be switched at a moment's notice:
|
||||
|
||||
{{< figure src="mhl_roomlist.png" caption="A list of rooms for a particular page." class="medium" >}}
|
||||
|
||||
### Current and Planned Features
|
||||
|
||||
The following are the current and planned features for Matrix Highlight:
|
||||
|
||||
* __Current__: Create and send website annotations over Matrix.
|
||||
* __Current__: Store data in a decentralized and federated manner.
|
||||
* __Current__: Share highlights with other users, including those on other servers.
|
||||
* __Current__: Group annotations together and create multiple annotation groups
|
||||
* __Planned__: Use Matrix's End-to-End encryption to ensure the secure transmission and storage of highlight data.
|
||||
* __Planned__: Leverage the new [`m.thread` MSC](https://github.com/matrix-org/matrix-doc/blob/gsouquet/threading-via-relations/proposals/3440-threading-via-relations.md) to allow users to comment on and discuss
|
||||
highlights.
|
||||
* __Planned__: Use something like [ArchiveBox](https://archivebox.io/) to cache the current version of a website and prevent annotations from breaking.
|
||||
* __Planned__ Highlight PDFs in addition to web pages.
|
||||
|
||||
### Project Status and Conclusion
|
||||
For the moment, I'm refraining from publishing the project's source or output extensions. This is a hobby project, and I don't want to share
|
||||
something half-baked with the world. However, I fully intend to share the code for the project as soon as I think it's ready (which would probably
|
||||
be when I feel perfectly comfortable using it for my own needs).
|
||||
BIN
content/blog/introducing_highlight/mhl_highlight.png
Normal file
|
After Width: | Height: | Size: 180 KiB |
BIN
content/blog/introducing_highlight/mhl_many.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
content/blog/introducing_highlight/mhl_multi.png
Normal file
|
After Width: | Height: | Size: 595 KiB |
BIN
content/blog/introducing_highlight/mhl_quotelist.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
content/blog/introducing_highlight/mhl_roomlist.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
content/blog/introducing_highlight/mhl_toolbar.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
content/blog/introducing_highlight/mhl_tooltip.png
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
content/blog/introducing_highlight/mhl_userlist.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
728
content/blog/modulo_patterns/index.md
Normal file
@@ -0,0 +1,728 @@
|
||||
---
|
||||
title: Digit Sum Patterns and Modular Arithmetic
|
||||
date: 2021-12-30T15:42:40-08:00
|
||||
tags: ["Ruby", "Mathematics"]
|
||||
description: "In this article, we explore the patterns created by remainders from division."
|
||||
---
|
||||
|
||||
When I was in elementary school, our class was briefly visited by our school's headmaster.
|
||||
He was there for a demonstration, probably intended to get us to practice our multiplication tables.
|
||||
_"Pick a number"_, he said, _"And I'll teach you how to draw a pattern from it."_
|
||||
|
||||
The procedure was rather simple:
|
||||
|
||||
1. Pick a number between 2 and 8 (inclusive).
|
||||
2. Start generating positive multiples of this number. If you picked 8,
|
||||
your multiples would be 8, 16, 24, and so on.
|
||||
3. If a multiple is more than one digit long, sum its digits. For instance, for 16, write 1+6=7.
|
||||
If the digits add up to a number that's still more than 1 digit long, add up the digits of _that_
|
||||
number (and so on).
|
||||
4. Start drawing on a grid. For each resulting number, draw that many squares in one direction,
|
||||
and then "turn". Using 8 as our example, we could draw 8 up, 7 to the right, 6 down, 5 to the left,
|
||||
and so on.
|
||||
5. As soon as you come back to where you started (_"And that will always happen"_, said my headmaster),
|
||||
you're done. You should have drawn a pretty pattern!
|
||||
|
||||
Sticking with our example of 8, the pattern you'd end up with would be something like this:
|
||||
|
||||
{{< figure src="pattern_8.svg" caption="Pattern generated by the number 8." class="tiny" alt="Pattern generated by the number 8." >}}
|
||||
|
||||
Before we go any further, let's observe that it's not too hard to write code to do this.
|
||||
For instance, the "add digits" algorithm can be naively
|
||||
written by turning the number into a string (`17` becomes `"17"`), splitting that string into
|
||||
characters (`"17"` becomes `["1", "7"]`), turning each of these character back into numbers
|
||||
(the array becomes `[1, 7]`) and then computing the sum of the array, leaving `8`.
|
||||
|
||||
{{< codelines "Ruby" "patterns/patterns.rb" 3 8 >}}
|
||||
|
||||
We may now encode the "drawing" logic. At any point, there's a "direction" we're going - which
|
||||
I'll denote by the Ruby symbols `:top`, `:bottom`, `:left`, and `:right`. Each step, we take
|
||||
the current `x`,`y` coordinates (our position on the grid), and shift them by `n` in a particular
|
||||
direction `dir`. We also return the new direction alongside the new coordinates.
|
||||
|
||||
{{< codelines "Ruby" "patterns/patterns.rb" 10 21 >}}
|
||||
|
||||
The top-level algorithm is captured by the following code, which produces a list of
|
||||
coordinates in the order that you'd visit them.
|
||||
|
||||
{{< codelines "Ruby" "patterns/patterns.rb" 23 35 >}}
|
||||
|
||||
I will omit the code for generating SVGs from the body of the article -- you can always find the complete
|
||||
source code in this blog's Git repo (or by clicking the link in the code block above). Let's run the code on a few other numbers. Here's one for 4, for instance:
|
||||
|
||||
{{< figure src="pattern_4.svg" caption="Pattern generated by the number 4." class="tiny" alt="Pattern generated by the number 4." >}}
|
||||
|
||||
And one more for 2, which I don't find as pretty.
|
||||
|
||||
{{< figure src="pattern_2.svg" caption="Pattern generated by the number 2." class="tiny" alt="Pattern generated by the number 2." >}}
|
||||
|
||||
It really does always work out! Young me was amazed, though I would often run out of space on my
|
||||
grid paper to complete the pattern, or miscount the length of my lines partway in. It was only
|
||||
recently that I started thinking about _why_ it works, and I think I figured it out. Let's take a look!
|
||||
|
||||
### Is a number divisible by 3?
|
||||
You might find the whole "add up the digits of a number" thing familiar, and for good reason:
|
||||
it's one way to check if a number is divisible by 3. The quick summary of this result is,
|
||||
|
||||
> If the sum of the digits of a number is divisible by 3, then so is the whole number.
|
||||
|
||||
For example, the sum of the digits of 72 is 9, which is divisible by 3; 72 itself is correspondingly
|
||||
also divisible by 3, since 24*3=72. On the other hand, the sum of the digits of 82 is 10, which
|
||||
is _not_ divisible by 3; 82 isn't divisible by 3 either (it's one more than 81, which _is_ divisible by 3).
|
||||
|
||||
Why does _this_ work? Let's talk remainders.
|
||||
|
||||
If a number doesn't cleanly divide another (we're sticking to integers here),
|
||||
what's left behind is the remainder. For instance, dividing 7 by 3 leaves us with a remainder 1.
|
||||
On the other hand, if the remainder is zero, then that means that our dividend is divisible by the
|
||||
divisor (what a mouthful). In mathematics, we typically use
|
||||
\\(a|b\\) to say \\(a\\) divides \\(b\\), or, as we have seen above, that the remainder of dividing
|
||||
\\(b\\) by \\(a\\) is zero.
|
||||
|
||||
Working with remainders actually comes up pretty frequently in discrete math. A well-known
|
||||
example I'm aware of is the [RSA algorithm](https://en.wikipedia.org/wiki/RSA_(cryptosystem)),
|
||||
which works with remainders resulting from dividing by a product of two large prime numbers.
|
||||
But what's a good way to write, in numbers and symbols, the claim that "\\(a\\) divides \\(b\\)
|
||||
with remainder \\(r\\)"? Well, we know that dividing yields a quotient (possibly zero) and a remainder
|
||||
(also possibly zero). Let's call the quotient \\(q\\).
|
||||
{{< sidenote "right" "r-less-note" "Then, we know that when dividing \(b\) by \(a\) we have:" >}}
|
||||
It's important to point out that for the equation in question to represent division
|
||||
with quotient \(q\) and remainder \(r\), it must be that \(r\) is less than \(a\).
|
||||
Otherwise, you could write \(r = s + a\) for some \(s\), and end up with
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
& b = qa + r \\
|
||||
\Rightarrow\ & b = qa + (s + a) \\
|
||||
\Rightarrow\ & b = (q+1)a + s
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
|
||||
In plain English, if \(r\) is bigger than \(a\) after you've divided, you haven't
|
||||
taken out "as much \(a\) from your dividend as you could", and the actual quotient is
|
||||
larger than \(q\).
|
||||
{{< /sidenote >}}
|
||||
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
& b = qa + r \\
|
||||
\Rightarrow\ & b-r = qa \\
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
|
||||
We only really care about the remainder here, not the quotient, since it's the remainder
|
||||
that determines if something is divisible or not. From the form of the second equation, we can
|
||||
deduce that \\(b-r\\) is divisible by \\(a\\) (it's literally equal to \\(a\\) times \\(q\\),
|
||||
so it must be divisible). Thus, we can write:
|
||||
|
||||
{{< latex >}}
|
||||
a|(b-r)
|
||||
{{< /latex >}}
|
||||
|
||||
There's another notation for this type of statement, though. To say that the difference between
|
||||
two numbers is divisible by a third number, we write:
|
||||
|
||||
{{< latex >}}
|
||||
b \equiv r\ (\text{mod}\ a)
|
||||
{{< /latex >}}
|
||||
|
||||
Some things that _seem_ like they would work from this "equation-like" notation do, indeed, work.
|
||||
For instance, we can "add two equations" (I'll omit the proof here; jump down to [this
|
||||
section](#adding-two-congruences) to see how it works):
|
||||
|
||||
{{< latex >}}
|
||||
\textbf{if}\ a \equiv b\ (\text{mod}\ k)\ \textbf{and}\ c \equiv d, (\text{mod}\ k),\ \textbf{then}\
|
||||
a+c \equiv b+d\ (\text{mod}\ k).
|
||||
{{< /latex >}}
|
||||
|
||||
Multiplying both sides by the same number (call it \\(n\\)) also works (once
|
||||
again, you can find the proof in [this section below](#multiplying-both-sides-of-a-congruence)).
|
||||
|
||||
{{< latex >}}
|
||||
\textbf{if}\ a \equiv b\ (\text{mod}\ k),\ \textbf{then}\ na \equiv nb\ (\text{mod}\ k).
|
||||
{{< /latex >}}
|
||||
|
||||
Ok, that's a lot of notation and other _stuff_. Let's talk specifics. Of particular interest
|
||||
is the number 10, since our number system is _base ten_ (the value of a digit is multiplied by 10
|
||||
for every place it moves to the left). The remainder of 10 when dividing by 3 is 1. Thus,
|
||||
we have:
|
||||
|
||||
{{< latex >}}
|
||||
10 \equiv 1\ (\text{mod}\ 3)
|
||||
{{< /latex >}}
|
||||
|
||||
From this, we can deduce that multiplying by 10, when it comes to remainders from dividing by 3,
|
||||
is the same as multiplying by 1. We can clearly see this by multiplying both sides by \\(n\\).
|
||||
In our notation:
|
||||
|
||||
{{< latex >}}
|
||||
10n \equiv n\ (\text{mod}\ 3)
|
||||
{{< /latex >}}
|
||||
|
||||
But wait, there's more. Take any power of ten, be it a hundred, a thousand, or a million.
|
||||
Multiplying by that number is _also_ equivalent to multiplying by 1!
|
||||
|
||||
{{< latex >}}
|
||||
10^kn = 10\times10\times...\times 10n \equiv n\ (\text{mod}\ 3)
|
||||
{{< /latex >}}
|
||||
|
||||
We can put this to good use. Let's take a large number that's divisible by 3. This number
|
||||
will be made of multiple digits, like \\(d_2d_1d_0\\). Note that I do __not__ mean multiplication
|
||||
here, but specifically that each \\(d_i\\) is a number between 0 and 9 in a particular place
|
||||
in the number -- it's a digit. Now, we can write:
|
||||
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
0 &\equiv d_2d_1d_0 \\
|
||||
& = 100d_2 + 10d_1 + d_0 \\
|
||||
& \equiv d_2 + d_1 + d_0
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
|
||||
We have just found that \\(d_2+d_1+d_0 \\equiv 0\\ (\\text{mod}\ 3)\\), or that the sum of the digits
|
||||
is also divisible by 3. The logic we use works in the other direction, too: if the sum of the digits
|
||||
is divisible, then so is the actual number.
|
||||
|
||||
There's only one property of the number 3 we used for this reasoning: that \\(10 \\equiv 1\\ (\\text{mod}\\ 3)\\). But it so happens that there's another number that has this property: 9. This means
|
||||
that to check if a number is divisible by _nine_, we can also check if the sum of the digits is
|
||||
divisible by 9. Try it on 18, 27, 81, and 198.
|
||||
|
||||
Here's the main takeaway: __summing the digits in the way described by my headmaster is
|
||||
the same as figuring out the remainder of the number from dividing by 9__. Well, almost.
|
||||
The difference is the case of 9 itself: the __remainder__ here is 0, but we actually use 9
|
||||
to draw our line. We can actually try just using 0. Here's the updated `sum_digits` code:
|
||||
|
||||
```Ruby
|
||||
def sum_digits(n)
|
||||
n % 9
|
||||
end
|
||||
```
|
||||
|
||||
The results are similarly cool:
|
||||
|
||||
{{< figure src="pattern_8_mod.svg" caption="Pattern generated by the number 8." class="tiny" alt="Pattern generated by the number 8 by just using remainders." >}}
|
||||
{{< figure src="pattern_4_mod.svg" caption="Pattern generated by the number 4." class="tiny" alt="Pattern generated by the number 4 by just using remainders." >}}
|
||||
{{< figure src="pattern_2_mod.svg" caption="Pattern generated by the number 2." class="tiny" alt="Pattern generated by the number 2 by just using remainders." >}}
|
||||
|
||||
### Sequences of Remainders
|
||||
So now we know what the digit-summing algorithm is really doing. But that algorithm isn't all there
|
||||
is to it! We're repeatedly applying this algorithm over and over to multiples of another number. How
|
||||
does this work, and why does it always loop around? Why don't we ever spiral farther and farther
|
||||
from the center?
|
||||
|
||||
First, let's take a closer look at our sequence of multiples. Suppose we're working with multiples
|
||||
of some number \\(n\\). Let's write \\(a_i\\) for the \\(i\\)th multiple. Then, we end up with:
|
||||
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
a_1 &= n \\
|
||||
a_2 &= 2n \\
|
||||
a_3 &= 3n \\
|
||||
a_4 &= 4n \\
|
||||
... \\
|
||||
a_i &= in
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
|
||||
This is actually called an [arithmetic sequence](https://mathworld.wolfram.com/ArithmeticProgression.html);
|
||||
for each multiple, the number increases by \\(n\\).
|
||||
|
||||
Here's a first seemingly trivial point: at some time, the remainder of \\(a_i\\) will repeat.
|
||||
There are only so many remainders when dividing by nine: specifically, the only possible remainders
|
||||
are the numbers 0 through 8. We can invoke the [pigeonhole principle](https://en.wikipedia.org/wiki/Pigeonhole_principle) and say that after 9 multiples, we will have to have looped. Another way
|
||||
of seeing this is as follows:
|
||||
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
& 9 \equiv 0\ (\text{mod}\ 9) \\
|
||||
\Rightarrow\ & 9n \equiv 0\ (\text{mod}\ 9) \\
|
||||
\Rightarrow\ & 10n \equiv n\ (\text{mod}\ 9) \\
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
|
||||
The 10th multiple is equivalent to n, and will thus have the same remainder. The looping may
|
||||
happen earlier: the simplest case is if we pick 9 as our \\(n\\), in which case the remainder
|
||||
will always be 0.
|
||||
|
||||
Repeating remainders alone do not guarantee that we will return to the center. The repeating sequence 1,2,3,4
|
||||
will certainly cause a spiral. The reason is that, if we start facing "up", we will always move up 1
|
||||
and down 3 after four steps, leaving us 2 steps below where we started. Next, the cycle will repeat,
|
||||
and since turning four times leaves us facing "up" again, we'll end up getting _further_ away. Here's
|
||||
a picture that captures this behvior:
|
||||
|
||||
{{< figure src="pattern_1_4.svg" caption="Spiral generated by the number 1 with divisor 4." class="tiny" alt="Spiral generated by the number 1 by summing digits." >}}
|
||||
|
||||
And here's one more where the cycle repeats after 8 steps instead of 4. You can see that it also
|
||||
leads to a spiral:
|
||||
|
||||
{{< figure src="pattern_1_8.svg" caption="Spiral generated by the number 1 with divisor 8." class="tiny" alt="Spiral generated by the number 1 by summing digits." >}}
|
||||
|
||||
From this, we can devise a simple condition to prevent spiraling -- the _length_ of the sequence before
|
||||
it repeats _cannot be a multiple of 4_. This way, whenever the cycle restarts, it will do so in a
|
||||
different direction: backwards, turned once to the left, or turned once to the right. Clearly repeating
|
||||
the sequence backwards is guaranteed to take us back to the start. The same is true for the left and right-turn sequences, though it's less obvious. If drawing our sequence once left us turned to the right,
|
||||
drawing our sequence twice will leave us turned more to the right. On a grid, two right turns are
|
||||
the same as turning around. The third repetition will then undo the effects of the first one
|
||||
(since we're facing backwards now), and the fourth will undo the effects of the second.
|
||||
|
||||
There is an exception to this
|
||||
multiple-of-4 rule: if a sequence makes it back to the origin right before it starts over.
|
||||
In that case, even if it's facing the very same direction it started with, all is well -- things
|
||||
are just like when it first started, and the cycle repeats. I haven't found a sequence that does this,
|
||||
so for our purposes, we'll stick with avoiding multiples of 4.
|
||||
|
||||
Okay, so we want to avoid cycles with lengths divisible by four. What does it mean for a cycle to be of length _k_? It effectively means the following:
|
||||
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
& a_{k+1} \equiv a_1\ (\text{mod}\ 9) \\
|
||||
\Rightarrow\ & (k+1)n \equiv n\ (\text{mod}\ 9) \\
|
||||
\Rightarrow\ & kn \equiv 0\ (\text{mod}\ 9) \\
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
|
||||
If we could divide both sides by \\(k\\), we could go one more step:
|
||||
|
||||
{{< latex >}}
|
||||
n \equiv 0\ (\text{mod}\ 9) \\
|
||||
{{< /latex >}}
|
||||
|
||||
That is, \\(n\\) would be divisible by 9! This would contradict our choice of \\(n\\) to be
|
||||
between 2 and 8. What went wrong? Turns out, it's that last step: we can't always divide by \\(k\\).
|
||||
Some values of \\(k\\) are special, and it's only _those_ values that can serve as cycle lengths
|
||||
without causing a contradiction. So, what are they?
|
||||
|
||||
They're values that have a common factor with 9 (an incomplete explanation is in
|
||||
[this section below](#invertible-numbers-textmod-d-share-no-factors-with-d)). There are many numbers that have a common
|
||||
factor with 9; 3, 6, 9, 12, and so on. However, those can't all serve as cycle lengths: as we said,
|
||||
cycles can't get longer than 9. This leaves us with 3, 6, and 9 as _possible_ cycle lengths,
|
||||
none of which are divisible by 4. We've eliminated the possibility of spirals!
|
||||
|
||||
### Generalizing to Arbitrary Divisors
|
||||
The trick was easily executable on paper because there's an easy way to compute the remainder of a number
|
||||
when dividing by 9 (adding up the digits). However, we have a computer, and we don't need to fall back on such
|
||||
cool-but-complicated techniques. To replicate our original behavior, we can just write:
|
||||
|
||||
```Ruby
|
||||
def sum_digits(n)
|
||||
x = n % 9
|
||||
x == 0 ? 9 : x
|
||||
end
|
||||
```
|
||||
|
||||
But now, we can change the `9` to something else. There are some numbers we'd like to avoid - specifically,
|
||||
we want to avoid those numbers that would allow for cycles of length 4 (or of a length divisible by 4).
|
||||
If we didn't avoid them, we might run into infinite loops, where our pencil might end up moving
|
||||
further and further from the center.
|
||||
|
||||
Actually, let's revisit that. When we were playing with paths of length \\(k\\) while dividing by 9,
|
||||
we noted that the only _possible_ values of \\(k\\) are those that share a common factor with 9,
|
||||
specifically 3, 6 and 9. But that's not quite as strong as it could be: try as you might, but
|
||||
you will not find a cycle of length 6 when dividing by 9. The same is true if we pick 6 instead of 9,
|
||||
and try to find a cycle of length 4. Even though 4 _does_ have a common factor with 6, and thus
|
||||
is not ruled out as a valid cycle by our previous condition, we don't find any cycles of length 4.
|
||||
|
||||
So what is it that _really_ determines if there can be cycles or not?
|
||||
|
||||
Let's do some more playing around. What are the actual cycle lengths when we divide by 9?
|
||||
For all but two numbers, the cycle lengths are 9. The two special numbers are 6 and 3, and they end up
|
||||
with a cycle length of 3. From this, we can say that the cycle length seems to depend on whether or
|
||||
not our \\(n\\) has any common factors with the divisor.
|
||||
|
||||
Let's explore this some more with a different divisor, say 12. We fill find that 8 has a cycle length
|
||||
of 3, 7 has a cycle length of 12, 9 has a cycle length of 4. What's
|
||||
happening here? To see, let's divide 12 __by these cycle lengths__. For 8, we get (12/3) = 4.
|
||||
For 7, this works out to 1. For 9, it works out to 3. These new numbers, 4, 1, and 3, are actually
|
||||
the __greatest common factors__ of 8, 7, and 3 with 12, respectively. The greatest common factor
|
||||
of two numbers is the largest number that divides them both. We thus write down our guess
|
||||
for the length of a cycle:
|
||||
|
||||
{{< latex >}}
|
||||
k = \frac{d}{\text{gcd}(d,n)}
|
||||
{{< /latex >}}
|
||||
|
||||
Where \\(d\\) is our divisor, which has been 9 until just recently, and \\(\\text{gcd}(d,n)\\)
|
||||
is the greatest common factor of \\(d\\) and \\(n\\). This equation is in agreement
|
||||
with our experiment for \\(d = 9\\), too. Why might this be? Recall that sequences with
|
||||
period \\(k\\) imply the following congruence:
|
||||
|
||||
{{< latex >}}
|
||||
kn \equiv 0\ (\text{mod}\ d)
|
||||
{{< /latex >}}
|
||||
|
||||
Here I've replaced 9 with \\(d\\), since we're trying to make it work for _any_ divisor, not just 9.
|
||||
Now, suppose the greatest common divisor of \\(n\\) and \\(d\\) is some number \\(f\\). Then,
|
||||
since this number divides \\(n\\) and \\(d\\), we can write \\(n=fm\\) for some \\(m\\), and
|
||||
\\(d=fg\\) for some \\(g\\). We can rewrite our congruence as follows:
|
||||
|
||||
{{< latex >}}
|
||||
kfm \equiv 0\ (\text{mod}\ fg)
|
||||
{{< /latex >}}
|
||||
|
||||
We can simplify this a little bit. Recall that what this congruence really means is that the
|
||||
difference of \\(kfm\\) and \\(0\\), which is just \\(kfm\\), is divisible by \\(fg\\):
|
||||
|
||||
{{< latex >}}
|
||||
fg|kfm
|
||||
{{< /latex >}}
|
||||
|
||||
But if \\(fg\\) divides \\(kfm\\), it must be that \\(g\\) divides \\(km\\)! This, in turn, means
|
||||
we can write:
|
||||
|
||||
{{< latex >}}
|
||||
g|km
|
||||
{{< /latex >}}
|
||||
|
||||
Can we distill this statement even further? It turns out that we can. Remember that we got \\(g\\)
|
||||
and \\(m\\) by dividing \\(d\\) and \\(n\\) by their greatest common factor, \\(f\\). This, in
|
||||
turn, means that \\(g\\) and \\(m\\) have no more common factors that aren't equal to 1 (see
|
||||
[this section below](#numbers-divided-by-their-textgcd-have-no-common-factors)). From this, in turn, we can deduce that \\(m\\) is not
|
||||
relevant to \\(g\\) dividing \\(km\\), and we get:
|
||||
|
||||
{{< latex >}}
|
||||
g|k
|
||||
{{< /latex >}}
|
||||
|
||||
That is, we get that \\(k\\) must be divisible by \\(g\\). Recall that we got \\(g\\) by dividing
|
||||
\\(d\\) by \\(f\\), which is our largest common factor -- aka \\(\\text{gcd}(d,n)\\). We can thus
|
||||
write:
|
||||
|
||||
{{< latex >}}
|
||||
\frac{d}{\text{gcd}(d,n)}|k
|
||||
{{< /latex >}}
|
||||
|
||||
Let's stop and appreciate this result. We have found a condition that is required for a sequnce
|
||||
of remainders from dividing by \\(d\\) (which was 9 in the original problem) to repeat after \\(k\\)
|
||||
numbers. Furthermore, all of our steps can be performed in reverse, which means that if a \\(k\\)
|
||||
matches this conditon, we can work backwards and determine that a sequence of numbers has
|
||||
to repeat after \\(k\\) steps.
|
||||
|
||||
Multiple \\(k\\)s will match this condition, and that's not surprising. If a sequence repeats after 5 steps,
|
||||
it also repeats after 10, 15, and so on. We're interested in the first time our sequences repeat after
|
||||
taking any steps, which means we have to pick the smallest possible non-zero value of \\(k\\). The smallest
|
||||
number divisible by \\(d/\\text{gcd}(d,n)\\) is \\(d/\\text{gcd}(d,n)\\) itself. We thus confirm
|
||||
our hypothesis:
|
||||
|
||||
{{< latex >}}
|
||||
k = \frac{d}{\text{gcd}(d,n)}
|
||||
{{< /latex >}}
|
||||
|
||||
Lastly, recall that our patterns would spiral away from the center whenever a \\(k\\) is a multiple of 4. Now that we know what
|
||||
\\(k\\) is, we can restate this as "\\(d/\\text{gcd}(d,n)\\) is divisible by 4". But if we pick
|
||||
\\(n=d-1\\), the greatest common factor has to be \\(1\\) (see [this section below](#divisors-of-n-and-n-1)), so we can even further simplify this "\\(d\\) is divisible by 4".
|
||||
Thus, we can state simply that any divisor divisible by 4 is off-limits, as it will induce loops.
|
||||
For example, pick \\(d=4\\). Running our algorithm
|
||||
{{< sidenote "right" "constructive-note" "for \(n=d-1=3\)," >}}
|
||||
Did you catch that? From our work above, we didn't just find a condition that would prevent spirals;
|
||||
we also found the precise number that would result in a spiral if this condition were violated!
|
||||
This is because our proof is <em>constructive</em>: instead of just claiming the existence
|
||||
of a thing, it also shows how to get that thing. Our proof in the earlier section (which
|
||||
claimed that the divisor 9 would never create spirals) went by contradiction, which was
|
||||
<em>not</em> constructive. Repeating that proof for a general \(d\) wouldn't have told us
|
||||
the specific numbers that would spiral.<br>
|
||||
<br>
|
||||
This is the reason that direct proofs tend to be preferred over proofs by contradiction.
|
||||
{{< /sidenote >}} we indeed find an infinite
|
||||
spiral:
|
||||
|
||||
{{< figure src="pattern_3_4.svg" caption="Spiral generated by the number 3 with divisor 4." class="tiny" alt="Spiral generated by the number 3 by summing digits." >}}
|
||||
|
||||
Let's try again. Pick \\(d=8\\); then, for \\(n=d-1=7\\), we also get a spiral:
|
||||
|
||||
{{< figure src="pattern_7_8.svg" caption="Spiral generated by the number 7 with divisor 8." class="tiny" alt="Spiral generated by the number 7 by summing digits." >}}
|
||||
|
||||
A poem comes to mind:
|
||||
> Turning and turning in the widening gyre
|
||||
>
|
||||
> The falcon cannot hear the falconner;
|
||||
|
||||
Fortunately, there are plenty of numbers that are not divisible by four, and we can pick
|
||||
any of them! I'll pick primes for good measure. Here are a few good ones from using 13
|
||||
(which corresponds to summing digits of base-14 numbers):
|
||||
|
||||
{{< figure src="pattern_8_13.svg" caption="Pattern generated by the number 8 in base 14." class="tiny" alt="Pattern generated by the number 8 by summing digits." >}}
|
||||
{{< figure src="pattern_4_13.svg" caption="Pattern generated by the number 4 in base 14." class="tiny" alt="Pattern generated by the number 4 by summing digits." >}}
|
||||
|
||||
Here's one from dividing by 17 (base-18 numbers).
|
||||
|
||||
{{< figure src="pattern_5_17.svg" caption="Pattern generated by the number 5 in base 18." class="tiny" alt="Pattern generated by the number 5 by summing digits." >}}
|
||||
|
||||
Finally, base-30:
|
||||
|
||||
{{< figure src="pattern_2_29.svg" caption="Pattern generated by the number 2 in base 30." class="tiny" alt="Pattern generated by the number 2 by summing digits." >}}
|
||||
|
||||
{{< figure src="pattern_6_29.svg" caption="Pattern generated by the number 6 in base 30." class="tiny" alt="Pattern generated by the number 6 by summing digits." >}}
|
||||
|
||||
### Generalizing to Arbitrary Numbers of Directions
|
||||
What if we didn't turn 90 degrees each time? What, if, instead, we turned 120 degrees (so that
|
||||
turning 3 times, not 4, would leave you facing the same direction you started)? We can pretty easily
|
||||
do that, too. Let's call this number of turns \\(c\\). Up until now, we had \\(c=4\\).
|
||||
|
||||
First, let's update our condition. Before, we had "\\(d\\) cannot be divisible by 4". Now,
|
||||
we aren't constraining ourselves to only 4, but rather using a generic variable \\(c\\).
|
||||
We then end up with "\\(d\\) cannot be divisible by \\(c\\)". For instance, suppose we kept
|
||||
our divisor as 9 for the time being, but started turning 3 times instead of 4. This
|
||||
violates our divisibility condtion, and we once again end up with a spiral:
|
||||
|
||||
{{< figure src="pattern_8_9_t3.svg" caption="Pattern generated by the number 8 in base 10 while turning 3 times." class="tiny" alt="Pattern generated by the number 3 by summing digits and turning 120 degrees." >}}
|
||||
|
||||
If, on the other hand, we pick \\(d=8\\) and \\(c=3\\), we get patterns for all numbers just like we hoped.
|
||||
Here's one such pattern:
|
||||
|
||||
{{< figure src="pattern_7_8_t3.svg" caption="Pattern generated by the number 7 in base 9 while turning 3 times." class="tiny" alt="Pattern generated by the number 7 by summing digits in base 9 and turning 120 degrees." >}}
|
||||
|
||||
Hold on a moment; it's actully not so obvious why our condition _still_ works. When we just turned
|
||||
on a grid, things were simple. As long as we didn't end up facing the same way we started, we will
|
||||
eventually perform the exact same motions in reverse. The same is not true when turning 120 degrees, like
|
||||
we suggested. Here's an animated circle all of the turns we would make:
|
||||
|
||||
{{< figure src="turn_3_1.gif" caption="Orientations when turning 120 degrees" class="small" alt="Possible orientations when turning 120 degrees." >}}
|
||||
|
||||
We never quite do the exact _opposite_ of any one of our movements. So then, will we come back to the
|
||||
origin anyway? Well, let's start simple. Suppose we always turn by exactly one 120-degree increment
|
||||
(we might end up turning more or less, just like we may end up turning left, right, or back in the
|
||||
90 degree case). Each time you face a particular direciton, after performing a cycle, you will have
|
||||
moved some distance away from when you started, and turned 120 degrees. If you then repeat the
|
||||
cycle, you will once again move by the same offset as before, but this time the offset will
|
||||
be rotated 120 degrees, and you will have rotated a total of 240 degrees. Finally, performing
|
||||
the cycle a third time, you'll have moved by the same offset (rotated 240 degrees).
|
||||
|
||||
If you overaly each offset such that their starting points overlap, they will look very similar
|
||||
to that circle above. And now, here's the beauty: you can arrange these rotated offsets into
|
||||
a triangle:
|
||||
|
||||
{{< figure src="turn_3_anim.gif" caption="Triangle formed by three 120-degree turns." class="small" alt="Triangle formed by three 120-degree turns." >}}
|
||||
|
||||
As long as you rotate by the same amount each time (and you will, since the cycle length determines
|
||||
how many times you turn, and the cycle length never changes), you can do so for any number
|
||||
of directions. For instance, here's a similar visualization in which
|
||||
there are 5 possible directions, and where each turn is consequently 72 degrees:
|
||||
|
||||
{{< figure src="turn_5_anim.gif" caption="Pentagon formed by five 72-degree turns." class="small" alt="Pentagon formed by five 72-degree turns." >}}
|
||||
|
||||
Each of these polygon shapes forms a loop. If you walk along its sides, you will eventually end up exactly
|
||||
where you started. This confirms that if you end up making one turn at the end of each cycle, you
|
||||
will eventually end up right where you started.
|
||||
|
||||
Things aren't always as simple as making a single turn, though. Let's go back to the version
|
||||
of the problem in which we have 3 possible directions, and think about what would happen if we turned by 240 degrees at a time: 2 turns
|
||||
instead of 1?
|
||||
|
||||
Even though we first turn a whole 240 degrees, the second time we turn we "overshoot" our initial bearing, and end up at 120 degrees
|
||||
compared to it. As soon as we turn 240 more degrees (turning the third time), we end up back at 0.
|
||||
In short, even though we "visited" each bearing in a different order, we visited them all, and
|
||||
exactly once at that. Here's a visualization:
|
||||
|
||||
{{< figure src="turn_3_2.gif" caption="Orientations when turning 120 degrees, twice at a time" class="small" alt="Possible orientations when turning 120 degrees, twice at a time." >}}
|
||||
|
||||
Note that even though in the above picture it looks like we're just turning left instead of right,
|
||||
that's not the case; a single turn of 240 degrees is more than half the circle, so our second
|
||||
bearing ends up on the left side of the circle even though we turn right.
|
||||
|
||||
Just to make sure we really see what's happening, let's try this when there are 5 possible directions,
|
||||
and when we still make two turns (now of 72 degrees each)
|
||||
|
||||
{{< figure src="turn_5_2.gif" caption="Orientations when turning 72 degrees, twice at a time" class="small" alt="Possible orientations when turning 72 degrees, twice at a time." >}}
|
||||
|
||||
Let's try put some mathematical backing to this "visited them all" idea, and turning in general.
|
||||
First, observe that as soon as we turn 360 degrees, it's as good as not turning at all - we end
|
||||
up facing up again. If we turned 480 degrees (that is, two turns of 240 degrees each), the first
|
||||
360 can be safely ignored, since it puts us where we started; only the 120 degrees that remain
|
||||
are needed to figure out our final bearing. In short, the final direction we're facing is
|
||||
the remainder from dividing by 360. We already know how to formulate this using modular arithmetic:
|
||||
if we turn \\(t\\) degrees \\(k\\) times, and end up at final bearing (remainder) \\(b\\), this
|
||||
is captured by:
|
||||
|
||||
{{< latex >}}
|
||||
kt \equiv b\ (\text{mod}\ 360)
|
||||
{{< /latex >}}
|
||||
|
||||
Of course, if we end up facing the same way we started, we get the familiar equivalence:
|
||||
|
||||
{{< latex >}}
|
||||
kt \equiv 0\ (\text{mod}\ 360)
|
||||
{{< /latex >}}
|
||||
|
||||
Even though the variables in this equivalence mean different things now than they did last
|
||||
time we saw it, the mathematical properties remain the same. For instance, we can say that
|
||||
after \\(360/\\text{gcd}(360, t)\\) turns, we'll end up facing the way that we started.
|
||||
|
||||
So far, so good. What I don't like about this, though, is that we have all of these
|
||||
numbers of degrees all over our equations: 72 degrees, 144 degrees, and so forth. However,
|
||||
something like 73 degrees (if there are five possible directions) is just not a valid bearing,
|
||||
and nor is 71. We have so many possible degrees (360 of them, to be exact), but we're only
|
||||
using a handful! That's wasteful. Instead, observe that for \\(c\\) possible turns,
|
||||
the smallest possible turn angle is \\(360/c\\). Let's call this angle \\(\\theta\\) (theta).
|
||||
Now, notice that we always turn in multiples of \\(\\theta\\): a single turn moves us \\(\\theta\\)
|
||||
degrees, two turns move us \\(2\\theta\\) degrees, and so on. If we define \\(r\\) to be
|
||||
the number of turns that we find ourselves rotated by after a single cycle,
|
||||
we have \\(t=r\\theta\\), and our turning equation can be written as:
|
||||
|
||||
{{< latex >}}
|
||||
kr\theta \equiv 0\ (\text{mod}\ c\theta)
|
||||
{{< /latex >}}
|
||||
|
||||
Now, once again, recall that the above equivalence is just notation for the following:
|
||||
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
& c\theta|kr\theta \\
|
||||
\Leftrightarrow\ & c|kr
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
|
||||
And finally, observing that \\(kr=kr-0\\), we have:
|
||||
|
||||
{{< latex >}}
|
||||
kr \equiv 0\ (\text{mod}\ c)
|
||||
{{< /latex >}}
|
||||
|
||||
This equivalence says the same thing as our earlier one; however, instead of being in terms
|
||||
of degrees, it's in terms of the number of turns \\(c\\) and the turns-per-cycle \\(r\\).
|
||||
Now, recall once again that the smallest number of steps \\(k>0\\) for which this equivalence holds is
|
||||
\\(k = c/\\text{gcd}(c,r)\\).
|
||||
|
||||
We're close now: we have a sequence of \\(k\\) steps that will lead us back to the beginning.
|
||||
What's left is to show that these \\(k\\) steps are evenly distributed throughout our circle,
|
||||
which is the key property that makes it possible for us to make a polygon out of them (and
|
||||
thus end up back where we started).
|
||||
|
||||
To show this, say that we have a largest common divisor \\(f=\\text{gcd}(c,r)\\), and that \\(c=fe\\) and \\(r=fs\\). We can once again "divide through" by \\(f\\), and
|
||||
get:
|
||||
|
||||
{{< latex >}}
|
||||
ks \equiv 0\ (\text{mod}\ e)
|
||||
{{< /latex >}}
|
||||
|
||||
Now, we know that \\(\\text{gcd}(e,s)=1\\) ([see this section below](#numbers-divided-by-their-textgcd-have-no-common-factors)), and thus:
|
||||
|
||||
{{< latex >}}
|
||||
k = e/\text{gcd}(e,s) = e
|
||||
{{< /latex >}}
|
||||
|
||||
That is, our cycle will repeat after \\(e\\) remainders. But wait, we've only got \\(e\\) possible
|
||||
remainders: the numbers \\(0\\) through \\(e-1\\)! Thus, for a cycle to repeat after \\(e\\) remainders,
|
||||
all possible remainders must occur. For a concrete example, take \\(e=5\\); our remainders will
|
||||
be the set \\(\\{0,1,2,3,4\\}\\). Now, let's "multiply back through"
|
||||
by \\(f\\):
|
||||
|
||||
{{< latex >}}
|
||||
kfs \equiv 0\ (\text{mod}\ fe)
|
||||
{{< /latex >}}
|
||||
|
||||
We still have \\(e\\) possible remainders, but this time they are multiplied by \\(f\\).
|
||||
For example, taking \\(e\\) to once again be equal to \\(5\\), we have the set of possible remainders
|
||||
\\(\\{0, f, 2f, 3f, 4f\\}\\). The important bit is that these remainders are all evenly spaced, and
|
||||
that space between them is \\(f=\\text{gcd}(c,r)\\).
|
||||
|
||||
Let's recap: we have confirmed that for \\(c\\) possible turns (4 in our original formulation),
|
||||
and \\(r\\) turns at a time, we will always loop after \\(k=c/\\text{gcd}(c,r)\\) steps,
|
||||
evenly spaced out at \\(\\text{gcd}(c,r)\\) turns. No specific properties from \\(c\\) or \\(r\\)
|
||||
are needed for this to work. Finally, recall from the previous
|
||||
section that \\(r\\) is zero (and thus, our pattern breaks down) whenever the divisor \\(d\\) (9 in our original formulation) is itself
|
||||
divisible by \\(c\\). And so, __as long as we pick a system with \\(c\\) possible directions
|
||||
and divisor \\(d\\), we will always loop back and create a pattern as long as \\(c\\nmid d\\) (\\(c\\)
|
||||
does not divide \\(d\\))__.
|
||||
|
||||
Let's try it out! There's a few pictures below. When reading the captions, keep in mind that the _base_
|
||||
is one more than the _divisor_ (we started with numbers in the usual base 10, but divided by 9).
|
||||
|
||||
{{< figure src="pattern_1_7_t5.svg" caption="Pattern generated by the number 1 in base 8 while turning 5 times." class="tiny" alt="Pattern generated by the number 1 by summing digits in base 8 and turning 72 degrees." >}}
|
||||
|
||||
{{< figure src="pattern_3_4_t7.svg" caption="Pattern generated by the number 3 in base 5 while turning 7 times." class="tiny" alt="Pattern generated by the number 3 by summing digits in base 5 and turning 51 degrees." >}}
|
||||
|
||||
{{< figure src="pattern_3_11_t6.svg" caption="Pattern generated by the number 3 in base 12 while turning 6 times." class="tiny" alt="Pattern generated by the number 3 by summing digits in base 12 and turning 60 degrees." >}}
|
||||
|
||||
{{< figure src="pattern_2_11_t7.svg" caption="Pattern generated by the number 2 in base 12 while turning 7 times." class="tiny" alt="Pattern generated by the number 2 by summing digits in base 12 and turning 51 degrees." >}}
|
||||
|
||||
### Conclusion
|
||||
Today we peeked under the hood of a neat mathematical trick that was shown to me by my headmaster
|
||||
over 10 years ago now. Studying what it was that made this trick work led us to play with
|
||||
the underlying mathematics some more, and extend the trick to more situations (and prettier
|
||||
patterns). I hope you found this as interesting as I did!
|
||||
|
||||
By the way, the kind of math that we did in this article is most closely categorized as
|
||||
_number theory_. Check it out if you're interested!
|
||||
|
||||
Finally, a huge thank you to Arthur for checking my math, helping me with proofs, and proofreading
|
||||
the article.
|
||||
|
||||
All that remains are some proofs I omitted from the original article since they were taking
|
||||
up a lot of space (and were interrupting the flow of the explanation). They are listed below.
|
||||
|
||||
### Referenced Proofs
|
||||
|
||||
#### Adding Two Congruences
|
||||
__Claim__: If for some numbers \\(a\\), \\(b\\), \\(c\\), \\(d\\), and \\(k\\), we have
|
||||
\\(a \\equiv b\\ (\\text{mod}\\ k)\\) and \\(c \\equiv d\\ (\\text{mod}\\ k)\\), then
|
||||
it's also true that \\(a+c \\equiv b+d\\ (\\text{mod}\\ k)\\).
|
||||
|
||||
__Proof__: By definition, we have \\(k|(a-b)\\) and \\(k|(c-d)\\). This, in turn, means
|
||||
that for some \\(i\\) and \\(j\\), \\(a-b=ik\\) and \\(c-d=jk\\). Add both sides to get:
|
||||
{{< latex >}}
|
||||
\begin{aligned}
|
||||
& (a-b)+(c-d) = ik+jk \\
|
||||
\Rightarrow\ & (a+c)-(b+d) = (i+j)k \\
|
||||
\Rightarrow\ & k\ |\left[(a+c)-(b+d)\right]\\
|
||||
\Rightarrow\ & a+c \equiv b+d\ (\text{mod}\ k) \\
|
||||
\end{aligned}
|
||||
{{< /latex >}}
|
||||
\\(\\blacksquare\\)
|
||||
|
||||
#### Multiplying Both Sides of a Congruence
|
||||
__Claim__: If for some numbers \\(a\\), \\(b\\), \\(n\\) and \\(k\\), we have
|
||||
\\(a \\equiv b\\ (\\text{mod}\\ k)\\) then we also have that \\(an \\equiv bn\\ (\\text{mod}\\ k)\\).
|
||||
|
||||
__Proof__: By definition, we have \\(k|(a-b)\\). Since multiplying \\(a-b\\) but \\(n\\) cannot
|
||||
make it _not_ divisible by \\(k\\), we also have \\(k|\\left[n(a-b)\\right]\\). Distributing
|
||||
\\(n\\), we have \\(k|(na-nb)\\). By definition, this means \\(na\\equiv nb\\ (\\text{mod}\\ k)\\).
|
||||
|
||||
\\(\\blacksquare\\)
|
||||
|
||||
#### Invertible Numbers \\(\\text{mod}\\ d\\) Share no Factors with \\(d\\)
|
||||
__Claim__: A number \\(k\\) is only invertible (can be divided by) in \\(\\text{mod}\\ d\\) if \\(k\\)
|
||||
and \\(d\\) share no common factors (except 1).
|
||||
|
||||
__Proof__: Write \\(\\text{gcd}(k,d)\\) for the greatest common factor divisor of \\(k\\) and \\(d\\).
|
||||
Another important fact (not proven here, but see something [like this](https://sharmaeklavya2.github.io/theoremdep/nodes/number-theory/gcd/gcd-is-min-lincomb.html)), is that if \\(\\text{gcd}(k,d) = r\\),
|
||||
then the smallest possible number that can be made by adding and subtracting \\(k\\)s and \\(d\\)s
|
||||
is \\(r\\). That is, for some \\(i\\) and \\(j\\), the smallest possible positive value of \\(ik + jd\\) is \\(r\\).
|
||||
|
||||
Now, note that \\(d \\equiv 0\\ (\\text{mod}\\ d)\\). Multiplying both sides by \\(j\\), get
|
||||
\\(jd\\equiv 0\\ (\\text{mod}\\ d)\\). This, in turn, means that the smallest possible
|
||||
value of \\(ik+jd \\equiv ik\\) is \\(r\\). If \\(r\\) is bigger than 1 (i.e., if
|
||||
\\(k\\) and \\(d\\) have common factors), then we can't pick \\(i\\) such that \\(ik\\equiv1\\),
|
||||
since we know that \\(r>1\\) is the least possible value we can make. There is therefore no
|
||||
multiplicative inverse to \\(k\\). Alternatively worded, we cannot divide by \\(k\\).
|
||||
|
||||
\\(\\blacksquare\\)
|
||||
|
||||
#### Numbers Divided by Their \\(\\text{gcd}\\) Have No Common Factors
|
||||
__Claim__: For any two numbers \\(a\\) and \\(b\\) and their largest common factor \\(f\\),
|
||||
if \\(a=fc\\) and \\(b=fd\\), then \\(c\\) and \\(d\\) have no common factors other than 1 (i.e.,
|
||||
\\(\\text{gcd}(c,d)=1\\)).
|
||||
|
||||
__Proof__: Suppose that \\(c\\) and \\(d\\) do have sommon factor, \\(e\\neq1\\). In that case, we have
|
||||
\\(c=ei\\) and \\(d=ej\\) for some \\(i\\) and \\(j\\). Then, we have \\(a=fei\\), and \\(b=fej\\).
|
||||
From this, it's clear that both \\(a\\) and \\(b\\) are divisible by \\(fe\\). Since \\(e\\)
|
||||
is greater than \\(1\\), \\(fe\\) is greater than \\(f\\). But our assumptions state that
|
||||
\\(f\\) is the greatest common divisor of \\(a\\) and \\(b\\)! We have arrived at a contradiction.
|
||||
|
||||
Thus, \\(c\\) and \\(d\\) cannot have a common factor other than 1.
|
||||
|
||||
\\(\\blacksquare\\)
|
||||
|
||||
#### Divisors of \\(n\\) and \\(n-1\\).
|
||||
__Claim__: For any \\(n\\), \\(\\text{gcd}(n,n-1)=1\\). That is, \\(n\\) and \\(n-1\\) share
|
||||
no common divisors.
|
||||
|
||||
__Proof__: Suppose some number \\(f\\) divides both \\(n\\) and \\(n-1\\).
|
||||
In that case, we can write \\(n=af\\), and \\((n-1)=bf\\) for some \\(a\\) and \\(b\\).
|
||||
Subtracting one equation from the other:
|
||||
|
||||
{{< latex >}}
|
||||
1 = (a-b)f
|
||||
{{< /latex >}}
|
||||
But this means that 1 is divisible by \\(f\\)! That's only possible if \\(f=1\\). Thus, the only
|
||||
number that divides \\(n\\) and \\(n-1\\) is 1; that's our greatest common factor.
|
||||
|
||||
\\(\\blacksquare\\)
|
||||
42
content/blog/modulo_patterns/pattern_1_4.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="220.0" height="200.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="169.99999999999997" x2="90.0" y2="169.99999999999997" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="169.99999999999997" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="169.99999999999997" x2="90.0" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="190.0" x2="50.0" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50.0" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50.0" y1="190.0" x2="49.99999999999999" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="49.99999999999999" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="49.99999999999999" y1="130.0" x2="130.0" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="130.0" x2="130.0" y2="149.99999999999997" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="149.99999999999997" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="149.99999999999997" x2="90.0" y2="150.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="150.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="150.0" x2="89.99999999999999" y2="89.99999999999999" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="89.99999999999999" cy="89.99999999999999" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="89.99999999999999" y1="89.99999999999999" x2="170.0" y2="89.99999999999999" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="89.99999999999999" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="89.99999999999999" x2="170.0" y2="110.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="110.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="110.0" x2="130.0" y2="110.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="110.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="110.0" x2="130.0" y2="50.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="50.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="50.0" x2="210.0" y2="50.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="50.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="50.0" x2="210.0" y2="70.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="70.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="70.0" x2="170.0" y2="70.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="70.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="70.0" x2="169.99999999999997" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="169.99999999999997" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
80
content/blog/modulo_patterns/pattern_1_7_t5.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="311.2461179749811" height="301.4817981115687"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="47.08203932499384" y1="78.81909602355867" x2="187.08203932499384" y2="78.81909602355867" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="187.08203932499384" cy="78.81909602355867" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="187.08203932499384" y1="78.81909602355867" x2="193.26237921249282" y2="97.84022634946174" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="193.26237921249282" cy="97.84022634946174" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="193.26237921249282" y1="97.84022634946174" x2="160.9016994374949" y2="121.35163644116068" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.9016994374949" cy="121.35163644116068" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.9016994374949" y1="121.35163644116068" x2="112.36067977499803" y2="86.08452130361229" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="112.36067977499803" cy="86.08452130361229" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="112.36067977499803" y1="86.08452130361229" x2="137.0820393249938" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="137.0820393249938" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="137.0820393249938" y1="10.0" x2="237.0820393249938" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="237.0820393249938" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="237.0820393249938" y1="10.0" x2="274.1640786499875" y2="124.12678195541842" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="274.1640786499875" cy="124.12678195541842" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="274.1640786499875" y1="124.12678195541842" x2="160.90169943749487" y2="206.41671727636466" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.90169943749487" cy="206.41671727636466" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.90169943749487" y1="206.41671727636466" x2="144.72135954999595" y2="194.6610122305152" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="144.72135954999595" cy="194.6610122305152" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="144.72135954999595" y1="194.6610122305152" x2="157.0820393249938" y2="156.61875157870907" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="157.0820393249938" cy="156.61875157870907" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="157.0820393249938" y1="156.61875157870907" x2="217.0820393249938" y2="156.61875157870907" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="217.0820393249938" cy="156.61875157870907" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="217.0820393249938" y1="156.61875157870907" x2="241.8033988749896" y2="232.70327288232136" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="241.8033988749896" cy="232.70327288232136" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="241.8033988749896" y1="232.70327288232136" x2="160.90169943749487" y2="291.4817981115687" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.90169943749487" cy="291.4817981115687" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.90169943749487" y1="291.4817981115687" x2="63.81966011250117" y2="220.94756783647193" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="63.81966011250117" cy="220.94756783647193" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="63.81966011250117" y1="220.94756783647193" x2="107.08203932499376" y2="87.7996555551504" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="107.08203932499376" cy="87.7996555551504" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="107.08203932499376" y1="87.7996555551504" x2="127.08203932499376" y2="87.7996555551504" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="127.08203932499376" cy="87.7996555551504" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="127.08203932499376" y1="87.7996555551504" x2="139.44271909999168" y2="125.84191620695655" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="139.44271909999168" cy="125.84191620695655" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="139.44271909999168" y1="125.84191620695655" x2="90.90169943749484" y2="161.10903134450496" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.90169943749484" cy="161.10903134450496" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.90169943749484" y1="161.10903134450496" x2="26.18033988749903" y2="114.0862111611071" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="26.18033988749903" cy="114.0862111611071" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="26.18033988749903" y1="114.0862111611071" x2="57.082039324993744" y2="18.98055953159174" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="57.082039324993744" cy="18.98055953159174" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="57.082039324993744" y1="18.98055953159174" x2="177.08203932499376" y2="18.98055953159174" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="177.08203932499376" cy="18.98055953159174" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="177.08203932499376" y1="18.98055953159174" x2="220.34441853748638" y2="152.12847181291323" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="220.34441853748638" cy="152.12847181291323" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="220.34441853748638" y1="152.12847181291323" x2="204.16407864998746" y2="163.88417685876271" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="204.16407864998746" cy="163.88417685876271" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="204.16407864998746" y1="163.88417685876271" x2="171.80339887498957" y2="140.37276676706375" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="171.80339887498957" cy="140.37276676706375" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="171.80339887498957" y1="140.37276676706375" x2="190.34441853748638" y2="83.30937578935456" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190.34441853748638" cy="83.30937578935456" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190.34441853748638" y1="83.30937578935456" x2="270.3444185374864" y2="83.30937578935456" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270.3444185374864" cy="83.30937578935456" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270.3444185374864" y1="83.30937578935456" x2="301.2461179749811" y2="178.4150274188699" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="301.2461179749811" cy="178.4150274188699" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="301.2461179749811" y1="178.4150274188699" x2="204.16407864998743" y2="248.94925769396673" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="204.16407864998743" cy="248.94925769396673" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="204.16407864998743" y1="248.94925769396673" x2="90.90169943749476" y2="166.65932237302047" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.90169943749476" cy="166.65932237302047" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.90169943749476" y1="166.65932237302047" x2="97.0820393249937" y2="147.6381920471174" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="97.0820393249937" cy="147.6381920471174" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="97.0820393249937" y1="147.6381920471174" x2="137.0820393249937" y2="147.6381920471174" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="137.0820393249937" cy="147.6381920471174" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="137.0820393249937" y1="147.6381920471174" x2="155.62305898749057" y2="204.70158302482662" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="155.62305898749057" cy="204.70158302482662" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="155.62305898749057" y1="204.70158302482662" x2="90.90169943749476" y2="251.7244032082245" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.90169943749476" cy="251.7244032082245" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.90169943749476" y1="251.7244032082245" x2="10.0" y2="192.9458779789772" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10.0" cy="192.9458779789772" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10.0" y1="192.9458779789772" x2="47.082039324993666" y2="78.81909602355874" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="47.082039324993666" cy="78.81909602355874" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
74
content/blog/modulo_patterns/pattern_1_8.svg
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="459.9999999999999" height="399.99999999999994"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="329.99999999999994" x2="170.0" y2="329.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="329.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="329.99999999999994" x2="170.0" y2="349.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="349.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="349.99999999999994" x2="130.0" y2="349.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="349.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="349.99999999999994" x2="130.0" y2="289.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="289.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="289.99999999999994" x2="210.0" y2="289.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="289.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="289.99999999999994" x2="210.0" y2="389.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="389.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="389.99999999999994" x2="90.0" y2="389.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="389.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="389.99999999999994" x2="89.99999999999997" y2="249.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="89.99999999999997" cy="249.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="89.99999999999997" y1="249.99999999999994" x2="249.99999999999997" y2="249.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.99999999999997" cy="249.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.99999999999997" y1="249.99999999999994" x2="249.99999999999997" y2="269.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.99999999999997" cy="269.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.99999999999997" y1="269.99999999999994" x2="209.99999999999997" y2="269.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="209.99999999999997" cy="269.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="209.99999999999997" y1="269.99999999999994" x2="209.99999999999997" y2="209.99999999999997" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="209.99999999999997" cy="209.99999999999997" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="209.99999999999997" y1="209.99999999999997" x2="289.99999999999994" y2="209.99999999999997" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999994" cy="209.99999999999997" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999994" y1="209.99999999999997" x2="289.99999999999994" y2="309.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999994" cy="309.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999994" y1="309.99999999999994" x2="169.99999999999997" y2="309.99999999999994" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="169.99999999999997" cy="309.99999999999994" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="169.99999999999997" y1="309.99999999999994" x2="169.99999999999994" y2="169.99999999999997" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="169.99999999999994" cy="169.99999999999997" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="169.99999999999994" y1="169.99999999999997" x2="329.99999999999994" y2="169.99999999999997" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="329.99999999999994" cy="169.99999999999997" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="329.99999999999994" y1="169.99999999999997" x2="329.99999999999994" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="329.99999999999994" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="329.99999999999994" y1="190.0" x2="289.99999999999994" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999994" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999994" y1="190.0" x2="289.99999999999994" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999994" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999994" y1="130.0" x2="369.99999999999994" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="369.99999999999994" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="369.99999999999994" y1="130.0" x2="369.99999999999994" y2="230.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="369.99999999999994" cy="230.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="369.99999999999994" y1="230.0" x2="249.99999999999994" y2="230.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.99999999999994" cy="230.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.99999999999994" y1="230.0" x2="249.9999999999999" y2="90.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.9999999999999" cy="90.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.9999999999999" y1="90.0" x2="409.9999999999999" y2="90.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="409.9999999999999" cy="90.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="409.9999999999999" y1="90.0" x2="409.9999999999999" y2="110.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="409.9999999999999" cy="110.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="409.9999999999999" y1="110.0" x2="369.9999999999999" y2="110.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="369.9999999999999" cy="110.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="369.9999999999999" y1="110.0" x2="369.9999999999999" y2="50.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="369.9999999999999" cy="50.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="369.9999999999999" y1="50.0" x2="449.9999999999999" y2="50.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="449.9999999999999" cy="50.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="449.9999999999999" y1="50.0" x2="449.9999999999999" y2="150.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="449.9999999999999" cy="150.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="449.9999999999999" y1="150.0" x2="329.9999999999999" y2="150.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="329.9999999999999" cy="150.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="329.9999999999999" y1="150.0" x2="329.99999999999983" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="329.99999999999983" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
82
content/blog/modulo_patterns/pattern_2.svg
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="520" height="520"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="330" y1="170" x2="330" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="210" x2="410" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="210" x2="410" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="90" x2="250" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="90" x2="250" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="110" x2="310" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="110" x2="310" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="10" x2="170" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="10" x2="170" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="190" x2="210" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="190" x2="210" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="110" x2="90" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="110" x2="90" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="270" x2="110" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="270" x2="110" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="210" x2="10" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="210" x2="10" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="350" x2="190" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="350" x2="190" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="310" x2="110" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="310" x2="110" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="430" x2="270" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="430" x2="270" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="410" x2="210" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="410" x2="210" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="510" x2="350" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="510" x2="350" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="330" x2="310" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="330" x2="310" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="410" x2="430" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="410" x2="430" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="250" x2="410" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="250" x2="410" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="310" x2="510" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="310" x2="510" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="170" x2="330" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
164
content/blog/modulo_patterns/pattern_2_11_t7.svg
Normal file
@@ -0,0 +1,164 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="600.1996835705016" height="587.5080813062355"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="345.26009149615226" y1="178.29249583438352" x2="565.2600914961523" y2="178.29249583438352" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="565.2600914961523" cy="178.29249583438352" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="565.2600914961523" y1="178.29249583438352" x2="590.1996835705016" y2="209.5657551331047" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590.1996835705016" cy="209.5657551331047" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590.1996835705016" y1="209.5657551331047" x2="572.3980088539964" y2="287.55998810765055" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="572.3980088539964" cy="287.55998810765055" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="572.3980088539964" y1="287.55998810765055" x2="464.28174470570616" y2="339.6260368017576" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="464.28174470570616" cy="339.6260368017576" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="464.28174470570616" y1="339.6260368017576" x2="320.1267258413191" y2="270.20463854294826" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="320.1267258413191" cy="270.20463854294826" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="320.1267258413191" y1="270.20463854294826" x2="275.62253905005616" y2="75.21905610658354" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="275.62253905005616" cy="75.21905610658354" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="275.62253905005616" y1="75.21905610658354" x2="288.0923350872309" y2="59.58242645722294" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="288.0923350872309" cy="59.58242645722294" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="288.0923350872309" y1="59.58242645722294" x2="348.0923350872308" y2="59.58242645722294" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="348.0923350872308" cy="59.58242645722294" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="348.0923350872308" y1="59.58242645722294" x2="410.4413152731042" y2="137.76557470402594" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410.4413152731042" cy="137.76557470402594" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410.4413152731042" y1="137.76557470402594" x2="379.2883845192202" y2="274.25548240948126" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="379.2883845192202" cy="274.25548240948126" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="379.2883845192202" y1="274.25548240948126" x2="217.11398829678478" y2="352.35455545064167" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="217.11398829678478" cy="352.35455545064167" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="217.11398829678478" y1="352.35455545064167" x2="18.900837358252573" y2="256.9001328447789" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="18.900837358252573" cy="256.9001328447789" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="18.900837358252573" y1="256.9001328447789" x2="10.0" y2="217.90301635750598" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10.0" cy="217.90301635750598" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10.0" y1="217.90301635750598" x2="59.87918414869867" y2="155.3564977600636" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="59.87918414869867" cy="155.3564977600636" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="59.87918414869867" y1="155.3564977600636" x2="179.87918414869867" y2="155.3564977600636" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="179.87918414869867" cy="155.3564977600636" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="179.87918414869867" y1="155.3564977600636" x2="279.63755244609604" y2="280.4495349549484" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="279.63755244609604" cy="280.4495349549484" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="279.63755244609604" y1="280.4495349549484" x2="235.13336565483317" y2="475.4351173913131" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="235.13336565483317" cy="475.4351173913131" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="235.13336565483317" y1="475.4351173913131" x2="217.11398829678478" y2="484.1127921736643" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="217.11398829678478" cy="484.1127921736643" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="217.11398829678478" y1="484.1127921736643" x2="163.05585622263965" y2="458.0797678266108" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="163.05585622263965" cy="458.0797678266108" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="163.05585622263965" y1="458.0797678266108" x2="140.8037628270082" y2="360.58697660842836" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="140.8037628270082" cy="360.58697660842836" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="140.8037628270082" y1="360.58697660842836" x2="228.09233508723085" y2="251.13056906290424" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="228.09233508723085" cy="251.13056906290424" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="228.09233508723085" y1="251.13056906290424" x2="408.0923350872308" y2="251.13056906290424" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="408.0923350872308" cy="251.13056906290424" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="408.0923350872308" y1="251.13056906290424" x2="545.2600914961523" y2="423.1334952058708" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="545.2600914961523" cy="423.1334952058708" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="545.2600914961523" y1="423.1334952058708" x2="536.3592541378997" y2="462.1306116931437" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="536.3592541378997" cy="462.1306116931437" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="536.3592541378997" y1="462.1306116931437" x2="464.28174470570616" y2="496.8413108225484" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="464.28174470570616" cy="496.8413108225484" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="464.28174470570616" y1="496.8413108225484" x2="356.1654805574159" y2="444.7752621284414" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="356.1654805574159" cy="444.7752621284414" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="356.1654805574159" y1="444.7752621284414" x2="320.56213112440554" y2="288.7867961793496" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="320.56213112440554" cy="288.7867961793496" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="320.56213112440554" y1="288.7867961793496" x2="445.2600914961522" y2="132.42049968574366" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="445.2600914961522" cy="132.42049968574366" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="445.2600914961522" y1="132.42049968574366" x2="465.2600914961522" y2="132.42049968574366" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="465.2600914961522" cy="132.42049968574366" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="465.2600914961522" y1="132.42049968574366" x2="502.66947960767624" y2="179.33038863382546" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="502.66947960767624" cy="179.33038863382546" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="502.66947960767624" y1="179.33038863382546" x2="480.4173862120448" y2="276.8231798520078" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="480.4173862120448" cy="276.8231798520078" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="480.4173862120448" y1="276.8231798520078" x2="354.2817447057061" y2="337.566903328466" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="354.2817447057061" cy="337.566903328466" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="354.2817447057061" y1="337.566903328466" x2="192.10734848327067" y2="259.46783028730556" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="192.10734848327067" cy="259.46783028730556" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="192.10734848327067" y1="259.46783028730556" x2="143.1527430128815" y2="44.98368960730435" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="143.1527430128815" cy="44.98368960730435" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="143.1527430128815" y1="44.98368960730435" x2="168.09233508723082" y2="13.710430308583135" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="168.09233508723082" cy="13.710430308583135" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="168.09233508723082" y1="13.710430308583135" x2="248.09233508723082" y2="13.710430308583135" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="248.09233508723082" cy="13.710430308583135" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="248.09233508723082" y1="13.710430308583135" x2="322.91111131027884" y2="107.53020820474671" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="322.91111131027884" cy="107.53020820474671" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="322.91111131027884" y1="107.53020820474671" x2="287.30776187726855" y2="263.5186741538385" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="287.30776187726855" cy="263.5186741538385" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="287.30776187726855" y1="263.5186741538385" x2="107.11398829678475" y2="350.2954219773501" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="107.11398829678475" cy="350.2954219773501" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="107.11398829678475" y1="350.2954219773501" x2="89.09461093873638" y2="341.617747194999" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="89.09461093873638" cy="341.617747194999" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="89.09461093873638" y1="341.617747194999" x2="75.74335490135752" y2="283.12207246408957" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="75.74335490135752" cy="283.12207246408957" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="75.74335490135752" y1="283.12207246408957" x2="138.09233508723085" y2="204.93892421728657" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="138.09233508723085" cy="204.93892421728657" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="138.09233508723085" y1="204.93892421728657" x2="278.0923350872309" y2="204.93892421728657" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="278.0923350872309" cy="204.93892421728657" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="278.0923350872309" y1="204.93892421728657" x2="390.32049942180294" y2="345.66859106153186" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390.32049942180294" cy="345.66859106153186" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390.32049942180294" y1="345.66859106153186" x2="341.3658939514137" y2="560.1527317415331" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="341.3658939514137" cy="560.1527317415331" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="341.3658939514137" y1="560.1527317415331" x2="305.32713923531696" y2="577.5080813062355" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="305.32713923531696" cy="577.5080813062355" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="305.32713923531696" y1="577.5080813062355" x2="233.24962980312347" y2="542.7973821768308" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="233.24962980312347" cy="542.7973821768308" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="233.24962980312347" y1="542.7973821768308" x2="206.54711772836572" y2="425.8060327150119" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="206.54711772836572" cy="425.8060327150119" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="206.54711772836572" y1="425.8060327150119" x2="306.30548602576306" y2="300.71299552012715" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="306.30548602576306" cy="300.71299552012715" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="306.30548602576306" y1="300.71299552012715" x2="506.30548602576306" y2="300.71299552012715" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="506.30548602576306" cy="300.71299552012715" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="506.30548602576306" y1="300.71299552012715" x2="518.7752820629378" y2="316.34962516948775" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="518.7752820629378" cy="316.34962516948775" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="518.7752820629378" y1="316.34962516948775" x2="505.4240260255588" y2="374.8452999003972" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="505.4240260255588" cy="374.8452999003972" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="505.4240260255588" y1="374.8452999003972" x2="415.32713923531696" y2="418.2336738121529" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="415.32713923531696" cy="418.2336738121529" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="415.32713923531696" y1="418.2336738121529" x2="289.1914977289782" y2="357.48995033569486" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.1914977289782" cy="357.48995033569486" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.1914977289782" y1="357.48995033569486" x2="249.13772961684163" y2="182.00292614296657" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.13772961684163" cy="182.00292614296657" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.13772961684163" y1="182.00292614296657" x2="386.305486025763" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="386.305486025763" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="386.305486025763" y1="10.0" x2="426.305486025763" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="426.305486025763" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="426.305486025763" y1="10.0" x2="476.18467017446164" y2="72.5465185974424" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="476.18467017446164" cy="72.5465185974424" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="476.18467017446164" y1="72.5465185974424" x2="449.48215809970395" y2="189.5378680592612" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="449.48215809970395" cy="189.5378680592612" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="449.48215809970395" y1="189.5378680592612" x2="305.32713923531685" y2="258.95926631807055" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="305.32713923531685" cy="258.95926631807055" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="305.32713923531685" y1="258.95926631807055" x2="125.13336565483307" y2="172.18251849455896" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="125.13336565483307" cy="172.18251849455896" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="125.13336565483307" y1="172.18251849455896" x2="120.68294697570678" y2="152.68396025092247" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="120.68294697570678" cy="152.68396025092247" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="120.68294697570678" y1="152.68396025092247" x2="158.0923350872308" y2="105.7740713028407" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="158.0923350872308" cy="105.7740713028407" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="158.0923350872308" y1="105.7740713028407" x2="258.0923350872308" y2="105.7740713028407" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="258.0923350872308" cy="105.7740713028407" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="258.0923350872308" y1="105.7740713028407" x2="345.38090734745356" y2="215.23047884836487" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="345.38090734745356" cy="215.23047884836487" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="345.38090734745356" y1="215.23047884836487" x2="305.3271392353169" y2="390.71750304109315" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="305.3271392353169" cy="390.71750304109315" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="305.3271392353169" y1="390.71750304109315" x2="107.11398829678473" y2="486.1719256469559" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="107.11398829678473" cy="486.1719256469559" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="107.11398829678473" y1="486.1719256469559" x2="71.07523358068796" y2="468.8165760822536" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="71.07523358068796" cy="468.8165760822536" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="71.07523358068796" y1="468.8165760822536" x2="53.27355886418278" y2="390.82234310770775" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="53.27355886418278" cy="390.82234310770775" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="53.27355886418278" y1="390.82234310770775" x2="128.0923350872308" y2="297.00256521154415" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="128.0923350872308" cy="297.00256521154415" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="128.0923350872308" y1="297.00256521154415" x2="288.09233508723077" y2="297.00256521154415" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="288.09233508723077" cy="297.00256521154415" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="288.09233508723077" y1="297.00256521154415" x2="412.7902954589775" y2="453.36886170515015" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="412.7902954589775" cy="453.36886170515015" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="412.7902954589775" y1="453.36886170515015" x2="408.33987677985124" y2="472.8674199487866" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="408.33987677985124" cy="472.8674199487866" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="408.33987677985124" y1="472.8674199487866" x2="354.2817447057061" y2="498.9004442958401" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="354.2817447057061" cy="498.9004442958401" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="354.2817447057061" y1="498.9004442958401" x2="264.18485791546414" y2="455.5120703840844" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="264.18485791546414" cy="455.5120703840844" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="264.18485791546414" y1="455.5120703840844" x2="233.0319271615801" y2="319.022162678629" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="233.0319271615801" cy="319.022162678629" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="233.0319271615801" y1="319.022162678629" x2="345.26009149615214" y2="178.2924958343836" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="345.26009149615214" cy="178.2924958343836" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 19 KiB |
242
content/blog/modulo_patterns/pattern_2_29.svg
Normal file
@@ -0,0 +1,242 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="1120" height="1120"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="250" y1="270" x2="250" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="310" x2="330" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="310" x2="330" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="190" x2="170" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="190" x2="170" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="390" x2="410" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="390" x2="410" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="110" x2="90" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="110" x2="90" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="470" x2="490" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="470" x2="490" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="30" x2="10" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="30" x2="10" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="550" x2="570" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="550" x2="570" y2="530" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="530" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="530" x2="510" y2="530" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="530" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="530" x2="510" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="630" x2="650" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="630" x2="650" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="450" x2="430" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="450" x2="430" y2="710" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="710" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="710" x2="730" y2="710" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="730" cy="710" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="730" y1="710" x2="730" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="730" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="730" y1="370" x2="350" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="370" x2="350" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="790" x2="810" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="810" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="810" y1="790" x2="810" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="810" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="810" y1="290" x2="270" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="290" x2="270" y2="870" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="870" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="870" x2="310" y2="870" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="870" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="870" x2="310" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="790" x2="190" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="790" x2="190" y2="950" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="950" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="950" x2="390" y2="950" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="950" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="950" x2="390" y2="710" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="710" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="710" x2="110" y2="710" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="710" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="710" x2="110" y2="1030" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="1030" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="1030" x2="470" y2="1030" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="1030" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="1030" x2="470" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="630" x2="30" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="630" x2="30" y2="1110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="1110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="1110" x2="550" y2="1110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="1110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="1110" x2="550" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="550" x2="530" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="530" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="530" y1="550" x2="530" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="530" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="530" y1="610" x2="630" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="610" x2="630" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="470" x2="450" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="470" x2="450" y2="690" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="690" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="690" x2="710" y2="690" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="710" cy="690" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="710" y1="690" x2="710" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="710" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="710" y1="390" x2="370" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370" y1="390" x2="370" y2="770" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="770" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370" y1="770" x2="790" y2="770" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="770" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="770" x2="790" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="310" x2="290" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="310" x2="290" y2="850" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="850" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="850" x2="870" y2="850" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="870" cy="850" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="870" y1="850" x2="870" y2="810" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="870" cy="810" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="870" y1="810" x2="790" y2="810" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="810" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="810" x2="790" y2="930" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="930" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="930" x2="950" y2="930" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="950" cy="930" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="950" y1="930" x2="950" y2="730" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="950" cy="730" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="950" y1="730" x2="710" y2="730" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="710" cy="730" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="710" y1="730" x2="710" y2="1010" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="710" cy="1010" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="710" y1="1010" x2="1030" y2="1010" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1030" cy="1010" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1030" y1="1010" x2="1030" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1030" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1030" y1="650" x2="630" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="650" x2="630" y2="1090" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="1090" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="1090" x2="1110" y2="1090" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1110" cy="1090" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1110" y1="1090" x2="1110" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1110" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1110" y1="570" x2="550" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="570" x2="550" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="590" x2="610" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="590" x2="610" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="490" x2="470" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="490" x2="470" y2="670" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="670" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="670" x2="690" y2="670" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="690" cy="670" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="690" y1="670" x2="690" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="690" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="690" y1="410" x2="390" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="410" x2="390" y2="750" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="750" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="750" x2="770" y2="750" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="770" cy="750" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="770" y1="750" x2="770" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="770" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="770" y1="330" x2="310" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="330" x2="310" y2="830" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="830" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="830" x2="850" y2="830" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="850" cy="830" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="850" y1="830" x2="850" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="850" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="850" y1="250" x2="810" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="810" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="810" y1="250" x2="810" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="810" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="810" y1="330" x2="930" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="930" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="930" y1="330" x2="930" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="930" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="930" y1="170" x2="730" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="730" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="730" y1="170" x2="730" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="730" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="730" y1="410" x2="1010" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1010" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1010" y1="410" x2="1010" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1010" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1010" y1="90" x2="650" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="90" x2="650" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="490" x2="1090" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1090" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1090" y1="490" x2="1090" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1090" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1090" y1="10" x2="570" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="10" x2="570" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="570" x2="590" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="570" x2="590" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="510" x2="490" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="510" x2="490" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="650" x2="670" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="670" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="670" y1="650" x2="670" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="670" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="670" y1="430" x2="410" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="430" x2="410" y2="730" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="730" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="730" x2="750" y2="730" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="750" cy="730" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="750" y1="730" x2="750" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="750" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="750" y1="350" x2="330" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="350" x2="330" y2="810" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="810" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="810" x2="830" y2="810" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="830" cy="810" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="830" y1="810" x2="830" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="830" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="830" y1="270" x2="250" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 19 KiB |
80
content/blog/modulo_patterns/pattern_2_mod.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="500" height="500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="410" y1="250" x2="410" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="290" x2="490" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="290" x2="490" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="170" x2="330" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="170" x2="330" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="190" x2="390" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="190" x2="390" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="90" x2="250" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="90" x2="250" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="90" x2="290" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="90" x2="290" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="10" x2="170" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="10" x2="170" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="170" x2="190" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="170" x2="190" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="110" x2="90" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="110" x2="90" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="250" x2="90" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="250" x2="90" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="210" x2="10" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="210" x2="10" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="330" x2="170" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="330" x2="170" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="310" x2="110" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="310" x2="110" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="410" x2="250" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="410" x2="250" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="410" x2="210" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="410" x2="210" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="490" x2="330" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="490" x2="330" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="330" x2="310" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="330" x2="310" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="390" x2="410" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="390" x2="410" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
142
content/blog/modulo_patterns/pattern_3_11_t6.svg
Normal file
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="600.0000000000006" height="574.2562584220407"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="140.00000000000088" y1="287.12812921102045" x2="360.00000000000085" y2="287.12812921102045" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="360.00000000000085" cy="287.12812921102045" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="360.00000000000085" y1="287.12812921102045" x2="390.00000000000085" y2="339.08965343808677" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390.00000000000085" cy="339.08965343808677" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390.00000000000085" y1="339.08965343808677" x2="330.0000000000009" y2="443.0127018922194" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330.0000000000009" cy="443.0127018922194" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330.0000000000009" y1="443.0127018922194" x2="150.0000000000009" y2="443.0127018922194" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150.0000000000009" cy="443.0127018922194" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150.0000000000009" y1="443.0127018922194" x2="140.00000000000088" y2="425.6921938165306" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="140.00000000000088" cy="425.6921938165306" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="140.00000000000088" y1="425.6921938165306" x2="180.00000000000085" y2="356.4101615137755" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="180.00000000000085" cy="356.4101615137755" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="180.00000000000085" y1="356.4101615137755" x2="320.00000000000085" y2="356.4101615137755" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="320.00000000000085" cy="356.4101615137755" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="320.00000000000085" y1="356.4101615137755" x2="420.00000000000085" y2="529.6152422706632" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="420.00000000000085" cy="529.6152422706632" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="420.00000000000085" y1="529.6152422706632" x2="400.00000000000085" y2="564.2562584220407" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="400.00000000000085" cy="564.2562584220407" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="400.00000000000085" y1="564.2562584220407" x2="300.00000000000085" y2="564.2562584220407" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="300.00000000000085" cy="564.2562584220407" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="300.00000000000085" y1="564.2562584220407" x2="220.0000000000008" y2="425.69219381653056" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="220.0000000000008" cy="425.69219381653056" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="220.0000000000008" y1="425.69219381653056" x2="330.0000000000006" y2="235.166604983954" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330.0000000000006" cy="235.166604983954" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330.0000000000006" y1="235.166604983954" x2="390.0000000000006" y2="235.166604983954" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390.0000000000006" cy="235.166604983954" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390.0000000000006" y1="235.166604983954" x2="450.0000000000006" y2="339.0896534380866" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450.0000000000006" cy="339.0896534380866" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450.0000000000006" y1="339.0896534380866" x2="360.0000000000007" y2="494.97422611928556" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="360.0000000000007" cy="494.97422611928556" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="360.0000000000007" y1="494.97422611928556" x2="340.0000000000007" y2="494.97422611928556" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="340.0000000000007" cy="494.97422611928556" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="340.0000000000007" y1="494.97422611928556" x2="300.0000000000006" y2="425.69219381653056" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="300.0000000000006" cy="425.69219381653056" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="300.0000000000006" y1="425.69219381653056" x2="370.00000000000057" y2="304.4486372867091" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370.00000000000057" cy="304.4486372867091" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370.00000000000057" y1="304.4486372867091" x2="570.0000000000006" y2="304.4486372867091" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570.0000000000006" cy="304.4486372867091" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570.0000000000006" y1="304.4486372867091" x2="590.0000000000006" y2="339.0896534380866" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590.0000000000006" cy="339.0896534380866" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590.0000000000006" y1="339.0896534380866" x2="540.0000000000006" y2="425.6921938165305" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="540.0000000000006" cy="425.6921938165305" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="540.0000000000006" y1="425.6921938165305" x2="380.00000000000057" y2="425.6921938165305" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="380.00000000000057" cy="425.6921938165305" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="380.00000000000057" y1="425.6921938165305" x2="270.0000000000005" y2="235.16660498395402" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270.0000000000005" cy="235.16660498395402" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270.0000000000005" y1="235.16660498395402" x2="300.00000000000045" y2="183.2050807568877" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="300.00000000000045" cy="183.2050807568877" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="300.00000000000045" y1="183.2050807568877" x2="420.00000000000045" y2="183.2050807568877" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="420.00000000000045" cy="183.2050807568877" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="420.00000000000045" y1="183.2050807568877" x2="510.00000000000045" y2="339.08965343808666" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510.00000000000045" cy="339.08965343808666" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510.00000000000045" y1="339.08965343808666" x2="500.00000000000045" y2="356.4101615137754" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="500.00000000000045" cy="356.4101615137754" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="500.00000000000045" y1="356.4101615137754" x2="420.00000000000045" y2="356.4101615137754" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="420.00000000000045" cy="356.4101615137754" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="420.00000000000045" y1="356.4101615137754" x2="350.00000000000034" y2="235.16660498395407" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350.00000000000034" cy="235.16660498395407" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350.00000000000034" y1="235.16660498395407" x2="450.0000000000002" y2="61.96152422706625" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450.0000000000002" cy="61.96152422706625" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450.0000000000002" y1="61.96152422706625" x2="490.0000000000002" y2="61.96152422706625" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490.0000000000002" cy="61.96152422706625" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490.0000000000002" y1="61.96152422706625" x2="540.0000000000002" y2="148.5640646055101" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="540.0000000000002" cy="148.5640646055101" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="540.0000000000002" y1="148.5640646055101" x2="460.0000000000003" y2="287.1281292110203" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="460.0000000000003" cy="287.1281292110203" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="460.0000000000003" y1="287.1281292110203" x2="240.00000000000028" y2="287.12812921102034" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="240.00000000000028" cy="287.12812921102034" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="240.00000000000028" y1="287.12812921102034" x2="210.00000000000026" y2="235.16660498395402" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.00000000000026" cy="235.16660498395402" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.00000000000026" y1="235.16660498395402" x2="270.0000000000001" y2="131.24355652982135" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270.0000000000001" cy="131.24355652982135" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270.0000000000001" y1="131.24355652982135" x2="450.0000000000001" y2="131.24355652982135" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450.0000000000001" cy="131.24355652982135" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450.0000000000001" y1="131.24355652982135" x2="460.0000000000001" y2="148.5640646055101" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="460.0000000000001" cy="148.5640646055101" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="460.0000000000001" y1="148.5640646055101" x2="420.0000000000001" y2="217.84609690826522" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="420.0000000000001" cy="217.84609690826522" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="420.0000000000001" y1="217.84609690826522" x2="280.0000000000001" y2="217.84609690826525" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="280.0000000000001" cy="217.84609690826525" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="280.0000000000001" y1="217.84609690826525" x2="180.00000000000006" y2="44.64101615137757" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="180.00000000000006" cy="44.64101615137757" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="180.00000000000006" y1="44.64101615137757" x2="200.00000000000003" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="200.00000000000003" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="200.00000000000003" y1="10.0" x2="300.00000000000006" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="300.00000000000006" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="300.00000000000006" y1="10.0" x2="380.00000000000006" y2="148.56406460551017" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="380.00000000000006" cy="148.56406460551017" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="380.00000000000006" y1="148.56406460551017" x2="270.0000000000001" y2="339.08965343808677" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270.0000000000001" cy="339.08965343808677" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270.0000000000001" y1="339.08965343808677" x2="210.00000000000014" y2="339.08965343808677" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.00000000000014" cy="339.08965343808677" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.00000000000014" y1="339.08965343808677" x2="150.00000000000009" y2="235.1666049839541" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150.00000000000009" cy="235.1666049839541" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150.00000000000009" y1="235.1666049839541" x2="239.99999999999997" y2="79.28203230275507" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="239.99999999999997" cy="79.28203230275507" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="239.99999999999997" y1="79.28203230275507" x2="260.0" y2="79.28203230275507" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="260.0" cy="79.28203230275507" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="260.0" y1="79.28203230275507" x2="300.0" y2="148.56406460551017" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="300.0" cy="148.56406460551017" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="300.0" y1="148.56406460551017" x2="230.0" y2="269.8076211353316" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230.0" cy="269.8076211353316" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230.0" y1="269.8076211353316" x2="30.000000000000018" y2="269.8076211353316" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30.000000000000018" cy="269.8076211353316" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30.000000000000018" y1="269.8076211353316" x2="10.0" y2="235.16660498395407" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10.0" cy="235.16660498395407" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10.0" y1="235.16660498395407" x2="59.99999999999993" y2="148.56406460551017" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="59.99999999999993" cy="148.56406460551017" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="59.99999999999993" y1="148.56406460551017" x2="219.99999999999994" y2="148.56406460551017" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="219.99999999999994" cy="148.56406460551017" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="219.99999999999994" y1="148.56406460551017" x2="329.99999999999994" y2="339.08965343808666" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="329.99999999999994" cy="339.08965343808666" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="329.99999999999994" y1="339.08965343808666" x2="299.99999999999994" y2="391.051177665153" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="299.99999999999994" cy="391.051177665153" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="299.99999999999994" y1="391.051177665153" x2="179.99999999999997" y2="391.051177665153" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="179.99999999999997" cy="391.051177665153" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="179.99999999999997" y1="391.051177665153" x2="89.99999999999989" y2="235.1666049839541" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="89.99999999999989" cy="235.1666049839541" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="89.99999999999989" y1="235.1666049839541" x2="99.99999999999987" y2="217.8460969082653" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="99.99999999999987" cy="217.8460969082653" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="99.99999999999987" y1="217.8460969082653" x2="179.99999999999986" y2="217.8460969082653" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="179.99999999999986" cy="217.8460969082653" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="179.99999999999986" y1="217.8460969082653" x2="249.9999999999999" y2="339.08965343808677" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.9999999999999" cy="339.08965343808677" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.9999999999999" y1="339.08965343808677" x2="149.99999999999994" y2="512.2947341949745" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="149.99999999999994" cy="512.2947341949745" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="149.99999999999994" y1="512.2947341949745" x2="109.99999999999993" y2="512.2947341949745" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="109.99999999999993" cy="512.2947341949745" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="109.99999999999993" y1="512.2947341949745" x2="59.99999999999987" y2="425.6921938165306" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="59.99999999999987" cy="425.6921938165306" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="59.99999999999987" y1="425.6921938165306" x2="139.99999999999977" y2="287.12812921102034" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="139.99999999999977" cy="287.12812921102034" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
42
content/blog/modulo_patterns/pattern_3_4.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="220.0" height="200.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="10" x2="90.0" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="10.0" x2="90.0" y2="70.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="70.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="70.0" x2="50.0" y2="70.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50.0" cy="70.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50.0" y1="70.0" x2="49.99999999999999" y2="50.00000000000001" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="49.99999999999999" cy="50.00000000000001" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="49.99999999999999" y1="50.00000000000001" x2="130.0" y2="50.00000000000001" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="50.00000000000001" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="50.00000000000001" x2="130.0" y2="110.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="110.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="110.0" x2="90.0" y2="110.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="110.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="110.0" x2="90.0" y2="90.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90.0" cy="90.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90.0" y1="90.0" x2="170.0" y2="90.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="90.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="90.0" x2="170.0" y2="150.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="150.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="150.0" x2="130.0" y2="150.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="150.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="150.0" x2="130.0" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="130.0" x2="210.0" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="130.0" x2="210.0" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="190.0" x2="170.0" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="190.0" x2="170.0" y2="170.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="170.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
66
content/blog/modulo_patterns/pattern_3_4_t7.svg
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="159.48689754371753" height="158.3975428930541"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="22.46979603717472" y1="39.89495294132937" x2="102.46979603717472" y2="39.89495294132937" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="102.46979603717472" cy="39.89495294132937" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="102.46979603717472" y1="39.89495294132937" x2="139.87918414869876" y2="86.80484188941116" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="139.87918414869876" cy="86.80484188941116" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="139.87918414869876" y1="86.80484188941116" x2="130.97834679044615" y2="125.8019583766841" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.97834679044615" cy="125.8019583766841" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.97834679044615" y1="125.8019583766841" x2="112.9589694323978" y2="134.47963315903525" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="112.9589694323978" cy="134.47963315903525" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="112.9589694323978" y1="134.47963315903525" x2="40.88146000020426" y2="99.7689340296306" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="40.88146000020426" cy="99.7689340296306" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="40.88146000020426" y1="99.7689340296306" x2="27.53020396282538" y2="41.2732592987212" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="27.53020396282538" cy="41.2732592987212" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="27.53020396282538" y1="41.2732592987212" x2="52.46979603717471" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="52.46979603717471" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="52.46979603717471" y1="10.0" x2="72.46979603717472" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="72.46979603717472" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="72.46979603717472" y1="10.0" x2="122.34898018587339" y2="72.54651859744239" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="122.34898018587339" cy="72.54651859744239" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="122.34898018587339" y1="72.54651859744239" x2="108.99772414849453" y2="131.0421933283518" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="108.99772414849453" cy="131.0421933283518" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="108.99772414849453" y1="131.0421933283518" x2="72.95896943239777" y2="148.3975428930541" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="72.95896943239777" cy="148.3975428930541" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="72.95896943239777" y1="148.3975428930541" x2="54.93959207434939" y2="139.71986811070295" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="54.93959207434939" cy="139.71986811070295" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="54.93959207434939" y1="139.71986811070295" x2="37.13791735784422" y2="61.72563513615707" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="37.13791735784422" cy="61.72563513615707" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="37.13791735784422" y1="61.72563513615707" x2="74.54730546936823" y2="14.815746188075272" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="74.54730546936823" cy="14.815746188075272" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="74.54730546936823" y1="14.815746188075272" x2="114.54730546936823" y2="14.815746188075272" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="114.54730546936823" cy="14.815746188075272" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="114.54730546936823" y1="14.815746188075272" x2="127.0171015065429" y2="30.45237583743587" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="127.0171015065429" cy="30.45237583743587" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="127.0171015065429" y1="30.45237583743587" x2="109.21542679003775" y2="108.44660881198178" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="109.21542679003775" cy="108.44660881198178" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="109.21542679003775" y1="108.44660881198178" x2="55.157294715892604" y2="134.47963315903525" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="55.157294715892604" cy="134.47963315903525" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="55.157294715892604" y1="134.47963315903525" x2="19.11853999979584" y2="117.12428359433292" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="19.11853999979584" cy="117.12428359433292" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="19.11853999979584" y1="117.12428359433292" x2="14.668121320669547" y2="97.62572535069648" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="14.668121320669547" cy="97.62572535069648" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="14.668121320669547" y1="97.62572535069648" x2="64.54730546936821" y2="35.07920675325408" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="64.54730546936821" cy="35.07920675325408" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="64.54730546936821" y1="35.07920675325408" x2="124.54730546936821" y2="35.07920675325408" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="124.54730546936821" cy="35.07920675325408" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="124.54730546936821" y1="35.07920675325408" x2="149.48689754371753" y2="66.35246605197526" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="149.48689754371753" cy="66.35246605197526" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="149.48689754371753" y1="66.35246605197526" x2="145.03647886459126" y2="85.85102429561174" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="145.03647886459126" cy="85.85102429561174" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="145.03647886459126" y1="85.85102429561174" x2="72.95896943239774" y2="120.5617234250164" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="72.95896943239774" cy="120.5617234250164" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="72.95896943239774" y1="120.5617234250164" x2="18.90083735825258" y2="94.52869907796291" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="18.90083735825258" cy="94.52869907796291" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="18.90083735825258" y1="94.52869907796291" x2="10.0" y2="55.53158259068997" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10.0" cy="55.53158259068997" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10.0" y1="55.53158259068997" x2="22.469796037174667" y2="39.89495294132938" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="22.469796037174667" cy="39.89495294132938" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
82
content/blog/modulo_patterns/pattern_4.svg
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="280" height="280"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="50" x2="10" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="130" x2="170" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="130" x2="170" y2="70" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="70" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="70" x2="30" y2="70" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="70" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="70" x2="30" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="110" x2="150" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="110" x2="150" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="90" x2="50" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="90" x2="50" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="270" x2="130" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="270" x2="130" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="110" x2="70" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="70" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="70" y1="110" x2="70" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="70" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="70" y1="250" x2="110" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="250" x2="110" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="130" x2="90" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="130" x2="90" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="230" x2="270" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="230" x2="270" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="150" x2="110" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="150" x2="110" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="210" x2="250" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="210" x2="250" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="170" x2="130" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="170" x2="130" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="190" x2="230" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="190" x2="230" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="10" x2="150" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="10" x2="150" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="170" x2="210" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="170" x2="210" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="30" x2="170" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="30" x2="170" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="150" x2="190" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="150" x2="190" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="50" x2="10" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
114
content/blog/modulo_patterns/pattern_4_13.svg
Normal file
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="600" height="600"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="130" y1="170" x2="130" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="250" x2="290" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="250" x2="290" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="10" x2="230" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="10" x2="230" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="150" x2="450" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="150" x2="450" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="110" x2="330" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="110" x2="330" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="310" x2="350" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="310" x2="350" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="210" x2="170" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="210" x2="170" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="470" x2="250" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="470" x2="250" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="310" x2="10" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="310" x2="10" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="370" x2="150" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="370" x2="150" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="150" x2="110" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="150" x2="110" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="270" x2="310" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="270" x2="310" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="250" x2="210" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="250" x2="210" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="430" x2="470" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="430" x2="470" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="350" x2="310" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="350" x2="310" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="590" x2="370" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370" y1="590" x2="370" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370" y1="450" x2="150" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="450" x2="150" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="490" x2="270" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="490" x2="270" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="290" x2="250" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="290" x2="250" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="390" x2="430" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="390" x2="430" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="130" x2="350" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="130" x2="350" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="290" x2="590" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="290" x2="590" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="230" x2="450" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="230" x2="450" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="450" x2="490" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="450" x2="490" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="330" x2="290" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="330" x2="290" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="350" x2="390" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="350" x2="390" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="170" x2="130" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
80
content/blog/modulo_patterns/pattern_4_mod.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="260" height="260"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="90" y1="130" x2="90" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="210" x2="250" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="210" x2="250" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="150" x2="110" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="150" x2="110" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="190" x2="230" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="190" x2="230" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="170" x2="130" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="170" x2="130" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="170" x2="210" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="170" x2="210" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="10" x2="150" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="10" x2="150" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="150" x2="190" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="150" x2="190" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="30" x2="170" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="30" x2="170" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="130" x2="170" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="130" x2="170" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="50" x2="10" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="50" x2="10" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="110" x2="150" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="110" x2="150" y2="70" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="70" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="70" x2="30" y2="70" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="70" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="70" x2="30" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="90" x2="130" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="90" x2="130" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="90" x2="50" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="90" x2="50" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="250" x2="110" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="250" x2="110" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="110" x2="70" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="70" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="70" y1="110" x2="70" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="70" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="70" y1="230" x2="90" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="230" x2="90" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
146
content/blog/modulo_patterns/pattern_5_17.svg
Normal file
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="800" height="800"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="230" x2="10" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="330" x2="210" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="330" x2="210" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="30" x2="150" y2="30" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="30" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="30" x2="150" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="190" x2="410" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="190" x2="410" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="170" x2="290" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="170" x2="290" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="390" x2="610" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="390" x2="610" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="310" x2="430" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="310" x2="430" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="590" x2="470" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="590" x2="470" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="450" x2="230" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="450" x2="230" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="790" x2="330" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="790" x2="330" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="590" x2="30" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="590" x2="30" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="30" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="30" y1="650" x2="190" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="650" x2="190" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="390" x2="170" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="390" x2="170" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="510" x2="390" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="510" x2="390" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="190" x2="310" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="190" x2="310" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="370" x2="590" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="370" x2="590" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="330" x2="450" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="330" x2="450" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="570" x2="790" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="570" x2="790" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="470" x2="590" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="470" x2="590" y2="770" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="770" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="770" x2="650" y2="770" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="770" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="770" x2="650" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="610" x2="390" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="610" x2="390" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="630" x2="510" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="630" x2="510" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="410" x2="190" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="410" x2="190" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="490" x2="370" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370" y1="490" x2="370" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370" y1="210" x2="330" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="210" x2="330" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="350" x2="570" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="350" x2="570" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="10" x2="470" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="10" x2="470" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="470" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="470" y1="210" x2="770" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="770" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="770" y1="210" x2="770" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="770" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="770" y1="150" x2="610" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="150" x2="610" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="410" x2="630" y2="410" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="410" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="410" x2="630" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="290" x2="410" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="290" x2="410" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410" y1="610" x2="490" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="610" x2="490" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="430" x2="210" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="430" x2="210" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="470" x2="350" y2="470" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="470" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="470" x2="350" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="230" x2="10" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
242
content/blog/modulo_patterns/pattern_6_29.svg
Normal file
@@ -0,0 +1,242 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="1640" height="1640"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="1050" y1="530" x2="1050" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1050" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1050" y1="650" x2="1290" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1290" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1290" y1="650" x2="1290" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1290" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1290" y1="290" x2="810" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="810" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="810" y1="290" x2="810" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="810" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="810" y1="310" x2="950" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="950" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="950" y1="310" x2="950" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="950" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="950" y1="50" x2="570" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="50" x2="570" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="550" x2="610" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="550" x2="610" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="390" x2="330" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="390" x2="330" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="790" x2="850" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="850" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="850" y1="790" x2="850" y2="730" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="850" cy="730" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="850" y1="730" x2="670" y2="730" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="670" cy="730" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="670" y1="730" x2="670" y2="1030" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="670" cy="1030" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="670" y1="1030" x2="1090" y2="1030" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1090" cy="1030" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1090" y1="1030" x2="1090" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1090" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1090" y1="490" x2="1010" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1010" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1010" y1="490" x2="1010" y2="690" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1010" cy="690" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1010" y1="690" x2="1330" y2="690" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1330" cy="690" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1330" y1="690" x2="1330" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1330" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1330" y1="250" x2="770" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="770" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="770" y1="250" x2="770" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="770" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="770" y1="350" x2="990" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="990" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="990" y1="350" x2="990" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="990" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="990" y1="10" x2="530" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="530" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="530" y1="10" x2="530" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="530" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="530" y1="590" x2="650" y2="590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="590" x2="650" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="350" x2="290" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="350" x2="290" y2="830" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="830" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="830" x2="310" y2="830" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="830" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="830" x2="310" y2="690" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="690" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="690" x2="50" y2="690" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="690" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="690" x2="50" y2="1070" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="1070" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="1070" x2="550" y2="1070" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="1070" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="1070" x2="550" y2="1030" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="1030" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="1030" x2="390" y2="1030" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="1030" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="1030" x2="390" y2="1310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="1310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="1310" x2="790" y2="1310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="1310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="1310" x2="790" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="790" x2="730" y2="790" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="730" cy="790" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="730" y1="790" x2="730" y2="970" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="730" cy="970" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="730" y1="970" x2="1030" y2="970" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1030" cy="970" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1030" y1="970" x2="1030" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1030" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1030" y1="550" x2="490" y2="550" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="550" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="550" x2="490" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="630" x2="690" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="690" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="690" y1="630" x2="690" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="690" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="690" y1="310" x2="250" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="310" x2="250" y2="870" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="870" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="870" x2="350" y2="870" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="870" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="870" x2="350" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="650" x2="10" y2="650" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="650" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="650" x2="10" y2="1110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="1110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="1110" x2="590" y2="1110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="1110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="1110" x2="590" y2="990" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="590" cy="990" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="590" y1="990" x2="350" y2="990" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="990" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="990" x2="350" y2="1350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="1350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="1350" x2="830" y2="1350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="830" cy="1350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="830" y1="1350" x2="830" y2="1330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="830" cy="1330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="830" y1="1330" x2="690" y2="1330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="690" cy="1330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="690" y1="1330" x2="690" y2="1590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="690" cy="1590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="690" y1="1590" x2="1070" y2="1590" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1070" cy="1590" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1070" y1="1590" x2="1070" y2="1090" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1070" cy="1090" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1070" y1="1090" x2="1030" y2="1090" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1030" cy="1090" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1030" y1="1090" x2="1030" y2="1250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1030" cy="1250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1030" y1="1250" x2="1310" y2="1250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1310" cy="1250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1310" y1="1250" x2="1310" y2="850" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1310" cy="850" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1310" y1="850" x2="790" y2="850" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="850" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="850" x2="790" y2="910" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="790" cy="910" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="790" y1="910" x2="970" y2="910" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="970" cy="910" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="970" y1="910" x2="970" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="970" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="970" y1="610" x2="550" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="610" x2="550" y2="1150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="550" cy="1150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="550" y1="1150" x2="630" y2="1150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="1150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="1150" x2="630" y2="950" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="950" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="950" x2="310" y2="950" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="950" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="950" x2="310" y2="1390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="1390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="1390" x2="870" y2="1390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="870" cy="1390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="870" y1="1390" x2="870" y2="1290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="870" cy="1290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="870" y1="1290" x2="650" y2="1290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="1290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="1290" x2="650" y2="1630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="650" cy="1630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="650" y1="1630" x2="1110" y2="1630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1110" cy="1630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1110" y1="1630" x2="1110" y2="1050" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1110" cy="1050" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1110" y1="1050" x2="990" y2="1050" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="990" cy="1050" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="990" y1="1050" x2="990" y2="1290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="990" cy="1290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="990" y1="1290" x2="1350" y2="1290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1350" cy="1290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1350" y1="1290" x2="1350" y2="810" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1350" cy="810" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1350" y1="810" x2="1330" y2="810" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1330" cy="810" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1330" y1="810" x2="1330" y2="950" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1330" cy="950" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1330" y1="950" x2="1590" y2="950" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1590" cy="950" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1590" y1="950" x2="1590" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1590" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1590" y1="570" x2="1090" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1090" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1090" y1="570" x2="1090" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1090" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1090" y1="610" x2="1250" y2="610" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1250" cy="610" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1250" y1="610" x2="1250" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1250" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1250" y1="330" x2="850" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="850" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="850" y1="330" x2="850" y2="850" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="850" cy="850" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="850" y1="850" x2="910" y2="850" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="910" cy="850" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="910" y1="850" x2="910" y2="670" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="910" cy="670" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="910" y1="670" x2="610" y2="670" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="670" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="670" x2="610" y2="1090" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="610" cy="1090" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="610" y1="1090" x2="1150" y2="1090" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1150" cy="1090" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1150" y1="1090" x2="1150" y2="1010" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1150" cy="1010" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1150" y1="1010" x2="950" y2="1010" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="950" cy="1010" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="950" y1="1010" x2="950" y2="1330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="950" cy="1330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="950" y1="1330" x2="1390" y2="1330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1390" cy="1330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1390" y1="1330" x2="1390" y2="770" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1390" cy="770" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1390" y1="770" x2="1290" y2="770" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1290" cy="770" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1290" y1="770" x2="1290" y2="990" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1290" cy="990" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1290" y1="990" x2="1630" y2="990" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1630" cy="990" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1630" y1="990" x2="1630" y2="530" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1630" cy="530" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="1630" y1="530" x2="1050" y2="530" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="1050" cy="530" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 19 KiB |
74
content/blog/modulo_patterns/pattern_7_8.svg
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="420.0" height="400.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="10" x2="170.0" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="10.0" x2="170.0" y2="150.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="150.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="150.0" x2="50.0" y2="150.00000000000003" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50.0" cy="150.00000000000003" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50.0" y1="150.00000000000003" x2="49.999999999999986" y2="50.000000000000014" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="49.999999999999986" cy="50.000000000000014" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="49.999999999999986" y1="50.000000000000014" x2="130.0" y2="50.000000000000014" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="50.000000000000014" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="50.000000000000014" x2="130.0" y2="110.00000000000001" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="110.00000000000001" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="110.00000000000001" x2="89.99999999999999" y2="110.00000000000001" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="89.99999999999999" cy="110.00000000000001" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="89.99999999999999" y1="110.00000000000001" x2="89.99999999999999" y2="90.00000000000001" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="89.99999999999999" cy="90.00000000000001" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="89.99999999999999" y1="90.00000000000001" x2="250.0" y2="90.00000000000001" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250.0" cy="90.00000000000001" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250.0" y1="90.00000000000001" x2="250.0" y2="230.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250.0" cy="230.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250.0" y1="230.0" x2="130.0" y2="230.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="230.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="230.0" x2="130.0" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="130.0" x2="210.0" y2="130.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="130.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="130.0" x2="210.0" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="190.0" x2="170.0" y2="190.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="190.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="190.0" x2="170.0" y2="170.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0" cy="170.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0" y1="170.0" x2="330.0" y2="170.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330.0" cy="170.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330.0" y1="170.0" x2="330.0" y2="310.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330.0" cy="310.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330.0" y1="310.0" x2="210.0" y2="310.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210.0" cy="310.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210.0" y1="310.0" x2="209.99999999999997" y2="210.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="209.99999999999997" cy="210.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="209.99999999999997" y1="210.0" x2="289.99999999999994" y2="210.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999994" cy="210.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999994" y1="210.0" x2="289.99999999999994" y2="270.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999994" cy="270.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999994" y1="270.0" x2="249.99999999999997" y2="270.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.99999999999997" cy="270.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.99999999999997" y1="270.0" x2="249.99999999999997" y2="250.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.99999999999997" cy="250.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.99999999999997" y1="250.0" x2="410.0" y2="250.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410.0" cy="250.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410.0" y1="250.0" x2="410.0" y2="390.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="410.0" cy="390.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="410.0" y1="390.0" x2="290.0" y2="390.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290.0" cy="390.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290.0" y1="390.0" x2="289.99999999999994" y2="290.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999994" cy="290.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999994" y1="290.0" x2="370.0" y2="290.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370.0" cy="290.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370.0" y1="290.0" x2="370.0" y2="350.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370.0" cy="350.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370.0" y1="350.0" x2="330.0" y2="350.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330.0" cy="350.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330.0" y1="350.0" x2="330.0" y2="330.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330.0" cy="330.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
58
content/blog/modulo_patterns/pattern_7_8_t3.svg
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="240.00000000000014" height="227.84609690826517"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="70.00000000000014" y1="96.60254037844375" x2="230.00000000000014" y2="96.60254037844375" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230.00000000000014" cy="96.60254037844375" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230.00000000000014" y1="96.60254037844375" x2="160.00000000000017" y2="217.84609690826517" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.00000000000017" cy="217.84609690826517" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.00000000000017" y1="217.84609690826517" x2="100.00000000000011" y2="113.92304845413257" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="100.00000000000011" cy="113.92304845413257" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="100.00000000000011" y1="113.92304845413257" x2="200.0000000000001" y2="113.92304845413257" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="200.0000000000001" cy="113.92304845413257" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="200.0000000000001" y1="113.92304845413257" x2="160.00000000000014" y2="183.20508075688767" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.00000000000014" cy="183.20508075688767" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.00000000000014" y1="183.20508075688767" x2="130.0000000000001" y2="131.24355652982138" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0000000000001" cy="131.24355652982138" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0000000000001" y1="131.24355652982138" x2="170.0000000000001" y2="131.24355652982138" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170.0000000000001" cy="131.24355652982138" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170.0000000000001" y1="131.24355652982138" x2="160.0000000000001" y2="148.56406460551014" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.0000000000001" cy="148.56406460551014" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.0000000000001" y1="148.56406460551014" x2="80.00000000000003" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="80.00000000000003" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="80.00000000000003" y1="10.0" x2="220.00000000000003" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="220.00000000000003" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="220.00000000000003" y1="10.0" x2="160.00000000000006" y2="113.92304845413264" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.00000000000006" cy="113.92304845413264" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.00000000000006" y1="113.92304845413264" x2="110.00000000000001" y2="27.320508075688803" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110.00000000000001" cy="27.320508075688803" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110.00000000000001" y1="27.320508075688803" x2="190.00000000000003" y2="27.320508075688803" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190.00000000000003" cy="27.320508075688803" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190.00000000000003" y1="27.320508075688803" x2="160.00000000000003" y2="79.28203230275513" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.00000000000003" cy="79.28203230275513" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.00000000000003" y1="79.28203230275513" x2="140.00000000000003" y2="44.641016151377585" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="140.00000000000003" cy="44.641016151377585" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="140.00000000000003" y1="44.641016151377585" x2="160.00000000000003" y2="44.641016151377585" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="160.00000000000003" cy="44.641016151377585" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="160.00000000000003" y1="44.641016151377585" x2="80.00000000000006" y2="183.20508075688778" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="80.00000000000006" cy="183.20508075688778" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="80.00000000000006" y1="183.20508075688778" x2="10.0" y2="61.961524227066406" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10.0" cy="61.961524227066406" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10.0" y1="61.961524227066406" x2="130.0" y2="61.961524227066406" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130.0" cy="61.961524227066406" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130.0" y1="61.961524227066406" x2="80.00000000000001" y2="148.56406460551028" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="80.00000000000001" cy="148.56406460551028" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="80.00000000000001" y1="148.56406460551028" x2="39.999999999999986" y2="79.28203230275521" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="39.999999999999986" cy="79.28203230275521" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="39.999999999999986" y1="79.28203230275521" x2="99.99999999999999" y2="79.28203230275521" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="99.99999999999999" cy="79.28203230275521" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="99.99999999999999" y1="79.28203230275521" x2="79.99999999999999" y2="113.92304845413277" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="79.99999999999999" cy="113.92304845413277" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="79.99999999999999" y1="113.92304845413277" x2="69.99999999999999" y2="96.60254037844399" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="69.99999999999999" cy="96.60254037844399" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
82
content/blog/modulo_patterns/pattern_8.svg
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="360" height="360"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="90" x2="10" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="250" x2="150" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="250" x2="150" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="130" x2="50" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="130" x2="50" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="210" x2="110" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="210" x2="110" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="170" x2="90" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="170" x2="90" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="350" x2="250" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="350" x2="250" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="210" x2="130" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="210" x2="130" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="310" x2="210" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="310" x2="210" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="250" x2="170" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="250" x2="170" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="270" x2="350" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="270" x2="350" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="110" x2="210" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="110" x2="210" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="230" x2="310" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="230" x2="310" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="150" x2="250" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="150" x2="250" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="190" x2="270" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="190" x2="270" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="10" x2="110" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="10" x2="110" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="150" x2="230" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="150" x2="230" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="50" x2="150" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="50" x2="150" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="110" x2="190" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="110" x2="190" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="90" x2="10" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
114
content/blog/modulo_patterns/pattern_8_13.svg
Normal file
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="640" height="640"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="370" y1="190" x2="370" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="370" y1="350" x2="430" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="350" x2="430" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="130" x2="310" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="130" x2="310" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="310" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="310" y1="150" x2="490" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="150" x2="490" y2="70" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="70" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="70" x2="250" y2="70" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="70" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="70" x2="250" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="210" x2="290" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="210" x2="290" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="10" x2="190" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="10" x2="190" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="270" x2="350" y2="270" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="270" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="270" x2="350" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="210" x2="130" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="210" x2="130" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="330" x2="150" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="330" x2="150" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="150" x2="70" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="70" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="70" y1="150" x2="70" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="70" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="70" y1="390" x2="210" y2="390" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="390" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="390" x2="210" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="350" x2="10" y2="350" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="350" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="350" x2="10" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="450" x2="270" y2="450" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="450" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="450" x2="270" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="270" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="270" y1="290" x2="210" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="290" x2="210" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="510" x2="330" y2="510" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="510" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="510" x2="330" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="490" x2="150" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="490" x2="150" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="570" x2="390" y2="570" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="570" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="570" x2="390" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="390" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="390" y1="430" x2="350" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="430" x2="350" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="350" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="350" y1="630" x2="450" y2="630" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="630" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="630" x2="450" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="450" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="450" y1="370" x2="290" y2="370" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="370" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="370" x2="290" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="430" x2="510" y2="430" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="430" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="430" x2="510" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="510" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="510" y1="310" x2="490" y2="310" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="310" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="310" x2="490" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="490" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="490" y1="490" x2="570" y2="490" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="490" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="490" x2="570" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="570" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="570" y1="250" x2="430" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="250" x2="430" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="430" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="430" y1="290" x2="630" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="290" x2="630" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="630" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="630" y1="190" x2="370" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="370" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
64
content/blog/modulo_patterns/pattern_8_9_t3.svg
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="379.9999999999999" height="262.487113059643"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="10" y1="10" x2="190.0" y2="10.0" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190.0" cy="10.0" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190.0" y1="10.0" x2="110.00000000000003" y2="148.5640646055102" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110.00000000000003" cy="148.5640646055102" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110.00000000000003" y1="148.5640646055102" x2="39.99999999999997" y2="27.32050807568882" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="39.99999999999997" cy="27.32050807568882" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="39.99999999999997" y1="27.32050807568882" x2="159.99999999999997" y2="27.32050807568882" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="159.99999999999997" cy="27.32050807568882" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="159.99999999999997" y1="27.32050807568882" x2="109.99999999999999" y2="113.9230484541327" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="109.99999999999999" cy="113.9230484541327" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="109.99999999999999" y1="113.9230484541327" x2="69.99999999999994" y2="44.64101615137763" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="69.99999999999994" cy="44.64101615137763" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="69.99999999999994" y1="44.64101615137763" x2="129.99999999999994" y2="44.64101615137763" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="129.99999999999994" cy="44.64101615137763" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="129.99999999999994" y1="44.64101615137763" x2="109.99999999999997" y2="79.28203230275517" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="109.99999999999997" cy="79.28203230275517" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="109.99999999999997" y1="79.28203230275517" x2="99.99999999999997" y2="61.961524227066406" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="99.99999999999997" cy="61.961524227066406" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="99.99999999999997" y1="61.961524227066406" x2="279.99999999999994" y2="61.961524227066406" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="279.99999999999994" cy="61.961524227066406" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="279.99999999999994" y1="61.961524227066406" x2="200.0" y2="200.5255888325766" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="200.0" cy="200.5255888325766" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="200.0" y1="200.5255888325766" x2="129.99999999999994" y2="79.28203230275523" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="129.99999999999994" cy="79.28203230275523" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="129.99999999999994" y1="79.28203230275523" x2="249.99999999999994" y2="79.28203230275523" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.99999999999994" cy="79.28203230275523" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.99999999999994" y1="79.28203230275523" x2="199.99999999999994" y2="165.8845726811991" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="199.99999999999994" cy="165.8845726811991" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="199.99999999999994" y1="165.8845726811991" x2="159.9999999999999" y2="96.60254037844403" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="159.9999999999999" cy="96.60254037844403" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="159.9999999999999" y1="96.60254037844403" x2="219.9999999999999" y2="96.60254037844403" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="219.9999999999999" cy="96.60254037844403" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="219.9999999999999" y1="96.60254037844403" x2="199.9999999999999" y2="131.2435565298216" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="199.9999999999999" cy="131.2435565298216" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="199.9999999999999" y1="131.2435565298216" x2="189.9999999999999" y2="113.92304845413281" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="189.9999999999999" cy="113.92304845413281" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="189.9999999999999" y1="113.92304845413281" x2="369.9999999999999" y2="113.92304845413281" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="369.9999999999999" cy="113.92304845413281" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="369.9999999999999" y1="113.92304845413281" x2="289.9999999999999" y2="252.48711305964298" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.9999999999999" cy="252.48711305964298" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.9999999999999" y1="252.48711305964298" x2="219.99999999999983" y2="131.2435565298216" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="219.99999999999983" cy="131.2435565298216" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="219.99999999999983" y1="131.2435565298216" x2="339.9999999999999" y2="131.2435565298216" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="339.9999999999999" cy="131.2435565298216" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="339.9999999999999" y1="131.2435565298216" x2="289.9999999999999" y2="217.8460969082655" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.9999999999999" cy="217.8460969082655" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.9999999999999" y1="217.8460969082655" x2="249.99999999999983" y2="148.56406460551042" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="249.99999999999983" cy="148.56406460551042" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="249.99999999999983" y1="148.56406460551042" x2="309.99999999999983" y2="148.56406460551042" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="309.99999999999983" cy="148.56406460551042" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="309.99999999999983" y1="148.56406460551042" x2="289.99999999999983" y2="183.20508075688795" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="289.99999999999983" cy="183.20508075688795" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="289.99999999999983" y1="183.20508075688795" x2="279.99999999999983" y2="165.8845726811992" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="279.99999999999983" cy="165.8845726811992" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
80
content/blog/modulo_patterns/pattern_8_mod.svg
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<svg width="340" height="340"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
|
||||
<line x1="90" y1="170" x2="90" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="330" x2="230" y2="330" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="330" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="330" x2="230" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="230" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="230" y1="210" x2="130" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="210" x2="130" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="290" x2="190" y2="290" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="290" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="290" x2="190" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="190" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="190" y1="250" x2="170" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="250" x2="170" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="250" x2="330" y2="250" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="250" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="250" x2="330" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="330" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="330" y1="110" x2="210" y2="110" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="110" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="110" x2="210" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="210" x2="290" y2="210" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="210" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="210" x2="290" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="290" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="290" y1="150" x2="250" y2="150" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="150" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="150" x2="250" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="170" x2="250" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="170" x2="250" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="250" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="250" y1="10" x2="110" y2="10" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="10" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="10" x2="110" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="110" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="110" y1="130" x2="210" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="130" x2="210" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="210" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="210" y1="50" x2="150" y2="50" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="50" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="50" x2="150" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="150" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="150" y1="90" x2="170" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="90" x2="170" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="170" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="170" y1="90" x2="10" y2="90" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="90" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="90" x2="10" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="10" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="10" y1="230" x2="130" y2="230" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="230" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="230" x2="130" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="130" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="130" y1="130" x2="50" y2="130" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="130" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="130" x2="50" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="50" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="50" y1="190" x2="90" y2="190" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="190" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
<line x1="90" y1="190" x2="90" y2="170" style="stroke:black; stroke-width:5"/>
|
||||
<circle cx="90" cy="170" r="3" style="stroke:black; stroke-width:5" fill="black"/>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
BIN
content/blog/modulo_patterns/turn_3_1.gif
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
content/blog/modulo_patterns/turn_3_2.gif
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
content/blog/modulo_patterns/turn_3_anim.gif
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
content/blog/modulo_patterns/turn_5_2.gif
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
content/blog/modulo_patterns/turn_5_anim.gif
Normal file
|
After Width: | Height: | Size: 4.5 MiB |
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "A Typesafe Representation of an Imperative Language"
|
||||
date: 2020-11-02T01:07:21-08:00
|
||||
tags: ["Idris"]
|
||||
tags: ["Idris", "Programming Languages"]
|
||||
---
|
||||
|
||||
A recent homework assignment for my university's programming languages
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Meaningfully Typechecking a Language in Idris
|
||||
date: 2020-02-27T21:58:55-08:00
|
||||
tags: ["Haskell", "Idris"]
|
||||
tags: ["Haskell", "Idris", "Programming Languages"]
|
||||
---
|
||||
|
||||
This term, I'm a TA for Oregon State University's Programming Languages course.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: Meaningfully Typechecking a Language in Idris, Revisited
|
||||
date: 2020-07-22T14:37:35-07:00
|
||||
tags: ["Idris"]
|
||||
tags: ["Idris", "Programming Languages"]
|
||||
favorite: true
|
||||
---
|
||||
|
||||
Some time ago, I wrote a post titled [Meaningfully Typechecking a Language in Idris]({{< relref "typesafe_interpreter.md" >}}). The gist of the post was as follows:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Meaningfully Typechecking a Language in Idris, With Tuples
|
||||
date: 2020-08-12T15:48:04-07:00
|
||||
tags: ["Idris"]
|
||||
tags: ["Idris", "Programming Languages"]
|
||||
---
|
||||
|
||||
Some time ago, I wrote a post titled
|
||||
|
||||
120
content/blog/typescript_typesafe_events.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: "Type-Safe Event Emitter in TypeScript"
|
||||
date: 2021-09-04T17:18:49-07:00
|
||||
tags: ["TypeScript"]
|
||||
---
|
||||
|
||||
I've been playing around with TypeScript recently, and enjoying it too.
|
||||
Nearly all of my compile-time type safety desires have been accomodated
|
||||
by the language, and in a rather intuitive and clean way. Today, I'm going
|
||||
to share a little trick I've discovered which allows me to do something that
|
||||
I suspect would normally require [dependent types](https://en.wikipedia.org/wiki/Dependent_type).
|
||||
|
||||
### The Problem
|
||||
Suppose you want to write a class that emits events. Clients can then install handlers,
|
||||
functions that are notified whenever an event is emitted. Easy enough; in JavaScript,
|
||||
this would look something like the following:
|
||||
|
||||
{{< codelines "JavaScript" "typescript-emitter/js1.js" 1 17 >}}
|
||||
|
||||
We can even write some code to test that this works (just to ease my nerves):
|
||||
|
||||
{{< codelines "JavaScript" "typescript-emitter/js1.js" 19 23 >}}
|
||||
|
||||
As expected, we get:
|
||||
|
||||
```
|
||||
Ended!
|
||||
Started!
|
||||
```
|
||||
|
||||
As you probably guessed, we're going to build on this problem a little bit.
|
||||
In certain situations, you don't just care that an event occured; you also
|
||||
care about additional event data. For instance, when a number changes, you
|
||||
may want to know the number's new value. In JavaScript, this is a trivial change:
|
||||
|
||||
{{< codelines "JavaScript" "typescript-emitter/js2.js" 1 17 "hl_lines = 6-8" >}}
|
||||
|
||||
That's literally it. Once again, let's ensure that this works by sending two new events:
|
||||
`stringChange` and `numberChange`.
|
||||
|
||||
{{< codelines "JavaScript" "typescript-emitter/js2.js" 19 23 >}}
|
||||
|
||||
The result of this code is once again unsurprising:
|
||||
|
||||
```
|
||||
New number value is: 1
|
||||
New string value is: 3
|
||||
```
|
||||
|
||||
But now, how would one go about encoding this in TypeScript? In particular, what is the
|
||||
type of a handler? We could, of course, give each handler the type `(value: any) => void`.
|
||||
This, however, makes handlers unsafe. We could very easily write:
|
||||
|
||||
```TypeScript
|
||||
emitter.addHandler("numberChanged", (value: string) => {
|
||||
console.log("String length is", value.length);
|
||||
});
|
||||
emitted.emit("numberChanged", 1);
|
||||
```
|
||||
|
||||
Which would print out:
|
||||
|
||||
```
|
||||
String length is undefined
|
||||
```
|
||||
|
||||
No, I don't like this. TypeScript is supposed to be all about adding type safety to our code,
|
||||
and this is not at all type safe. We could do all sorts of weird things! There is a way,
|
||||
however, to make this use case work.
|
||||
|
||||
### The Solution
|
||||
|
||||
Let me show you what I came up with:
|
||||
|
||||
{{< codelines "TypeScript" "typescript-emitter/ts.ts" 1 19 "hl_lines=1 2 8 12">}}
|
||||
|
||||
The important changes are on lines 1, 2, 8, and 12 (highlighted in the above code block).
|
||||
Let's go through each one of them step-by-step.
|
||||
|
||||
* __Line 1__: Parameterize the `EventEmitter` by some type `T`. We will use this type `T`
|
||||
to specify the exact kind of events that our `EventEmitter` will be able to emit and handle.
|
||||
Specifically, this type will be in the form `{ event: EventValueType }`. For example,
|
||||
for a `mouseClick` event, we may write `{ mouseClick: { x: number, y: number }}`.
|
||||
* __Line 2__: Add a proper type to `handlers`. This requires several ingredients of its own:
|
||||
* We use [index signatures](https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures)
|
||||
to limit the possible names to which handlers can be assigned. We limit these names
|
||||
to the keys of our type `T`; in the preceding example, `keyof T` would be `"mouseClick"`.
|
||||
* We also limit the values: `T[eventName]` retrieves the type of the value associated with
|
||||
key `eventName`. In the mouse example, this type would be `{ x: number, y: number }`. We require
|
||||
that a key can only be associated with an array of functions to void, each of which accepts
|
||||
`T[K]` as first argument. This is precisely what we want; for example, `mouseClick` would map to
|
||||
an array of functions that accept the mouse click location.
|
||||
* __Line 8__: We restrict the type of `emit` to only accept values that correspond to the keys
|
||||
of the type `T`. We can't simply write `event: keyof T`, because this would not give us enough
|
||||
information to retrieve the type of `value`: if `event` is just `keyof T`,
|
||||
then `value` can be any of the values of `T`. Instead, we use generics; this way, when the
|
||||
function is called with `"mouseClick"`, the type of `K` is inferred to also be `"mouseClick"`, which
|
||||
gives TypeScript enough information to narrow the type of `value`.
|
||||
* __Line 12__: We use the exact same trick here as we did on line 8.
|
||||
|
||||
Let's give this a spin with our `numberChange`/`stringChange` example from earlier:
|
||||
|
||||
{{< codelines "TypeScript" "typescript-emitter/ts.ts" 21 27 >}}
|
||||
|
||||
The function calls on lines 24 and 25 are correct, but the subsequent two (on lines 26 and 27)
|
||||
are not, as they attempt to emit the _opposite_ type of the one they're supposed to. And indeed,
|
||||
TypeScript complains about only these two lines:
|
||||
|
||||
```
|
||||
code/typescript-emitter/ts.ts:26:30 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
|
||||
26 emitter.emit("numberChange", "1");
|
||||
~~~
|
||||
code/typescript-emitter/ts.ts:27:30 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
|
||||
27 emitter.emit("stringChange", 3);
|
||||
~
|
||||
Found 2 errors.
|
||||
```
|
||||
|
||||
And there you have it! This approach is now also in use in [Hydrogen](https://github.com/vector-im/hydrogen-web),
|
||||
a lightweight chat client for the [Matrix](https://matrix.org/) protocol. In particular, check out [`EventEmitter.ts`](https://github.com/vector-im/hydrogen-web/blob/master/src/utils/EventEmitter.ts).
|
||||
9
content/favorites.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Favorites
|
||||
type: "favorites"
|
||||
description: Posts from Daniel's personal blog that he has enjoyed writing the most, or that he thinks turned out very well.
|
||||
---
|
||||
The amount of content on this blog is monotonically increasing. Thus, as time goes on, it's becoming
|
||||
harder and harder to see at a glance what kind of articles I write. To address this, I've curated
|
||||
a small selection of articles from this site that I've particularly enjoyed writing, or that I think
|
||||
turned out especially well. They're listed below, most recent first.
|
||||
14
content/search.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Search
|
||||
type: "search"
|
||||
description: Interactive search for posts on Daniel's personal site.
|
||||
---
|
||||
|
||||
Here's a [Stork](https://github.com/jameslittle230/stork)-powered search for all articles on
|
||||
this site. Stork takes some time to load on slower devices, which is why this isn't on
|
||||
every page (or even on the index page). Because the LaTeX rendering occurs _after_
|
||||
the search indexing, you may see raw LaTeX code in the content of the presented
|
||||
articles, like `\beta`. This does, however, also mean that you can search for mathematical
|
||||
symbols using only the English alphabet!
|
||||
|
||||
If you're just browsing, you could alternatively check out [all posts](/blog), or perhaps my [favorite articles](/favorites) from this blog.
|
||||
2
layouts/shortcodes/donate_css.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{{ $style := resources.Get "scss/donate.scss" | resources.ToCSS | resources.Minify }}
|
||||
<link rel="stylesheet" href="{{ $style.Permalink }}">
|
||||
4
layouts/shortcodes/donation_method.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<tr>
|
||||
<td>{{ .Get 0 }}</td>
|
||||
<td><code>{{ .Get 1 }}</code></td>
|
||||
</tr>
|
||||
3
layouts/shortcodes/donation_methods.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<table class="donation-methods">
|
||||
{{ .Inner }}
|
||||
</table>
|
||||
BIN
static/index.st
27
submodule-links.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
puts "[params]"
|
||||
puts " [params.submoduleLinks]"
|
||||
|
||||
def each_submodule(base_path)
|
||||
`cd #{base_path} && git submodule status`.lines do |line|
|
||||
hash, path = line[1..].split " "
|
||||
full_path = "#{base_path}/#{path}"
|
||||
url = `git config --file #{base_path}/.gitmodules --get 'submodule.#{path}.url'`.chomp.delete_suffix(".git")
|
||||
safe_name = full_path.gsub(/\/|-|_\./, "")
|
||||
|
||||
if url =~ /dev.danilafe.com/
|
||||
file_url = "#{url}/src/commit/#{hash}"
|
||||
else
|
||||
raise "Submodule URL #{url.dump} not in a known format!"
|
||||
end
|
||||
|
||||
yield ({ :path => full_path, :url => file_url, :name => safe_name })
|
||||
each_submodule(full_path) { |m| yield m }
|
||||
end
|
||||
end
|
||||
|
||||
each_submodule(".") do |m|
|
||||
next unless m[:path].start_with? "./code/"
|
||||
puts " [params.submoduleLinks.#{m[:name].delete_prefix(".code")}]"
|
||||
puts " url = #{m[:url].dump}"
|
||||
puts " path = #{m[:path].delete_prefix("./code/").dump}"
|
||||
end
|
||||
1
themes/vanilla
Submodule
@@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 YOUR_NAME_HERE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,2 +0,0 @@
|
||||
+++
|
||||
+++
|
||||
|
Before Width: | Height: | Size: 376 B |
|
Before Width: | Height: | Size: 536 B |
@@ -1,93 +0,0 @@
|
||||
@import "variables.scss";
|
||||
|
||||
$code-color-lineno: grey;
|
||||
$code-color-keyword: black;
|
||||
$code-color-type: black;
|
||||
$code-color-comment: grey;
|
||||
|
||||
.highlight-label {
|
||||
padding: 0.25rem 0.5rem 0.25rem 0.5rem;
|
||||
border: $code-border;
|
||||
border-bottom: none;
|
||||
|
||||
a {
|
||||
font-family: $font-code;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: $font-code;
|
||||
background-color: $code-color;
|
||||
border: $code-border;
|
||||
padding: 0 0.25rem 0 0.25rem;
|
||||
}
|
||||
|
||||
pre code {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
padding: 0.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.chroma {
|
||||
.lntable {
|
||||
border-spacing: 0;
|
||||
padding: 0.5rem 0 0.5rem 0;
|
||||
background-color: $code-color;
|
||||
border-radius: 0;
|
||||
border: $code-border;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.lntd:last-child {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.lntr {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.lnt {
|
||||
display: block;
|
||||
padding: 0 1rem 0 1rem;
|
||||
color: $code-color-lineno;
|
||||
}
|
||||
|
||||
.hl {
|
||||
display: block;
|
||||
background-color: #fffd99;
|
||||
|
||||
.lnt::before {
|
||||
content: "*";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.kr, .k {
|
||||
font-weight: bold;
|
||||
color: $code-color-keyword;
|
||||
}
|
||||
|
||||
.kt {
|
||||
font-weight: bold;
|
||||
color: $code-color-type;
|
||||
}
|
||||
|
||||
.c, .c1 {
|
||||
color: $code-color-comment;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
@import "variables.scss";
|
||||
@import "mixins.scss";
|
||||
|
||||
$margin-width: 30rem;
|
||||
$margin-inner-offset: 0.5rem;
|
||||
$margin-outer-offset: 1rem;
|
||||
|
||||
@mixin below-two-margins {
|
||||
@media screen and
|
||||
(max-width: $container-width-threshold +
|
||||
2 * ($margin-width + $margin-inner-offset + $margin-outer-offset)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin below-one-margin {
|
||||
@media screen and
|
||||
(max-width: $container-width-threshold +
|
||||
($margin-width + $margin-inner-offset + $margin-outer-offset)) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin margin-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: $margin-width;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@mixin margin-content-left {
|
||||
left: 0;
|
||||
margin-left: -($margin-width + $container-min-padding + $margin-inner-offset);
|
||||
|
||||
@include below-two-margins {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin margin-content-right {
|
||||
right: 0;
|
||||
margin-right: -($margin-width + $container-min-padding + $margin-inner-offset);
|
||||
|
||||
@include below-one-margin {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
@import "variables.scss";
|
||||
|
||||
@mixin bordered-block {
|
||||
border: $standard-border;
|
||||
border-radius: .2rem;
|
||||
}
|
||||
|
||||
@mixin below-container-width {
|
||||
@media screen and (max-width: $container-width-threshold){
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||