implica 0.3.2__tar.gz → 0.3.4__tar.gz

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.

Files changed (30) hide show
  1. {implica-0.3.2 → implica-0.3.4}/PKG-INFO +136 -1
  2. {implica-0.3.2 → implica-0.3.4}/README.md +135 -0
  3. {implica-0.3.2 → implica-0.3.4}/pyproject.toml +1 -1
  4. {implica-0.3.2 → implica-0.3.4}/src/implica/__init__.py +1 -1
  5. {implica-0.3.2 → implica-0.3.4}/src/implica/core/types.py +40 -0
  6. {implica-0.3.2 → implica-0.3.4}/LICENSE +0 -0
  7. {implica-0.3.2 → implica-0.3.4}/src/implica/core/__init__.py +0 -0
  8. {implica-0.3.2 → implica-0.3.4}/src/implica/core/combinator.py +0 -0
  9. {implica-0.3.2 → implica-0.3.4}/src/implica/graph/__init__.py +0 -0
  10. {implica-0.3.2 → implica-0.3.4}/src/implica/graph/connection.py +0 -0
  11. {implica-0.3.2 → implica-0.3.4}/src/implica/graph/elements.py +0 -0
  12. {implica-0.3.2 → implica-0.3.4}/src/implica/graph/graph.py +0 -0
  13. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/__init__.py +0 -0
  14. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/add_edge.py +0 -0
  15. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/add_many_edges.py +0 -0
  16. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/add_many_nodes.py +0 -0
  17. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/add_node.py +0 -0
  18. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/base.py +0 -0
  19. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/remove_edge.py +0 -0
  20. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/remove_many_edges.py +0 -0
  21. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/remove_many_nodes.py +0 -0
  22. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/remove_node.py +0 -0
  23. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_add_edge.py +0 -0
  24. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_add_many_edges.py +0 -0
  25. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_add_many_nodes.py +0 -0
  26. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_add_node.py +0 -0
  27. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_remove_edge.py +0 -0
  28. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_remove_many_edges.py +0 -0
  29. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_remove_many_nodes.py +0 -0
  30. {implica-0.3.2 → implica-0.3.4}/src/implica/mutations/try_remove_node.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: implica
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: A package for working with graphs representing minimal implicational logic models
5
5
  Author: Carlos Fernandez
6
6
  Author-email: carlos.ferlo@outlook.com
@@ -24,6 +24,7 @@ A Python package for working with graphs representing minimal implicational logi
24
24
  ## Features
25
25
 
26
26
  - 🎯 **Type System**: Build complex type expressions using variables and applications (function types)
27
+ - 🔍 **Variable Extraction**: Recursively extract all variables from any type expression
27
28
  - 🧩 **Combinators**: Work with S and K combinators from combinatory logic
28
29
  - 📊 **Graph Structure**: Represent type transformations as nodes and edges in a directed graph
29
30
  - 🔄 **Transactional Operations**: Safely modify graphs with automatic rollback on failure
@@ -106,6 +107,43 @@ print(complex_function) # Output: (A -> B) -> C
106
107
  print(nested_function) # Output: A -> B -> C
