diff --git a/.hypothesis/constants/021e69632fac0091 b/.hypothesis/constants/021e69632fac0091 new file mode 100644 index 0000000..3413e35 --- /dev/null +++ b/.hypothesis/constants/021e69632fac0091 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/export.py +# hypothesis_version: 6.151.9 + +['AdmissibilityDep', 'AtomicBlock', 'BlockIR', 'BoundaryAction', 'CanonicalGDS', 'ControlAction', 'Entity', 'Finding', 'GDSSpec', 'HierarchyNodeIR', 'InputIR', 'Interface', 'Mechanism', 'ParameterDef', 'Policy', 'Port', 'Space', 'SpaceField', 'SpecWiring', 'StateVariable', 'SystemIR', 'TransitionReadEntry', 'TransitionSignature', 'TypeDef', 'UpdateMapEntry', 'VerificationReport', 'Wire', 'WiringIR', 'admissibility', 'block', 'blockName', 'blockType', 'boundaryBlock', 'canonical', 'category', 'checkId', 'colorCode', 'compositionType', 'constrainsBoundary', 'constraint', 'controlBlock', 'depEntity', 'depVariable', 'dependsOnBlock', 'description', 'direction', 'entity', 'exitCondition', 'exportablePredicate', 'fieldName', 'fieldType', 'finding', 'formula', 'generic', 'hasBackwardIn', 'hasBackwardOut', 'hasBlock', 'hasBlockIR', 'hasChild', 'hasConstraint', 'hasDependency', 'hasEntity', 'hasField', 'hasFinding', 'hasForwardIn', 'hasForwardOut', 'hasHierarchy', 'hasInputIR', 'hasInterface', 'hasParameter', 'hasReadEntry', 'hasSpace', 'hasType', 'hasVariable', 'hasWire', 'hasWiring', 'hasWiringIR', 'hierarchy', 'input', 'inst', 'interface', 'isFeedback', 'isTemporal', 'kind', 'label', 'logic', 'lowerBound', 'mechanismBlock', 'message', 'name', 'option', 'paramType', 'parameter', 'passed', 'policyBlock', 'portName', 'preservesInvariant', 'pythonType', 'readEntity', 'readVariable', 'report', 'severity', 'signatureBackwardIn', 'signatureBackwardOut', 'signatureForwardIn', 'signatureForwardOut', 'signatureMechanism', 'source', 'sourceElement', 'sourceLabel', 'space', 'spec', 'state_var', 'symbol', 'system', 'systemName', 'target', 'transition_sig', 'type', 'typeToken', 'units', 'updatesEntity', 'updatesEntry', 'updatesVariable', 'upperBound', 'usesParameter', 'usesType', 'wireOptional', 'wireSource', 'wireSpace', 'wireTarget', 'wiring', 'wiringBlock', 'wiringType'] \ No newline at end of file diff --git a/.hypothesis/constants/0a02c611be85e11a b/.hypothesis/constants/0a02c611be85e11a new file mode 100644 index 0000000..aee30e0 --- /dev/null +++ b/.hypothesis/constants/0a02c611be85e11a @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/spec.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/0d9a027faceff8d7 b/.hypothesis/constants/0d9a027faceff8d7 new file mode 100644 index 0000000..1e8ecc5 --- /dev/null +++ b/.hypothesis/constants/0d9a027faceff8d7 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/__init__.py +# hypothesis_version: 6.151.9 + +['0.2.3', 'AgentID', 'AtomicBlock', 'Block', 'BlockIR', 'BoundaryAction', 'CanonicalGDS', 'CompositionType', 'ControlAction', 'EMPTY', 'Entity', 'FeedbackLoop', 'Finding', 'FlowDirection', 'GDSCompositionError', 'GDSError', 'GDSSpec', 'GDSTypeError', 'HasConstraints', 'HasOptions', 'HasParams', 'HierarchyNodeIR', 'IRDocument', 'IRMetadata', 'InputIR', 'Interface', 'Mechanism', 'NonNegativeFloat', 'ParallelComposition', 'ParameterDef', 'ParameterSchema', 'Policy', 'Port', 'PositiveInt', 'Probability', 'Severity', 'Space', 'SpecQuery', 'SpecWiring', 'StackComposition', 'StateMetric', 'StateVariable', 'StructuralWiring', 'SystemIR', 'TERMINAL', 'Tagged', 'TemporalLoop', 'Timestamp', 'TokenAmount', 'TransitionSignature', 'TypeDef', 'VerificationReport', 'Wire', 'Wiring', 'WiringIR', 'WiringOrigin', 'all_checks', 'check_completeness', 'check_determinism', 'check_reachability', 'check_type_safety', 'compile_system', 'entity', 'extract_hierarchy', 'extract_wirings', 'flatten_blocks', 'gds_check', 'get_custom_checks', 'interface', 'load_ir', 'port', 'project_canonical', 'sanitize_id', 'save_ir', 'space', 'spec_to_dict', 'spec_to_json', 'state_var', 'tokenize', 'tokens_overlap', 'tokens_subset', 'typedef', 'verify'] \ No newline at end of file diff --git a/.hypothesis/constants/0f2d12dedcb06219 b/.hypothesis/constants/0f2d12dedcb06219 new file mode 100644 index 0000000..be3a99f --- /dev/null +++ b/.hypothesis/constants/0f2d12dedcb06219 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/_namespace.py +# hypothesis_version: 6.151.9 + +['gds', 'gds-core', 'gds-ir', 'gds-verif'] \ No newline at end of file diff --git a/.hypothesis/constants/1560a51ac9c66efb b/.hypothesis/constants/1560a51ac9c66efb new file mode 100644 index 0000000..5fa8c3f --- /dev/null +++ b/.hypothesis/constants/1560a51ac9c66efb @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/ir/serialization.py +# hypothesis_version: 6.151.9 + +['0.2.1', '1.0'] \ No newline at end of file diff --git a/.hypothesis/constants/19377a3a38683453 b/.hypothesis/constants/19377a3a38683453 new file mode 100644 index 0000000..2a1a08b --- /dev/null +++ b/.hypothesis/constants/19377a3a38683453 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/query.py +# hypothesis_version: 6.151.9 + +['boundary', 'control', 'generic', 'kind', 'mechanism', 'policy'] \ No newline at end of file diff --git a/.hypothesis/constants/1e5ba8804462cfbf b/.hypothesis/constants/1e5ba8804462cfbf new file mode 100644 index 0000000..5076d91 --- /dev/null +++ b/.hypothesis/constants/1e5ba8804462cfbf @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/types/typedef.py +# hypothesis_version: 6.151.9 + +[0.0, 1.0, 'A value in [0, 1]', 'AgentID', 'NonNegativeFloat', 'PositiveInt', 'Probability', 'Timestamp', 'TokenAmount', 'seconds', 'tokens'] \ No newline at end of file diff --git a/.hypothesis/constants/2442a95b57e46661 b/.hypothesis/constants/2442a95b57e46661 new file mode 100644 index 0000000..374f5f8 --- /dev/null +++ b/.hypothesis/constants/2442a95b57e46661 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/compiler/__init__.py +# hypothesis_version: 6.151.9 + +['StructuralWiring', 'WiringOrigin', 'compile_system', 'extract_hierarchy', 'extract_wirings', 'flatten_blocks'] \ No newline at end of file diff --git a/.hypothesis/constants/2a846dbd39b70828 b/.hypothesis/constants/2a846dbd39b70828 new file mode 100644 index 0000000..d212106 --- /dev/null +++ b/.hypothesis/constants/2a846dbd39b70828 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/.venv/bin/pytest +# hypothesis_version: 6.151.9 + +['-script.pyw', '.exe', '__main__'] \ No newline at end of file diff --git a/.hypothesis/constants/2ca0f2b446657c76 b/.hypothesis/constants/2ca0f2b446657c76 new file mode 100644 index 0000000..413af79 --- /dev/null +++ b/.hypothesis/constants/2ca0f2b446657c76 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/shacl.py +# hypothesis_version: 6.151.9 + +['AtomicBlock', 'BlockIR', 'BlockIRShape', 'BoundaryAction', 'BoundaryActionShape', 'Entity', 'EntityShape', 'Finding', 'FindingShape', 'GDSSpec', 'GDSSpecShape', 'Mechanism', 'MechanismShape', 'ParameterDef', 'Policy', 'PolicyShape', 'SC005ParamRefShape', 'Space', 'SpaceShape', 'SystemIR', 'SystemIRShape', 'TransitionSignature', 'TypeDef', 'TypeDefShape', 'WiringIR', 'WiringIRShape', 'checkId', 'class', 'constrainsBoundary', 'gds-shape', 'hasInterface', 'name', 'passed', 'pythonType', 'severity', 'sh', 'signatureMechanism', 'source', 'target', 'updatesEntry', 'usesParameter', 'xsd'] \ No newline at end of file diff --git a/.hypothesis/constants/30e5b6fa780f16d4 b/.hypothesis/constants/30e5b6fa780f16d4 new file mode 100644 index 0000000..aee30e0 --- /dev/null +++ b/.hypothesis/constants/30e5b6fa780f16d4 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/spec.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/3287dee2c51eebb5 b/.hypothesis/constants/3287dee2c51eebb5 new file mode 100644 index 0000000..44608cd --- /dev/null +++ b/.hypothesis/constants/3287dee2c51eebb5 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/types/__init__.py +# hypothesis_version: 6.151.9 + +['AgentID', 'Interface', 'NonNegativeFloat', 'Port', 'PositiveInt', 'Probability', 'Timestamp', 'TokenAmount', 'TypeDef', 'port', 'tokenize', 'tokens_overlap', 'tokens_subset'] \ No newline at end of file diff --git a/.hypothesis/constants/3a356dc3c4c1aeae b/.hypothesis/constants/3a356dc3c4c1aeae new file mode 100644 index 0000000..043ab49 --- /dev/null +++ b/.hypothesis/constants/3a356dc3c4c1aeae @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/ir/models.py +# hypothesis_version: 6.151.9 + +['[^A-Za-z0-9_]', '_', 'contravariant', 'covariant', 'dataflow', 'feedback', 'parallel', 'sequential', 'temporal'] \ No newline at end of file diff --git a/.hypothesis/constants/3a6447c305dd0a5a b/.hypothesis/constants/3a6447c305dd0a5a new file mode 100644 index 0000000..6bd8ff3 --- /dev/null +++ b/.hypothesis/constants/3a6447c305dd0a5a @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/helpers.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/3af1f6bf1e0072a9 b/.hypothesis/constants/3af1f6bf1e0072a9 new file mode 100644 index 0000000..8aa77f5 --- /dev/null +++ b/.hypothesis/constants/3af1f6bf1e0072a9 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/blocks/roles.py +# hypothesis_version: 6.151.9 + +['after', 'boundary', 'control', 'mechanism', 'policy'] \ No newline at end of file diff --git a/.hypothesis/constants/41e7702e913f3269 b/.hypothesis/constants/41e7702e913f3269 new file mode 100644 index 0000000..a6e0213 --- /dev/null +++ b/.hypothesis/constants/41e7702e913f3269 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/verification/spec_checks.py +# hypothesis_version: 6.151.9 + +['SC-001', 'SC-002', 'SC-003', 'SC-004', 'SC-005', 'SC-006', 'SC-007', 'SC-008', 'SC-009'] \ No newline at end of file diff --git a/.hypothesis/constants/47afa93e3c04db97 b/.hypothesis/constants/47afa93e3c04db97 new file mode 100644 index 0000000..2d62b59 --- /dev/null +++ b/.hypothesis/constants/47afa93e3c04db97 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/export.py +# hypothesis_version: 6.151.9 + +['AdmissibilityDep', 'AtomicBlock', 'BlockIR', 'BoundaryAction', 'CanonicalGDS', 'ControlAction', 'Entity', 'Finding', 'GDSSpec', 'HierarchyNodeIR', 'InputIR', 'Interface', 'Mechanism', 'MetricVariableEntry', 'ParameterDef', 'Policy', 'Port', 'Space', 'SpaceField', 'SpecWiring', 'StateMetric', 'StateVariable', 'SystemIR', 'TransitionReadEntry', 'TransitionSignature', 'TypeDef', 'UpdateMapEntry', 'VerificationReport', 'Wire', 'WiringIR', 'admissibility', 'block', 'blockName', 'blockType', 'boundaryBlock', 'canonical', 'category', 'checkId', 'colorCode', 'compositionType', 'constrainsBoundary', 'constraint', 'controlBlock', 'depEntity', 'depVariable', 'dependsOnBlock', 'description', 'direction', 'entity', 'exitCondition', 'exportablePredicate', 'fieldName', 'fieldType', 'finding', 'formula', 'generic', 'hasBackwardIn', 'hasBackwardOut', 'hasBlock', 'hasBlockIR', 'hasChild', 'hasConstraint', 'hasDependency', 'hasEntity', 'hasField', 'hasFinding', 'hasForwardIn', 'hasForwardOut', 'hasHierarchy', 'hasInputIR', 'hasInterface', 'hasMetricVariable', 'hasParameter', 'hasReadEntry', 'hasSpace', 'hasStateMetric', 'hasType', 'hasVariable', 'hasWire', 'hasWiring', 'hasWiringIR', 'hierarchy', 'input', 'inst', 'interface', 'isFeedback', 'isTemporal', 'kind', 'label', 'logic', 'lowerBound', 'mechanismBlock', 'message', 'metricEntity', 'metricHasDistance', 'metricType', 'metricVariable', 'name', 'option', 'paramType', 'parameter', 'passed', 'policyBlock', 'portName', 'preservesInvariant', 'pythonType', 'readEntity', 'readVariable', 'report', 'severity', 'signatureBackwardIn', 'signatureBackwardOut', 'signatureForwardIn', 'signatureForwardOut', 'signatureMechanism', 'source', 'sourceElement', 'sourceLabel', 'space', 'spec', 'state_metric', 'state_var', 'symbol', 'system', 'systemName', 'target', 'transition_sig', 'type', 'typeToken', 'units', 'updatesEntity', 'updatesEntry', 'updatesVariable', 'upperBound', 'usesParameter', 'usesType', 'wireOptional', 'wireSource', 'wireSpace', 'wireTarget', 'wiring', 'wiringBlock', 'wiringType'] \ No newline at end of file diff --git a/.hypothesis/constants/49ec8bc0e2c1e8bf b/.hypothesis/constants/49ec8bc0e2c1e8bf new file mode 100644 index 0000000..4dbd0e3 --- /dev/null +++ b/.hypothesis/constants/49ec8bc0e2c1e8bf @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/state.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/4b4bd64e908536e9 b/.hypothesis/constants/4b4bd64e908536e9 new file mode 100644 index 0000000..eea7d2c --- /dev/null +++ b/.hypothesis/constants/4b4bd64e908536e9 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/tagged.py +# hypothesis_version: 6.151.9 + +['tags'] \ No newline at end of file diff --git a/.hypothesis/constants/4cb330c12c1cf3b9 b/.hypothesis/constants/4cb330c12c1cf3b9 new file mode 100644 index 0000000..95c35a7 --- /dev/null +++ b/.hypothesis/constants/4cb330c12c1cf3b9 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/blocks/composition.py +# hypothesis_version: 6.151.9 + +['after'] \ No newline at end of file diff --git a/.hypothesis/constants/4d01b94ed49cf45f b/.hypothesis/constants/4d01b94ed49cf45f new file mode 100644 index 0000000..46edcea --- /dev/null +++ b/.hypothesis/constants/4d01b94ed49cf45f @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/__init__.py +# hypothesis_version: 6.151.9 + +['0.1.0', 'GDS', 'GDS_CORE', 'GDS_IR', 'GDS_VERIF', 'PREFIXES', 'TEMPLATES', 'build_all_shapes', 'build_core_ontology', 'build_generic_shapes', 'canonical_to_graph', 'canonical_to_turtle', 'graph_to_canonical', 'graph_to_report', 'graph_to_spec', 'graph_to_system_ir', 'report_to_graph', 'report_to_turtle', 'run_query', 'spec_to_graph', 'spec_to_turtle', 'system_ir_to_graph', 'system_ir_to_turtle', 'to_jsonld', 'to_ntriples', 'to_turtle', 'validate_graph'] \ No newline at end of file diff --git a/.hypothesis/constants/539a07d14ba9c631 b/.hypothesis/constants/539a07d14ba9c631 new file mode 100644 index 0000000..c04e2fe --- /dev/null +++ b/.hypothesis/constants/539a07d14ba9c631 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/blocks/__init__.py +# hypothesis_version: 6.151.9 + +['AtomicBlock', 'Block', 'BoundaryAction', 'ControlAction', 'FeedbackLoop', 'GDSCompositionError', 'GDSError', 'GDSTypeError', 'Mechanism', 'ParallelComposition', 'Policy', 'StackComposition', 'TemporalLoop', 'Wiring'] \ No newline at end of file diff --git a/.hypothesis/constants/5778a306b1e17786 b/.hypothesis/constants/5778a306b1e17786 new file mode 100644 index 0000000..b13f27d --- /dev/null +++ b/.hypothesis/constants/5778a306b1e17786 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/blocks/errors.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/5c5ddce1df04a26c b/.hypothesis/constants/5c5ddce1df04a26c new file mode 100644 index 0000000..7ba6084 --- /dev/null +++ b/.hypothesis/constants/5c5ddce1df04a26c @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/verification/generic_checks.py +# hypothesis_version: 6.151.9 + +[' — MISMATCH', ' — type mismatch', 'G-001', 'G-002', 'G-003', 'G-004', 'G-005', 'G-006', 'boundary', 'no inputs', 'no outputs'] \ No newline at end of file diff --git a/.hypothesis/constants/5ef2b46dafb2642c b/.hypothesis/constants/5ef2b46dafb2642c new file mode 100644 index 0000000..78cb9f2 --- /dev/null +++ b/.hypothesis/constants/5ef2b46dafb2642c @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/ir/__init__.py +# hypothesis_version: 6.151.9 + +['BlockIR', 'CompositionType', 'FlowDirection', 'HierarchyNodeIR', 'SystemIR', 'WiringIR'] \ No newline at end of file diff --git a/.hypothesis/constants/6002e064d9837fb8 b/.hypothesis/constants/6002e064d9837fb8 new file mode 100644 index 0000000..39d628e --- /dev/null +++ b/.hypothesis/constants/6002e064d9837fb8 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/verification/__init__.py +# hypothesis_version: 6.151.9 + +['Finding', 'Severity', 'VerificationReport', 'check_completeness', 'check_determinism', 'check_reachability', 'check_type_safety', 'verify'] \ No newline at end of file diff --git a/.hypothesis/constants/66aa6126fffe2c48 b/.hypothesis/constants/66aa6126fffe2c48 new file mode 100644 index 0000000..2d62b59 --- /dev/null +++ b/.hypothesis/constants/66aa6126fffe2c48 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/export.py +# hypothesis_version: 6.151.9 + +['AdmissibilityDep', 'AtomicBlock', 'BlockIR', 'BoundaryAction', 'CanonicalGDS', 'ControlAction', 'Entity', 'Finding', 'GDSSpec', 'HierarchyNodeIR', 'InputIR', 'Interface', 'Mechanism', 'MetricVariableEntry', 'ParameterDef', 'Policy', 'Port', 'Space', 'SpaceField', 'SpecWiring', 'StateMetric', 'StateVariable', 'SystemIR', 'TransitionReadEntry', 'TransitionSignature', 'TypeDef', 'UpdateMapEntry', 'VerificationReport', 'Wire', 'WiringIR', 'admissibility', 'block', 'blockName', 'blockType', 'boundaryBlock', 'canonical', 'category', 'checkId', 'colorCode', 'compositionType', 'constrainsBoundary', 'constraint', 'controlBlock', 'depEntity', 'depVariable', 'dependsOnBlock', 'description', 'direction', 'entity', 'exitCondition', 'exportablePredicate', 'fieldName', 'fieldType', 'finding', 'formula', 'generic', 'hasBackwardIn', 'hasBackwardOut', 'hasBlock', 'hasBlockIR', 'hasChild', 'hasConstraint', 'hasDependency', 'hasEntity', 'hasField', 'hasFinding', 'hasForwardIn', 'hasForwardOut', 'hasHierarchy', 'hasInputIR', 'hasInterface', 'hasMetricVariable', 'hasParameter', 'hasReadEntry', 'hasSpace', 'hasStateMetric', 'hasType', 'hasVariable', 'hasWire', 'hasWiring', 'hasWiringIR', 'hierarchy', 'input', 'inst', 'interface', 'isFeedback', 'isTemporal', 'kind', 'label', 'logic', 'lowerBound', 'mechanismBlock', 'message', 'metricEntity', 'metricHasDistance', 'metricType', 'metricVariable', 'name', 'option', 'paramType', 'parameter', 'passed', 'policyBlock', 'portName', 'preservesInvariant', 'pythonType', 'readEntity', 'readVariable', 'report', 'severity', 'signatureBackwardIn', 'signatureBackwardOut', 'signatureForwardIn', 'signatureForwardOut', 'signatureMechanism', 'source', 'sourceElement', 'sourceLabel', 'space', 'spec', 'state_metric', 'state_var', 'symbol', 'system', 'systemName', 'target', 'transition_sig', 'type', 'typeToken', 'units', 'updatesEntity', 'updatesEntry', 'updatesVariable', 'upperBound', 'usesParameter', 'usesType', 'wireOptional', 'wireSource', 'wireSpace', 'wireTarget', 'wiring', 'wiringBlock', 'wiringType'] \ No newline at end of file diff --git a/.hypothesis/constants/720bb2d392c98802 b/.hypothesis/constants/720bb2d392c98802 new file mode 100644 index 0000000..bedb8c5 --- /dev/null +++ b/.hypothesis/constants/720bb2d392c98802 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/serialize.py +# hypothesis_version: 6.151.9 + +['blocks', 'boundary_block', 'bounds', 'constraints', 'depends_on', 'depends_on_blocks', 'description', 'entities', 'generic', 'has_constraint', 'kind', 'mechanism', 'name', 'optional', 'options', 'parameters', 'params_used', 'preserves_invariant', 'python_type', 'reads', 'schema', 'source', 'space', 'spaces', 'symbol', 'target', 'type', 'typedef', 'types', 'units', 'updates', 'variables', 'wires', 'wirings'] \ No newline at end of file diff --git a/.hypothesis/constants/7c2096717ac2bba5 b/.hypothesis/constants/7c2096717ac2bba5 new file mode 100644 index 0000000..6665ace --- /dev/null +++ b/.hypothesis/constants/7c2096717ac2bba5 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/types/interface.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/7da98be9df90a320 b/.hypothesis/constants/7da98be9df90a320 new file mode 100644 index 0000000..2e9d0a8 --- /dev/null +++ b/.hypothesis/constants/7da98be9df90a320 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/verification/engine.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/7fd7ee5dc4ea2ae8 b/.hypothesis/constants/7fd7ee5dc4ea2ae8 new file mode 100644 index 0000000..2264e69 --- /dev/null +++ b/.hypothesis/constants/7fd7ee5dc4ea2ae8 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/compiler/compile.py +# hypothesis_version: 6.151.9 + +[' + ', 'auto', 'children', 'explicit', 'feedback', 'forward_in', 'forward_out', 'kind', 'temporal'] \ No newline at end of file diff --git a/.hypothesis/constants/8a7e4acf124a93cd b/.hypothesis/constants/8a7e4acf124a93cd new file mode 100644 index 0000000..c7cbc24 --- /dev/null +++ b/.hypothesis/constants/8a7e4acf124a93cd @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/ontology.py +# hypothesis_version: 6.151.9 + +['AdmissibilityDep', 'Atomic Block', 'AtomicBlock', 'Block', 'Block IR', 'BlockIR', 'Boundary Action', 'BoundaryAction', 'Canonical GDS', 'CanonicalGDS', 'Control Action', 'ControlAction', 'Entity', 'Feedback Loop', 'FeedbackLoop', 'Finding', 'GDS Specification', 'GDSSpec', 'Hierarchy Node IR', 'HierarchyNodeIR', 'Input IR', 'InputIR', 'Interface', 'Mechanism', 'Parallel Composition', 'ParallelComposition', 'Parameter Definition', 'ParameterDef', 'Policy', 'Port', 'Space', 'Space Field', 'SpaceField', 'Spec Wiring', 'SpecWiring', 'Stack Composition', 'StackComposition', 'State Variable', 'StateVariable', 'System IR', 'SystemIR', 'Temporal Loop', 'TemporalLoop', 'Top-level flat IR', 'Transition Signature', 'TransitionReadEntry', 'TransitionSignature', 'Type Definition', 'TypeDef', 'Update Map Entry', 'UpdateMapEntry', 'Verification Report', 'VerificationReport', 'Wire', 'Wiring IR', 'WiringIR', 'blockName', 'blockType', 'boolean', 'boundaryBlock', 'category', 'checkId', 'colorCode', 'compositionType', 'constrainsBoundary', 'constraint', 'controlBlock', 'depEntity', 'depVariable', 'dependsOnBlock', 'description', 'direction', 'exitCondition', 'exportablePredicate', 'fieldName', 'fieldType', 'first', 'formula', 'hasBackwardIn', 'hasBackwardOut', 'hasBlock', 'hasBlockIR', 'hasCanonical', 'hasChild', 'hasConstraint', 'hasDependency', 'hasEntity', 'hasField', 'hasFinding', 'hasForwardIn', 'hasForwardOut', 'hasHierarchy', 'hasInputIR', 'hasInterface', 'hasParameter', 'hasReadEntry', 'hasSpace', 'hasType', 'hasVariable', 'hasWire', 'hasWiring', 'hasWiringIR', 'inner', 'integer', 'isFeedback', 'isTemporal', 'kind', 'label', 'left', 'logic', 'lowerBound', 'mechanismBlock', 'message', 'name', 'ontology', 'option', 'owl', 'paramType', 'passed', 'policyBlock', 'portName', 'preservesInvariant', 'pythonType', 'rdfs', 'readEntity', 'readVariable', 'right', 'second', 'severity', 'signatureBackwardIn', 'signatureBackwardOut', 'signatureForwardIn', 'signatureForwardOut', 'signatureMechanism', 'source', 'sourceElement', 'sourceLabel', 'string', 'symbol', 'systemName', 'target', 'typeToken', 'units', 'updatesEntity', 'updatesEntry', 'updatesVariable', 'upperBound', 'usesParameter', 'usesType', 'wireOptional', 'wireSource', 'wireSpace', 'wireTarget', 'wiringBlock', 'wiringType', 'xsd'] \ No newline at end of file diff --git a/.hypothesis/constants/8ad6b25a6bd095e8 b/.hypothesis/constants/8ad6b25a6bd095e8 new file mode 100644 index 0000000..82eb359 --- /dev/null +++ b/.hypothesis/constants/8ad6b25a6bd095e8 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/parameters.py +# hypothesis_version: 6.151.9 + +['after', 'parameters'] \ No newline at end of file diff --git a/.hypothesis/constants/8e319d735c5b99ac b/.hypothesis/constants/8e319d735c5b99ac new file mode 100644 index 0000000..df734ba --- /dev/null +++ b/.hypothesis/constants/8e319d735c5b99ac @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/verification/findings.py +# hypothesis_version: 6.151.9 + +['error', 'info', 'warning'] \ No newline at end of file diff --git a/.hypothesis/constants/8f29e7654bf98441 b/.hypothesis/constants/8f29e7654bf98441 new file mode 100644 index 0000000..aee30e0 --- /dev/null +++ b/.hypothesis/constants/8f29e7654bf98441 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/spec.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/a3ac6bf68fb7c3f0 b/.hypothesis/constants/a3ac6bf68fb7c3f0 new file mode 100644 index 0000000..3e8877f --- /dev/null +++ b/.hypothesis/constants/a3ac6bf68fb7c3f0 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/canonical.py +# hypothesis_version: 6.151.9 + +['f', 'f ∘ g', 'f_θ', 'g', 'g_θ', 'id'] \ No newline at end of file diff --git a/.hypothesis/constants/a8dcac1926006e66 b/.hypothesis/constants/a8dcac1926006e66 new file mode 100644 index 0000000..e025e79 --- /dev/null +++ b/.hypothesis/constants/a8dcac1926006e66 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/constraints.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/b7262b122a557242 b/.hypothesis/constants/b7262b122a557242 new file mode 100644 index 0000000..310ba06 --- /dev/null +++ b/.hypothesis/constants/b7262b122a557242 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/import_.py +# hypothesis_version: 6.151.9 + +['.', '/', '1', 'CanonicalGDS', 'GDSSpec', 'SystemIR', 'TypeDef', 'VerificationReport', 'blockName', 'blockType', 'bool', 'boundary', 'boundaryBlock', 'bytes', 'category', 'checkId', 'colorCode', 'complex', 'compositionType', 'constraint', 'controlBlock', 'dataflow', 'depEntity', 'depVariable', 'dependsOnBlock', 'description', 'dict', 'direction', 'exitCondition', 'exportablePredicate', 'fieldName', 'fieldType', 'float', 'hasBackwardIn', 'hasBackwardOut', 'hasBlock', 'hasBlockIR', 'hasChild', 'hasDependency', 'hasEntity', 'hasField', 'hasFinding', 'hasForwardIn', 'hasForwardOut', 'hasHierarchy', 'hasInputIR', 'hasInterface', 'hasMetricVariable', 'hasParameter', 'hasReadEntry', 'hasSpace', 'hasStateMetric', 'hasType', 'hasVariable', 'hasWire', 'hasWiring', 'hasWiringIR', 'int', 'isFeedback', 'isTemporal', 'kind', 'label', 'list', 'logic', 'mechanism', 'mechanismBlock', 'message', 'metricEntity', 'metricType', 'metricVariable', 'name', 'option', 'paramType', 'passed', 'policy', 'policyBlock', 'portName', 'preservesInvariant', 'pythonType', 'python_type', 'readEntity', 'readVariable', 'set', 'severity', 'signatureBackwardIn', 'signatureBackwardOut', 'signatureForwardIn', 'signatureForwardOut', 'signatureMechanism', 'source', 'sourceElement', 'sourceLabel', 'str', 'symbol', 'systemName', 'target', 'true', 'tuple', 'units', 'unknown', 'updatesEntity', 'updatesEntry', 'updatesVariable', 'usesParameter', 'usesType', 'wireOptional', 'wireSource', 'wireSpace', 'wireTarget', 'wiringBlock', 'wiringType'] \ No newline at end of file diff --git a/.hypothesis/constants/bf856ca69f9a345f b/.hypothesis/constants/bf856ca69f9a345f new file mode 100644 index 0000000..31eba75 --- /dev/null +++ b/.hypothesis/constants/bf856ca69f9a345f @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/serialize.py +# hypothesis_version: 6.151.9 + +['json-ld', 'nt', 'turtle'] \ No newline at end of file diff --git a/.hypothesis/constants/d342168950488c1c b/.hypothesis/constants/d342168950488c1c new file mode 100644 index 0000000..a6be89d --- /dev/null +++ b/.hypothesis/constants/d342168950488c1c @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/blocks/base.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/d8bcc210fb778f34 b/.hypothesis/constants/d8bcc210fb778f34 new file mode 100644 index 0000000..b150b65 --- /dev/null +++ b/.hypothesis/constants/d8bcc210fb778f34 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/__init__.py +# hypothesis_version: 6.151.9 + +['0.2.3', 'AgentID', 'AtomicBlock', 'Block', 'BlockIR', 'BoundaryAction', 'CanonicalGDS', 'CompositionType', 'ControlAction', 'EMPTY', 'Entity', 'FeedbackLoop', 'Finding', 'FlowDirection', 'GDSCompositionError', 'GDSError', 'GDSSpec', 'GDSTypeError', 'HasConstraints', 'HasOptions', 'HasParams', 'HierarchyNodeIR', 'IRDocument', 'IRMetadata', 'InputIR', 'Interface', 'Mechanism', 'NonNegativeFloat', 'ParallelComposition', 'ParameterDef', 'ParameterSchema', 'Policy', 'Port', 'PositiveInt', 'Probability', 'Severity', 'Space', 'SpecQuery', 'SpecWiring', 'StackComposition', 'StateVariable', 'StructuralWiring', 'SystemIR', 'TERMINAL', 'Tagged', 'TemporalLoop', 'Timestamp', 'TokenAmount', 'TransitionSignature', 'TypeDef', 'VerificationReport', 'Wire', 'Wiring', 'WiringIR', 'WiringOrigin', 'all_checks', 'check_completeness', 'check_determinism', 'check_reachability', 'check_type_safety', 'compile_system', 'entity', 'extract_hierarchy', 'extract_wirings', 'flatten_blocks', 'gds_check', 'get_custom_checks', 'interface', 'load_ir', 'port', 'project_canonical', 'sanitize_id', 'save_ir', 'space', 'spec_to_dict', 'spec_to_json', 'state_var', 'tokenize', 'tokens_overlap', 'tokens_subset', 'typedef', 'verify'] \ No newline at end of file diff --git a/.hypothesis/constants/deca5da6fbf8bb84 b/.hypothesis/constants/deca5da6fbf8bb84 new file mode 100644 index 0000000..e025e79 --- /dev/null +++ b/.hypothesis/constants/deca5da6fbf8bb84 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/constraints.py +# hypothesis_version: 6.151.9 + +[] \ No newline at end of file diff --git a/.hypothesis/constants/ec040768b087f92e b/.hypothesis/constants/ec040768b087f92e new file mode 100644 index 0000000..a12810b --- /dev/null +++ b/.hypothesis/constants/ec040768b087f92e @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/shacl.py +# hypothesis_version: 6.151.9 + +['AtomicBlock', 'BlockIR', 'BlockIRShape', 'BoundaryAction', 'BoundaryActionShape', 'Entity', 'EntityShape', 'Finding', 'FindingShape', 'GDSSpec', 'GDSSpecShape', 'Mechanism', 'MechanismShape', 'ParameterDef', 'Policy', 'PolicyShape', 'SC005ParamRefShape', 'Space', 'SpaceShape', 'StateMetric', 'StateMetricShape', 'SystemIR', 'SystemIRShape', 'TransitionSignature', 'TypeDef', 'TypeDefShape', 'WiringIR', 'WiringIRShape', 'checkId', 'class', 'constrainsBoundary', 'gds-shape', 'hasInterface', 'name', 'passed', 'pythonType', 'severity', 'sh', 'signatureMechanism', 'source', 'target', 'updatesEntry', 'usesParameter', 'xsd'] \ No newline at end of file diff --git a/.hypothesis/constants/ef32655eadbc0527 b/.hypothesis/constants/ef32655eadbc0527 new file mode 100644 index 0000000..1817f32 --- /dev/null +++ b/.hypothesis/constants/ef32655eadbc0527 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/import_.py +# hypothesis_version: 6.151.9 + +['.', '/', '1', 'CanonicalGDS', 'GDSSpec', 'SystemIR', 'TypeDef', 'VerificationReport', 'blockName', 'blockType', 'bool', 'boundary', 'boundaryBlock', 'bytes', 'category', 'checkId', 'colorCode', 'complex', 'compositionType', 'constraint', 'controlBlock', 'dataflow', 'depEntity', 'depVariable', 'dependsOnBlock', 'description', 'dict', 'direction', 'exitCondition', 'exportablePredicate', 'fieldName', 'fieldType', 'float', 'hasBackwardIn', 'hasBackwardOut', 'hasBlock', 'hasBlockIR', 'hasChild', 'hasDependency', 'hasEntity', 'hasField', 'hasFinding', 'hasForwardIn', 'hasForwardOut', 'hasHierarchy', 'hasInputIR', 'hasInterface', 'hasParameter', 'hasReadEntry', 'hasSpace', 'hasType', 'hasVariable', 'hasWire', 'hasWiring', 'hasWiringIR', 'int', 'isFeedback', 'isTemporal', 'kind', 'label', 'list', 'logic', 'mechanism', 'mechanismBlock', 'message', 'name', 'option', 'paramType', 'passed', 'policy', 'policyBlock', 'portName', 'preservesInvariant', 'pythonType', 'python_type', 'readEntity', 'readVariable', 'set', 'severity', 'signatureBackwardIn', 'signatureBackwardOut', 'signatureForwardIn', 'signatureForwardOut', 'signatureMechanism', 'source', 'sourceElement', 'sourceLabel', 'str', 'symbol', 'systemName', 'target', 'true', 'tuple', 'units', 'unknown', 'updatesEntity', 'updatesEntry', 'updatesVariable', 'usesParameter', 'usesType', 'wireOptional', 'wireSource', 'wireSpace', 'wireTarget', 'wiringBlock', 'wiringType'] \ No newline at end of file diff --git a/.hypothesis/constants/f9d48c7aa2e36b76 b/.hypothesis/constants/f9d48c7aa2e36b76 new file mode 100644 index 0000000..ea2b183 --- /dev/null +++ b/.hypothesis/constants/f9d48c7aa2e36b76 @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/types/tokens.py +# hypothesis_version: 6.151.9 + +[' + ', ', ', 'NFC'] \ No newline at end of file diff --git a/.hypothesis/constants/fbb749017b4acc6f b/.hypothesis/constants/fbb749017b4acc6f new file mode 100644 index 0000000..dff9b22 --- /dev/null +++ b/.hypothesis/constants/fbb749017b4acc6f @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-framework/gds/spaces.py +# hypothesis_version: 6.151.9 + +['empty', 'terminal'] \ No newline at end of file diff --git a/.hypothesis/constants/fd3f27174d64426f b/.hypothesis/constants/fd3f27174d64426f new file mode 100644 index 0000000..2c98fd6 --- /dev/null +++ b/.hypothesis/constants/fd3f27174d64426f @@ -0,0 +1,4 @@ +# file: /home/rohan/Documents/Github/blockscience/gds-core/packages/gds-owl/gds_owl/sparql.py +# hypothesis_version: 6.151.9 + +['blocks_by_role', 'dependency_path', 'entity_update_map', 'ir_block_list', 'ir_wiring_list', 'param_impact', 'verification_summary'] \ No newline at end of file diff --git a/.hypothesis/examples/04e6b3400353b141/b49931b7267ee947 b/.hypothesis/examples/04e6b3400353b141/b49931b7267ee947 new file mode 100644 index 0000000..fa5f012 --- /dev/null +++ b/.hypothesis/examples/04e6b3400353b141/b49931b7267ee947 @@ -0,0 +1 @@ +'q3ks3KC|ݟrV0rqDE.secondary \ No newline at end of file diff --git a/.hypothesis/examples/04e6b3400353b141/ed99f499e43388f3 b/.hypothesis/examples/04e6b3400353b141/ed99f499e43388f3 new file mode 100644 index 0000000..2f2e053 --- /dev/null +++ b/.hypothesis/examples/04e6b3400353b141/ed99f499e43388f3 @@ -0,0 +1 @@ +'q3ks3KC|ݟrV0rqDE \ No newline at end of file diff --git a/.hypothesis/examples/b49931b7267ee947/0543361dfba75a59 b/.hypothesis/examples/b49931b7267ee947/0543361dfba75a59 new file mode 100644 index 0000000..2e21197 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/0543361dfba75a59 differ diff --git a/.hypothesis/examples/b49931b7267ee947/1049ff0dc3aab43c b/.hypothesis/examples/b49931b7267ee947/1049ff0dc3aab43c new file mode 100644 index 0000000..a0ec327 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/1049ff0dc3aab43c differ diff --git a/.hypothesis/examples/b49931b7267ee947/164e1245cc26fb8d b/.hypothesis/examples/b49931b7267ee947/164e1245cc26fb8d new file mode 100644 index 0000000..2250b88 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/164e1245cc26fb8d differ diff --git a/.hypothesis/examples/b49931b7267ee947/23d1752498f95adf b/.hypothesis/examples/b49931b7267ee947/23d1752498f95adf new file mode 100644 index 0000000..02ee894 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/23d1752498f95adf differ diff --git a/.hypothesis/examples/b49931b7267ee947/266e1f058beea852 b/.hypothesis/examples/b49931b7267ee947/266e1f058beea852 new file mode 100644 index 0000000..7a08e12 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/266e1f058beea852 differ diff --git a/.hypothesis/examples/b49931b7267ee947/2b02a80e23a526e1 b/.hypothesis/examples/b49931b7267ee947/2b02a80e23a526e1 new file mode 100644 index 0000000..4222da6 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/2b02a80e23a526e1 differ diff --git a/.hypothesis/examples/b49931b7267ee947/422a60ac68f60a02 b/.hypothesis/examples/b49931b7267ee947/422a60ac68f60a02 new file mode 100644 index 0000000..b78360e Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/422a60ac68f60a02 differ diff --git a/.hypothesis/examples/b49931b7267ee947/451cd693385cf745 b/.hypothesis/examples/b49931b7267ee947/451cd693385cf745 new file mode 100644 index 0000000..5f46ae9 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/451cd693385cf745 differ diff --git a/.hypothesis/examples/b49931b7267ee947/48cc79232b67dd49 b/.hypothesis/examples/b49931b7267ee947/48cc79232b67dd49 new file mode 100644 index 0000000..a969cfd Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/48cc79232b67dd49 differ diff --git a/.hypothesis/examples/b49931b7267ee947/5b37403a3a59c4cb b/.hypothesis/examples/b49931b7267ee947/5b37403a3a59c4cb new file mode 100644 index 0000000..aa8c3d1 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/5b37403a3a59c4cb differ diff --git a/.hypothesis/examples/b49931b7267ee947/616cfafd2b4a2345 b/.hypothesis/examples/b49931b7267ee947/616cfafd2b4a2345 new file mode 100644 index 0000000..ed83395 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/616cfafd2b4a2345 differ diff --git a/.hypothesis/examples/b49931b7267ee947/64772f249cd44cfb b/.hypothesis/examples/b49931b7267ee947/64772f249cd44cfb new file mode 100644 index 0000000..aa12ae3 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/64772f249cd44cfb differ diff --git a/.hypothesis/examples/b49931b7267ee947/67f04224a33a9ece b/.hypothesis/examples/b49931b7267ee947/67f04224a33a9ece new file mode 100644 index 0000000..2622561 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/67f04224a33a9ece differ diff --git a/.hypothesis/examples/b49931b7267ee947/680511320d043c24 b/.hypothesis/examples/b49931b7267ee947/680511320d043c24 new file mode 100644 index 0000000..a164149 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/680511320d043c24 differ diff --git a/.hypothesis/examples/b49931b7267ee947/752b9801f678ac25 b/.hypothesis/examples/b49931b7267ee947/752b9801f678ac25 new file mode 100644 index 0000000..1bfc1f8 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/752b9801f678ac25 differ diff --git a/.hypothesis/examples/b49931b7267ee947/7e68e98e5d80931b b/.hypothesis/examples/b49931b7267ee947/7e68e98e5d80931b new file mode 100644 index 0000000..0a457ed Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/7e68e98e5d80931b differ diff --git a/.hypothesis/examples/b49931b7267ee947/82eb9d7c308acada b/.hypothesis/examples/b49931b7267ee947/82eb9d7c308acada new file mode 100644 index 0000000..9d66acd Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/82eb9d7c308acada differ diff --git a/.hypothesis/examples/b49931b7267ee947/895b8d8b6192d3f7 b/.hypothesis/examples/b49931b7267ee947/895b8d8b6192d3f7 new file mode 100644 index 0000000..6531b3c Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/895b8d8b6192d3f7 differ diff --git a/.hypothesis/examples/b49931b7267ee947/94aaa5209fae71ad b/.hypothesis/examples/b49931b7267ee947/94aaa5209fae71ad new file mode 100644 index 0000000..a7a5c65 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/94aaa5209fae71ad differ diff --git a/.hypothesis/examples/b49931b7267ee947/a393120ffb1191fb b/.hypothesis/examples/b49931b7267ee947/a393120ffb1191fb new file mode 100644 index 0000000..354a39e Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/a393120ffb1191fb differ diff --git a/.hypothesis/examples/b49931b7267ee947/d410d3b92129ff57 b/.hypothesis/examples/b49931b7267ee947/d410d3b92129ff57 new file mode 100644 index 0000000..886fe6d Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/d410d3b92129ff57 differ diff --git a/.hypothesis/examples/b49931b7267ee947/db24c04a00e5f4ac b/.hypothesis/examples/b49931b7267ee947/db24c04a00e5f4ac new file mode 100644 index 0000000..be1f71a Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/db24c04a00e5f4ac differ diff --git a/.hypothesis/examples/b49931b7267ee947/dcc8b45911ce99c1 b/.hypothesis/examples/b49931b7267ee947/dcc8b45911ce99c1 new file mode 100644 index 0000000..8194740 Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/dcc8b45911ce99c1 differ diff --git a/.hypothesis/examples/b49931b7267ee947/f3adc15933f970d9 b/.hypothesis/examples/b49931b7267ee947/f3adc15933f970d9 new file mode 100644 index 0000000..e6eb73f Binary files /dev/null and b/.hypothesis/examples/b49931b7267ee947/f3adc15933f970d9 differ diff --git a/.hypothesis/examples/ed99f499e43388f3/c5dcf1cf8046cee8 b/.hypothesis/examples/ed99f499e43388f3/c5dcf1cf8046cee8 new file mode 100644 index 0000000..20b8823 Binary files /dev/null and b/.hypothesis/examples/ed99f499e43388f3/c5dcf1cf8046cee8 differ diff --git a/.hypothesis/unicode_data/16.0.0/charmap.json.gz b/.hypothesis/unicode_data/16.0.0/charmap.json.gz new file mode 100644 index 0000000..751db27 Binary files /dev/null and b/.hypothesis/unicode_data/16.0.0/charmap.json.gz differ diff --git a/docs/research/journal.md b/docs/research/journal.md index 8ed4059..50710ca 100644 --- a/docs/research/journal.md +++ b/docs/research/journal.md @@ -547,3 +547,72 @@ Commit: `081cb9c` conditions that go beyond trajectory sampling. --- + +## Entry 007 — 2026-03-28 + +**Subject:** gds-analysis audits and Phase 1-2 fixes + +### Motivation + +Two independent audits (software architecture + data science methodology) +identified 9+8 findings across gds-analysis. Phase 1 and Phase 2 items +addressed in this session. + +### Software Architecture Audit (7 findings) + +| # | Finding | Severity | +|---|---|---| +| 1 | Adapter collapses topology (single StateUpdateBlock) | Documented | +| 2 | State key convention undocumented | **Fixed** | +| 3 | Phantom `spec` parameter in reachability | **Fixed** | +| 4 | No batch execution for reachability | Documented (future) | +| 5 | guarded_policy wrapping invisible | Minor (has `__wrapped__`) | +| 6 | No bridge/analysis module separation | Documented (future) | +| 7 | PBT strategies linear-only | Documented (future) | + +### Data Science Audit (8 findings) + +| # | Finding | Severity | +|---|---|---| +| 1 | No coverage guarantee for Monte Carlo reachability | **Fixed** (ReachabilityResult) | +| 2 | Euclidean distance meaningless on discrete states | Documented | +| 3 | SIR discrete-time lacks stability analysis | Low risk (1e-6 tolerance has 5 orders headroom) | +| 4 | SCC only valid for sampled graph | **Fixed** (documented caveat) | +| 5 | Unreachability by enumeration not proof | **Fixed** (exhaustive flag + comments) | +| 6 | PBT low coverage (same as SW #7) | Documented | +| 7 | No baseline for trajectory distances | Future | +| 8 | 1e-6 conservation tolerance unjustified | Low risk | + +### Fixes Applied + +**Phase 1 (commits `a304d86`):** +- Removed phantom `spec` parameter from `reachable_set()` and + `reachable_graph()` (15 call sites updated) +- Documented state key convention ("Entity.Variable") in adapter docstring +- Added multi-update Mechanism warning +- Documented exhaustive/sampled distinction in all reachability docstrings + +**Phase 2 (commits `250308b`, `4c5967e`):** +- `ReachabilityResult` dataclass: `states`, `n_samples`, `n_distinct`, + `is_exhaustive` metadata for coverage tracking +- `float_tolerance` parameter: rounds float values before fingerprinting to + absorb rounding noise (number of decimal places) +- `exhaustive=True` flag on crosswalk tests (3 inputs are provably + exhaustive for the discrete space) +- Tightened float tolerance test assertion to `assertEqual(n_distinct, 1)` + +**Prior audit fixes (commit `85e2f4a`):** +- `_step_once` strips metadata keys from state dicts +- ControlAction blocks handled by adapter +- `depends_on` projected at runtime in constraint enforcement +- Iterative Tarjan SCC (no recursion limit) +- `assert` replaced with `ValueError` in metrics +- Docstring corrections + +### Outcome + +gds-analysis now has 52 tests at 94% coverage. The API is cleaner +(no phantom parameters), the reachability results are interpretable +(coverage metadata), and the float fingerprinting is robust. + +--- diff --git a/packages/gds-analysis/gds_analysis/__init__.py b/packages/gds-analysis/gds_analysis/__init__.py index 7a88489..4c5dc0a 100644 --- a/packages/gds-analysis/gds_analysis/__init__.py +++ b/packages/gds-analysis/gds_analysis/__init__.py @@ -11,12 +11,14 @@ from gds_analysis.constraints import guarded_policy from gds_analysis.metrics import trajectory_distances from gds_analysis.reachability import ( + ReachabilityResult, configuration_space, reachable_graph, reachable_set, ) __all__ = [ + "ReachabilityResult", "configuration_space", "guarded_policy", "reachable_graph", diff --git a/packages/gds-analysis/gds_analysis/adapter.py b/packages/gds-analysis/gds_analysis/adapter.py index a960090..7b50230 100644 --- a/packages/gds-analysis/gds_analysis/adapter.py +++ b/packages/gds-analysis/gds_analysis/adapter.py @@ -6,6 +6,19 @@ Users must supply the behavioral functions (policies, SUFs) that gds-framework deliberately leaves as R3. The adapter wires them together using the structural skeleton. + +State Key Convention +-------------------- +State dict keys use the format ``"EntityName.VariableName"`` throughout +gds-analysis. This convention is used by: + +- ``_default_initial_state()`` when building initial state from entities +- ``guarded_policy()`` when projecting state to ``depends_on`` fields +- ``_extract_metric_state()`` when extracting metric variables +- ``_state_fingerprint()`` when deduplicating reached states + +SUF callables must return ``("EntityName.VariableName", value)`` tuples +matching this convention. """ from __future__ import annotations @@ -146,7 +159,21 @@ def _build_state_update_blocks( f"Missing state update function for block '{name}' (Mechanism)" ) # Key by target state variable, not block name. - # gds-sim validates that SUF dict keys exist in initial_state. + # gds-sim validates that SUF dict keys exist in + # initial_state. + # + # WARNING: if a Mechanism updates multiple variables, + # the same SUF is registered for each. The SUF must + # return the correct (key, value) for each state_key. + # gds-sim calls each SUF independently. + if len(block.updates) > 1: + warnings.warn( + f"Mechanism '{name}' updates " + f"{len(block.updates)} variables. The same " + f"SUF will be called once per variable — " + f"ensure it returns the correct key each time.", + stacklevel=3, + ) for entity_name, var_name in block.updates: state_key = f"{entity_name}.{var_name}" block_sufs[state_key] = sufs[name] diff --git a/packages/gds-analysis/gds_analysis/reachability.py b/packages/gds-analysis/gds_analysis/reachability.py index fabdc53..b8e1dfc 100644 --- a/packages/gds-analysis/gds_analysis/reachability.py +++ b/packages/gds-analysis/gds_analysis/reachability.py @@ -4,94 +4,141 @@ Given a state x, the reachable set R(x) is the set of all states reachable in one step by applying any admissible input. For discrete -input spaces, this can be computed exactly by enumeration. For -continuous spaces, Monte Carlo sampling approximates R(x). +input spaces with exhaustive enumeration, R(x) is exact. For +continuous spaces, Monte Carlo sampling approximates R(x) without +coverage guarantees. Paper Definition 4.2: X_C is the configuration space -- the largest set of mutually reachable states (largest SCC of the reachability graph). + +Note: configuration_space operates on the sampled graph, not the true +transition structure. Missing edges (unsampled inputs) may split or +merge SCCs. For discrete systems, provide exhaustive input samples. """ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from dataclasses import dataclass, field +from typing import Any from gds_sim import Model, Simulation -if TYPE_CHECKING: - from gds import GDSSpec - _META_KEYS = frozenset({"timestep", "substep", "run", "subset"}) +@dataclass +class ReachabilityResult: + """Result of a reachable set computation with coverage metadata. + + Attributes + ---------- + states + Distinct reached states. + n_samples + Number of input samples tried. + n_distinct + Number of distinct states found. + is_exhaustive + Whether the caller declared the input samples as exhaustive. + When True, ``states`` is the exact R(x). When False, it is + a lower bound (unsampled inputs may reach additional states). + """ + + states: list[dict[str, Any]] = field(default_factory=list) + n_samples: int = 0 + n_distinct: int = 0 + is_exhaustive: bool = False + + def reachable_set( - spec: GDSSpec, model: Model, state: dict[str, Any], *, input_samples: list[dict[str, Any]], state_key: str | None = None, -) -> list[dict[str, Any]]: + exhaustive: bool = False, + float_tolerance: float | None = None, +) -> ReachabilityResult: """Compute the reachable set R(x) by running one timestep per input. Parameters ---------- - spec - GDSSpec (used for structural metadata; not directly executed). model A gds_sim.Model with policies and SUFs already wired. state The current state x from which to compute reachability. input_samples List of input dicts to try. Each dict overrides the policy - outputs for one simulation step. For BoundaryAction blocks, - these represent exogenous inputs u. + outputs for one simulation step. For discrete state spaces, + pass exhaustive inputs and set ``exhaustive=True`` for exact + R(x). For continuous spaces, results are approximate (no + coverage guarantee). state_key If provided, extract only this key from each reached state for comparison. Otherwise return full state dicts. + exhaustive + If True, declares that ``input_samples`` covers the full + input space. The result's ``is_exhaustive`` flag is set + accordingly. This is a caller assertion, not verified. + float_tolerance + If provided, round float values to this number of decimal + places before fingerprinting. This prevents distinct + fingerprints from float rounding noise. For example, + ``float_tolerance=6`` rounds to 6 decimal places. Returns ------- - List of distinct reached states (one per input sample that - produced a unique next state). + ReachabilityResult + Contains the distinct reached states plus coverage metadata. """ reached: list[dict[str, Any]] = [] seen: set[tuple[Any, ...]] = set() for sample in input_samples: next_state = _step_once(model, state, sample) - fingerprint = _state_fingerprint(next_state, state_key) + fingerprint = _state_fingerprint(next_state, state_key, float_tolerance) if fingerprint not in seen: seen.add(fingerprint) reached.append(next_state) - return reached + return ReachabilityResult( + states=reached, + n_samples=len(input_samples), + n_distinct=len(reached), + is_exhaustive=exhaustive, + ) def reachable_graph( - spec: GDSSpec, model: Model, initial_states: list[dict[str, Any]], *, input_samples: list[dict[str, Any]], max_depth: int = 1, state_key: str | None = None, + exhaustive: bool = False, + float_tolerance: float | None = None, ) -> dict[tuple[Any, ...], list[tuple[Any, ...]]]: """Build a reachability graph by BFS from initial states. Parameters ---------- - spec - GDSSpec for structural metadata. model A gds_sim.Model with policies and SUFs wired. initial_states Starting states for the BFS. input_samples Inputs to try at each state (same set applied everywhere). + For discrete systems, use exhaustive enumeration for exact + graphs. For continuous systems, results are approximate. max_depth Maximum BFS depth (number of steps from initial states). state_key Key to extract for state fingerprinting. + exhaustive + Passed through to ``reachable_set`` at each node. + float_tolerance + Passed through to ``reachable_set`` at each node. Returns ------- @@ -105,21 +152,24 @@ def reachable_graph( for _ in range(max_depth): next_frontier: list[dict[str, Any]] = [] for state in frontier: - fp = _state_fingerprint(state, state_key) + fp = _state_fingerprint(state, state_key, float_tolerance) if fp in visited: continue visited.add(fp) - neighbors = reachable_set( - spec, + result = reachable_set( model, state, input_samples=input_samples, state_key=state_key, + exhaustive=exhaustive, + float_tolerance=float_tolerance, ) - neighbor_fps = [_state_fingerprint(n, state_key) for n in neighbors] + neighbor_fps = [ + _state_fingerprint(n, state_key, float_tolerance) for n in result.states + ] graph[fp] = neighbor_fps - next_frontier.extend(neighbors) + next_frontier.extend(result.states) frontier = next_frontier if not frontier: @@ -139,6 +189,10 @@ def configuration_space( the configuration space X_C. Uses iterative Tarjan's algorithm (no recursion limit). + + Note: SCCs are only as complete as the input graph. For sampled + (non-exhaustive) graphs, missing edges may cause SCCs to be + smaller than the true configuration space. """ index_counter = 0 stack: list[tuple[Any, ...]] = [] @@ -179,7 +233,6 @@ def configuration_space( if found_unvisited: continue - # All neighbors processed — check for SCC root. if lowlink[v] == index[v]: scc: set[tuple[Any, ...]] = set() while True: @@ -191,7 +244,6 @@ def configuration_space( sccs.append(scc) work.pop() - # Update parent's lowlink. if work: parent = work[-1][0] lowlink[parent] = min(lowlink[parent], lowlink[v]) @@ -210,7 +262,6 @@ def _step_once( runs for 1 timestep, and returns the resulting state with metadata keys stripped. """ - # Strip any metadata keys from incoming state (from prior BFS steps). clean_state = {k: v for k, v in state.items() if k not in _META_KEYS} def _override_policy(st: dict, params: dict, **kw: Any) -> dict: @@ -234,15 +285,37 @@ def _override_policy(st: dict, params: dict, **kw: Any) -> dict: results = sim.run() rows = results.to_list() raw = rows[-1] if rows else dict(clean_state) - # Strip gds-sim metadata keys from the result. return {k: v for k, v in raw.items() if k not in _META_KEYS} def _state_fingerprint( state: dict[str, Any], state_key: str | None, + float_tolerance: float | None = None, ) -> tuple[Any, ...]: - """Create a hashable fingerprint of a state for deduplication.""" + """Create a hashable fingerprint of a state for deduplication. + + Parameters + ---------- + state + State dict to fingerprint. + state_key + If provided, fingerprint only this key. + float_tolerance + If provided (as number of decimal places), round float values + before fingerprinting to absorb rounding noise. + """ if state_key is not None: - return (state_key, state.get(state_key)) - return tuple(sorted((k, v) for k, v in state.items() if k not in _META_KEYS)) + val = state.get(state_key) + if float_tolerance is not None and isinstance(val, float): + val = round(val, int(float_tolerance)) + return (state_key, val) + + items = [] + for k, v in sorted(state.items()): + if k in _META_KEYS: + continue + if float_tolerance is not None and isinstance(v, float): + v = round(v, int(float_tolerance)) + items.append((k, v)) + return tuple(items) diff --git a/packages/gds-analysis/tests/test_crosswalk_integration.py b/packages/gds-analysis/tests/test_crosswalk_integration.py index ed085fe..e675dcd 100644 --- a/packages/gds-analysis/tests/test_crosswalk_integration.py +++ b/packages/gds-analysis/tests/test_crosswalk_integration.py @@ -232,31 +232,31 @@ def test_crosswalk_safety_guarantee(self) -> None: Even with bad luck (l=0), crossing at the crosswalk is safe. """ - spec, model = self._build_model(enforce=False) + _, model = self._build_model(enforce=False) state = {"Street.traffic_state": 1} # Cross at crosswalk with bad luck: still safe samples = [{"cross": 1, "safe_crossing": 1}] reached = reachable_set( - spec, model, state, input_samples=samples, state_key="Street.traffic_state", - ) + exhaustive=True, + ).states assert all(r["Street.traffic_state"] == 0 for r in reached) def test_accident_reachable_via_jaywalking(self) -> None: """Jaywalking with bad luck → Accident (-1).""" - spec, model = self._build_model(enforce=False) + _, model = self._build_model(enforce=False) state = {"Street.traffic_state": 1} samples = [{"cross": 1, "safe_crossing": 0}] reached = reachable_set( - spec, model, state, input_samples=samples, state_key="Street.traffic_state", - ) + exhaustive=True, + ).states assert any(r["Street.traffic_state"] == -1 for r in reached) def test_flowing_unreachable_from_accident(self) -> None: @@ -265,7 +265,7 @@ def test_flowing_unreachable_from_accident(self) -> None: Accident can only recover to Stopped (0), never directly to Flowing (+1). """ - spec, model = self._build_model(enforce=False) + _, model = self._build_model(enforce=False) state = {"Street.traffic_state": -1} # Try all possible inputs from Accident samples = [ @@ -274,12 +274,12 @@ def test_flowing_unreachable_from_accident(self) -> None: {"cross": 1, "safe_crossing": 0}, ] reached = reachable_set( - spec, model, state, input_samples=samples, state_key="Street.traffic_state", - ) + exhaustive=True, + ).states reached_states = {r["Street.traffic_state"] for r in reached} assert 1 not in reached_states, ( "Flowing (+1) should be unreachable from Accident (-1)" @@ -287,21 +287,21 @@ def test_flowing_unreachable_from_accident(self) -> None: def test_not_crossing_preserves_flowing(self) -> None: """Not crossing (s=0) keeps traffic Flowing (+1).""" - spec, model = self._build_model(enforce=False) + _, model = self._build_model(enforce=False) state = {"Street.traffic_state": 1} samples = [{"cross": 0, "safe_crossing": 1}] reached = reachable_set( - spec, model, state, input_samples=samples, state_key="Street.traffic_state", - ) + exhaustive=True, + ).states assert all(r["Street.traffic_state"] == 1 for r in reached) def test_all_three_states_reachable_from_flowing(self) -> None: """From Flowing, all three states are reachable.""" - spec, model = self._build_model(enforce=False) + _, model = self._build_model(enforce=False) state = {"Street.traffic_state": 1} samples = [ {"cross": 0, "safe_crossing": 1}, # → Flowing @@ -309,18 +309,18 @@ def test_all_three_states_reachable_from_flowing(self) -> None: {"cross": 1, "safe_crossing": 0}, # → Accident ] reached = reachable_set( - spec, model, state, input_samples=samples, state_key="Street.traffic_state", - ) + exhaustive=True, + ).states reached_states = {r["Street.traffic_state"] for r in reached} assert reached_states == {-1, 0, 1} def test_configuration_space_from_all_states(self) -> None: """SCCs from a 2-depth BFS starting from all three states.""" - spec, model = self._build_model(enforce=False) + _, model = self._build_model(enforce=False) samples = [ {"cross": 0, "safe_crossing": 1}, {"cross": 1, "safe_crossing": 1}, @@ -328,7 +328,6 @@ def test_configuration_space_from_all_states(self) -> None: ] initials = [{"Street.traffic_state": s} for s in [1, 0, -1]] graph = reachable_graph( - spec, model, initials, input_samples=samples, diff --git a/packages/gds-analysis/tests/test_reachability.py b/packages/gds-analysis/tests/test_reachability.py index 75f77e1..f81b406 100644 --- a/packages/gds-analysis/tests/test_reachability.py +++ b/packages/gds-analysis/tests/test_reachability.py @@ -44,12 +44,11 @@ def test_single_input(self, thermostat_spec: GDSSpec) -> None: state = {"Room.temperature": 20.0} samples = [{"command": 1.0}] reached = reachable_set( - thermostat_spec, model, state, input_samples=samples, state_key="Room.temperature", - ) + ).states assert len(reached) == 1 assert reached[0]["Room.temperature"] != 20.0 @@ -62,12 +61,11 @@ def test_multiple_inputs_distinct(self, thermostat_spec: GDSSpec) -> None: {"command": 2.0}, ] reached = reachable_set( - thermostat_spec, model, state, input_samples=samples, state_key="Room.temperature", - ) + ).states assert len(reached) == 3 def test_duplicate_inputs_deduplicated(self, thermostat_spec: GDSSpec) -> None: @@ -78,24 +76,76 @@ def test_duplicate_inputs_deduplicated(self, thermostat_spec: GDSSpec) -> None: {"command": 1.0}, ] reached = reachable_set( - thermostat_spec, model, state, input_samples=samples, state_key="Room.temperature", - ) + ).states assert len(reached) == 1 def test_empty_inputs(self, thermostat_spec: GDSSpec) -> None: model = self._make_model(thermostat_spec) reached = reachable_set( - thermostat_spec, model, {"Room.temperature": 20.0}, input_samples=[], - ) + ).states assert reached == [] + def test_result_metadata(self, thermostat_spec: GDSSpec) -> None: + """ReachabilityResult carries coverage metadata.""" + model = self._make_model(thermostat_spec) + state = {"Room.temperature": 20.0} + samples = [{"command": 0.0}, {"command": 1.0}, {"command": 1.0}] + result = reachable_set( + model, + state, + input_samples=samples, + state_key="Room.temperature", + ) + assert result.n_samples == 3 + assert result.n_distinct == 2 # duplicate deduped + assert len(result.states) == 2 + assert result.is_exhaustive is False + + def test_exhaustive_flag(self, thermostat_spec: GDSSpec) -> None: + model = self._make_model(thermostat_spec) + result = reachable_set( + model, + {"Room.temperature": 20.0}, + input_samples=[{"command": 1.0}], + exhaustive=True, + ) + assert result.is_exhaustive is True + + def test_float_tolerance(self, thermostat_spec: GDSSpec) -> None: + """Float tolerance collapses near-identical states.""" + model = self._make_model(thermostat_spec) + state = {"Room.temperature": 20.0} + # These produce slightly different floats + samples = [ + {"command": 1.0000001}, + {"command": 1.0000002}, + ] + # Without tolerance: may produce 2 distinct states + r1 = reachable_set( + model, + state, + input_samples=samples, + state_key="Room.temperature", + ) + # With tolerance=4: rounds to 4 decimal places, should collapse + r2 = reachable_set( + model, + state, + input_samples=samples, + state_key="Room.temperature", + float_tolerance=4, + ) + # Commands differ by ~1e-7; after SUF the temperature diff is + # ~1e-8. Rounding to 4 decimal places collapses them. + assert r2.n_distinct == 1 + class TestReachableGraph: def _make_model(self, spec: GDSSpec): @@ -113,7 +163,6 @@ def _make_model(self, spec: GDSSpec): def test_depth_1(self, thermostat_spec: GDSSpec) -> None: model = self._make_model(thermostat_spec) graph = reachable_graph( - thermostat_spec, model, [{"Room.temperature": 20.0}], input_samples=[{"command": 1.0}, {"command": -1.0}], @@ -125,7 +174,6 @@ def test_depth_1(self, thermostat_spec: GDSSpec) -> None: def test_depth_2_expands(self, thermostat_spec: GDSSpec) -> None: model = self._make_model(thermostat_spec) graph_1 = reachable_graph( - thermostat_spec, model, [{"Room.temperature": 20.0}], input_samples=[{"command": 1.0}], @@ -133,7 +181,6 @@ def test_depth_2_expands(self, thermostat_spec: GDSSpec) -> None: state_key="Room.temperature", ) graph_2 = reachable_graph( - thermostat_spec, model, [{"Room.temperature": 20.0}], input_samples=[{"command": 1.0}], @@ -194,7 +241,6 @@ def test_integration(self, thermostat_spec: GDSSpec) -> None: enforce_constraints=False, ) graph = reachable_graph( - thermostat_spec, model, [{"Room.temperature": 20.0}], input_samples=[ diff --git a/packages/gds-analysis/tests/test_sir_integration.py b/packages/gds-analysis/tests/test_sir_integration.py index b9c93b1..0d7b725 100644 --- a/packages/gds-analysis/tests/test_sir_integration.py +++ b/packages/gds-analysis/tests/test_sir_integration.py @@ -255,7 +255,7 @@ def test_trajectory_distances(self) -> None: def test_reachable_set(self) -> None: """R(x) from initial state with varied contact rates.""" - spec, model = self._build_sir_model(enforce_constraints=False) + _, model = self._build_sir_model(enforce_constraints=False) state = { "Susceptible.count": 999.0, "Infected.count": 1.0, @@ -266,17 +266,16 @@ def test_reachable_set(self) -> None: for c in [1.0, 5.0, 10.0, 20.0] ] reached = reachable_set( - spec, model, state, input_samples=samples, state_key="Infected.count", - ) + ).states assert len(reached) >= 1 def test_reachability_graph(self) -> None: """Build a small reachability graph from initial state.""" - spec, model = self._build_sir_model(enforce_constraints=False) + _, model = self._build_sir_model(enforce_constraints=False) initial = { "Susceptible.count": 999.0, "Infected.count": 1.0, @@ -287,7 +286,6 @@ def test_reachability_graph(self) -> None: {"delta_s": 0, "delta_i": -1, "delta_r": 1}, ] graph = reachable_graph( - spec, model, [initial], input_samples=samples, @@ -298,7 +296,7 @@ def test_reachability_graph(self) -> None: def test_configuration_space(self) -> None: """SCCs should exist in the reachability graph.""" - spec, model = self._build_sir_model(enforce_constraints=False) + _, model = self._build_sir_model(enforce_constraints=False) initial = { "Susceptible.count": 999.0, "Infected.count": 1.0, @@ -309,7 +307,6 @@ def test_configuration_space(self) -> None: {"delta_s": 0, "delta_i": -1, "delta_r": 1}, ] graph = reachable_graph( - spec, model, [initial], input_samples=samples, diff --git a/packages/gds-examples/games/crosswalk/analysis.py b/packages/gds-examples/games/crosswalk/analysis.py index 3b2722b..1db84cd 100644 --- a/packages/gds-examples/games/crosswalk/analysis.py +++ b/packages/gds-examples/games/crosswalk/analysis.py @@ -124,12 +124,11 @@ def main(): for start_state in [1, 0, -1]: state = {"Street.traffic_state": start_state} reached = reachable_set( - spec, model, state, input_samples=input_samples, state_key="Street.traffic_state", - ) + ).states reached_vals = sorted({r["Street.traffic_state"] for r in reached}) reached_names = [STATE_NAMES[v] for v in reached_vals] print(f" R({STATE_NAMES[start_state]:>8}) = {{{', '.join(reached_names)}}}") @@ -142,7 +141,6 @@ def main(): initials = [{"Street.traffic_state": s} for s in [1, 0, -1]] graph = reachable_graph( - spec, model, initials, input_samples=input_samples, @@ -209,12 +207,11 @@ def main(): print("=" * 60) # Check: Flowing unreachable from Accident? accident_reached = reachable_set( - spec, model, {"Street.traffic_state": -1}, input_samples=input_samples, state_key="Street.traffic_state", - ) + ).states accident_reachable = {r["Street.traffic_state"] for r in accident_reached} if 1 not in accident_reachable: print(" VERIFIED: Flowing (+1) is unreachable from Accident (-1) in one step.") diff --git a/packages/gds-examples/stockflow/sir_epidemic/analysis.py b/packages/gds-examples/stockflow/sir_epidemic/analysis.py index 1407f48..80ad6f9 100644 --- a/packages/gds-examples/stockflow/sir_epidemic/analysis.py +++ b/packages/gds-examples/stockflow/sir_epidemic/analysis.py @@ -196,12 +196,11 @@ def main(): for d in [0.0, 0.3, 1.0, 5.0, 10.0] ] reached = reachable_set( - spec, model, initial, input_samples=samples, state_key="Infected.count", - ) + ).states print(f" {len(reached)} distinct next states found:") for r in sorted(reached, key=lambda x: x.get("Infected.count", 0)): s = r.get("Susceptible.count", 0) diff --git a/packages/gds-games/ogs/__init__.py b/packages/gds-games/ogs/__init__.py index 32e1097..0d4bb51 100644 --- a/packages/gds-games/ogs/__init__.py +++ b/packages/gds-games/ogs/__init__.py @@ -62,3 +62,17 @@ "InputType", "CompositionType", ] + + +def __getattr__(name: str) -> object: + """Lazy import for optional equilibrium module.""" + if name in ( + "compute_nash", + "extract_payoff_matrices", + "NashResult", + "PayoffMatrices", + ): + from ogs import equilibrium + + return getattr(equilibrium, name) + raise AttributeError(f"module 'ogs' has no attribute {name!r}") diff --git a/packages/gds-games/ogs/dsl/pattern.py b/packages/gds-games/ogs/dsl/pattern.py index ec81004..623dc13 100644 --- a/packages/gds-games/ogs/dsl/pattern.py +++ b/packages/gds-games/ogs/dsl/pattern.py @@ -42,6 +42,7 @@ class TerminalCondition(BaseModel): outcome: str description: str = "" payoff_description: str = "" + payoffs: dict[str, float] = Field(default_factory=dict) class ActionSpace(BaseModel): diff --git a/packages/gds-games/ogs/equilibrium.py b/packages/gds-games/ogs/equilibrium.py new file mode 100644 index 0000000..7c8a494 --- /dev/null +++ b/packages/gds-games/ogs/equilibrium.py @@ -0,0 +1,199 @@ +"""Nash equilibrium computation for 2-player normal-form games. + +Extracts payoff matrices from PatternIR terminal conditions and +action spaces, then delegates to Nashpy for equilibrium solving. + +Requires ``nashpy`` (optional dependency):: + + uv add gds-games[nash] + +Example:: + + from ogs.equilibrium import compute_nash, extract_payoff_matrices + + matrices = extract_payoff_matrices(pattern_ir) + equilibria = compute_nash(pattern_ir) +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from ogs.ir.models import PatternIR + + +def _require_nashpy() -> None: + """Raise ImportError if nashpy is not installed.""" + try: + import nashpy # noqa: F401 + except ImportError as exc: + raise ImportError( + "nashpy is required for equilibrium computation. " + "Install with: uv add gds-games[nash]" + ) from exc + + +@dataclass(frozen=True) +class PayoffMatrices: + """Payoff matrices for a 2-player normal-form game. + + ``A[i, j]`` is player 1's payoff when player 1 plays action ``i`` + and player 2 plays action ``j``. ``B[i, j]`` is player 2's payoff. + """ + + A: Any # numpy ndarray, shape (m, n) + B: Any # numpy ndarray, shape (m, n) + player1: str + player2: str + actions1: list[str] + actions2: list[str] + + +@dataclass(frozen=True) +class NashResult: + """A Nash equilibrium in mixed strategies. + + ``sigma1[i]`` is the probability player 1 assigns to action ``i``. + ``sigma2[j]`` is the probability player 2 assigns to action ``j``. + """ + + sigma1: Any # numpy ndarray, shape (m,) + sigma2: Any # numpy ndarray, shape (n,) + player1: str + player2: str + actions1: list[str] + actions2: list[str] + + def support(self) -> tuple[list[str], list[str]]: + """Actions played with positive probability.""" + + s1 = [a for a, p in zip(self.actions1, self.sigma1, strict=True) if p > 1e-10] + s2 = [a for a, p in zip(self.actions2, self.sigma2, strict=True) if p > 1e-10] + return s1, s2 + + def expected_payoffs(self, matrices: PayoffMatrices) -> tuple[float, float]: + """Compute expected payoffs under this equilibrium.""" + + u1 = float(self.sigma1 @ matrices.A @ self.sigma2) + u2 = float(self.sigma1 @ matrices.B @ self.sigma2) + return u1, u2 + + +def extract_payoff_matrices(ir: PatternIR) -> PayoffMatrices: + """Extract payoff matrices from a PatternIR with 2 players. + + Requires: + - Exactly 2 action spaces defined + - Terminal conditions covering all joint action profiles + - Each terminal condition has numeric ``payoffs`` for both players + + Raises + ------ + ValueError + If the PatternIR does not represent a valid 2-player normal-form game. + """ + import numpy as np + + if not ir.action_spaces or len(ir.action_spaces) != 2: + msg = f"Expected exactly 2 action spaces, got {len(ir.action_spaces or [])}" + raise ValueError(msg) + + if not ir.terminal_conditions: + msg = "No terminal conditions defined" + raise ValueError(msg) + + p1_space = ir.action_spaces[0] + p2_space = ir.action_spaces[1] + player1 = p1_space.game + player2 = p2_space.game + actions1 = p1_space.actions + actions2 = p2_space.actions + + m, n = len(actions1), len(actions2) + A = np.zeros((m, n)) + B = np.zeros((m, n)) + + # Build lookup from (action1, action2) -> terminal condition + for tc in ir.terminal_conditions: + if player1 not in tc.actions or player2 not in tc.actions: + continue + a1 = tc.actions[player1] + a2 = tc.actions[player2] + if a1 not in actions1 or a2 not in actions2: + continue + + i = actions1.index(a1) + j = actions2.index(a2) + + if player1 not in tc.payoffs or player2 not in tc.payoffs: + msg = ( + f"Terminal condition '{tc.name}' missing numeric payoffs " + f"for {player1} and/or {player2}. " + f"Set payoffs={{'{player1}': ..., '{player2}': ...}}" + ) + raise ValueError(msg) + + A[i, j] = tc.payoffs[player1] + B[i, j] = tc.payoffs[player2] + + return PayoffMatrices( + A=A, + B=B, + player1=player1, + player2=player2, + actions1=actions1, + actions2=actions2, + ) + + +def compute_nash( + ir: PatternIR, + *, + method: str = "support_enumeration", +) -> list[NashResult]: + """Compute Nash equilibria for a 2-player normal-form game. + + Parameters + ---------- + ir + A PatternIR with exactly 2 action spaces and complete + terminal conditions with numeric payoffs. + method + Nashpy solver method: ``"support_enumeration"`` (default), + ``"vertex_enumeration"``, or ``"lemke_howson"``. + + Returns + ------- + List of NashResult, one per equilibrium found. + """ + _require_nashpy() + import nashpy + + matrices = extract_payoff_matrices(ir) + game = nashpy.Game(matrices.A, matrices.B) + + if method == "support_enumeration": + raw = list(game.support_enumeration()) + elif method == "vertex_enumeration": + raw = list(game.vertex_enumeration()) + elif method == "lemke_howson": + raw = [game.lemke_howson(initial_label=0)] + else: + msg = f"Unknown method '{method}'. Use 'support_enumeration', 'vertex_enumeration', or 'lemke_howson'" + raise ValueError(msg) + + results = [] + for sigma1, sigma2 in raw: + results.append( + NashResult( + sigma1=sigma1, + sigma2=sigma2, + player1=matrices.player1, + player2=matrices.player2, + actions1=matrices.actions1, + actions2=matrices.actions2, + ) + ) + return results diff --git a/packages/gds-games/pyproject.toml b/packages/gds-games/pyproject.toml index a9449b6..46ba164 100644 --- a/packages/gds-games/pyproject.toml +++ b/packages/gds-games/pyproject.toml @@ -38,6 +38,9 @@ dependencies = [ "jinja2>=3.1", ] +[project.optional-dependencies] +nash = ["nashpy>=0.0.41", "numpy>=1.26"] + [tool.uv.sources] gds-framework = { workspace = true } diff --git a/packages/gds-games/tests/test_equilibrium.py b/packages/gds-games/tests/test_equilibrium.py new file mode 100644 index 0000000..3362ed0 --- /dev/null +++ b/packages/gds-games/tests/test_equilibrium.py @@ -0,0 +1,267 @@ +"""Tests for Nash equilibrium computation.""" + +from __future__ import annotations + +import pytest + +nashpy = pytest.importorskip("nashpy") +numpy = pytest.importorskip("numpy") + +import numpy as np # noqa: E402 + +from ogs.dsl.pattern import ActionSpace, TerminalCondition # noqa: E402 +from ogs.equilibrium import ( # noqa: E402 + compute_nash, + extract_payoff_matrices, +) +from ogs.ir.models import CompositionType, PatternIR # noqa: E402 + + +def _prisoners_dilemma_ir() -> PatternIR: + """Prisoner's Dilemma: classic 2x2 game. + + Payoff matrix (row=P1, col=P2): + Cooperate Defect + Cooperate (3,3) (0,5) + Defect (5,0) (1,1) + + Unique Nash equilibrium: (Defect, Defect) with payoffs (1, 1). + """ + return PatternIR( + composition_type=CompositionType.SEQUENTIAL, + source_canvas="test", + name="prisoners_dilemma", + games=[], + flows=[], + action_spaces=[ + ActionSpace(game="Player1", actions=["Cooperate", "Defect"]), + ActionSpace(game="Player2", actions=["Cooperate", "Defect"]), + ], + terminal_conditions=[ + TerminalCondition( + name="CC", + actions={"Player1": "Cooperate", "Player2": "Cooperate"}, + outcome="mutual_cooperation", + payoffs={"Player1": 3.0, "Player2": 3.0}, + ), + TerminalCondition( + name="CD", + actions={"Player1": "Cooperate", "Player2": "Defect"}, + outcome="sucker", + payoffs={"Player1": 0.0, "Player2": 5.0}, + ), + TerminalCondition( + name="DC", + actions={"Player1": "Defect", "Player2": "Cooperate"}, + outcome="temptation", + payoffs={"Player1": 5.0, "Player2": 0.0}, + ), + TerminalCondition( + name="DD", + actions={"Player1": "Defect", "Player2": "Defect"}, + outcome="mutual_defection", + payoffs={"Player1": 1.0, "Player2": 1.0}, + ), + ], + ) + + +def _matching_pennies_ir() -> PatternIR: + """Matching Pennies: no pure NE, unique mixed NE at (0.5, 0.5). + + Heads Tails + Heads (1,-1) (-1,1) + Tails (-1,1) (1,-1) + """ + return PatternIR( + composition_type=CompositionType.SEQUENTIAL, + source_canvas="test", + name="matching_pennies", + games=[], + flows=[], + action_spaces=[ + ActionSpace(game="Row", actions=["Heads", "Tails"]), + ActionSpace(game="Col", actions=["Heads", "Tails"]), + ], + terminal_conditions=[ + TerminalCondition( + name="HH", + actions={"Row": "Heads", "Col": "Heads"}, + outcome="match_heads", + payoffs={"Row": 1.0, "Col": -1.0}, + ), + TerminalCondition( + name="HT", + actions={"Row": "Heads", "Col": "Tails"}, + outcome="mismatch", + payoffs={"Row": -1.0, "Col": 1.0}, + ), + TerminalCondition( + name="TH", + actions={"Row": "Tails", "Col": "Heads"}, + outcome="mismatch", + payoffs={"Row": -1.0, "Col": 1.0}, + ), + TerminalCondition( + name="TT", + actions={"Row": "Tails", "Col": "Tails"}, + outcome="match_tails", + payoffs={"Row": 1.0, "Col": -1.0}, + ), + ], + ) + + +def _battle_of_sexes_ir() -> PatternIR: + """Battle of the Sexes: two pure NE + one mixed NE. + + Opera Football + Opera (3,2) (0,0) + Football (0,0) (2,3) + """ + return PatternIR( + composition_type=CompositionType.SEQUENTIAL, + source_canvas="test", + name="battle_of_sexes", + games=[], + flows=[], + action_spaces=[ + ActionSpace(game="Alice", actions=["Opera", "Football"]), + ActionSpace(game="Bob", actions=["Opera", "Football"]), + ], + terminal_conditions=[ + TerminalCondition( + name="OO", + actions={"Alice": "Opera", "Bob": "Opera"}, + outcome="opera", + payoffs={"Alice": 3.0, "Bob": 2.0}, + ), + TerminalCondition( + name="OF", + actions={"Alice": "Opera", "Bob": "Football"}, + outcome="mismatch", + payoffs={"Alice": 0.0, "Bob": 0.0}, + ), + TerminalCondition( + name="FO", + actions={"Alice": "Football", "Bob": "Opera"}, + outcome="mismatch", + payoffs={"Alice": 0.0, "Bob": 0.0}, + ), + TerminalCondition( + name="FF", + actions={"Alice": "Football", "Bob": "Football"}, + outcome="football", + payoffs={"Alice": 2.0, "Bob": 3.0}, + ), + ], + ) + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +class TestExtractPayoffMatrices: + def test_prisoners_dilemma(self) -> None: + matrices = extract_payoff_matrices(_prisoners_dilemma_ir()) + assert matrices.player1 == "Player1" + assert matrices.player2 == "Player2" + assert matrices.actions1 == ["Cooperate", "Defect"] + # A[0,0]=3 (CC), A[0,1]=0 (CD), A[1,0]=5 (DC), A[1,1]=1 (DD) + assert matrices.A[0, 0] == 3.0 + assert matrices.A[0, 1] == 0.0 + assert matrices.A[1, 0] == 5.0 + assert matrices.A[1, 1] == 1.0 + + def test_symmetric_payoffs(self) -> None: + """Matching Pennies: A = -B (zero-sum).""" + matrices = extract_payoff_matrices(_matching_pennies_ir()) + np.testing.assert_array_equal(matrices.A, -matrices.B) + + def test_wrong_player_count(self) -> None: + ir = PatternIR( + composition_type=CompositionType.SEQUENTIAL, + source_canvas="test", + name="bad", + games=[], + flows=[], + action_spaces=[ActionSpace(game="P1", actions=["a"])], + ) + with pytest.raises(ValueError, match="exactly 2"): + extract_payoff_matrices(ir) + + def test_missing_payoffs(self) -> None: + ir = PatternIR( + composition_type=CompositionType.SEQUENTIAL, + source_canvas="test", + name="bad", + games=[], + flows=[], + action_spaces=[ + ActionSpace(game="P1", actions=["a"]), + ActionSpace(game="P2", actions=["b"]), + ], + terminal_conditions=[ + TerminalCondition( + name="ab", + actions={"P1": "a", "P2": "b"}, + outcome="x", + # no payoffs field + ), + ], + ) + with pytest.raises(ValueError, match="missing numeric payoffs"): + extract_payoff_matrices(ir) + + +class TestComputeNash: + def test_prisoners_dilemma_unique_ne(self) -> None: + """PD has unique NE: (Defect, Defect).""" + results = compute_nash(_prisoners_dilemma_ir()) + assert len(results) == 1 + ne = results[0] + # Pure strategy: Defect (index 1) has probability 1 + assert ne.sigma1[1] == pytest.approx(1.0) + assert ne.sigma2[1] == pytest.approx(1.0) + + def test_matching_pennies_mixed_ne(self) -> None: + """Matching Pennies: unique NE at (0.5, 0.5).""" + results = compute_nash(_matching_pennies_ir()) + assert len(results) == 1 + ne = results[0] + assert ne.sigma1[0] == pytest.approx(0.5) + assert ne.sigma1[1] == pytest.approx(0.5) + assert ne.sigma2[0] == pytest.approx(0.5) + assert ne.sigma2[1] == pytest.approx(0.5) + + def test_battle_of_sexes_multiple_ne(self) -> None: + """Battle of Sexes: 3 NE (2 pure + 1 mixed).""" + results = compute_nash(_battle_of_sexes_ir()) + assert len(results) == 3 + + def test_support(self) -> None: + """Mixed NE in Matching Pennies: both actions in support.""" + results = compute_nash(_matching_pennies_ir()) + s1, s2 = results[0].support() + assert set(s1) == {"Heads", "Tails"} + assert set(s2) == {"Heads", "Tails"} + + def test_expected_payoffs(self) -> None: + """PD NE payoffs: (1, 1).""" + ir = _prisoners_dilemma_ir() + matrices = extract_payoff_matrices(ir) + results = compute_nash(ir) + u1, u2 = results[0].expected_payoffs(matrices) + assert u1 == pytest.approx(1.0) + assert u2 == pytest.approx(1.0) + + def test_vertex_enumeration(self) -> None: + """Alternative solver method.""" + results = compute_nash(_prisoners_dilemma_ir(), method="vertex_enumeration") + assert len(results) >= 1 + + def test_unknown_method(self) -> None: + with pytest.raises(ValueError, match="Unknown method"): + compute_nash(_prisoners_dilemma_ir(), method="bogus")