diff --git a/DATABASE.md b/DATABASE.md index 5d991586..c975225e 100644 --- a/DATABASE.md +++ b/DATABASE.md @@ -23,7 +23,7 @@ But they are abstracted away by using the view `category_implications_view`. Further tables are: -- `tags` +- `category_tags` - `category_tag_assignments` - `related_categories` - `relations` diff --git a/databases/catdat/data/config.yaml b/databases/catdat/data/config.yaml index 2a89391c..f7220786 100644 --- a/databases/catdat/data/config.yaml +++ b/databases/catdat/data/config.yaml @@ -1,4 +1,4 @@ -tags: +shared_tags: - algebra - algebraic geometry - analysis @@ -8,10 +8,14 @@ tags: - order theory - set theory - topology + +category_tags: - finite - thin - single object +functor_tags: [] + relations: - relation: is negation: is not diff --git a/databases/catdat/data/functor-properties/cocontinuous.yaml b/databases/catdat/data/functor-properties/cocontinuous.yaml index 487a8d0e..048659fa 100644 --- a/databases/catdat/data/functor-properties/cocontinuous.yaml +++ b/databases/catdat/data/functor-properties/cocontinuous.yaml @@ -4,3 +4,9 @@ description: A functor is cocontinuous when it preserves all small colimi nlab_link: https://ncatlab.org/nlab/show/cocontinuous+functor invariant_under_equivalences: true dual_property: continuous +related_properties: + - coproduct-preserving + - right exact + - left adjoint + - coequalizer-preserving + - finitary diff --git a/databases/catdat/data/functor-properties/coequalizer-preserving.yaml b/databases/catdat/data/functor-properties/coequalizer-preserving.yaml index 2eb95bc6..8b611a86 100644 --- a/databases/catdat/data/functor-properties/coequalizer-preserving.yaml +++ b/databases/catdat/data/functor-properties/coequalizer-preserving.yaml @@ -4,3 +4,6 @@ description: 'A functor $F$ preserves coequalizers when for every paralle nlab_link: null invariant_under_equivalences: true dual_property: equalizer-preserving +related_properties: + - cocontinuous + - right exact diff --git a/databases/catdat/data/functor-properties/cofinitary.yaml b/databases/catdat/data/functor-properties/cofinitary.yaml index 4fb5c7ee..6f27d9f8 100644 --- a/databases/catdat/data/functor-properties/cofinitary.yaml +++ b/databases/catdat/data/functor-properties/cofinitary.yaml @@ -4,3 +4,5 @@ description: A functor is cofinitary when it preserves cofiltered limits. nlab_link: null invariant_under_equivalences: true dual_property: finitary +related_properties: + - continuous diff --git a/databases/catdat/data/functor-properties/comonadic.yaml b/databases/catdat/data/functor-properties/comonadic.yaml index 2a64f9fd..157f4779 100644 --- a/databases/catdat/data/functor-properties/comonadic.yaml +++ b/databases/catdat/data/functor-properties/comonadic.yaml @@ -4,3 +4,6 @@ description: 'A functor $F : \C \to \D$ is comonadic when there is a como nlab_link: https://ncatlab.org/nlab/show/comonadic+functor invariant_under_equivalences: true dual_property: monadic +related_properties: + - left adjoint + - faithful diff --git a/databases/catdat/data/functor-properties/conservative.yaml b/databases/catdat/data/functor-properties/conservative.yaml index 3f05f486..b1755d7f 100644 --- a/databases/catdat/data/functor-properties/conservative.yaml +++ b/databases/catdat/data/functor-properties/conservative.yaml @@ -4,3 +4,5 @@ description: 'A functor $F : \C \to \D$ is conservative when it is isomor nlab_link: https://ncatlab.org/nlab/show/conservative+functor invariant_under_equivalences: true dual_property: conservative +related_properties: + - equivalence diff --git a/databases/catdat/data/functor-properties/continuous.yaml b/databases/catdat/data/functor-properties/continuous.yaml index 73c9586d..0eaa26c6 100644 --- a/databases/catdat/data/functor-properties/continuous.yaml +++ b/databases/catdat/data/functor-properties/continuous.yaml @@ -4,3 +4,9 @@ description: A functor is continuous when it preserves all small limits. nlab_link: https://ncatlab.org/nlab/show/continuous+functor invariant_under_equivalences: true dual_property: cocontinuous +related_properties: + - product-preserving + - left exact + - right adjoint + - equalizer-preserving + - cofinitary diff --git a/databases/catdat/data/functor-properties/coproduct-preserving.yaml b/databases/catdat/data/functor-properties/coproduct-preserving.yaml index 3d9f681f..301b403a 100644 --- a/databases/catdat/data/functor-properties/coproduct-preserving.yaml +++ b/databases/catdat/data/functor-properties/coproduct-preserving.yaml @@ -4,3 +4,7 @@ description: A functor $F$ preserves coproducts when for every family of nlab_link: null invariant_under_equivalences: true dual_property: product-preserving +related_properties: + - finite-coproduct-preserving + - initial-object-preserving + - cocontinuous diff --git a/databases/catdat/data/functor-properties/epimorphism-preserving.yaml b/databases/catdat/data/functor-properties/epimorphism-preserving.yaml index e800de97..0990d603 100644 --- a/databases/catdat/data/functor-properties/epimorphism-preserving.yaml +++ b/databases/catdat/data/functor-properties/epimorphism-preserving.yaml @@ -6,3 +6,5 @@ description: |- nlab_link: https://ncatlab.org/nlab/show/epimorphism invariant_under_equivalences: true dual_property: monomorphism-preserving +related_properties: + - right exact diff --git a/databases/catdat/data/functor-properties/equalizer-preserving.yaml b/databases/catdat/data/functor-properties/equalizer-preserving.yaml index 5447dea2..67434dc5 100644 --- a/databases/catdat/data/functor-properties/equalizer-preserving.yaml +++ b/databases/catdat/data/functor-properties/equalizer-preserving.yaml @@ -4,3 +4,6 @@ description: 'A functor $F$ preserves equalizers when for every parallel nlab_link: null invariant_under_equivalences: true dual_property: coequalizer-preserving +related_properties: + - continuous + - left exact diff --git a/databases/catdat/data/functor-properties/equivalence.yaml b/databases/catdat/data/functor-properties/equivalence.yaml index da972413..273909a7 100644 --- a/databases/catdat/data/functor-properties/equivalence.yaml +++ b/databases/catdat/data/functor-properties/equivalence.yaml @@ -4,3 +4,9 @@ description: A functor is an equivalence if it has a pseudo-inverse funct nlab_link: https://ncatlab.org/nlab/show/equivalence+of+categories invariant_under_equivalences: true dual_property: equivalence +related_properties: + - full + - faithful + - essentially surjective + - continuous + - cocontinuous diff --git a/databases/catdat/data/functor-properties/essentially surjective.yaml b/databases/catdat/data/functor-properties/essentially surjective.yaml index 8304fd31..4758d739 100644 --- a/databases/catdat/data/functor-properties/essentially surjective.yaml +++ b/databases/catdat/data/functor-properties/essentially surjective.yaml @@ -4,3 +4,5 @@ description: 'A functor $F : \C \to \D$ is essentially surjective when ev nlab_link: https://ncatlab.org/nlab/show/essentially+surjective+functor invariant_under_equivalences: true dual_property: essentially surjective +related_properties: + - equivalence diff --git a/databases/catdat/data/functor-properties/exact.yaml b/databases/catdat/data/functor-properties/exact.yaml index fba1cf05..bf21e571 100644 --- a/databases/catdat/data/functor-properties/exact.yaml +++ b/databases/catdat/data/functor-properties/exact.yaml @@ -4,3 +4,8 @@ description: A functor is exact when it is left exact and right exact. nlab_link: https://ncatlab.org/nlab/show/exact+functor invariant_under_equivalences: true dual_property: exact +related_properties: + - left exact + - right exact + - continuous + - cocontinuous diff --git a/databases/catdat/data/functor-properties/faithful.yaml b/databases/catdat/data/functor-properties/faithful.yaml index 9582fb29..8939812f 100644 --- a/databases/catdat/data/functor-properties/faithful.yaml +++ b/databases/catdat/data/functor-properties/faithful.yaml @@ -4,3 +4,6 @@ description: 'A functor is faithful when it is injective on Hom-sets: If nlab_link: https://ncatlab.org/nlab/show/faithful+functor invariant_under_equivalences: true dual_property: faithful +related_properties: + - equivalence + - full diff --git a/databases/catdat/data/functor-properties/finitary.yaml b/databases/catdat/data/functor-properties/finitary.yaml index 840fa308..5aa51b47 100644 --- a/databases/catdat/data/functor-properties/finitary.yaml +++ b/databases/catdat/data/functor-properties/finitary.yaml @@ -4,3 +4,5 @@ description: A functor is finitary when it preserves filtered colimits. nlab_link: https://ncatlab.org/nlab/show/finitary+functor invariant_under_equivalences: true dual_property: cofinitary +related_properties: + - cocontinuous diff --git a/databases/catdat/data/functor-properties/finite-coproduct-preserving.yaml b/databases/catdat/data/functor-properties/finite-coproduct-preserving.yaml index 5dc526e2..568b5cee 100644 --- a/databases/catdat/data/functor-properties/finite-coproduct-preserving.yaml +++ b/databases/catdat/data/functor-properties/finite-coproduct-preserving.yaml @@ -4,3 +4,7 @@ description: A functor $F$ preserves finite coproducts when for every fam nlab_link: null invariant_under_equivalences: true dual_property: finite-product-preserving +related_properties: + - coproduct-preserving + - initial-object-preserving + - cocontinuous diff --git a/databases/catdat/data/functor-properties/finite-product-preserving.yaml b/databases/catdat/data/functor-properties/finite-product-preserving.yaml index 874285c1..f6b2cae0 100644 --- a/databases/catdat/data/functor-properties/finite-product-preserving.yaml +++ b/databases/catdat/data/functor-properties/finite-product-preserving.yaml @@ -4,3 +4,7 @@ description: A functor $F$ preserves finite products when for every finit nlab_link: null invariant_under_equivalences: true dual_property: finite-coproduct-preserving +related_properties: + - product-preserving + - terminal-object-preserving + - continuous diff --git a/databases/catdat/data/functor-properties/full.yaml b/databases/catdat/data/functor-properties/full.yaml index 1507d88f..1d5547fe 100644 --- a/databases/catdat/data/functor-properties/full.yaml +++ b/databases/catdat/data/functor-properties/full.yaml @@ -4,3 +4,6 @@ description: 'A functor is full when it is surjective on Hom-sets: Every nlab_link: https://ncatlab.org/nlab/show/full+functor invariant_under_equivalences: true dual_property: full +related_properties: + - equivalence + - faithful diff --git a/databases/catdat/data/functor-properties/initial-object-preserving.yaml b/databases/catdat/data/functor-properties/initial-object-preserving.yaml index cd64792a..fa1a03e6 100644 --- a/databases/catdat/data/functor-properties/initial-object-preserving.yaml +++ b/databases/catdat/data/functor-properties/initial-object-preserving.yaml @@ -4,3 +4,6 @@ description: A functor $F$ preserves initial objects when it maps every i nlab_link: null invariant_under_equivalences: true dual_property: terminal-object-preserving +related_properties: + - finite-coproduct-preserving + - cocontinuous diff --git a/databases/catdat/data/functor-properties/left adjoint.yaml b/databases/catdat/data/functor-properties/left adjoint.yaml index 58ce7413..810f29cb 100644 --- a/databases/catdat/data/functor-properties/left adjoint.yaml +++ b/databases/catdat/data/functor-properties/left adjoint.yaml @@ -4,3 +4,6 @@ description: 'A functor $F : \C \to \D$ is a left adjoint when there is a nlab_link: https://ncatlab.org/nlab/show/left+adjoint invariant_under_equivalences: true dual_property: right adjoint +related_properties: + - cocontinuous + - comonadic diff --git a/databases/catdat/data/functor-properties/left exact.yaml b/databases/catdat/data/functor-properties/left exact.yaml index 44f9c15d..093a9eb3 100644 --- a/databases/catdat/data/functor-properties/left exact.yaml +++ b/databases/catdat/data/functor-properties/left exact.yaml @@ -4,3 +4,6 @@ description: A functor is left exact when it preserves finite limits. nlab_link: https://ncatlab.org/nlab/show/exact+functor invariant_under_equivalences: true dual_property: right exact +related_properties: + - exact + - continuous diff --git a/databases/catdat/data/functor-properties/monadic.yaml b/databases/catdat/data/functor-properties/monadic.yaml index 7404ccb3..f28acda1 100644 --- a/databases/catdat/data/functor-properties/monadic.yaml +++ b/databases/catdat/data/functor-properties/monadic.yaml @@ -4,3 +4,6 @@ description: 'A functor $F : \C \to \D$ is monadic when there is a monad nlab_link: https://ncatlab.org/nlab/show/monadic+functor invariant_under_equivalences: true dual_property: comonadic +related_properties: + - right adjoint + - faithful diff --git a/databases/catdat/data/functor-properties/monomorphism-preserving.yaml b/databases/catdat/data/functor-properties/monomorphism-preserving.yaml index dfe4c1c5..1921f9d9 100644 --- a/databases/catdat/data/functor-properties/monomorphism-preserving.yaml +++ b/databases/catdat/data/functor-properties/monomorphism-preserving.yaml @@ -6,3 +6,5 @@ description: |- nlab_link: https://ncatlab.org/nlab/show/monomorphism invariant_under_equivalences: true dual_property: epimorphism-preserving +related_properties: + - left exact diff --git a/databases/catdat/data/functor-properties/product-preserving.yaml b/databases/catdat/data/functor-properties/product-preserving.yaml index 6de48e43..3ffcc260 100644 --- a/databases/catdat/data/functor-properties/product-preserving.yaml +++ b/databases/catdat/data/functor-properties/product-preserving.yaml @@ -4,6 +4,10 @@ description: A functor $F$ preserves products when for every family of ob nlab_link: null invariant_under_equivalences: true dual_property: coproduct-preserving +related_properties: + - finite-product-preserving + - terminal-object-preserving + - continuous # Here is why we do not call this property "preserves products": # Either we name the property "preserves products" and choose the diff --git a/databases/catdat/data/functor-properties/representable.yaml b/databases/catdat/data/functor-properties/representable.yaml index b1e517ec..bec89ed0 100644 --- a/databases/catdat/data/functor-properties/representable.yaml +++ b/databases/catdat/data/functor-properties/representable.yaml @@ -4,3 +4,5 @@ description: 'A functor $F : \C \to \D$ is representable if $\C$ is local nlab_link: https://ncatlab.org/nlab/show/representable+functor invariant_under_equivalences: true dual_property: null +related_properties: + - continuous diff --git a/databases/catdat/data/functor-properties/right adjoint.yaml b/databases/catdat/data/functor-properties/right adjoint.yaml index 6f3c4940..8369ac73 100644 --- a/databases/catdat/data/functor-properties/right adjoint.yaml +++ b/databases/catdat/data/functor-properties/right adjoint.yaml @@ -4,3 +4,6 @@ description: 'A functor $F : \C \to \D$ is a right adjoint when there is nlab_link: https://ncatlab.org/nlab/show/right+adjoint invariant_under_equivalences: true dual_property: left adjoint +related_properties: + - continuous + - monadic diff --git a/databases/catdat/data/functor-properties/right exact.yaml b/databases/catdat/data/functor-properties/right exact.yaml index dce36f2f..9790abd6 100644 --- a/databases/catdat/data/functor-properties/right exact.yaml +++ b/databases/catdat/data/functor-properties/right exact.yaml @@ -4,3 +4,6 @@ description: A functor is right exact when it preserves finite colimits. nlab_link: https://ncatlab.org/nlab/show/exact+functor invariant_under_equivalences: true dual_property: left exact +related_properties: + - exact + - cocontinuous diff --git a/databases/catdat/data/functor-properties/terminal-object-preserving.yaml b/databases/catdat/data/functor-properties/terminal-object-preserving.yaml index 7775658c..a567ec76 100644 --- a/databases/catdat/data/functor-properties/terminal-object-preserving.yaml +++ b/databases/catdat/data/functor-properties/terminal-object-preserving.yaml @@ -4,3 +4,6 @@ description: A functor $F$ preserves terminal objects when it maps every nlab_link: null invariant_under_equivalences: true dual_property: initial-object-preserving +related_properties: + - finite-product-preserving + - continuous diff --git a/databases/catdat/data/functors/abelianization.yaml b/databases/catdat/data/functors/abelianization.yaml index a46a43bd..6b64dcca 100644 --- a/databases/catdat/data/functors/abelianization.yaml +++ b/databases/catdat/data/functors/abelianization.yaml @@ -1,10 +1,16 @@ id: abelianization name: abelianization functor for groups +notation: $(-)^{\ab}$ source: Grp target: Ab description: This functor maps a group $G$ to its abelianization $G^{\ab} \coloneqq G/[G,G]$. nlab_link: https://ncatlab.org/nlab/show/abelianization +tags: + - algebra + +related_functors: [] + satisfied_properties: - property: essentially surjective reason: For abelian groups $G$ we have $G \cong G^{\ab}$. diff --git a/databases/catdat/data/functors/forget_vector.yaml b/databases/catdat/data/functors/forget_vector.yaml index 537dd6f2..471b58f2 100644 --- a/databases/catdat/data/functors/forget_vector.yaml +++ b/databases/catdat/data/functors/forget_vector.yaml @@ -1,10 +1,16 @@ id: forget_vector name: forgetful functor for vector spaces +notation: $U_{\Vect}$ source: Vect target: Set -description: This functor $U$ maps a vector space $V$ (over a fixed field $K$) to its underlying set $U(V)$. +description: This functor maps a vector space $V$ (over a fixed field $K$) to its underlying set $U_{\Vect}(V)$. nlab_link: https://ncatlab.org/nlab/show/forgetful+functor +tags: + - algebra + +related_functors: [] + satisfied_properties: - property: conservative reason: It is standard that the inverse of a bijective linear map is also linear. diff --git a/databases/catdat/data/functors/free_group.yaml b/databases/catdat/data/functors/free_group.yaml index 7bf04ac2..8f0d72a8 100644 --- a/databases/catdat/data/functors/free_group.yaml +++ b/databases/catdat/data/functors/free_group.yaml @@ -1,10 +1,16 @@ id: free_group name: free group functor +notation: $F_{\Grp}$ source: Set target: Grp -description: This functor maps a set $X$ to the free group $F(X)$ on that set. +description: This functor maps a set $X$ to the free group $F_{\Grp}(X)$ on that set. We abbreviate $F \coloneqq F_{\Grp}$. nlab_link: https://ncatlab.org/nlab/show/free+functor +tags: + - algebra + +related_functors: [] + satisfied_properties: - property: conservative reason: 'Let $f : X \to Y$ be a map of sets such that $F(f) : F(X) \to F(Y)$ is an isomorphism of groups. We know that $F$ is faithful, so that it reflects monomorphisms. Thus, $f$ is injective. Choose a complement $U \subseteq Y$ of $f(X) \subseteq Y$. Then $F(X) \to F(Y) = F(X) \sqcup F(U)$ is an isomorphism. This implies $F(U)=1$ and hence $U = \varnothing$.' diff --git a/databases/catdat/data/functors/id_Set.yaml b/databases/catdat/data/functors/id_Set.yaml index 017ec767..3ceadef1 100644 --- a/databases/catdat/data/functors/id_Set.yaml +++ b/databases/catdat/data/functors/id_Set.yaml @@ -1,10 +1,16 @@ id: id_Set name: identity functor on the category of sets +notation: $\id_{\Set}$ source: Set target: Set description: Every category $\C$ has an identity functor $\id_{\C}$. Here, we specify that it is for the category of sets. nlab_link: https://ncatlab.org/nlab/show/identity+functor +tags: + - set theory + +related_functors: [] + satisfied_properties: - property: equivalence reason: This is trivial. diff --git a/databases/catdat/data/functors/power_set_contravariant.yaml b/databases/catdat/data/functors/power_set_contravariant.yaml index 80a0f4a5..a2e52950 100644 --- a/databases/catdat/data/functors/power_set_contravariant.yaml +++ b/databases/catdat/data/functors/power_set_contravariant.yaml @@ -1,10 +1,17 @@ id: power_set_contravariant name: contravariant power set functor +notation: $\Hom(-,2)$ source: Set_op target: Set -description: 'This functor maps a set $X$ to its power set $P(X)$ and a map of sets $f : X \to Y$ to the induced preimage operator $f^* : P(Y) \to P(X)$.' +description: 'This functor maps a set $X$ to its power set $P(X)$ and a map of sets $f : X \to Y$ to the induced preimage operator $f^* : P(Y) \to P(X)$. It is isomorphic to the representable functor $\Hom(-,2) : \Set^{\op} \to \Set$.' nlab_link: https://ncatlab.org/nlab/show/power+set +tags: + - set theory + +related_functors: + - power_set_covariant + satisfied_properties: - property: epimorphism-preserving reason: 'If $f : X \to Y$ is injective, then $f^* : P(Y) \to P(X)$ is surjective, since for all $A \subseteq X$ we have $A = f^*(f_*(A))$.' diff --git a/databases/catdat/data/functors/power_set_covariant.yaml b/databases/catdat/data/functors/power_set_covariant.yaml index 4990a422..8479bf63 100644 --- a/databases/catdat/data/functors/power_set_covariant.yaml +++ b/databases/catdat/data/functors/power_set_covariant.yaml @@ -1,10 +1,17 @@ id: power_set_covariant name: covariant power set functor +notation: $P$ source: Set target: Set description: 'This functor maps a set $X$ to its power set $P(X)$ and a map of sets $f : X \to Y$ to the induced image operator $f_* : P(X) \to P(Y)$.' nlab_link: https://ncatlab.org/nlab/show/power+set +tags: + - set theory + +related_functors: + - power_set_contravariant + satisfied_properties: - property: conservative reason: 'Assume that $f : X \to Y$ is a map such that $f_* : P(X) \to P(Y)$ is an isomorphism. There is some $A \subseteq X$ with $Y = f_*(A)$, this proves that $f$ is surjective. It is also injective: If $x,y \in X$ satisfy $f(x) = f(y)$, then $f_*(\{x\}) = f_*(\{y\})$, and hence $\{x\} = \{y\}$, i.e. $x = y$.' diff --git a/databases/catdat/schema/001_tags.sql b/databases/catdat/schema/001_tags.sql new file mode 100644 index 00000000..544a385d --- /dev/null +++ b/databases/catdat/schema/001_tags.sql @@ -0,0 +1,9 @@ +CREATE TABLE category_tags ( + id INTEGER PRIMARY KEY, + tag TEXT NOT NULL UNIQUE +); + +CREATE TABLE functor_tags ( + id INTEGER PRIMARY KEY, + tag TEXT NOT NULL UNIQUE +); \ No newline at end of file diff --git a/databases/catdat/schema/003_relations.sql b/databases/catdat/schema/002_relations.sql similarity index 100% rename from databases/catdat/schema/003_relations.sql rename to databases/catdat/schema/002_relations.sql diff --git a/databases/catdat/schema/002_tags.sql b/databases/catdat/schema/002_tags.sql deleted file mode 100644 index bfe0b272..00000000 --- a/databases/catdat/schema/002_tags.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE tags ( - id INTEGER PRIMARY KEY, - tag TEXT NOT NULL UNIQUE -); - -CREATE TABLE category_tag_assignments ( - category_id TEXT NOT NULL, - tag TEXT NOT NULL, - PRIMARY KEY (category_id, tag), - FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE, - FOREIGN KEY (tag) REFERENCES tags (tag) ON DELETE CASCADE -); diff --git a/databases/catdat/schema/001_categories.sql b/databases/catdat/schema/003_categories.sql similarity index 77% rename from databases/catdat/schema/001_categories.sql rename to databases/catdat/schema/003_categories.sql index 883dac23..87ec2192 100644 --- a/databases/catdat/schema/001_categories.sql +++ b/databases/catdat/schema/003_categories.sql @@ -11,6 +11,14 @@ CREATE TABLE categories ( CREATE UNIQUE INDEX categories_lower_id_unique ON categories (lower(id)); +CREATE TABLE category_tag_assignments ( + category_id TEXT NOT NULL, + tag TEXT NOT NULL, + PRIMARY KEY (category_id, tag), + FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE, + FOREIGN KEY (tag) REFERENCES category_tags (tag) ON DELETE CASCADE +); + CREATE TABLE related_categories ( category_id TEXT NOT NULL, related_category_id TEXT NOT NULL, @@ -26,4 +34,6 @@ CREATE TABLE category_comments ( FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE ); -CREATE INDEX idx_category_comments ON category_comments (category_id); \ No newline at end of file +CREATE INDEX idx_category_comments ON category_comments (category_id); + + diff --git a/databases/catdat/schema/009_functors.sql b/databases/catdat/schema/009_functors.sql index 706a7d70..418c3b6f 100644 --- a/databases/catdat/schema/009_functors.sql +++ b/databases/catdat/schema/009_functors.sql @@ -1,6 +1,7 @@ CREATE TABLE functors ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, + notation TEXT NOT NULL, source TEXT NOT NULL, target TEXT NOT NULL, description TEXT NOT NULL CHECK (length(description) > 0), @@ -9,4 +10,29 @@ CREATE TABLE functors ( FOREIGN KEY (target) REFERENCES categories (id) ON DELETE CASCADE ); -CREATE UNIQUE INDEX functors_lower_id_unique ON functors (lower(id)); \ No newline at end of file +CREATE UNIQUE INDEX functors_lower_id_unique ON functors (lower(id)); + +CREATE TABLE functor_tag_assignments ( + functor_id TEXT NOT NULL, + tag TEXT NOT NULL, + PRIMARY KEY (functor_id, tag), + FOREIGN KEY (functor_id) REFERENCES functors (id) ON DELETE CASCADE, + FOREIGN KEY (tag) REFERENCES functor_tags (tag) ON DELETE CASCADE +); + +CREATE TABLE related_functors ( + functor_id TEXT NOT NULL, + related_functor_id TEXT NOT NULL, + PRIMARY KEY (functor_id, related_functor_id), + FOREIGN KEY (functor_id) REFERENCES functors (id) ON DELETE CASCADE, + FOREIGN KEY (related_functor_id) REFERENCES functors (id) ON DELETE CASCADE +); + +CREATE TABLE functor_comments ( + id INTEGER PRIMARY KEY, + functor_id TEXT NOT NULL, + comment TEXT NOT NULL, + FOREIGN KEY (functor_id) REFERENCES functors (id) ON DELETE CASCADE +); + +CREATE INDEX idx_functor_comments ON functor_comments (functor_id); diff --git a/databases/catdat/schema/010_functor-properties.sql b/databases/catdat/schema/010_functor-properties.sql index 25aba020..48aafdef 100644 --- a/databases/catdat/schema/010_functor-properties.sql +++ b/databases/catdat/schema/010_functor-properties.sql @@ -11,6 +11,14 @@ CREATE TABLE functor_properties ( CREATE UNIQUE INDEX functor_properties_lower_id_unique ON functor_properties (lower(id)); +CREATE TABLE related_functor_properties ( + property_id TEXT NOT NULL, + related_property_id TEXT NOT NULL, + PRIMARY KEY (property_id, related_property_id), + FOREIGN KEY (property_id) REFERENCES functor_properties (id) ON DELETE CASCADE, + FOREIGN KEY (related_property_id) REFERENCES functor_properties (id) ON DELETE CASCADE +); + CREATE TABLE functor_property_assignments ( id INTEGER PRIMARY KEY, functor_id TEXT NOT NULL, diff --git a/databases/catdat/scripts/deduce-entity-properties.ts b/databases/catdat/scripts/deduce-structure-properties.ts similarity index 83% rename from databases/catdat/scripts/deduce-entity-properties.ts rename to databases/catdat/scripts/deduce-structure-properties.ts index 55d6e997..98ca7a59 100644 --- a/databases/catdat/scripts/deduce-entity-properties.ts +++ b/databases/catdat/scripts/deduce-structure-properties.ts @@ -6,16 +6,17 @@ import { type Database, SqliteError } from 'better-sqlite3' import { get_client, is_subset } from './utils/helpers' import { - get_entities, + get_structures, get_normalized_implications, get_properties_dict, get_property_assignments, - is_dual_entity, - type EntityMeta, + is_dual_structure, + type StructureMeta, type NormalizedImplication, type PropertyMeta, } from './utils/deduction' import { get_contradiction_string, get_reason_string } from './utils/implications' +import type { StructureType } from './types' /** * Returns the set of satisfied properties that can be deduced from a set @@ -29,7 +30,7 @@ export function get_deduced_satisfied_properties( properties_dict?: Record stop_when_found?: string }, - type: 'category' | 'functor', + type: StructureType, // used for source and target properties of a functor associated_satisfied_properties?: Record>, ) { @@ -99,7 +100,7 @@ export function get_deduced_unsatisfied_properties( properties_dict?: Record stop_when_found?: string }, - type: 'category' | 'functor', + type: StructureType, // used for source and target properties of a functor associated_satisfied_properties?: Record>, ) { @@ -169,14 +170,14 @@ export function get_deduced_unsatisfied_properties( */ function save_satisfied_properties( db: Database, - entity_id: string, + structure_id: string, found: Set, reasons: Record, - type: 'category' | 'functor', + type: StructureType, ) { if (found.size === 0) return - const err_msg = `❌ Failed to complete deduction of satisfied properties for ${entity_id} because of a conflict. The likely cause is a contradiction between its assigned properties.` + const err_msg = `❌ Failed to complete deduction of satisfied properties for ${structure_id} because of a conflict. The likely cause is a contradiction between its assigned properties.` const property_insert = db.prepare(` INSERT INTO ${type}_property_assignments @@ -186,7 +187,7 @@ function save_satisfied_properties( try { for (const p of found) { - property_insert.run(entity_id, p, reasons[p]) + property_insert.run(structure_id, p, reasons[p]) } } catch (err) { if (err instanceof SqliteError) { @@ -206,14 +207,14 @@ function save_satisfied_properties( */ function save_unsatisfied_properties( db: Database, - entity_id: string, + structure_id: string, found: Set, reasons: Record, - type: 'category' | 'functor', + type: StructureType, ) { if (found.size === 0) return - const err_msg = `❌ Failed to complete deduction of unsatisfied properties for ${entity_id} because of a conflict. The likely cause is a contradiction between its assigned properties.` + const err_msg = `❌ Failed to complete deduction of unsatisfied properties for ${structure_id} because of a conflict. The likely cause is a contradiction between its assigned properties.` const property_insert = db.prepare(` INSERT INTO ${type}_property_assignments @@ -223,7 +224,7 @@ function save_unsatisfied_properties( try { for (const p of found) { - property_insert.run(entity_id, p, reasons[p]) + property_insert.run(structure_id, p, reasons[p]) } } catch (err) { if (err instanceof SqliteError) { @@ -239,46 +240,46 @@ function save_unsatisfied_properties( } /** - * Deduce satisfied properties for a given entity from given ones + * Deduce satisfied properties for a given structure from given ones * by using the list of normalized implications. * Warning: This mutates the set of satisfied properties. */ function deduce_satisfied_properties( db: Database, - entity: EntityMeta, + structure: StructureMeta, implications: NormalizedImplication[], satisfied_properties: Set, properties_dict: Record, - type: 'category' | 'functor', + type: StructureType, ) { const { found, reasons } = get_deduced_satisfied_properties( satisfied_properties, implications, { properties_dict }, type, - entity.associated_satisfied_properties, + structure.associated_satisfied_properties, ) for (const p of found) satisfied_properties.add(p) - save_satisfied_properties(db, entity.id, found, reasons, type) + save_satisfied_properties(db, structure.id, found, reasons, type) - console.info(`Deduced ${found.size} satisfied properties for ${entity.id}`) + console.info(`Deduced ${found.size} satisfied properties for ${structure.id}`) } /** - * Deduce unsatisfied properties for a given entity from given ones + * Deduce unsatisfied properties for a given structure from given ones * by using the satisfied properties and the list of normalized implications. * Warning: This mutates the set of unsatisfied properties. */ function deduce_unsatisfied_properties( db: Database, - entity: EntityMeta, + structure: StructureMeta, implications: NormalizedImplication[], satisfied_properties: Set, unsatisfied_properties: Set, properties_dict: Record, - type: 'category' | 'functor', + type: StructureType, ) { const { found, reasons } = get_deduced_unsatisfied_properties( satisfied_properties, @@ -286,24 +287,24 @@ function deduce_unsatisfied_properties( implications, { properties_dict }, type, - entity.associated_satisfied_properties, + structure.associated_satisfied_properties, ) for (const p of found) unsatisfied_properties.add(p) - save_unsatisfied_properties(db, entity.id, found, reasons, type) + save_unsatisfied_properties(db, structure.id, found, reasons, type) - console.info(`Deduced ${found.size} unsatisfied properties for ${entity.id}`) + console.info(`Deduced ${found.size} unsatisfied properties for ${structure.id}`) } /** - * Assign dual properties to dual entities: + * Assign dual properties to dual structures: * If C has property P, then C^op has property P^op (if defined). * Warning: This mutates the sets of assigned properties. */ function deduce_dual_properties( db: Database, - entity: EntityMeta & { dual: string }, + structure: StructureMeta & { dual: string }, satisfied: Set, unsatisfied: Set, undecidable: Set, @@ -351,61 +352,61 @@ function deduce_dual_properties( const reason_undecidable = `The dual property is undecidable for its dual ${type}.` for (const p of new_satisfied) { - property_insert.run(entity.id, p, 1, reason_satisfied) + property_insert.run(structure.id, p, 1, reason_satisfied) } console.info( - `Deduced ${new_satisfied.size} satisfied properties by duality for ${entity.id}`, + `Deduced ${new_satisfied.size} satisfied properties by duality for ${structure.id}`, ) for (const q of new_unsatisfied) { - property_insert.run(entity.id, q, 0, reason_unsatisfied) + property_insert.run(structure.id, q, 0, reason_unsatisfied) } console.info( - `Deduced ${new_unsatisfied.size} unsatisfied properties by duality for ${entity.id}`, + `Deduced ${new_unsatisfied.size} unsatisfied properties by duality for ${structure.id}`, ) for (const q of new_undecidable) { - property_insert.run(entity.id, q, null, reason_undecidable) + property_insert.run(structure.id, q, null, reason_undecidable) } console.info( - `Deduced ${new_undecidable.size} undecidable properties by duality for ${entity.id}`, + `Deduced ${new_undecidable.size} undecidable properties by duality for ${structure.id}`, ) } /** * Clears all the deduced properties. This runs before the deduction starts. */ -function delete_deduced_properties(db: Database, type: 'category' | 'functor') { +function delete_deduced_properties(db: Database, type: StructureType) { db.prepare(`DELETE FROM ${type}_property_assignments WHERE is_deduced = TRUE`).run() } /** * --- MAIN FUNCTION --- - * Deduce properties of entities from given ones + * Deduce properties of structures from given ones * by using the list of implications. */ -export function deduce_properties_for_entities(type: 'category' | 'functor') { +export function deduce_properties_for_structures(type: StructureType) { console.info(`\n--- Deduce ${type} properties ---`) const db = get_client() const implications = get_normalized_implications(db, type) - const entities = get_entities(db, type) + const structures = get_structures(db, type) const properties_dict = get_properties_dict(db, type) - const get_assigned_properties = get_property_assignments(db, entities, type) + const get_assigned_properties = get_property_assignments(db, structures, type) const deduction = db.transaction(() => { delete_deduced_properties(db, type) - for (const entity of entities) { - const assigned = get_assigned_properties[entity.id] + for (const structure of structures) { + const assigned = get_assigned_properties[structure.id] deduce_satisfied_properties( db, - entity, + structure, implications, assigned.satisfied, properties_dict, @@ -414,7 +415,7 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') { deduce_unsatisfied_properties( db, - entity, + structure, implications, assigned.satisfied, assigned.unsatisfied, @@ -430,15 +431,15 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') { if (type !== 'category') return const dual_deduction = db.transaction(() => { - for (const entity of entities) { - if (!is_dual_entity(entity)) continue + for (const structure of structures) { + if (!is_dual_structure(structure)) continue - const assigned = get_assigned_properties[entity.id] - const dual_assigned = get_assigned_properties[entity.dual] + const assigned = get_assigned_properties[structure.id] + const dual_assigned = get_assigned_properties[structure.dual] deduce_dual_properties( db, - entity, + structure, assigned.satisfied, assigned.unsatisfied, assigned.undecidable, @@ -451,7 +452,7 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') { deduce_satisfied_properties( db, - entity, + structure, implications, assigned.satisfied, properties_dict, @@ -460,7 +461,7 @@ export function deduce_properties_for_entities(type: 'category' | 'functor') { deduce_unsatisfied_properties( db, - entity, + structure, implications, assigned.satisfied, assigned.unsatisfied, diff --git a/databases/catdat/scripts/deduce.ts b/databases/catdat/scripts/deduce.ts index e3556229..bda7e95d 100644 --- a/databases/catdat/scripts/deduce.ts +++ b/databases/catdat/scripts/deduce.ts @@ -2,7 +2,7 @@ import { deduce_category_implications } from './deduce-category-implications' import { deduce_functor_implications } from './deduce-functor-implications' import { deduce_special_objects } from './deduce-special-objects' import { deduce_special_morphisms } from './deduce-special-morphisms' -import { deduce_properties_for_entities } from './deduce-entity-properties' +import { deduce_properties_for_structures } from './deduce-structure-properties' deduce() @@ -11,11 +11,11 @@ deduce() */ function deduce() { deduce_category_implications() - deduce_properties_for_entities('category') + deduce_properties_for_structures('category') deduce_special_objects() deduce_special_morphisms() deduce_functor_implications() - deduce_properties_for_entities('functor') + deduce_properties_for_structures('functor') } diff --git a/databases/catdat/scripts/redundancies.ts b/databases/catdat/scripts/redundancies.ts index fc3cd3d4..cfeed8a9 100644 --- a/databases/catdat/scripts/redundancies.ts +++ b/databases/catdat/scripts/redundancies.ts @@ -8,7 +8,7 @@ import { import { get_deduced_satisfied_properties, get_deduced_unsatisfied_properties, -} from './deduce-entity-properties' +} from './deduce-structure-properties' import { get_property_assignments_by_deduction } from './utils/deduction' const db = get_client() diff --git a/databases/catdat/scripts/seed.ts b/databases/catdat/scripts/seed.ts index e849af3d..ffa83197 100644 --- a/databases/catdat/scripts/seed.ts +++ b/databases/catdat/scripts/seed.ts @@ -63,39 +63,38 @@ function clear_all_data() { db.transaction(() => { db.pragma('foreign_keys = OFF') - db.prepare(`DELETE FROM category_implication_assumptions`).run() - db.prepare(`DELETE FROM category_implication_conclusions`).run() - db.prepare(`DELETE FROM category_implications`).run() - db.prepare(`DELETE FROM special_morphisms`).run() db.prepare(`DELETE FROM special_morphism_types`).run() - db.prepare(`DELETE FROM special_objects`).run() db.prepare(`DELETE FROM special_object_types`).run() + db.prepare(`DELETE FROM category_implication_assumptions`).run() + db.prepare(`DELETE FROM category_implication_conclusions`).run() + db.prepare(`DELETE FROM category_implications`).run() db.prepare(`DELETE FROM category_property_assignments`).run() - db.prepare(`DELETE FROM category_comments`).run() db.prepare(`DELETE FROM related_categories`).run() db.prepare(`DELETE FROM category_tag_assignments`).run() - db.prepare(`DELETE FROM categories`).run() - - db.prepare(`DELETE FROM tags`).run() - db.prepare(`DELETE FROM related_category_properties`).run() db.prepare(`DELETE FROM category_properties`).run() - - db.prepare(`DELETE FROM functor_property_assignments`).run() - db.prepare(`DELETE FROM functor_properties`).run() - db.prepare(`DELETE FROM functors`).run() - - db.prepare(`DELETE FROM relations`).run() + db.prepare(`DELETE FROM categories`).run() + db.prepare(`DELETE FROM category_tags`).run() db.prepare(`DELETE FROM functor_implication_assumptions`).run() db.prepare(`DELETE FROM functor_implication_conclusions`).run() db.prepare(`DELETE FROM functor_implication_source_assumptions`).run() db.prepare(`DELETE FROM functor_implication_target_assumptions`).run() db.prepare(`DELETE FROM functor_implications`).run() + db.prepare(`DELETE FROM functor_property_assignments`).run() + db.prepare(`DELETE FROM functor_comments`).run() + db.prepare(`DELETE FROM related_functors`).run() + db.prepare(`DELETE FROM functor_tag_assignments`).run() + db.prepare(`DELETE FROM related_functor_properties`).run() + db.prepare(`DELETE FROM functor_properties`).run() + db.prepare(`DELETE FROM functors`).run() + db.prepare(`DELETE FROM functor_tags`).run() + + db.prepare(`DELETE FROM relations`).run() })() } catch (err) { console.error(`Error clearing data:`, err) @@ -108,7 +107,8 @@ function seed_config() { const config = read_yaml_file(data_folder, 'config.yaml') - const tag_insert = db.prepare(`INSERT INTO tags (tag) VALUES (?)`) + const category_tag_insert = db.prepare(`INSERT INTO category_tags (tag) VALUES (?)`) + const functor_tag_insert = db.prepare(`INSERT INTO functor_tags (tag) VALUES (?)`) const relation_insert = db.prepare( `INSERT INTO relations (relation, negation, conditional) VALUES (?, ?, ?)`, @@ -126,7 +126,18 @@ function seed_config() { db.transaction(() => { db.pragma('defer_foreign_keys = ON') - for (const tag of config.tags) tag_insert.run(tag) + for (const tag of config.shared_tags) { + category_tag_insert.run(tag) + functor_tag_insert.run(tag) + } + + for (const tag of config.category_tags) { + category_tag_insert.run(tag) + } + + for (const tag of config.functor_tags) { + functor_tag_insert.run(tag) + } for (const { relation, negation, conditional } of config.relations) { relation_insert.run(relation, negation, conditional) @@ -396,6 +407,10 @@ function seed_functor_properties() { dual_property_id, invariant_under_equivalences ) VALUES (?, ?, ?, ?, ?, ?)`) + const related_insert = db.prepare( + `INSERT INTO related_functor_properties (property_id, related_property_id) VALUES (?, ?)`, + ) + function insert_property(property: FunctorPropertyYaml) { property_insert.run( property.id, @@ -405,6 +420,10 @@ function seed_functor_properties() { property.dual_property || null, Number(property.invariant_under_equivalences), ) + + for (const related of property.related_properties) { + related_insert.run(property.id, related) + } } const tx = db.transaction(() => { @@ -513,8 +532,20 @@ function seed_functors() { const functor_insert = db.prepare( `INSERT INTO functors ( - id, name, source, target, description, nlab_link - ) VALUES (?, ?, ?, ?, ?, ?)`, + id, name, notation, source, target, description, nlab_link + ) VALUES (?, ?, ?, ?, ?, ?, ?)`, + ) + + const tag_insert = db.prepare( + `INSERT INTO functor_tag_assignments (functor_id, tag) VALUES (?, ?)`, + ) + + const comment_insert = db.prepare( + `INSERT INTO functor_comments (functor_id, comment) VALUES (?, ?)`, + ) + + const related_insert = db.prepare( + `INSERT INTO related_functors (functor_id, related_functor_id) VALUES (?, ?)`, ) const property_assignment_insert = db.prepare( @@ -527,12 +558,25 @@ function seed_functors() { functor_insert.run( functor.id, functor.name, + functor.notation, functor.source, functor.target, functor.description || null, functor.nlab_link || null, ) + for (const tag of functor.tags) { + tag_insert.run(functor.id, tag) + } + + for (const comment of functor.comments ?? []) { + comment_insert.run(functor.id, comment) + } + + for (const related of functor.related_functors) { + related_insert.run(functor.id, related) + } + for (const entry of functor.satisfied_properties) { property_assignment_insert.run(functor.id, entry.property, 1, entry.reason) } @@ -540,6 +584,10 @@ function seed_functors() { for (const entry of functor.unsatisfied_properties) { property_assignment_insert.run(functor.id, entry.property, 0, entry.reason) } + + for (const entry of functor.undecidable_properties ?? []) { + property_assignment_insert.run(functor.id, entry.property, null, entry.reason) + } } const tx = db.transaction(() => { diff --git a/databases/catdat/scripts/seed.types.ts b/databases/catdat/scripts/seed.types.ts index 74ab71ac..a859f4da 100644 --- a/databases/catdat/scripts/seed.types.ts +++ b/databases/catdat/scripts/seed.types.ts @@ -1,5 +1,7 @@ export type ConfigYaml = { - tags: string[] + shared_tags: string[] + category_tags: string[] + functor_tags: string[] relations: { relation: string negation: string @@ -84,15 +86,21 @@ export type FunctorPropertyYaml = { nlab_link?: string | null dual_property?: string | null invariant_under_equivalences: boolean + related_properties: string[] } export type FunctorYaml = { id: string name: string + notation: string source: string target: string description?: string | null nlab_link?: string + tags: string[] + related_functors: string[] satisfied_properties: PropertyEntry[] unsatisfied_properties: PropertyEntry[] + undecidable_properties?: PropertyEntry[] + comments?: string[] } diff --git a/databases/catdat/scripts/test.ts b/databases/catdat/scripts/test.ts index 3b088d4e..320cd85d 100644 --- a/databases/catdat/scripts/test.ts +++ b/databases/catdat/scripts/test.ts @@ -12,6 +12,7 @@ import id_Set_expected from './expected-data/id_Set.json' import decided_categories from './expected-data/decided-categories.json' import decided_functors from './expected-data/decided-functors.json' import { capitalize, get_client } from './utils/helpers' +import { StructureType } from './types' const db = get_client() @@ -26,16 +27,16 @@ function execute_tests() { test_mutual_category_duals() test_properties_of_trivial_category() test_mutual_property_duals('category') - test_decided_entities(decided_categories, 'category') - test_properties_of_selected_entities( + test_decided_structures(decided_categories, 'category') + test_properties_of_selected_structures( { Set: Set_expected, Ab: Ab_expected, Top: Top_expected }, 'category', ) console.info('\n--- Test functors ---') test_mutual_property_duals('functor') - test_decided_entities(decided_functors, 'functor') - test_properties_of_selected_entities( + test_decided_structures(decided_functors, 'functor') + test_properties_of_selected_structures( { forget_vector: forget_vector_expected, id_Set: id_Set_expected }, 'functor', ) @@ -98,7 +99,7 @@ function test_properties_of_trivial_category() { * Tests for all properties p,q of categories or functors that * if p is dual to q, then q is dual to p. */ -function test_mutual_property_duals(type: 'category' | 'functor') { +function test_mutual_property_duals(type: StructureType) { const dict: Record = {} const properties = db @@ -123,7 +124,7 @@ function test_mutual_property_duals(type: 'category' | 'functor') { * Tests that for a specified list of categories or functors all properties have * been decided. If this test fails, property assignments or implications are missing. */ -function test_decided_entities(entities: string[], type: 'category' | 'functor') { +function test_decided_structures(structure_ids: string[], type: StructureType) { const unknown_query = db.prepare( `SELECT p.id FROM ${type}_properties p WHERE NOT EXISTS (SELECT 1 FROM ${type}_property_assignments @@ -132,17 +133,17 @@ function test_decided_entities(entities: string[], type: 'category' | 'functor') `, ) - for (const entity_id of entities) { - const res = unknown_query.all(entity_id) as { id: string }[] + for (const structure_id of structure_ids) { + const res = unknown_query.all(structure_id) as { id: string }[] const unknown_properties = res.map((row) => row.id) if (unknown_properties.length > 0) { throw new Error( - `❌ Found unknown properties of ${entity_id}:\n${unknown_properties.join(', ')}.\nEvery property needs to be decided for this ${type}.`, + `❌ Found unknown properties of ${structure_id}:\n${unknown_properties.join(', ')}.\nEvery property needs to be decided for this ${type}.`, ) } - console.info(`✅ All properties have been decided for ${entity_id}`) + console.info(`✅ All properties have been decided for ${structure_id}`) } } @@ -152,27 +153,27 @@ function test_decided_entities(entities: string[], type: 'category' | 'functor') * respective JSON files in the subfolder "expected-data". * We exclude undecidable properties here. */ -function test_properties_of_selected_entities( +function test_properties_of_selected_structures( expected: Record>, - type: 'category' | 'functor', + type: StructureType, ) { const property_query = db.prepare( `SELECT property_id, is_satisfied FROM ${type}_property_assignments WHERE ${type}_id = ? AND is_satisfied IS NOT NULL`, ) - for (const entity_id in expected) { - const properties = property_query.all(entity_id) as { + for (const structure_id in expected) { + const properties = property_query.all(structure_id) as { property_id: string is_satisfied: 0 | 1 }[] for (const { property_id, is_satisfied } of properties) { - const ok = Boolean(is_satisfied) === expected[entity_id][property_id] + const ok = Boolean(is_satisfied) === expected[structure_id][property_id] if (ok) continue - throw new Error(`❌ Incorrect property of ${entity_id}: ${property_id}`) + throw new Error(`❌ Incorrect property of ${structure_id}: ${property_id}`) } - console.info(`✅ Properties of ${entity_id} are correct`) + console.info(`✅ Properties of ${structure_id} are correct`) } } diff --git a/databases/catdat/scripts/types.ts b/databases/catdat/scripts/types.ts new file mode 100644 index 00000000..d4529fbc --- /dev/null +++ b/databases/catdat/scripts/types.ts @@ -0,0 +1 @@ +export type StructureType = 'category' | 'functor' diff --git a/databases/catdat/scripts/utils/deduction.ts b/databases/catdat/scripts/utils/deduction.ts index 80462d6d..630d6b0c 100644 --- a/databases/catdat/scripts/utils/deduction.ts +++ b/databases/catdat/scripts/utils/deduction.ts @@ -1,11 +1,12 @@ import { type Database } from 'better-sqlite3' import { get_categories, get_normalized_category_implications } from './categories' import { get_functors, get_normalized_functor_implications } from './functors' +import { StructureType } from '../types' /** - * An entity is a category or a functor. + * A structure is a category or a functor. */ -export type EntityMeta = { +export type StructureMeta = { id: string name: string dual?: string | null @@ -30,9 +31,9 @@ export type PropertyMeta = { } /** - * Returns the list of entities saved in the database of a given type. + * Returns the list of stored categorical structures of a given type. */ -export function get_entities(db: Database, type: 'category' | 'functor'): EntityMeta[] { +export function get_structures(db: Database, type: StructureType): StructureMeta[] { if (type === 'category') return get_categories(db) if (type === 'functor') return get_functors(db) throw new Error('Unsupported type') @@ -43,7 +44,7 @@ export function get_entities(db: Database, type: 'category' | 'functor'): Entity */ export function get_normalized_implications( db: Database, - type: 'category' | 'functor', + type: StructureType, ): NormalizedImplication[] { if (type === 'category') return get_normalized_category_implications(db) if (type === 'functor') return get_normalized_functor_implications(db) @@ -53,7 +54,7 @@ export function get_normalized_implications( /** * Returns a dictionary of properties saved in the database. */ -export function get_properties_dict(db: Database, type: 'category' | 'functor') { +export function get_properties_dict(db: Database, type: StructureType) { const properties = db .prepare( `SELECT @@ -73,29 +74,33 @@ export function get_properties_dict(db: Database, type: 'category' | 'functor') } /** - * Returns a dictionary with all assigned properties of a list of entities + * Returns a dictionary with all assigned properties of a list of structures * (categories or functors), grouped by id and * value (satisfied / unsatisfied / undecidable). */ export function get_property_assignments( db: Database, - entities: { id: string }[], - type: 'category' | 'functor', + structures: { id: string }[], + type: StructureType, ) { const rows = db .prepare( - `SELECT property_id, ${type}_id as entity_id, is_satisfied + `SELECT property_id, ${type}_id as structure_id, is_satisfied FROM ${type}_property_assignments`, ) - .all() as { property_id: string; entity_id: string; is_satisfied: 0 | 1 | null }[] + .all() as { + property_id: string + structure_id: string + is_satisfied: 0 | 1 | null + }[] const grouped: Record< string, { satisfied: Set; unsatisfied: Set; undecidable: Set } > = {} - for (const entity of entities) { - grouped[entity.id] = { + for (const structure of structures) { + grouped[structure.id] = { satisfied: new Set(), unsatisfied: new Set(), undecidable: new Set(), @@ -103,7 +108,7 @@ export function get_property_assignments( } for (const row of rows) { - const { property_id, entity_id, is_satisfied } = row + const { property_id, structure_id, is_satisfied } = row let group_key: 'satisfied' | 'unsatisfied' | 'undecidable' if (is_satisfied === 1) { group_key = 'satisfied' @@ -112,30 +117,30 @@ export function get_property_assignments( } else { group_key = 'undecidable' } - grouped[entity_id][group_key].add(property_id) + grouped[structure_id][group_key].add(property_id) } return grouped } /** - * Returns a dictionary with all assigned properties of entities, - * grouped by entity, value (satisfied / unsatisfied), and deduced status. + * Returns a dictionary with all assigned properties of structures, + * grouped by structure, value (satisfied / unsatisfied), and deduced status. * We exclude undecidable properties here. */ export function get_property_assignments_by_deduction( db: Database, - entities: { id: string }[], - type: 'category' | 'functor', + structures: { id: string }[], + type: StructureType, ) { const rows = db .prepare( - `SELECT property_id, ${type}_id as entity_id, is_satisfied, is_deduced + `SELECT property_id, ${type}_id as structure_id, is_satisfied, is_deduced FROM ${type}_property_assignments WHERE is_satisfied IS NOT NULL`, ) .all() as { property_id: string - entity_id: string + structure_id: string is_satisfied: 0 | 1 is_deduced: 0 | 1 }[] @@ -148,16 +153,16 @@ export function get_property_assignments_by_deduction( } > = {} - for (const entity of entities) { - grouped[entity.id] = { + for (const structure of structures) { + grouped[structure.id] = { satisfied: { non_deduced: new Set(), deduced: new Set() }, unsatisfied: { non_deduced: new Set(), deduced: new Set() }, } } for (const row of rows) { - const { property_id, entity_id, is_satisfied, is_deduced } = row - grouped[entity_id][is_satisfied ? 'satisfied' : 'unsatisfied'][ + const { property_id, structure_id, is_satisfied, is_deduced } = row + grouped[structure_id][is_satisfied ? 'satisfied' : 'unsatisfied'][ is_deduced ? 'deduced' : 'non_deduced' ].add(property_id) } @@ -166,11 +171,11 @@ export function get_property_assignments_by_deduction( } /** - * Checks if an entity is a dual, but not the - * original entity to prevent circular reasoning. + * Checks if an structure is a dual, but not the + * original structure to prevent circular reasoning. */ -export function is_dual_entity( - entity: EntityMeta, -): entity is EntityMeta & { dual: string } { - return Boolean(entity.dual) && entity.name.toLowerCase().startsWith('dual') +export function is_dual_structure( + structure: StructureMeta, +): structure is StructureMeta & { dual: string } { + return Boolean(structure.dual) && structure.name.toLowerCase().startsWith('dual') } diff --git a/databases/catdat/scripts/utils/implications.ts b/databases/catdat/scripts/utils/implications.ts index b37eca5d..c01aed63 100644 --- a/databases/catdat/scripts/utils/implications.ts +++ b/databases/catdat/scripts/utils/implications.ts @@ -1,6 +1,7 @@ import { type Database } from 'better-sqlite3' import type { NormalizedImplication, PropertyMeta } from './deduction' import { are_equal_sets, parse_json_set } from './helpers' +import { StructureType } from '../types' function get_assumption_string( implication: NormalizedImplication, @@ -30,7 +31,7 @@ function get_conclusion_string( export function get_reason_string( implication: NormalizedImplication, properties_dict: Record, - type: 'category' | 'functor', + type: StructureType, ) { const assumption_string = get_assumption_string(implication, properties_dict) const conclusion_string = get_conclusion_string(implication, properties_dict) @@ -43,7 +44,7 @@ export function get_contradiction_string( implication: NormalizedImplication, properties_dict: Record, property: string, - type: 'category' | 'functor', + type: StructureType, ) { const assumption_string = get_assumption_string(implication, properties_dict, true) const conclusion_string = get_conclusion_string(implication, properties_dict, true) @@ -62,11 +63,11 @@ export function get_contradiction_string( /** * Clears all deduced implications. This is done before the deduction starts. */ -export function clear_deduced_implications(db: Database, type: 'category' | 'functor') { +export function clear_deduced_implications(db: Database, type: StructureType) { db.prepare(`DELETE FROM ${type}_implications WHERE is_deduced = TRUE`).run() } -type EntityImplicationWithDualProperties = { +type ImplicationWithDualProperties = { assumptions: string conclusions: string dual_assumptions: string @@ -79,7 +80,7 @@ type EntityImplicationWithDualProperties = { * Checks if an implication can be dualized (i.e. if all the involved properties * have a dual) and if the dual is different from it. */ -export function is_dualizable(impl: EntityImplicationWithDualProperties): boolean { +export function is_dualizable(impl: ImplicationWithDualProperties): boolean { const assumptions = parse_json_set(impl.assumptions) const conclusions = parse_json_set(impl.conclusions) const dual_assumptions = parse_json_set(impl.dual_assumptions) diff --git a/src/components/CategoryDescription.svelte b/src/components/CategoryDescription.svelte new file mode 100644 index 00000000..1a5b8159 --- /dev/null +++ b/src/components/CategoryDescription.svelte @@ -0,0 +1,70 @@ + + +
+
    +
  • + notation: + {@html category.notation} +
  • + +
  • + objects: + {@html category.objects} +
  • + +
  • + morphisms: + {@html category.morphisms} +
  • + + {#if related_categories.length} +
  • + Related categories: + {#each related_categories as { id, name, notation }, i} + + {@html notation} + {#if i < related_categories.length - 1} + ,  + {/if} + {/each} +
  • + {/if} + + {#if category.nlab_link} +
  • + nLab Link +
  • + {/if} + + {#if category.dual_category_id} +
  • + Dual category: + + {@html category.dual_category_notation} + +
  • + {/if} +
+ + {#if category.description} +

{@html category.description}

+ {/if} +
+ + diff --git a/src/components/CategoryImplicationItem.svelte b/src/components/CategoryImplicationItem.svelte deleted file mode 100644 index b563cb0d..00000000 --- a/src/components/CategoryImplicationItem.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - -{#each implication.assumptions as assumption, i} - {assumption} - {#if i < implication.assumptions.length - 1} - - and - {/if} -{/each} - - - - - {#if implication.is_equivalence} - is equivalent to - {:else} - implies - {/if} - - -{#each implication.conclusions as conclusion, i} - {conclusion} - {#if i < implication.conclusions.length - 1} - - and - {/if} -{/each} - - diff --git a/src/components/CategoryImplicationList.svelte b/src/components/CategoryImplicationList.svelte deleted file mode 100644 index b1151447..00000000 --- a/src/components/CategoryImplicationList.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -{#if implications.length} -
    - {#each implications as implication} -
  • - -
  • - {/each} -
-{:else} -

-{/if} diff --git a/src/components/CategoryList.svelte b/src/components/CategoryList.svelte deleted file mode 100644 index 13820daf..00000000 --- a/src/components/CategoryList.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - -{#if categories.length} -
    - {#each categories as item} -
  • - - {item.name} - - {#if item.count !== undefined} - ({item.count}) - {/if} -
  • - {/each} -
-{:else} -

-{/if} - - diff --git a/src/components/CommentList.svelte b/src/components/CommentList.svelte new file mode 100644 index 00000000..92157b76 --- /dev/null +++ b/src/components/CommentList.svelte @@ -0,0 +1,21 @@ + + +{#if comments.length} +
+

Comments

+ +
    + {#each comments as { id, comment } (id)} +
  • {@html comment}
  • + {/each} +
+
+{/if} diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte index 03635242..4c5bb265 100644 --- a/src/components/Footer.svelte +++ b/src/components/Footer.svelte @@ -1,42 +1,16 @@ diff --git a/src/components/FunctorDescription.svelte b/src/components/FunctorDescription.svelte new file mode 100644 index 00000000..c52d7e07 --- /dev/null +++ b/src/components/FunctorDescription.svelte @@ -0,0 +1,56 @@ + + +
+
    +
  • + notation: + {@html functor.notation} +
  • + +
  • + Source: + {functor.source_name} +
  • + +
  • + Target: + {functor.target_name} +
  • + + {#if related_functors.length} +
  • + Related functors: + {#each related_functors as { id, notation, name }, i} + + {@html notation} + {#if i < related_functors.length - 1} + ,  + {/if} + {/each} +
  • + {/if} + + {#if functor.nlab_link} +
  • + nLab Link +
  • + {/if} +
+ +

{@html functor.description}

+
+ + diff --git a/src/components/FunctorImplicationItem.svelte b/src/components/FunctorImplicationItem.svelte deleted file mode 100644 index 953a03d5..00000000 --- a/src/components/FunctorImplicationItem.svelte +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - -{#each implication.assumptions as assumption, i} - {assumption} - {#if i < implication.assumptions.length - 1} - - and - {/if} -{/each} - - - - - {#if implication.is_equivalence} - is equivalent to - {:else} - implies - {/if} - - -{#each implication.conclusions as conclusion, i} - {conclusion} - {#if i < implication.conclusions.length - 1} - - and - {/if} -{/each} -{#if implication.source_assumptions.length > 0 || implication.target_assumptions.length > 0} - * -{/if} - - diff --git a/src/components/FunctorImplicationList.svelte b/src/components/FunctorImplicationList.svelte deleted file mode 100644 index fcdc5eb1..00000000 --- a/src/components/FunctorImplicationList.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - -{#if implications.length} -
    - {#each implications as implication} -
  • - -
  • - {/each} -
- -

- *Those implications also require assumptions on the source or target category. -

-{:else} -

-{/if} diff --git a/src/components/FunctorList.svelte b/src/components/FunctorList.svelte deleted file mode 100644 index 851e6d0d..00000000 --- a/src/components/FunctorList.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - -{#if functors.length} -
    - {#each functors as functor} -
  • - - {functor.name} - - {#if functor.count !== undefined} - ({functor.count}) - {/if} -
  • - {/each} -
-{:else} -

-{/if} - - diff --git a/src/components/Header.svelte b/src/components/Header.svelte index 578700de..63777e94 100644 --- a/src/components/Header.svelte +++ b/src/components/Header.svelte @@ -2,14 +2,14 @@ import { faBars } from '@fortawesome/free-solid-svg-icons' import Fa from 'svelte-fa' import StructureSelector from './StructureSelector.svelte' - import type { Structure } from '$lib/commons/types' + import type { StructureType } from '$lib/commons/types' type Props = { open_mobile_nav: () => void - structure: Structure + selected_type: StructureType } - let { open_mobile_nav, structure }: Props = $props() + let { open_mobile_nav, selected_type }: Props = $props()
@@ -33,7 +33,7 @@
- +
diff --git a/src/components/PropertyAssignmentList.svelte b/src/components/PropertyAssignmentList.svelte new file mode 100644 index 00000000..55ad116f --- /dev/null +++ b/src/components/PropertyAssignmentList.svelte @@ -0,0 +1,134 @@ + + + + New here? Click any + icon to view the proof for that property. + + +
+
+

Satisfied Properties

+ + {#if assignment_level.value === 'all'} +

Assigned properties

+ !p.is_deduced)} + {type} + /> + +

Deduced properties

+ p.is_deduced)} + {type} + /> + {:else if assignment_level.value === 'merged'} + + {:else if assignment_level.value === 'basic'} +

Assigned properties; further properties can be deduced.

+ !p.is_deduced)} + {type} + /> + {/if} +
+ +
+

Unsatisfied Properties

+ + {#if assignment_level.value === 'all'} +

Assigned properties

+ !p.is_deduced)} + {type} + /> + +

Deduced properties*

+ p.is_deduced)} + {type} + /> + +

*This also uses the deduced satisfied properties.

+ {:else if assignment_level.value === 'merged'} + + {:else if assignment_level.value === 'basic'} +

Assigned properties; further properties can be deduced.

+ !p.is_deduced)} + {type} + /> + {/if} +
+
+ +
+

Unknown properties

+ + {#if unknown_properties.length > 0} +

+ {pluralize(unknown_properties.length, { + one: "There is {count} property for which the database doesn't have an answer if it is satisfied or not.", + other: "There are {count} properties for which the database doesn't have an answer if they are satisfied or not.", + })} + + Please help to contribute the data! +

+ {/if} + + +
+ +{#if undecidable_properties.length > 0} +
+

Undecidable properties

+ + {#if undecidable_properties.length > 0} +

+ {pluralize(undecidable_properties.length, { + one: 'There is {count} property for which it cannot be decided if it is satisfied or not.', + other: 'There are {count} properties for which it cannot be decided if they are satisfied or not.', + })} +

+ {/if} + + +
+{/if} + + diff --git a/src/components/PropertyList.svelte b/src/components/PropertyList.svelte index 65da0434..f782525a 100644 --- a/src/components/PropertyList.svelte +++ b/src/components/PropertyList.svelte @@ -1,5 +1,6 @@ + +{#if structures.length} +
    + {#each structures as structure} +
  • + + {structure.name} + + {#if structure.count !== undefined} + ({structure.count}) + {/if} +
  • + {/each} +
+{:else} +

+{/if} + + diff --git a/src/components/StructureSelector.svelte b/src/components/StructureSelector.svelte index f44d7a25..c2b3dedd 100644 --- a/src/components/StructureSelector.svelte +++ b/src/components/StructureSelector.svelte @@ -1,27 +1,30 @@ @@ -30,12 +33,12 @@ diff --git a/src/components/TagList.svelte b/src/components/TagList.svelte new file mode 100644 index 00000000..0f7a986a --- /dev/null +++ b/src/components/TagList.svelte @@ -0,0 +1,24 @@ + + + + {#each tags as tag} + filter_by_tag(tag)}>{tag} + {/each} + diff --git a/src/components/UndistinguishableStructures.svelte b/src/components/UndistinguishableStructures.svelte new file mode 100644 index 00000000..fa822890 --- /dev/null +++ b/src/components/UndistinguishableStructures.svelte @@ -0,0 +1,27 @@ + + +{#if structures.length} +
+

Undistinguishable {PLURALS[type]}

+ +

+ These {PLURALS[type]} in the database currently have exactly the same properties + as the {name}. This indicates that the data may be incomplete or that a + distinguishing property may be missing from the database. +

+ + +
+{/if} diff --git a/src/lib/client/config.ts b/src/lib/client/config.ts deleted file mode 100644 index 6e309903..00000000 --- a/src/lib/client/config.ts +++ /dev/null @@ -1 +0,0 @@ -export const STRUCTURES = ['categories', 'functors'] diff --git a/src/lib/client/nav.ts b/src/lib/client/nav.ts new file mode 100644 index 00000000..caac1959 --- /dev/null +++ b/src/lib/client/nav.ts @@ -0,0 +1,100 @@ +import type { StructureType } from '$lib/commons/types' +import { + faArrowsSplitUpAndLeft, + faBook, + faChartBar, + faCog, + faCubes, + faDatabase, + faDownload, + faHome, + faList, + faPenToSquare, + faPuzzlePiece, + faSearch, + type IconDefinition, +} from '@fortawesome/free-solid-svg-icons' +import { capitalize } from './utils' +import { PLURALS } from '$lib/commons/structures' + +type Link = { + href: string + text: string + nested?: string + icon: IconDefinition +} + +export function get_navigation_links(type: StructureType): Link[] { + return [ + { + href: '/', + text: 'Home', + icon: faHome, + }, + { + href: `/${PLURALS[type]}`, + text: capitalize(PLURALS[type]), + nested: `/${type}/`, + icon: faDatabase, + }, + { + href: `/${type}-properties`, + text: `Properties`, + nested: `/${type}-property/`, + icon: faList, + }, + { + href: `/${type}-implications`, + text: `Implications`, + nested: `/${type}-implication`, + icon: faArrowsSplitUpAndLeft, + }, + { + href: `/${type}-comparison`, + text: `Compare`, + icon: faChartBar, + nested: `/${type}-comparison`, + }, + { + href: `/${type}-search`, + text: `Search`, + icon: faSearch, + nested: `/${type}-search/results`, + }, + ] +} + +export function get_footer_links() { + return [ + { + href: '/content/contribute', + text: 'Contribute', + icon: faPenToSquare, + }, + { + href: '/settings', + text: 'Settings', + icon: faCog, + }, + { + href: '/missing', + text: 'Missing data', + icon: faPuzzlePiece, + }, + { + href: '/content/resources', + text: 'Resources', + icon: faBook, + }, + { + href: '/content/foundations', + text: 'Foundations', + icon: faCubes, + }, + { + href: '/download', + text: 'Download', + icon: faDownload, + }, + ] +} diff --git a/src/lib/client/utils.ts b/src/lib/client/utils.ts index a8d57074..08731e49 100644 --- a/src/lib/client/utils.ts +++ b/src/lib/client/utils.ts @@ -1,4 +1,3 @@ -import { goto } from '$app/navigation' import type { Attachment } from 'svelte/attachments' export function get_device_type() { @@ -8,15 +7,15 @@ export function get_device_type() { return 'desktop' } -export function filter_by_tag(tag: string) { - goto(`/categories/${tag}`) -} - export function pluralize(count: number, forms: { one: string; other: string }) { const word = count === 1 ? forms.one : forms.other return word.replace('{count}', String(count)) } +export function capitalize(txt: string) { + return txt[0].toUpperCase() + txt.slice(1) +} + /** * Removes accents from letters and transforms to lowercase */ diff --git a/src/lib/commons/compare.utils.ts b/src/lib/commons/compare.utils.ts new file mode 100644 index 00000000..e91e4777 --- /dev/null +++ b/src/lib/commons/compare.utils.ts @@ -0,0 +1,30 @@ +import { browser } from '$app/environment' +import type { StructureType } from './types' +import { is_string_array } from './utils' + +export const MAX_STRUCTURES_COMPARE = 10 + +export function get_compared_structures(type: StructureType): string[] { + if (!browser) return [] + + const names_string = window.sessionStorage.getItem(`comparison:${type}`) + if (!names_string) return [] + + try { + const parsed_names: unknown = JSON.parse(names_string) + const is_valid = is_string_array(parsed_names) + return is_valid ? parsed_names : [] + } catch { + console.error('Error parsing saved structured from sessionStorage') + return [] + } +} + +export function save_comparison(type: StructureType, compared_categories: string[]) { + if (!browser) return + + window.sessionStorage.setItem( + `comparison:${type}`, + JSON.stringify(compared_categories), + ) +} diff --git a/src/lib/commons/property.url.ts b/src/lib/commons/property.url.ts index 3d9afbe8..6fb6598c 100644 --- a/src/lib/commons/property.url.ts +++ b/src/lib/commons/property.url.ts @@ -1,3 +1,5 @@ +import type { StructureType } from './types' + const ENCODE_MAP: Record = { ' ': '_', 'ℵ₀': 'aleph0', @@ -28,6 +30,6 @@ export function decode_property_ID(str: string): string { return decoded } -export function get_property_url(id: string, type: 'category' | 'functor') { +export function get_property_url(id: string, type: StructureType) { return `/${type}-property/${encode_property_ID(id)}` } diff --git a/src/lib/commons/structures.ts b/src/lib/commons/structures.ts new file mode 100644 index 00000000..12584308 --- /dev/null +++ b/src/lib/commons/structures.ts @@ -0,0 +1,8 @@ +import type { StructureType } from './types' + +export const STRUCTURES: StructureType[] = ['category', 'functor'] + +export const PLURALS = { + category: 'categories', + functor: 'functors', +} diff --git a/src/lib/commons/types.ts b/src/lib/commons/types.ts index bed2e1ef..61c5fcd9 100644 --- a/src/lib/commons/types.ts +++ b/src/lib/commons/types.ts @@ -4,6 +4,15 @@ export type Arrayed = { type Replace>> = Omit & R +export type StructureType = 'category' | 'functor' + +export type StructureShort = { + id: string + name: string +} + +export type RelatedStructure = StructureShort & { notation: string } + export type CategoryDisplay = { id: string name: string @@ -17,34 +26,10 @@ export type CategoryDisplay = { dual_category_notation: string | null } -export type CategoryShort = Pick - -export type RelatedCategory = Pick - export type TagObject = { tag: string } export type CommentObject = { id: number; comment: string } -export type ImplicationDB = { - id: string - is_equivalence: 0 | 1 - reason: string - assumptions: string - conclusions: string - is_deduced: 0 | 1 - dualized_from?: string | null -} - -export type ImplicationDisplay = Replace< - ImplicationDB, - { - is_equivalence: boolean - is_deduced: boolean - assumptions: string[] - conclusions: string[] - } -> - export type PropertyDB = { id: string relation: string @@ -61,19 +46,25 @@ export type PropertyDisplay = Replace< export type PropertyShort = Pick -export type DescriptionWithReason = { - description: string - reason: string | null -} +export type GroupedPropertyShort = Pick< + PropertyDB, + 'id' | 'relation' | 'dual_property_id' +> -export type CategoryPropertyDB = { +export type PropertyAssignmentDB = { id: string reason: string relation: string is_deduced: 0 | 1 + is_satisfied: 0 | 1 | null } -export type CategoryProperty = Replace +export type PropertyAssignmentDisplay = { + id: string + reason: string + relation: string + is_deduced: boolean +} export type SpecialObject = { type: string @@ -86,16 +77,10 @@ export type SpecialMorphism = { reason: string } -export type Structure = 'categories' | 'functors' - -export type FunctorShort = { - id: string - name: string -} - -export type FunctorDB = { +export type FunctorDisplay = { id: string name: string + notation: string source: string target: string source_name: string @@ -104,54 +89,43 @@ export type FunctorDB = { nlab_link: string | null } -export type FunctorPropertyDB = { - id: string - relation: string - description: string - nlab_link: string | null - invariant_under_equivalences: 0 | 1 - dual_property_id: string | null -} - -export type FunctorPropertyShort = Pick - -export type FunctorProperty = Replace< - FunctorPropertyDB, - { - invariant_under_equivalences: boolean - } -> - -export type FunctorImplicationDB = { +export type ImplicationDB = { id: string is_equivalence: 0 | 1 + is_deduced: 0 | 1 + dualized_from: string | null reason: string assumptions: string conclusions: string - source_assumptions: string - target_assumptions: string - dualized_from?: string | null + source_assumptions?: string // for functors + target_assumptions?: string // for functors } -export type FunctorImplicationDisplay = Replace< - FunctorImplicationDB, +export type ImplicationDisplay = Replace< + ImplicationDB, { is_equivalence: boolean + is_deduced: boolean assumptions: string[] conclusions: string[] - source_assumptions: string[] - target_assumptions: string[] + source_assumptions?: string[] + target_assumptions?: string[] } > -export type FunctorPropertyAssignmentDB = { - id: string - reason: string - relation: string - is_deduced: 0 | 1 +export type SearchResults = { + type: StructureType + contradiction: string[] | null + satisfied_properties: string[] + unsatisfied_properties: string[] + dual_satisfied_properties: (string | null)[] + dual_unsatisfied_properties: (string | null)[] + dual_search_available: boolean + found_structures: StructureShort[] } -export type FunctorPropertyAssignment = Replace< - FunctorPropertyAssignmentDB, - { is_deduced: boolean } -> +export type ComparisonResult = { + type: StructureType + structures: RelatedStructure[] + comparison_table: string[][] +} diff --git a/src/lib/server/compare.ts b/src/lib/server/compare.ts new file mode 100644 index 00000000..9309ab38 --- /dev/null +++ b/src/lib/server/compare.ts @@ -0,0 +1,97 @@ +import { error, type RequestEvent } from '@sveltejs/kit' +import { query } from '$lib/server/db.catdat' +import { render_nested_formulas } from '$lib/server/formulas' +import { MAX_STRUCTURES_COMPARE } from '$lib/commons/compare.utils' +import type { ComparisonResult, StructureType } from '$lib/commons/types' +import { PLURALS } from '$lib/commons/structures' + +export function compare_handler( + event: RequestEvent, + type: StructureType, +): ComparisonResult { + if (!event.params.ids) error(400, `No ${type} selected for comparison`) + + const compared_ids = event.params.ids?.split('/') + + if (!compared_ids.length) error(400, `No ${type} selected for comparison`) + + if (compared_ids.length > MAX_STRUCTURES_COMPARE) { + error( + 400, + `It is only possible to compare up to ${MAX_STRUCTURES_COMPARE} ${PLURALS[type]}`, + ) + } + + const placeholders = compared_ids.map(() => '?').join(', ') + + const { rows, err: err_cat } = query<{ + id: string + name: string + notation: string + }>({ + sql: ` + SELECT id, name, notation + FROM ${PLURALS[type]} + WHERE id in (${placeholders})`, + values: compared_ids, + }) + + if (err_cat) error(500, `Could not load ${PLURALS[type]}`) + + const invalid_id = compared_ids.find((id) => rows.every((row) => row.id !== id)) + if (invalid_id) error(404, `Could not find ${type} with ID '${invalid_id}'`) + + const structures = rows.sort( + (a, b) => compared_ids.indexOf(a.id) - compared_ids.indexOf(b.id), + ) + + const select_columns = compared_ids + .map( + (_, i) => + `CASE + WHEN a${i}.is_satisfied = TRUE THEN 'yes' + WHEN a${i}.is_satisfied = FALSE THEN 'no' + ELSE 'unknown' + END AS struct${i}`, + ) + .join(',\n') + + const join_fragments: string[] = [] + const values: string[] = [] + + compared_ids.forEach((id, i) => { + join_fragments.push(` + LEFT JOIN ${type}_property_assignments a${i} + ON a${i}.property_id = p.id AND a${i}.${type}_id = ? + `) + values.push(id) + }) + + const stmt = ` + SELECT + p.id AS property_id, + ${select_columns} + FROM ${type}_properties p + ${join_fragments.join('\n')} + ORDER BY lower(p.id)` + + const { rows: comparison_objects, err } = query>({ + sql: stmt, + values, + }) + + if (err) error(500, 'Could not load properties') + + const comparison_table = comparison_objects.map((obj) => Object.values(obj)) + + event.setHeaders({ + // shared cache for 30min + 'cache-control': 'public, max-age=0, s-maxage=1800', + }) + + return { + type, + structures: render_nested_formulas(structures), + comparison_table, + } +} diff --git a/src/lib/server/consistency.ts b/src/lib/server/consistency.ts index 5a6caee1..fbf94cdf 100644 --- a/src/lib/server/consistency.ts +++ b/src/lib/server/consistency.ts @@ -1,42 +1,49 @@ import sql from 'sql-template-tag' import { query } from '$lib/server/db.catdat' import { is_subset } from './utils' +import type { SqliteError } from 'better-sqlite3' +import { + get_normalized_implications, + stringify_implication, + type NormalizedImplication, +} from './implications' +import type { StructureType } from '$lib/commons/types' // TODO: If possible, remove the code duplication with deduction and redundancy scripts. -type NormalizedCategoryImplication = { - id: string - assumptions: Set - conclusion: string -} - export function get_contradiction( satisfied_properties: Set, unsatisfied_properties: Set, -): string[] | null { + type: StructureType, +): { contradiction: string[] | null; err: SqliteError | null } { for (const p of satisfied_properties) { - if (unsatisfied_properties.has(p)) return [`${p} ⟹ ${p}`] + const contradiction = [`${p} ⟹ ${p}`] + if (unsatisfied_properties.has(p)) return { contradiction, err: null } } - const implications = get_normalized_category_implications() + const { implications, err } = get_normalized_implications(type) + + if (err) return { contradiction: null, err } - return contradiction_worker( + const contradiction = contradiction_worker( satisfied_properties, unsatisfied_properties, implications, ) + + return { contradiction, err: null } } function contradiction_worker( satisfied_properties: Set, unsatisfied_properties: Set, - implications: NormalizedCategoryImplication[], + implications: NormalizedImplication[], ): string[] | null { for (const p of satisfied_properties) { if (unsatisfied_properties.has(p)) return [`${p} ⟹ ${p}`] } - const deduction_dict: Record = {} + const deduction_dict: Record = {} const deduced_satisfied_properties = new Set(satisfied_properties) // bfs to find contradiction @@ -78,7 +85,7 @@ function contradiction_worker( function build_shortest_proof( satisfied_properties: Set, - deduction_dict: Record, + deduction_dict: Record, target_property: string, ) { const proof: string[] = [] @@ -103,55 +110,17 @@ function build_shortest_proof( return proof } -function get_normalized_category_implications() { - const { rows, err } = query<{ - id: string - assumptions: string - conclusions: string - is_equivalence: 0 | 1 - }>( - sql`SELECT id, assumptions, conclusions, is_equivalence FROM category_implications_view`, - ) - - if (err) throw err - - const implications: NormalizedCategoryImplication[] = [] - - for (const impl of rows) { - const assumptions = new Set(JSON.parse(impl.assumptions)) - const conclusions = new Set(JSON.parse(impl.conclusions)) - - for (const conclusion of conclusions) { - implications.push({ id: impl.id, assumptions, conclusion }) - } - - if (impl.is_equivalence) { - for (const assumption of assumptions) { - implications.push({ - id: impl.id, - assumptions: conclusions, - conclusion: assumption, - }) - } - } - } - - return implications -} - -function stringify_implication(implication: NormalizedCategoryImplication) { - return `${[...implication.assumptions].join(' ∧ ')} ⟹ ${implication.conclusion}` -} - export function get_missing_combinations() { - const implications = get_normalized_category_implications() + const { implications, err: err_imp } = get_normalized_implications('category') + + if (err_imp) return { err: err_imp, missing_combinations: [] } const { rows: properties, err } = query<{ id: string dual_property_id: string | null }>(sql`SELECT id, dual_property_id FROM category_properties ORDER BY lower(id)`) - if (err) throw err + if (err) return { err, missing_combinations: [] } const { rows: existing, err: err_existing } = query<{ p: string @@ -164,11 +133,11 @@ export function get_missing_combinations() { WHERE cp.is_satisfied = TRUE AND cnp.is_satisfied = FALSE `) - if (err_existing) throw err_existing + if (err_existing) return { err: err_existing, missing_combinations: [] } const witnessed_pairs = new Set(existing.map(({ p, q }) => `${p}|${q}`)) - const missing_pairs: [string, string][] = [] + const missing_combinations: [string, string][] = [] for (const p of properties) { for (const q of properties) { @@ -190,9 +159,9 @@ export function get_missing_combinations() { implications, ) - if (!contradiction) missing_pairs.push([p.id, q.id]) + if (!contradiction) missing_combinations.push([p.id, q.id]) } } - return missing_pairs + return { missing_combinations, err: null } } diff --git a/src/lib/server/db.catdat.ts b/src/lib/server/db.catdat.ts index 1d065f59..e61c4cf6 100644 --- a/src/lib/server/db.catdat.ts +++ b/src/lib/server/db.catdat.ts @@ -1,5 +1,5 @@ import type { Arrayed } from '$lib/commons/types' -import Database, { SqliteError } from 'better-sqlite3' +import Database, { type SqliteError } from 'better-sqlite3' import path from 'node:path' const db_path = path.resolve('databases', 'catdat', 'catdat.db') diff --git a/src/lib/server/implications.ts b/src/lib/server/implications.ts new file mode 100644 index 00000000..2160e127 --- /dev/null +++ b/src/lib/server/implications.ts @@ -0,0 +1,114 @@ +import sql from 'sql-template-tag' +import { query } from './db.catdat' +import { parse_json_set } from './utils' +import type { StructureType } from '$lib/commons/types' + +// TODO: If possible, remove the code duplication with deduction and redundancy scripts. + +export type NormalizedImplication = { + id: string + assumptions: Set + conclusion: string +} + +/** + * List of normalized implications of a given type of structure. + */ +export function get_normalized_implications(type: StructureType) { + if (type === 'category') return get_normalized_category_implications() + if (type === 'functor') return get_normalized_functor_implications() + throw new Error('Unsupported type') +} + +/** + * List of normalized implications of categories. + */ +function get_normalized_category_implications() { + const { rows, err } = query<{ + id: string + assumptions: string + conclusions: string + is_equivalence: 0 | 1 + }>( + sql`SELECT id, assumptions, conclusions, is_equivalence FROM category_implications_view`, + ) + + if (err) return { implications: null, err } + + const implications: NormalizedImplication[] = [] + + for (const impl of rows) { + const assumptions = parse_json_set(impl.assumptions) + const conclusions = parse_json_set(impl.conclusions) + + for (const conclusion of conclusions) { + implications.push({ id: impl.id, assumptions, conclusion }) + } + + if (impl.is_equivalence) { + for (const assumption of assumptions) { + implications.push({ + id: impl.id, + assumptions: conclusions, + conclusion: assumption, + }) + } + } + } + + return { implications, err: null } +} + +/** + * List of normalized implications of functors that have no source or target + * assumptions, i.e. those that are universally true. Only those are relevant + * for the consistency check. + */ +function get_normalized_functor_implications() { + const { rows, err } = query<{ + id: string + assumptions: string + conclusions: string + source_assumptions: string + target_assumptions: string + is_equivalence: 0 | 1 + }>( + sql` + SELECT id, assumptions, source_assumptions, target_assumptions, + conclusions, is_equivalence + FROM functor_implications_view`, + ) + + if (err) return { implications: null, err } + + const implications: NormalizedImplication[] = [] + + for (const impl of rows) { + const assumptions = parse_json_set(impl.assumptions) + const source_assumptions = parse_json_set(impl.source_assumptions) + const target_assumptions = parse_json_set(impl.target_assumptions) + const conclusions = parse_json_set(impl.conclusions) + + if (source_assumptions.size > 0 || target_assumptions.size > 0) continue + + for (const conclusion of conclusions) { + implications.push({ id: impl.id, assumptions, conclusion }) + } + + if (impl.is_equivalence) { + for (const assumption of assumptions) { + implications.push({ + id: impl.id, + assumptions: conclusions, + conclusion: assumption, + }) + } + } + } + + return { implications, err: null } +} + +export function stringify_implication(implication: NormalizedImplication) { + return `${[...implication.assumptions].join(' ∧ ')} ⟹ ${implication.conclusion}` +} diff --git a/src/lib/server/properties.ts b/src/lib/server/properties.ts new file mode 100644 index 00000000..b275757b --- /dev/null +++ b/src/lib/server/properties.ts @@ -0,0 +1,14 @@ +import type { StructureType } from '$lib/commons/types' +import { query } from './db.catdat' +import { error } from '@sveltejs/kit' + +export function get_property_ids(type: StructureType) { + const { rows, err } = query<{ id: string }>({ + sql: `SELECT id FROM ${type}_properties ORDER BY lower(id)`, + values: [], + }) + + if (err) error(500, 'Failed to load properties') + + return rows.map(({ id }) => id) +} diff --git a/src/lib/server/search.ts b/src/lib/server/search.ts index 538a64fc..246afeae 100644 --- a/src/lib/server/search.ts +++ b/src/lib/server/search.ts @@ -2,16 +2,19 @@ import type { RequestEvent } from '@sveltejs/kit' import { decode_property_ID } from '$lib/commons/property.url' import { query } from '$lib/server/db.catdat' import { error } from '@sveltejs/kit' -import sql from 'sql-template-tag' import { SEARCH_SEPARATOR } from '$lib/commons/search.config' import { get_contradiction } from '$lib/server/consistency' +import type { SearchResults, StructureShort, StructureType } from '$lib/commons/types' +import { to_placeholders } from './utils' +import { PLURALS } from '$lib/commons/structures' -type NamedObject = { - id: string - name: string +function cache_page(event: RequestEvent) { + event.setHeaders({ + 'cache-control': 'public, max-age=0, s-maxage=1800', // shared cache for 30min + }) } -export function search_handler(event: RequestEvent, type: 'category' | 'functor') { +export function search_handler(event: RequestEvent, type: StructureType): SearchResults { const satisfied_query = event.url.searchParams.get('satisfied') const unsatisfied_query = event.url.searchParams.get('unsatisfied') @@ -22,11 +25,14 @@ export function search_handler(event: RequestEvent, type: 'category' | 'functor' const { rows: all_properties_objects, err: err_all } = query<{ id: string dual_property_id: string | null - }>(get_property_query(type)) + }>({ + sql: `SELECT id, dual_property_id FROM ${type}_properties ORDER BY lower(id)`, + values: [], + }) if (err_all) error(500, 'Failed to load properties') - const all_properties = all_properties_objects.map(({ id }) => id) + const all_properties = new Set(all_properties_objects.map(({ id }) => id)) const dual_properties_dict: Record = {} for (const row of all_properties_objects) { @@ -38,7 +44,7 @@ export function search_handler(event: RequestEvent, type: 'category' | 'functor' : [] const invalid_satisfied_property = satisfied_properties.find( - (p) => !all_properties.includes(p), + (p) => !all_properties.has(p), ) if (invalid_satisfied_property) { @@ -50,7 +56,7 @@ export function search_handler(event: RequestEvent, type: 'category' | 'functor' : [] const invalid_unsatisfied_property = unsatisfied_properties.find( - (p) => !all_properties.includes(p), + (p) => !all_properties.has(p), ) if (invalid_unsatisfied_property) { @@ -69,46 +75,60 @@ export function search_handler(event: RequestEvent, type: 'category' | 'functor' dual_satisfied_properties.every(Boolean) && dual_unsatisfied_properties.every(Boolean) - // TODO: implement this for functors as well - if (type === 'category') { - try { - const contradiction = get_contradiction( - new Set(satisfied_properties), - new Set(unsatisfied_properties), - ) - - if (contradiction) { - event.setHeaders({ - // shared cache for 30min - 'cache-control': 'public, max-age=0, s-maxage=1800', - }) - - return { - contradiction, - all_properties, - satisfied_properties, - unsatisfied_properties, - found_objects: [], - dual_satisfied_properties, - dual_unsatisfied_properties, - dual_search_available, - } - } - } catch (err) { - error(500, 'Consistency check failed') - } - } - - const all_selected_properties = satisfied_properties.concat(unsatisfied_properties) - - const search_query = get_search_query( - satisfied_properties, - unsatisfied_properties, - all_selected_properties, + const { contradiction, err: err_con } = get_contradiction( + new Set(satisfied_properties), + new Set(unsatisfied_properties), type, ) - const { rows: found_objects, err } = query({ + if (err_con) error(500, 'Consistency check failed') + + if (contradiction) { + cache_page(event) + + return { + type, + contradiction, + satisfied_properties, + unsatisfied_properties, + dual_satisfied_properties, + dual_unsatisfied_properties, + dual_search_available, + found_structures: [], + } + } + + const all_selected_properties = [...satisfied_properties, ...unsatisfied_properties] + + const search_query = ` + SELECT s.id, s.name FROM ${PLURALS[type]} s + INNER JOIN ${type}_property_assignments a ON a.${type}_id = s.id + WHERE property_id IN (${to_placeholders(all_selected_properties)}) + GROUP BY ${type}_id + HAVING + SUM ( + CASE + WHEN + property_id IN (${to_placeholders(satisfied_properties)}) + AND is_satisfied = TRUE + THEN 1 + ELSE 0 + END + ) = ${satisfied_properties.length} + AND + SUM( + CASE + WHEN + property_id IN (${to_placeholders(unsatisfied_properties)}) + AND is_satisfied = FALSE + THEN 1 + ELSE 0 + END + ) = ${unsatisfied_properties.length} + ORDER BY lower(s.name) + ` + + const { rows: found_structures, err } = query({ sql: search_query, values: [ ...all_selected_properties, @@ -119,102 +139,16 @@ export function search_handler(event: RequestEvent, type: 'category' | 'functor' if (err) error(500, 'Search failed') - event.setHeaders({ - // shared cache for 30min - 'cache-control': 'public, max-age=0, s-maxage=1800', - }) + cache_page(event) return { + type, contradiction: null, - all_properties, satisfied_properties, unsatisfied_properties, - found_objects, dual_satisfied_properties, dual_unsatisfied_properties, dual_search_available, + found_structures, } } - -function get_property_query(type: 'category' | 'functor') { - if (type === 'category') { - return sql`SELECT id, dual_property_id FROM category_properties ORDER BY lower(id)` - } - if (type === 'functor') { - return sql`SELECT id, dual_property_id FROM functor_properties ORDER BY lower(id)` - } - throw new Error('Invalid type') -} - -function to_placeholders(arr: string[]): string { - return arr.map(() => '?').join(', ') -} - -function get_search_query( - satisfied_properties: string[], - unsatisfied_properties: string[], - all_selected_properties: string[], - type: 'category' | 'functor', -) { - if (type === 'category') { - return ` - SELECT c.id, c.name FROM categories c - INNER JOIN category_property_assignments cp ON cp.category_id = c.id - WHERE property_id IN (${to_placeholders(all_selected_properties)}) - GROUP BY category_id - HAVING - SUM ( - CASE - WHEN - property_id IN (${to_placeholders(satisfied_properties)}) - AND is_satisfied = TRUE - THEN 1 - ELSE 0 - END - ) = ${satisfied_properties.length} - AND - SUM( - CASE - WHEN - property_id IN (${to_placeholders(unsatisfied_properties)}) - AND is_satisfied = FALSE - THEN 1 - ELSE 0 - END - ) = ${unsatisfied_properties.length} - ORDER BY lower(c.name) - ` - } - - if (type === 'functor') { - return ` - SELECT f.id, f.name FROM functors f - INNER JOIN functor_property_assignments fp ON fp.functor_id = f.id - WHERE property_id IN (${to_placeholders(all_selected_properties)}) - GROUP BY functor_id - HAVING - SUM ( - CASE - WHEN - property_id IN (${to_placeholders(satisfied_properties)}) - AND is_satisfied = TRUE - THEN 1 - ELSE 0 - END - ) = ${satisfied_properties.length} - AND - SUM( - CASE - WHEN - property_id IN (${to_placeholders(unsatisfied_properties)}) - AND is_satisfied = FALSE - THEN 1 - ELSE 0 - END - ) = ${unsatisfied_properties.length} - ORDER BY lower(f.name) - ` - } - - throw new Error('Invalid type') -} diff --git a/src/lib/server/transforms.ts b/src/lib/server/transforms.ts new file mode 100644 index 00000000..dbb08a07 --- /dev/null +++ b/src/lib/server/transforms.ts @@ -0,0 +1,48 @@ +import type { + PropertyDB, + PropertyDisplay, + PropertyAssignmentDB, + PropertyAssignmentDisplay, + ImplicationDB, + ImplicationDisplay, +} from '$lib/commons/types' + +export function display_property(property: PropertyDB): PropertyDisplay { + return { + id: property.id, + relation: property.relation, + description: property.description, + dual_property_id: property.dual_property_id, + nlab_link: property.nlab_link, + invariant_under_equivalences: Boolean(property.invariant_under_equivalences), + } +} + +export function display_property_assignment( + property: PropertyAssignmentDB, +): PropertyAssignmentDisplay { + return { + id: property.id, + reason: property.reason, + is_deduced: Boolean(property.is_deduced), + relation: property.relation, + } +} + +export function display_implication(implication: ImplicationDB): ImplicationDisplay { + return { + id: implication.id, + is_equivalence: Boolean(implication.is_equivalence), + is_deduced: Boolean(implication.is_deduced), + dualized_from: implication.dualized_from, + reason: implication.reason, + assumptions: JSON.parse(implication.assumptions), + source_assumptions: implication.source_assumptions + ? JSON.parse(implication.source_assumptions) + : undefined, + target_assumptions: implication.target_assumptions + ? JSON.parse(implication.target_assumptions) + : undefined, + conclusions: JSON.parse(implication.conclusions), + } +} diff --git a/src/lib/server/utils.ts b/src/lib/server/utils.ts index 20d4ec3f..ab5fbc3c 100644 --- a/src/lib/server/utils.ts +++ b/src/lib/server/utils.ts @@ -1,18 +1,3 @@ -import type { - CategoryProperty, - CategoryPropertyDB, - FunctorImplicationDB, - FunctorImplicationDisplay, - FunctorProperty, - FunctorPropertyAssignment, - FunctorPropertyAssignmentDB, - FunctorPropertyDB, - ImplicationDB, - ImplicationDisplay, - PropertyDB, - PropertyDisplay, -} from '$lib/commons/types' - export function is_object(obj: unknown): obj is Record { return obj != null && obj.constructor.name === 'Object' } @@ -24,75 +9,12 @@ export function is_subset(a: Set, b: Set) { return true } -export const sleep = (delay: number) => new Promise((res) => setTimeout(res, delay)) - -export function display_implication(implication: ImplicationDB): ImplicationDisplay { - return { - id: implication.id, - is_equivalence: Boolean(implication.is_equivalence), - reason: implication.reason, - is_deduced: Boolean(implication.is_deduced), - assumptions: JSON.parse(implication.assumptions), - conclusions: JSON.parse(implication.conclusions), - dualized_from: implication.dualized_from, - } +export function to_placeholders(arr: string[]): string { + return arr.map(() => '?').join(', ') } -export function display_property(property: PropertyDB): PropertyDisplay { - return { - id: property.id, - relation: property.relation, - description: property.description, - dual_property_id: property.dual_property_id, - nlab_link: property.nlab_link, - invariant_under_equivalences: Boolean(property.invariant_under_equivalences), - } -} - -export function display_category_property_assignment( - property: CategoryPropertyDB, -): CategoryProperty { - return { - id: property.id, - reason: property.reason, - is_deduced: Boolean(property.is_deduced), - relation: property.relation, - } -} - -export function display_functor_property(property: FunctorPropertyDB): FunctorProperty { - return { - id: property.id, - relation: property.relation, - description: property.description, - dual_property_id: property.dual_property_id, - nlab_link: property.nlab_link, - invariant_under_equivalences: Boolean(property.invariant_under_equivalences), - } -} - -export function display_functor_property_assignment( - property: FunctorPropertyAssignmentDB, -): FunctorPropertyAssignment { - return { - id: property.id, - reason: property.reason, - is_deduced: Boolean(property.is_deduced), - relation: property.relation, - } -} +export const sleep = (delay: number) => new Promise((res) => setTimeout(res, delay)) -export function display_functor_implication( - implication: FunctorImplicationDB, -): FunctorImplicationDisplay { - return { - id: implication.id, - is_equivalence: Boolean(implication.is_equivalence), - reason: implication.reason, - assumptions: JSON.parse(implication.assumptions), - source_assumptions: JSON.parse(implication.source_assumptions), - target_assumptions: JSON.parse(implication.target_assumptions), - conclusions: JSON.parse(implication.conclusions), - dualized_from: implication.dualized_from, - } +export function parse_json_set(json: string): Set { + return new Set(JSON.parse(json)) } diff --git a/src/lib/states/assignment_level.svelte.ts b/src/lib/states/assignment_level.svelte.ts new file mode 100644 index 00000000..af9586a9 --- /dev/null +++ b/src/lib/states/assignment_level.svelte.ts @@ -0,0 +1,33 @@ +import { browser } from '$app/environment' + +export const ASSIGNMENT_LEVELS = { + all: 'Show all properties for a structure. Indicate which properties have been manually assigned and which have been deduced. This is the default.', + merged: "Show all properties for a structure, but don't indicate which properties are manually assigned and which have been deduced.", + basic: 'Show only those properties for a structure that have been manually assigned. Deduced properties are not shown.', +} as const + +type AssignmentLevel = keyof typeof ASSIGNMENT_LEVELS + +function is_valid_assignment_level(level: string | null): level is AssignmentLevel { + return level != null && Object.keys(ASSIGNMENT_LEVELS).includes(level) +} + +const DEFAULT_ASSIGNMENT_LEVEL: AssignmentLevel = 'all' + +export const assignment_level = $state<{ value: AssignmentLevel }>({ + value: get_saved_assignment_level(), +}) + +function get_saved_assignment_level(): AssignmentLevel { + if (!browser) return DEFAULT_ASSIGNMENT_LEVEL + const saved_assignment_level = localStorage.getItem('assignment_level') + + return is_valid_assignment_level(saved_assignment_level) + ? saved_assignment_level + : DEFAULT_ASSIGNMENT_LEVEL +} + +export function update_assignment_level(level: AssignmentLevel) { + if (!browser) return + localStorage.setItem('assignment_level', level) +} diff --git a/src/lib/states/detail_level.svelte.ts b/src/lib/states/detail_level.svelte.ts deleted file mode 100644 index 63e3c491..00000000 --- a/src/lib/states/detail_level.svelte.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { browser } from '$app/environment' - -export const CATEGORY_DETAIL_LEVELS = { - all: 'Show all properties for a category. Indicate which properties have been manually assigned and which have been deduced. This is the default.', - merged: "Show all properties for a category, but don't indicate which properties are manually assigned and which have been deduced.", - basic: 'Show only those properties for a category that have been manually assigned. Deduced properties are not shown.', -} as const - -type CategoryDetailLevel = keyof typeof CATEGORY_DETAIL_LEVELS - -function is_valid_category_detail_level( - level: string | null, -): level is CategoryDetailLevel { - return level != null && Object.keys(CATEGORY_DETAIL_LEVELS).includes(level) -} - -const DEFAULT_CATEGORY_DETAIL_LEVEL: CategoryDetailLevel = 'all' - -export const category_detail_level = $state<{ value: CategoryDetailLevel }>({ - value: get_saved_category_detail_level(), -}) - -function get_saved_category_detail_level(): CategoryDetailLevel { - if (!browser) return DEFAULT_CATEGORY_DETAIL_LEVEL - const saved_category_detail_level = localStorage.getItem('category_detail_level') - - return is_valid_category_detail_level(saved_category_detail_level) - ? saved_category_detail_level - : DEFAULT_CATEGORY_DETAIL_LEVEL -} - -export function update_category_detail_level(level: CategoryDetailLevel) { - if (!browser) return - localStorage.setItem('category_detail_level', level) -} diff --git a/src/pages/ComparisonPage.svelte b/src/pages/ComparisonPage.svelte new file mode 100644 index 00000000..7b34ccec --- /dev/null +++ b/src/pages/ComparisonPage.svelte @@ -0,0 +1,85 @@ + + + + +

Compare {PLURALS[type]}

+ +

+ Select up to {MAX_STRUCTURES_COMPARE} + {PLURALS[type]} to compare their properties with each other. + {#if compared_structures.length === MAX_STRUCTURES_COMPARE} + The maximum is reached. + {/if} +

+ + s.name)} + section_label="selected {PLURALS[type]}" + item_label={type} + bind:selected_items={compared_structures} + max={MAX_STRUCTURES_COMPARE} +/> + +

+ +

diff --git a/src/pages/ComparisonResultPage.svelte b/src/pages/ComparisonResultPage.svelte new file mode 100644 index 00000000..3db0ddc7 --- /dev/null +++ b/src/pages/ComparisonResultPage.svelte @@ -0,0 +1,150 @@ + + + + +

Comparison of {PLURALS[type]}

+ +

+ Selected: + {structures.map((c) => c.name).join(', ')}. + Choose different {PLURALS[type]}. +

+ + + + + + {#each structures as structure} + + {/each} + + + + + {#each comparison_table as [property, ...values]} + {@const is_different = new Set(values).size > 1} + + + {#each structures as _, i} + {@const value = values[i]} + + {/each} + + {/each} + +
Property + + {@html structure.notation} + +
+ {property} + + +
+ + diff --git a/src/pages/ImplicationListPage.svelte b/src/pages/ImplicationListPage.svelte new file mode 100644 index 00000000..d79c961c --- /dev/null +++ b/src/pages/ImplicationListPage.svelte @@ -0,0 +1,80 @@ + + + + +

Implications of {PLURALS[type]}

+ + + +

+ {pluralize(filtered_implications.length, { + one: 'Found {count} implication*', + other: 'Found {count} implications*', + })} +

+ + + New here? Click any icon to view the proof for that implication. + + + + +{#if hints} + {@render hints()} +{/if} + + + + diff --git a/src/pages/ImplicationPage.svelte b/src/pages/ImplicationPage.svelte new file mode 100644 index 00000000..98916216 --- /dev/null +++ b/src/pages/ImplicationPage.svelte @@ -0,0 +1,113 @@ + + + + +

Implication Details

+ +

+ Assumptions: + + {#each implication.assumptions as property, index} + {property}{#if index < implication.assumptions.length - 1} + ,  + {/if} + {/each} +

+ +{#if implication.source_assumptions?.length} +

+ Assumptions on source {associated_type}: + + {#each implication.source_assumptions as property, index} + {property}{#if index < implication.source_assumptions.length - 1} + ,  + {/if} + {/each} +

+{/if} + +{#if implication.target_assumptions?.length} +

+ Assumptions on target {associated_type}: + + {#each implication.target_assumptions as property, index} + {property}{#if index < implication.target_assumptions.length - 1} + ,  + {/if} + {/each} +

+{/if} + +

+ Conclusions: + {#each implication.conclusions as property, index} + {property}{#if index < implication.conclusions.length - 1} + ,  + {/if} + {/each} +

+ +{#if implication.is_equivalence} +

+ + This is an equivalence. +

+{/if} + +{#if implication.dualized_from} +

+ This implication has been dualized from + this implication + . +

+{:else} +

+ Reason: + {@html implication.reason} +

+{/if} + +{#if structures.length > 0} +
+ + {pluralize(structures.length, { + one: `Show {count} ${type} using this implication`, + other: `Show {count} ${PLURALS[type]} using this implication`, + })} + + +
+{/if} + + + + diff --git a/src/pages/PropertyListPage.svelte b/src/pages/PropertyListPage.svelte new file mode 100644 index 00000000..4862ab0a --- /dev/null +++ b/src/pages/PropertyListPage.svelte @@ -0,0 +1,66 @@ + + + + +

Properties of {PLURALS[type]}

+ + + +

+ {#if !search} + Found {total} properties ({grouped_total} grouped) + {:else} + {pluralize(searched_properties.length, { + one: 'Found {count} group', + other: 'Found {count} groups', + })} + {/if} +

+ +
    + {#each searched_properties as { id, relation, dual_property_id }} +
  • + {relation} + {id} + {#if dual_property_id && id !== dual_property_id} + / + {dual_property_id} + + {/if} +
  • + {/each} +
+ + diff --git a/src/pages/PropertyPage.svelte b/src/pages/PropertyPage.svelte new file mode 100644 index 00000000..c55d0592 --- /dev/null +++ b/src/pages/PropertyPage.svelte @@ -0,0 +1,151 @@ + + + + +

{property.id}

+ +

+ {@html property.description} + + {#if property.invariant_under_equivalences === false} + Warning: This property is not invariant under equivalences. + {/if} +

+ +{#if property.dual_property_id || related_properties.length || property.nlab_link} +
    + {#if property.dual_property_id} +
  • + Dual property: + {property.dual_property_id} + {#if property.dual_property_id === property.id} + (self-dual) + {/if} +
  • + {/if} + + {#if related_properties.length} +
  • + Related properties: + {#each related_properties as related_property, i} + + {related_property} + {#if i < related_properties.length - 1} + ,  + {/if} + {/each} +
  • + {/if} + + {#if property.nlab_link} +
  • + nLab Link +
  • + {/if} +
+{/if} + + + New here? Click any icon to view the proof for that implication. + + +

Relevant implications

+ + + +

Examples

+ +

+ {pluralize(examples.length, { + one: `There is {count} ${type} with this property.`, + other: `There are {count} ${PLURALS[type]} with this property.`, + })} +

+ + + +

Counterexamples

+ +

+ {pluralize(counterexamples.length, { + one: `There is {count} ${type} without this property.`, + other: `There are {count} ${PLURALS[type]} without this property.`, + })} +

+ + + +

Unknown

+ +

+ {pluralize(unknown_structures.length, { + one: `There is {count} ${type} for which the database has no information on whether it satisfies this property.`, + other: `There are {count} ${PLURALS[type]} for which the database has no information on whether they satisfy this property.`, + })} + {#if unknown_structures.length > 0} + Please help us fill in the gaps by + contributing to this project. + {/if} +

+ + + +{#if undecidable_structures.length} +

Undecidable {PLURALS[type]}

+ +

+ {pluralize(undecidable_structures.length, { + one: `There is {count} ${type} for which it cannot be decided if this property is satisfied or not.`, + other: `There are {count} ${PLURALS[type]} for which it cannot be decided if this property is satisfied or not.`, + })} +

+ + +{/if} + + diff --git a/src/pages/SearchPage.svelte b/src/pages/SearchPage.svelte new file mode 100644 index 00000000..e2b7a747 --- /dev/null +++ b/src/pages/SearchPage.svelte @@ -0,0 +1,134 @@ + + + + +

Property combo search

+ +

+ {@render children()} +

+ + + + + + + + + + + + diff --git a/src/pages/SearchResultsPage.svelte b/src/pages/SearchResultsPage.svelte new file mode 100644 index 00000000..136a70f8 --- /dev/null +++ b/src/pages/SearchResultsPage.svelte @@ -0,0 +1,115 @@ + + + + +

Search results

+ +{#if satisfied_properties.length > 0} +
+ Satisfied properties: + + {#each satisfied_properties as property, index} + {property}{#if index < satisfied_properties.length - 1} + ,  + {/if} + {/each} +
+{/if} + +{#if unsatisfied_properties.length > 0} +
+ Unsatisfied properties: + + {#each unsatisfied_properties as property, index} + {property}{#if index < unsatisfied_properties.length - 1} + ,  + {/if} + {/each} +
+{/if} + +{#if !contradiction} +

+ {pluralize(found_structures.length, { + one: `Found {count} ${type}`, + other: `Found {count} ${PLURALS[type]}`, + })} +

+ + +{:else} +

+ + No {PLURALS[type]} found because the requirements are inconsistent: +

+ +
    + {#each contradiction as segment} +
  1. {segment}
  2. + {/each} +
+{/if} + + + Adjust search + + {#if dual_search_available} + Dualize search + {/if} + + + diff --git a/src/pages/StructureListPage.svelte b/src/pages/StructureListPage.svelte new file mode 100644 index 00000000..7bbd34ad --- /dev/null +++ b/src/pages/StructureListPage.svelte @@ -0,0 +1,59 @@ + + + + +
+

List of {PLURALS[type]}

+ + + +

+ {pluralize(searched_structures.length, { + one: `Found {count} ${type}`, + other: `Found {count} ${PLURALS[type]}`, + })} +

+ + +
+ +
+

List of tags

+ + +
+ + + + diff --git a/src/pages/TaggedPage.svelte b/src/pages/TaggedPage.svelte new file mode 100644 index 00000000..7b7486f2 --- /dev/null +++ b/src/pages/TaggedPage.svelte @@ -0,0 +1,28 @@ + + + + +

List of {PLURALS[type]} tagged with '{tag}'

+ +

+ {pluralize(structures.length, { + one: `Found {count} ${type}`, + other: `Found {count} ${PLURALS[type]}`, + })} +

+ + diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f04ea735..c2fc92a3 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,7 +7,7 @@ import NavMobile from '$components/NavMobile.svelte' import Popup from '$components/Popup.svelte' import { track_visit } from '$lib/client/track' - import type { Structure } from '$lib/commons/types' + import type { StructureType } from '$lib/commons/types' import { tracking } from '$lib/states/tracking.svelte' import './app.css' @@ -31,18 +31,18 @@ let nav_dialog = $state(null) - let structure = $state( - page.url.pathname.startsWith('/functor') ? 'functors' : 'categories', + let selected_type = $state( + page.url.pathname.startsWith('/functor') ? 'functor' : 'category', ) $effect(() => { if (page.url.pathname.startsWith('/functor')) { - structure = 'functors' + selected_type = 'functor' } else if ( page.url.pathname.startsWith('/category') || page.url.pathname.startsWith('/categories') ) { - structure = 'categories' + selected_type = 'category' } }) @@ -72,8 +72,8 @@
-
-