107
108
  ```
108
109
 
110
+ #### Extracting Variables from Types
111
+
112
+ Every type has a `variables` property that returns a list of all variables contained in that type. This is computed recursively and may include duplicate variables if they appear multiple times:
113
+
114
+ ```python
115
+ import implica as imp
116
+
117
+ # Simple variable returns itself
118
+ A = imp.var("A")
119
+ print(A.variables) # Output: [Variable(name='A')]
120
+
121
+ # Application returns all variables from both input and output
122
+ B = imp.var("B")
123
+ func = imp.app(A, B)
124
+ print([v.name for v in func.variables]) # Output: ['A', 'B']
125
+
126
+ # Nested types return all variables recursively
127
+ C = imp.var("C")
128
+ nested = imp.app(imp.app(A, B), C) # (A -> B) -> C
129
+ print([v.name for v in nested.variables]) # Output: ['A', 'B', 'C']
130
+
131
+ # Duplicates are included
132
+ same_var = imp.app(A, A) # A -> A
133
+ print([v.name for v in same_var.variables]) # Output: ['A', 'A']
134
+
135
+ # Complex example with duplicates
136
+ complex = imp.app(imp.app(A, B), A) # (A -> B) -> A
137
+ print([v.name for v in complex.variables]) # Output: ['A', 'B', 'A']
138
+ ```
139
+
140
+ **Use cases:**
141
+
142
+ - Analyze which variables are used in a complex type expression
143
+ - Count occurrences of specific variables in a type
144
+ - Validate that certain variables are present or absent
145
+ - Generate variable lists for quantification or substitution operations
146
+
109
147
  ### Combinators
110
148
 
111
149
  Combinators represent transformations between types. The library includes the S and K combinators from combinatory logic:
@@ -800,6 +838,96 @@ ensure_graph_state(
800
838
  print(f"Final state: {graph.node_count()} nodes") # Output: 2 (X and Y)
801
839
  ```
802
840
 
841
+ ### Example 10: Analyzing Type Variables
842
+
843
+ Extract and analyze variables from complex type expressions:
844
+
845
+ ```python
846
+ import implica as imp
847
+
848
+ # Create a complex type expression
849
+ A = imp.var("A")
850
+ B = imp.var("B")
851
+ C = imp.var("C")
852
+
853
+ # ((A -> B) -> C) -> (A -> B)
854
+ inner = imp.app(A, B)
855
+ middle = imp.app(inner, C)
856
+ complex_type = imp.app(middle, inner)
857
+
858
+ print(f"Type: {complex_type}")
859
+ # Output: ((A -> B) -> C) -> A -> B
860
+
861
+ # Get all variables (including duplicates)
862
+ variables = complex_type.variables
863
+ print(f"All variables: {[v.name for v in variables]}")
864
+ # Output: ['A', 'B', 'C', 'A', 'B']
865
+
866
+ # Count unique variables
867
+ unique_vars = {v.name for v in variables}
868
+ print(f"Unique variables: {unique_vars}")
869
+ # Output: {'A', 'B', 'C'}
870
+
871
+ # Count occurrences
872
+ from collections import Counter
873
+ var_counts = Counter(v.name for v in variables)
874
+ print(f"Variable counts: {dict(var_counts)}")
875
+ # Output: {'A': 2, 'B': 2, 'C': 1}
876
+
877
+ # Check if a specific variable is used
878
+ def uses_variable(type_expr, var_name):
879
+ """Check if a type expression uses a specific variable."""
880
+ return any(v.name == var_name for v in type_expr.variables)
881
+
882
+ print(f"Uses A: {uses_variable(complex_type, 'A')}") # Output: True
883
+ print(f"Uses D: {uses_variable(complex_type, 'D')}") # Output: False
884
+
885
+ # Find all types in a graph that use a specific variable
886
+ graph = imp.Graph()
887
+
888
+ # Create various types
889
+ types_to_add = [
890
+ imp.var("X"),
891
+ imp.var("Y"),
892
+ imp.app(A, B),
893
+ imp.app(B, C),
894
+ imp.app(A, imp.app(B, C)),
895
+ ]
896
+
897
+ with graph.connect() as conn:
898
+ for t in types_to_add:
899
+ conn.add_node(imp.node(t))
900
+
901
+ # Find all nodes containing variable "B"
902
+ nodes_with_B = [
903
+ n for n in graph.nodes()
904
+ if any(v.name == "B" for v in n.type.variables)
905
+ ]
906
+
907
+ print(f"\nNodes containing variable B:")
908
+ for n in nodes_with_B:
909
+ print(f" - {n.type}")
910
+ # Output:
911
+ # - A -> B
912
+ # - B -> C
913
+ # - A -> B -> C
914
+
915
+ # Calculate the "complexity" of a type by counting its variables
916
+ def type_complexity(type_expr):
917
+ """Calculate complexity as the total number of variables."""
918
+ return len(type_expr.variables)
919
+
920
+ print(f"\nType complexities:")
921
+ for n in graph.nodes():
922
+ print(f" {n.type}: {type_complexity(n.type)}")
923
+ # Output:
924
+ # X: 1
925
+ # Y: 1
926
+ # A -> B: 2
927
+ # B -> C: 2
928
+ # A -> B -> C: 3
929
+ ```
930
+
803
931
  ## API Reference
