implica 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of implica might be problematic. Click here for more details.
- implica/__init__.py +44 -0
- implica/core/__init__.py +4 -0
- implica/core/combinator.py +149 -0
- implica/core/types.py +140 -0
- implica/graph/__init__.py +5 -0
- implica/graph/connection.py +389 -0
- implica/graph/elements.py +194 -0
- implica/graph/graph.py +443 -0
- implica/mutations/__init__.py +29 -0
- implica/mutations/add_edge.py +45 -0
- implica/mutations/add_many_edges.py +60 -0
- implica/mutations/add_many_nodes.py +60 -0
- implica/mutations/add_node.py +45 -0
- implica/mutations/base.py +51 -0
- implica/mutations/remove_edge.py +54 -0
- implica/mutations/remove_many_edges.py +64 -0
- implica/mutations/remove_many_nodes.py +69 -0
- implica/mutations/remove_node.py +62 -0
- implica/mutations/try_add_edge.py +53 -0
- implica/mutations/try_add_node.py +50 -0
- implica/mutations/try_remove_edge.py +58 -0
- implica/mutations/try_remove_node.py +62 -0
- implica-0.3.0.dist-info/LICENSE +21 -0
- implica-0.3.0.dist-info/METADATA +945 -0
- implica-0.3.0.dist-info/RECORD +26 -0
- implica-0.3.0.dist-info/WHEEL +4 -0
implica/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Guanciale - Implicational Logic Graph Library
|
|
3
|
+
|
|
4
|
+
A Python package for working with graphs representing minimal implicational
|
|
5
|
+
logic models. This library provides tools for constructing and manipulating
|
|
6
|
+
type systems based on combinatory logic, with support for transactional
|
|
7
|
+
graph operations.
|
|
8
|
+
|
|
9
|
+
Import as 'gil' for a clean, concise API:
|
|
10
|
+
import guanciale as gil
|
|
11
|
+
|
|
12
|
+
graph = gil.Graph()
|
|
13
|
+
A = gil.var("A")
|
|
14
|
+
B = gil.var("B")
|
|
15
|
+
|
|
16
|
+
with graph.connect() as conn:
|
|
17
|
+
conn.add_node(gil.node(A))
|
|
18
|
+
conn.add_node(gil.node(B))
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .core import *
|
|
22
|
+
from .graph import Node, Edge, node, edge, Graph, Connection
|
|
23
|
+
from . import mutations
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"BaseType",
|
|
28
|
+
"Variable",
|
|
29
|
+
"Application",
|
|
30
|
+
"var",
|
|
31
|
+
"app",
|
|
32
|
+
"Combinator",
|
|
33
|
+
"S",
|
|
34
|
+
"K",
|
|
35
|
+
"Node",
|
|
36
|
+
"Edge",
|
|
37
|
+
"node",
|
|
38
|
+
"edge",
|
|
39
|
+
"Graph",
|
|
40
|
+
"Connection",
|
|
41
|
+
"mutations",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
__version__ = "0.3.0"
|
implica/core/__init__.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Combinator system for minimal implicational logic.
|
|
3
|
+
|
|
4
|
+
This module defines combinators that represent transformations between types
|
|
5
|
+
in the implicational logic graph.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
from functools import cached_property
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
12
|
+
|
|
13
|
+
from .types import BaseType, Application, app
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Combinator(BaseModel):
|
|
17
|
+
"""
|
|
18
|
+
A combinator with a specific type.
|
|
19
|
+
|
|
20
|
+
In combinatory logic:
|
|
21
|
+
- S combinator: S x y z = x z (y z) with type (A -> B -> C) -> (A -> B) -> A -> C
|
|
22
|
+
- K combinator: K x y = x with type A -> B -> A
|
|
23
|
+
|
|
24
|
+
Each combinator has a name (e.g., "S", "K") and an application type.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name: str = Field(..., min_length=1, description="Name of the combinator")
|
|
28
|
+
type: BaseType = Field(..., description="Type of the combinator")
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(frozen=True) # Make immutable
|
|
31
|
+
|
|
32
|
+
@cached_property
|
|
33
|
+
def uid(self) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Return a unique identifier for this combinator.
|
|
36
|
+
|
|
37
|
+
The uid includes both the name and the type, as combinators with the same
|
|
38
|
+
name but different types are different combinators.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
str: SHA256 hash of the combinator name and type
|
|
42
|
+
"""
|
|
43
|
+
content = f"Comb({self.name},{self.type.uid})"
|
|
44
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
45
|
+
|
|
46
|
+
def __str__(self) -> str:
|
|
47
|
+
"""Return the combinator name and type."""
|
|
48
|
+
return f"{self.name}: {self.type}"
|
|
49
|
+
|
|
50
|
+
def __repr__(self) -> str:
|
|
51
|
+
"""Return a detailed representation."""
|
|
52
|
+
return f"Combinator(name='{self.name}', type={self.type})"
|
|
53
|
+
|
|
54
|
+
def __hash__(self) -> int:
|
|
55
|
+
"""Make combinator hashable for use in sets and dicts."""
|
|
56
|
+
return hash((self.name, self.type.uid))
|
|
57
|
+
|
|
58
|
+
def __call__(self, other: "Combinator") -> "Combinator":
|
|
59
|
+
"""
|
|
60
|
+
Apply this combinator to another combinator.
|
|
61
|
+
|
|
62
|
+
If this combinator has type A -> B and the input has type A,
|
|
63
|
+
the result will be a combinator with type B.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
other: The combinator to apply this combinator to
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Combinator: The result of the application
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
TypeError: If the types are incompatible
|
|
73
|
+
"""
|
|
74
|
+
if not isinstance(self.type, Application):
|
|
75
|
+
raise TypeError(
|
|
76
|
+
f"Cannot apply combinator {self.name} with type {self.type} "
|
|
77
|
+
f"(not a function type)"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Check if the input type matches
|
|
81
|
+
if self.type.input_type.uid != other.type.uid:
|
|
82
|
+
raise TypeError(
|
|
83
|
+
f"Type mismatch: expected {self.type.input_type}, " f"got {other.type}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Create the result combinator
|
|
87
|
+
result_name = f"({self.name} {other.name})"
|
|
88
|
+
return Combinator(name=result_name, type=self.type.output_type)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Helper functions for creating S and K combinators with specific types
|
|
92
|
+
def S(A: BaseType, B: BaseType, C: BaseType) -> Combinator:
|
|
93
|
+
"""
|
|
94
|
+
Create the S combinator with specific types.
|
|
95
|
+
|
|
96
|
+
Type: (A -> B -> C) -> (A -> B) -> A -> C
|
|
97
|
+
|
|
98
|
+
The S combinator implements the following computation:
|
|
99
|
+
S x y z = x z (y z)
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
A: Type variable A
|
|
103
|
+
B: Type variable B
|
|
104
|
+
C: Type variable C
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Combinator: The S combinator with the specified types
|
|
108
|
+
"""
|
|
109
|
+
s_type = app(
|
|
110
|
+
app(A, app(B, C)), # (A -> B -> C)
|
|
111
|
+
app(app(A, B), app(A, C)), # (A -> B) -> A -> C
|
|
112
|
+
)
|
|
113
|
+
return Combinator(name="S", type=s_type)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def K(A: BaseType, B: BaseType) -> Combinator:
|
|
117
|
+
"""
|
|
118
|
+
Create the K combinator with specific types.
|
|
119
|
+
|
|
120
|
+
Type: A -> B -> A
|
|
121
|
+
|
|
122
|
+
The K combinator implements the following computation:
|
|
123
|
+
K x y = x
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
A: Type variable A
|
|
127
|
+
B: Type variable B
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Combinator: The K combinator with type A -> B -> A
|
|
131
|
+
"""
|
|
132
|
+
k_type = app(A, app(B, A)) # A -> B -> A
|
|
133
|
+
return Combinator(name="K", type=k_type)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def combinator(name: str, type: BaseType) -> Combinator:
|
|
137
|
+
"""
|
|
138
|
+
Helper function to create a combinator from a sequence string.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
name: Sequence of 'S' and 'K' characters
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Combinator: A new Combinator instance
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
ValueError: If the name contains invalid characters
|
|
148
|
+
"""
|
|
149
|
+
return Combinator(name=name, type=type)
|
implica/core/types.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type system for minimal implicational logic.
|
|
3
|
+
|
|
4
|
+
This module defines the type hierarchy for the implicational logic graph:
|
|
5
|
+
- BaseType: Abstract base for all types
|
|
6
|
+
- Variable: Simple type variables (e.g., A, B, C)
|
|
7
|
+
- Application: Function application types (e.g., A -> B)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from functools import cached_property
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseType(BaseModel, ABC):
|
|
18
|
+
"""Abstract base class for all types in the implicational logic system."""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(frozen=True) # Make immutable
|
|
21
|
+
|
|
22
|
+
@cached_property
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def uid(self) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Unique identifier for this type, computed as a hash.
|
|
27
|
+
|
|
28
|
+
This property is cached for performance and should uniquely identify
|
|
29
|
+
the type based on its structure.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
str: A unique string identifier for this type
|
|
33
|
+
"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
"""String representation of the type."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Variable(BaseType):
|
|
43
|
+
"""
|
|
44
|
+
A simple type variable (e.g., A, B, C).
|
|
45
|
+
|
|
46
|
+
Variables represent atomic types in the type system.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
name: str = Field(..., min_length=1, description="Name of the type variable")
|
|
50
|
+
|
|
51
|
+
@field_validator("name")
|
|
52
|
+
@classmethod
|
|
53
|
+
def validate_name(cls, v: str) -> str:
|
|
54
|
+
"""Validate that the variable name is non-empty and contains valid characters."""
|
|
55
|
+
if not v or not v.strip():
|
|
56
|
+
raise ValueError("Variable name cannot be empty or whitespace")
|
|
57
|
+
return v.strip()
|
|
58
|
+
|
|
59
|
+
@cached_property
|
|
60
|
+
def uid(self) -> str:
|
|
61
|
+
"""Return the variable name hashed as its unique identifier."""
|
|
62
|
+
content = f"Var({self.name})"
|
|
63
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
64
|
+
|
|
65
|
+
def __str__(self) -> str:
|
|
66
|
+
"""Return the variable name."""
|
|
67
|
+
return self.name
|
|
68
|
+
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
|
+
"""Return a detailed representation."""
|
|
71
|
+
return f"Variable(name='{self.name}')"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Application(BaseType):
|
|
75
|
+
"""
|
|
76
|
+
A function application type (e.g., A -> B).
|
|
77
|
+
|
|
78
|
+
Represents the type of a function that takes `input_type` and returns `output_type`.
|
|
79
|
+
In notation: input_type -> output_type
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
input_type: BaseType = Field(..., description="Input type of the function")
|
|
83
|
+
output_type: BaseType = Field(..., description="Output type of the function")
|
|
84
|
+
|
|
85
|
+
@cached_property
|
|
86
|
+
def uid(self) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Compute unique identifier based on input and output types.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
str: A SHA256 hash of the structure
|
|
92
|
+
"""
|
|
93
|
+
content = f"App({self.input_type.uid},{self.output_type.uid})"
|
|
94
|
+
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
95
|
+
|
|
96
|
+
def __str__(self) -> str:
|
|
97
|
+
"""
|
|
98
|
+
Return a human-readable string representation.
|
|
99
|
+
|
|
100
|
+
Uses parentheses for nested applications to maintain clarity.
|
|
101
|
+
"""
|
|
102
|
+
# Add parentheses around input if it's also an Application
|
|
103
|
+
if isinstance(self.input_type, Application):
|
|
104
|
+
input_str = f"({self.input_type})"
|
|
105
|
+
else:
|
|
106
|
+
input_str = str(self.input_type)
|
|
107
|
+
|
|
108
|
+
return f"{input_str} -> {self.output_type}"
|
|
109
|
+
|
|
110
|
+
def __repr__(self) -> str:
|
|
111
|
+
"""Return a detailed representation."""
|
|
112
|
+
return f"Application(input_type={repr(self.input_type)}, output_type={repr(self.output_type)})"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Helper function to create types more easily
|
|
116
|
+
def var(name: str) -> Variable:
|
|
117
|
+
"""
|
|
118
|
+
Helper function to create a Variable.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
name: Name of the variable
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Variable: A new Variable instance
|
|
125
|
+
"""
|
|
126
|
+
return Variable(name=name)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def app(input_type: BaseType, output_type: BaseType) -> Application:
|
|
130
|
+
"""
|
|
131
|
+
Helper function to create an Application.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
input_type: The input type
|
|
135
|
+
output_type: The output type
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Application: A new Application instance
|
|
139
|
+
"""
|
|
140
|
+
return Application(input_type=input_type, output_type=output_type)
|