804
932
 
805
933
  ### Core Module (`implica.core`)
@@ -809,7 +937,14 @@ print(f"Final state: {graph.node_count()} nodes") # Output: 2 (X and Y)
809
937
  - `var(name: str) -> Variable`: Create a type variable
810
938
  - `app(input_type: BaseType, output_type: BaseType) -> Application`: Create a function type
811
939
  - `Variable`: Atomic type variable
940
+ - `name: str`: The name of the variable
941
+ - `uid: str`: Unique identifier (SHA256 hash)
942
+ - `variables: list[Variable]`: Returns `[self]`
812
943
  - `Application`: Function application type
944
+ - `input_type: BaseType`: Input type of the function
945
+ - `output_type: BaseType`: Output type of the function
946
+ - `uid: str`: Unique identifier (SHA256 hash)
947
+ - `variables: list[Variable]`: Returns all variables from input and output types (may include duplicates)
813
948
 
814
949
  **Combinators:**
815
950
 
@@ -10,6 +10,7 @@ A Python package for working with graphs representing minimal implicational logi
10
10
  ## Features
11
11
 
12
12
  - 🎯 **Type System**: Build complex type expressions using variables and applications (function types)
13
+ - 🔍 **Variable Extraction**: Recursively extract all variables from any type expression
13
14
  - 🧩 **Combinators**: Work with S and K combinators from combinatory logic
14
15
  - 📊 **Graph Structure**: Represent type transformations as nodes and edges in a directed graph
15
16
  - 🔄 **Transactional Operations**: Safely modify graphs with automatic rollback on failure
@@ -92,6 +93,43 @@ print(complex_function) # Output: (A -> B) -> C
92
93
  print(nested_function) # Output: A -> B -> C
93
94
  ```
94
95
 
96
+ #### Extracting Variables from Types
97
+
98
+ Every type has a `variables` property that returns a list of all variables contained in that type. This is computed recursively and may include duplicate variables if they appear multiple times:
99
+
100
+ ```python
101
+ import implica as imp
102
+
103
+ # Simple variable returns itself
104
+ A = imp.var("A")
105
+ print(A.variables) # Output: [Variable(name='A')]
106
+
107
+ # Application returns all variables from both input and output
108
+ B = imp.var("B")
109
+ func = imp.app(A, B)
110
+ print([v.name for v in func.variables]) # Output: ['A', 'B']
111
+
112
+ # Nested types return all variables recursively
113
+ C = imp.var("C")
114
+ nested = imp.app(imp.app(A, B), C) # (A -> B) -> C
115
+ print([v.name for v in nested.variables]) # Output: ['A', 'B', 'C']
116
+
117
+ # Duplicates are included
118
+ same_var = imp.app(A, A) # A -> A
119
+ print([v.name for v in same_var.variables]) # Output: ['A', 'A']
120
+
121
+ # Complex example with duplicates
122
+ complex = imp.app(imp.app(A, B), A) # (A -> B) -> A
123
+ print([v.name for v in complex.variables]) # Output: ['A', 'B', 'A']
124
+ ```
125
+
126
+ **Use cases:**
127
+
128
+ - Analyze which variables are used in a complex type expression
129
+ - Count occurrences of specific variables in a type
130
+ - Validate that certain variables are present or absent
131
+ - Generate variable lists for quantification or substitution operations
132
+
95
133
  ### Combinators
96
134
 
97
135
  Combinators represent transformations between types. The library includes the S and K combinators from combinatory logic:
@@ -786,6 +824,96 @@ ensure_graph_state(
786
824
  print(f"Final state: {graph.node_count()} nodes") # Output: 2 (X and Y)
787
825
  ```
788
826
 
827
+ ### Example 10: Analyzing Type Variables
828
+
829
+ Extract and analyze variables from complex type expressions:
830
+
831
+ ```python
832
+ import implica as imp
833
+
834
+ # Create a complex type expression
835
+ A = imp.var("A")
836
+ B = imp.var("B")
837
+ C = imp.var("C")
838
+
839
+ # ((A -> B) -> C) -> (A -> B)
840
+ inner = imp.app(A, B)
841
+ middle = imp.app(inner, C)
842
+ complex_type = imp.app(middle, inner)
843
+
844
+ print(f"Type: {complex_type}")
845
+ # Output: ((A -> B) -> C) -> A -> B
846
+
847
+ # Get all variables (including duplicates)
848
+ variables = complex_type.variables
849
+ print(f"All variables: {[v.name for v in variables]}")
850
+ # Output: ['A', 'B', 'C', 'A', 'B']
851
+
852
+ # Count unique variables
853
+ unique_vars = {v.name for v in variables}
854
+ print(f"Unique variables: {unique_vars}")
855
+ # Output: {'A', 'B', 'C'}
856
+
857
+ # Count occurrences
858
+ from collections import Counter
859
+ var_counts = Counter(v.name for v in variables)
860
+ print(f"Variable counts: {dict(var_counts)}")
861
+ # Output: {'A': 2, 'B': 2, 'C': 1}
862
+
863
+ # Check if a specific variable is used
864
+ def uses_variable(type_expr, var_name):
865
+ """Check if a type expression uses a specific variable."""
866
+ return any(v.name == var_name for v in type_expr.variables)
867
+
868
+ print(f"Uses A: {uses_variable(complex_type, 'A')}") # Output: True
869
+ print(f"Uses D: {uses_variable(complex_type, 'D')}") # Output: False
870
+
871
+ # Find all types in a graph that use a specific variable
872
+ graph = imp.Graph()
873
+
874
+ # Create various types
875
+ types_to_add = [
876
+ imp.var("X"),
877
+ imp.var("Y"),
878
+ imp.app(A, B),
879
+ imp.app(B, C),
880
+ imp.app(A, imp.app(B, C)),
881
+ ]
882
+
883
+ with graph.connect() as conn:
884
+ for t in types_to_add:
885
+ conn.add_node(imp.node(t))
886
+
887
+ # Find all nodes containing variable "B"
888
+ nodes_with_B = [
889
+ n for n in graph.nodes()
890
+ if any(v.name == "B" for v in n.type.variables)
891
+ ]
892
+
893
+ print(f"\nNodes containing variable B:")
894
+ for n in nodes_with_B:
895
+ print(f" - {n.type}")
896
+ # Output:
897
+ # - A -> B
898
+ # - B -> C
899
+ # - A -> B -> C
900
+
901
+ # Calculate the "complexity" of a type by counting its variables
902
+ def type_complexity(type_expr):
903
+ """Calculate complexity as the total number of variables."""
904
+ return len(type_expr.variables)
905
+
906
+ print(f"\nType complexities:")
907
+ for n in graph.nodes():
908
+ print(f" {n.type}: {type_complexity(n.type)}")
909
+ # Output:
910
+ # X: 1
911
+ # Y: 1
912
+ # A -> B: 2
913
+ # B -> C: 2
914
+ # A -> B -> C: 3
915
+ ```
916
+
789
917
  ## API Reference
790
918
 
791
919
  ### Core Module (`implica.core`)
@@ -795,7 +923,14 @@ print(f"Final state: {graph.node_count()} nodes") # Output: 2 (X and Y)
795
923
  - `var(name: str) -> Variable`: Create a type variable
796
924
  - `app(input_type: BaseType, output_type: BaseType) -> Application`: Create a function type
797
925
  - `Variable`: Atomic type variable
926
+ - `name: str`: The name of the variable
927
+ - `uid: str`: Unique identifier (SHA256 hash)
928
+ - `variables: list[Variable]`: Returns `[self]`
798
929
  - `Application`: Function application type
930
+ - `input_type: BaseType`: Input type of the function
931
+ - `output_type: BaseType`: Output type of the function
932
+ - `uid: str`: Unique identifier (SHA256 hash)
933
+ - `variables: list[Variable]`: Returns all variables from input and output types (may include duplicates)
799
934
 
800
935
  **Combinators:**
801
936
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "implica"
3
- version = "0.3.2"
3
+ version = "0.3.4"
4
4
  description = "A package for working with graphs representing minimal implicational logic models"
5
5
  authors = ["Carlos Fernandez <carlos.ferlo@outlook.com>"]
6
6
  readme = "README.md"
@@ -41,4 +41,4 @@ __all__ = [
41
41
  "mutations",
42
42
  ]
43
43
 
44
- __version__ = "0.3.2"
44
+ __version__ = "0.3.4"
@@ -10,6 +10,7 @@ This module defines the type hierarchy for the implicational logic graph:
10
10
  import hashlib
11
11
  from abc import ABC, abstractmethod
12
12
  from functools import cached_property
13
+ from typing import List
13
14
 
14
15
  from pydantic import BaseModel, ConfigDict, Field, field_validator
15
16
 
@@ -33,6 +34,20 @@ class BaseType(BaseModel, ABC):
33
34
  """
34
35
  pass
35
36
 
37
+ @property
38
+ @abstractmethod
39
+ def variables(self) -> List["Variable"]:
40
+ """
41
+ Return the list of all variables in this type.
42
+
43
+ This property is computed recursively on each access.
44
+ The list may contain duplicate variables if they appear multiple times.
45
+
46
+ Returns:
47
+ List[Variable]: A list of all Variable instances in this type
48
+ """
49
+ pass
50
+
36
51
  @abstractmethod
37
52
  def __str__(self) -> str:
38
53
  """String representation of the type."""
@@ -62,6 +77,18 @@ class Variable(BaseType):
62
77
  content = f"Var({self.name})"
63
78
  return hashlib.sha256(content.encode("utf-8")).hexdigest()
64
79
 
80
+ @property
81
+ def variables(self) -> List["Variable"]:
82
+ """
83
+ Return the list of variables in this type.
84
+
85
+ For a Variable, this returns a list containing only itself.
86
+
87
+ Returns:
88
+ List[Variable]: A list containing only this variable
89
+ """
90
+ return [self]
91
+
65
92
  def __str__(self) -> str:
66
93
  """Return the variable name."""
67
94
  return self.name
@@ -93,6 +120,19 @@ class Application(BaseType):
93
120
  content = f"App({self.input_type.uid},{self.output_type.uid})"
94
121
  return hashlib.sha256(content.encode("utf-8")).hexdigest()
95
122
 
123
+ @property
124
+ def variables(self) -> List[Variable]:
125
+ """
126
+ Return the list of variables in this type.
127
+
128
+ For an Application, this recursively collects all variables from
129
+ both the input_type and output_type.
130
+
131
+ Returns:
132
+ List[Variable]: A list of all variables in this application type
133
+ """
134
+ return self.input_type.variables + self.output_type.variables
135
+
96
136
  def __str__(self) -> str:
97
137
  """
98
138
  Return a human-readable string representation.
File without changes