diff --git a/.gitignore b/.gitignore index 0b231ca..b1c2ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ uv.lock .pytest_cache/ htmlcov/ .mypy_cache/ +.hypothesis/ # MkDocs site/ diff --git a/.hypothesis/constants/021e69632fac0091 b/.hypothesis/constants/021e69632fac0091 deleted file mode 100644 index 3413e35..0000000 --- a/.hypothesis/constants/021e69632fac0091 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index aee30e0..0000000 --- a/.hypothesis/constants/0a02c611be85e11a +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 1e8ecc5..0000000 --- a/.hypothesis/constants/0d9a027faceff8d7 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index be3a99f..0000000 --- a/.hypothesis/constants/0f2d12dedcb06219 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 5fa8c3f..0000000 --- a/.hypothesis/constants/1560a51ac9c66efb +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 2a1a08b..0000000 --- a/.hypothesis/constants/19377a3a38683453 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 5076d91..0000000 --- a/.hypothesis/constants/1e5ba8804462cfbf +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 374f5f8..0000000 --- a/.hypothesis/constants/2442a95b57e46661 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index d212106..0000000 --- a/.hypothesis/constants/2a846dbd39b70828 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 413af79..0000000 --- a/.hypothesis/constants/2ca0f2b446657c76 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index aee30e0..0000000 --- a/.hypothesis/constants/30e5b6fa780f16d4 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 44608cd..0000000 --- a/.hypothesis/constants/3287dee2c51eebb5 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 043ab49..0000000 --- a/.hypothesis/constants/3a356dc3c4c1aeae +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 6bd8ff3..0000000 --- a/.hypothesis/constants/3a6447c305dd0a5a +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 8aa77f5..0000000 --- a/.hypothesis/constants/3af1f6bf1e0072a9 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index a6e0213..0000000 --- a/.hypothesis/constants/41e7702e913f3269 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 2d62b59..0000000 --- a/.hypothesis/constants/47afa93e3c04db97 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 4dbd0e3..0000000 --- a/.hypothesis/constants/49ec8bc0e2c1e8bf +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index eea7d2c..0000000 --- a/.hypothesis/constants/4b4bd64e908536e9 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 95c35a7..0000000 --- a/.hypothesis/constants/4cb330c12c1cf3b9 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 46edcea..0000000 --- a/.hypothesis/constants/4d01b94ed49cf45f +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index c04e2fe..0000000 --- a/.hypothesis/constants/539a07d14ba9c631 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index b13f27d..0000000 --- a/.hypothesis/constants/5778a306b1e17786 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 7ba6084..0000000 --- a/.hypothesis/constants/5c5ddce1df04a26c +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 78cb9f2..0000000 --- a/.hypothesis/constants/5ef2b46dafb2642c +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 39d628e..0000000 --- a/.hypothesis/constants/6002e064d9837fb8 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 2d62b59..0000000 --- a/.hypothesis/constants/66aa6126fffe2c48 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index bedb8c5..0000000 --- a/.hypothesis/constants/720bb2d392c98802 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 6665ace..0000000 --- a/.hypothesis/constants/7c2096717ac2bba5 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 2e9d0a8..0000000 --- a/.hypothesis/constants/7da98be9df90a320 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 2264e69..0000000 --- a/.hypothesis/constants/7fd7ee5dc4ea2ae8 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index c7cbc24..0000000 --- a/.hypothesis/constants/8a7e4acf124a93cd +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 82eb359..0000000 --- a/.hypothesis/constants/8ad6b25a6bd095e8 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index df734ba..0000000 --- a/.hypothesis/constants/8e319d735c5b99ac +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index aee30e0..0000000 --- a/.hypothesis/constants/8f29e7654bf98441 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 3e8877f..0000000 --- a/.hypothesis/constants/a3ac6bf68fb7c3f0 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index e025e79..0000000 --- a/.hypothesis/constants/a8dcac1926006e66 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 310ba06..0000000 --- a/.hypothesis/constants/b7262b122a557242 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 31eba75..0000000 --- a/.hypothesis/constants/bf856ca69f9a345f +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index a6be89d..0000000 --- a/.hypothesis/constants/d342168950488c1c +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index b150b65..0000000 --- a/.hypothesis/constants/d8bcc210fb778f34 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index e025e79..0000000 --- a/.hypothesis/constants/deca5da6fbf8bb84 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index a12810b..0000000 --- a/.hypothesis/constants/ec040768b087f92e +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 1817f32..0000000 --- a/.hypothesis/constants/ef32655eadbc0527 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index ea2b183..0000000 --- a/.hypothesis/constants/f9d48c7aa2e36b76 +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index dff9b22..0000000 --- a/.hypothesis/constants/fbb749017b4acc6f +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index 2c98fd6..0000000 --- a/.hypothesis/constants/fd3f27174d64426f +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index fa5f012..0000000 --- a/.hypothesis/examples/04e6b3400353b141/b49931b7267ee947 +++ /dev/null @@ -1 +0,0 @@ -'q3ks3KC|ݟrV0rqDE.secondary \ No newline at end of file diff --git a/.hypothesis/examples/04e6b3400353b141/ed99f499e43388f3 b/.hypothesis/examples/04e6b3400353b141/ed99f499e43388f3 deleted file mode 100644 index 2f2e053..0000000 --- a/.hypothesis/examples/04e6b3400353b141/ed99f499e43388f3 +++ /dev/null @@ -1 +0,0 @@ -'q3ks3KC|ݟrV0rqDE \ No newline at end of file diff --git a/.hypothesis/examples/b49931b7267ee947/0543361dfba75a59 b/.hypothesis/examples/b49931b7267ee947/0543361dfba75a59 deleted file mode 100644 index 2e21197..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/0543361dfba75a59 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/1049ff0dc3aab43c b/.hypothesis/examples/b49931b7267ee947/1049ff0dc3aab43c deleted file mode 100644 index a0ec327..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/1049ff0dc3aab43c and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/164e1245cc26fb8d b/.hypothesis/examples/b49931b7267ee947/164e1245cc26fb8d deleted file mode 100644 index 2250b88..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/164e1245cc26fb8d and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/23d1752498f95adf b/.hypothesis/examples/b49931b7267ee947/23d1752498f95adf deleted file mode 100644 index 02ee894..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/23d1752498f95adf and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/266e1f058beea852 b/.hypothesis/examples/b49931b7267ee947/266e1f058beea852 deleted file mode 100644 index 7a08e12..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/266e1f058beea852 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/2b02a80e23a526e1 b/.hypothesis/examples/b49931b7267ee947/2b02a80e23a526e1 deleted file mode 100644 index 4222da6..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/2b02a80e23a526e1 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/422a60ac68f60a02 b/.hypothesis/examples/b49931b7267ee947/422a60ac68f60a02 deleted file mode 100644 index b78360e..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/422a60ac68f60a02 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/451cd693385cf745 b/.hypothesis/examples/b49931b7267ee947/451cd693385cf745 deleted file mode 100644 index 5f46ae9..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/451cd693385cf745 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/48cc79232b67dd49 b/.hypothesis/examples/b49931b7267ee947/48cc79232b67dd49 deleted file mode 100644 index a969cfd..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/48cc79232b67dd49 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/5b37403a3a59c4cb b/.hypothesis/examples/b49931b7267ee947/5b37403a3a59c4cb deleted file mode 100644 index aa8c3d1..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/5b37403a3a59c4cb and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/616cfafd2b4a2345 b/.hypothesis/examples/b49931b7267ee947/616cfafd2b4a2345 deleted file mode 100644 index ed83395..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/616cfafd2b4a2345 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/64772f249cd44cfb b/.hypothesis/examples/b49931b7267ee947/64772f249cd44cfb deleted file mode 100644 index aa12ae3..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/64772f249cd44cfb and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/67f04224a33a9ece b/.hypothesis/examples/b49931b7267ee947/67f04224a33a9ece deleted file mode 100644 index 2622561..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/67f04224a33a9ece and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/680511320d043c24 b/.hypothesis/examples/b49931b7267ee947/680511320d043c24 deleted file mode 100644 index a164149..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/680511320d043c24 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/752b9801f678ac25 b/.hypothesis/examples/b49931b7267ee947/752b9801f678ac25 deleted file mode 100644 index 1bfc1f8..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/752b9801f678ac25 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/7e68e98e5d80931b b/.hypothesis/examples/b49931b7267ee947/7e68e98e5d80931b deleted file mode 100644 index 0a457ed..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/7e68e98e5d80931b and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/82eb9d7c308acada b/.hypothesis/examples/b49931b7267ee947/82eb9d7c308acada deleted file mode 100644 index 9d66acd..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/82eb9d7c308acada and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/895b8d8b6192d3f7 b/.hypothesis/examples/b49931b7267ee947/895b8d8b6192d3f7 deleted file mode 100644 index 6531b3c..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/895b8d8b6192d3f7 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/94aaa5209fae71ad b/.hypothesis/examples/b49931b7267ee947/94aaa5209fae71ad deleted file mode 100644 index a7a5c65..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/94aaa5209fae71ad and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/a393120ffb1191fb b/.hypothesis/examples/b49931b7267ee947/a393120ffb1191fb deleted file mode 100644 index 354a39e..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/a393120ffb1191fb and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/d410d3b92129ff57 b/.hypothesis/examples/b49931b7267ee947/d410d3b92129ff57 deleted file mode 100644 index 886fe6d..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/d410d3b92129ff57 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/db24c04a00e5f4ac b/.hypothesis/examples/b49931b7267ee947/db24c04a00e5f4ac deleted file mode 100644 index be1f71a..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/db24c04a00e5f4ac and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/dcc8b45911ce99c1 b/.hypothesis/examples/b49931b7267ee947/dcc8b45911ce99c1 deleted file mode 100644 index 8194740..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/dcc8b45911ce99c1 and /dev/null differ diff --git a/.hypothesis/examples/b49931b7267ee947/f3adc15933f970d9 b/.hypothesis/examples/b49931b7267ee947/f3adc15933f970d9 deleted file mode 100644 index e6eb73f..0000000 Binary files a/.hypothesis/examples/b49931b7267ee947/f3adc15933f970d9 and /dev/null differ diff --git a/.hypothesis/examples/ed99f499e43388f3/c5dcf1cf8046cee8 b/.hypothesis/examples/ed99f499e43388f3/c5dcf1cf8046cee8 deleted file mode 100644 index 20b8823..0000000 Binary files a/.hypothesis/examples/ed99f499e43388f3/c5dcf1cf8046cee8 and /dev/null differ diff --git a/.hypothesis/unicode_data/16.0.0/charmap.json.gz b/.hypothesis/unicode_data/16.0.0/charmap.json.gz deleted file mode 100644 index 751db27..0000000 Binary files a/.hypothesis/unicode_data/16.0.0/charmap.json.gz and /dev/null differ diff --git a/CLAUDE.md b/CLAUDE.md index 6894cee..7eb0dc7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,20 +4,26 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project -`gds-core` — monorepo for the Generalized Dynamical Systems ecosystem. Typed compositional specifications for complex systems, grounded in [GDS theory](https://doi.org/10.57938/e8d456ea-d975-4111-ac41-052ce73cb0cc). Seven packages managed as a uv workspace. +`gds-core` — monorepo for the Generalized Dynamical Systems ecosystem. Typed compositional specifications for complex systems, grounded in [GDS theory](https://doi.org/10.57938/e8d456ea-d975-4111-ac41-052ce73cb0cc). Fourteen packages managed as a uv workspace. ## Packages -| Package | Import | Location | -|---------|--------|----------| -| gds-framework | `gds` | `packages/gds-framework/` | -| gds-viz | `gds_viz` | `packages/gds-viz/` | -| gds-games | `ogs` | `packages/gds-games/` | -| gds-stockflow | `stockflow` | `packages/gds-stockflow/` | -| gds-control | `gds_control` | `packages/gds-control/` | -| gds-software | `gds_software` | `packages/gds-software/` | -| gds-sim | `gds_sim` | `packages/gds-sim/` | -| gds-examples | — | `packages/gds-examples/` | +| Package | Import | Location | Role | +|---------|--------|----------|------| +| gds-framework | `gds` | `packages/gds-framework/` | Core engine | +| gds-viz | `gds_viz` | `packages/gds-viz/` | Mermaid + phase portraits | +| gds-games | `ogs` | `packages/gds-games/` | Game theory DSL | +| gds-stockflow | `stockflow` | `packages/gds-stockflow/` | Stock-flow DSL | +| gds-control | `gds_control` | `packages/gds-control/` | Control systems DSL | +| gds-software | `gds_software` | `packages/gds-software/` | Software architecture DSL | +| gds-business | `gds_business` | `packages/gds-business/` | Business dynamics DSL | +| gds-sim | `gds_sim` | `packages/gds-sim/` | Discrete-time simulation | +| gds-continuous | `gds_continuous` | `packages/gds-continuous/` | Continuous-time ODE engine | +| gds-symbolic | `gds_symbolic` | `packages/gds-symbolic/` | SymPy bridge for control | +| gds-analysis | `gds_analysis` | `packages/gds-analysis/` | Spec-to-sim bridge | +| gds-psuu | `gds_psuu` | `packages/gds-psuu/` | Parameter sweep + Optuna | +| gds-owl | `gds_owl` | `packages/gds-owl/` | OWL/SHACL/SPARQL export | +| gds-examples | — | `packages/gds-examples/` | Tutorials + examples | ## Commands @@ -32,14 +38,18 @@ uv run --package gds-games pytest packages/gds-games/tests -v uv run --package gds-stockflow pytest packages/gds-stockflow/tests -v uv run --package gds-control pytest packages/gds-control/tests -v uv run --package gds-software pytest packages/gds-software/tests -v -uv run --package gds-examples pytest packages/gds-examples -v +uv run --package gds-continuous pytest packages/gds-continuous/tests -v +uv run --package gds-symbolic pytest packages/gds-symbolic/tests -v +uv run --package gds-analysis pytest packages/gds-analysis/tests -v uv run --package gds-sim pytest packages/gds-sim/tests -v +uv run --package gds-owl pytest packages/gds-owl/tests -v +uv run --package gds-examples pytest packages/gds-examples -v # Run a single test uv run --package gds-framework pytest packages/gds-framework/tests/test_blocks.py::TestStackComposition::test_rshift_operator -v # Run all tests across all packages -uv run --package gds-framework pytest packages/gds-framework/tests packages/gds-viz/tests packages/gds-games/tests packages/gds-stockflow/tests packages/gds-control/tests packages/gds-software/tests packages/gds-examples packages/gds-sim/tests -v +uv run --package gds-framework pytest packages/gds-framework/tests packages/gds-viz/tests packages/gds-games/tests packages/gds-stockflow/tests packages/gds-control/tests packages/gds-software/tests packages/gds-continuous/tests packages/gds-symbolic/tests packages/gds-analysis/tests packages/gds-sim/tests packages/gds-owl/tests packages/gds-examples -v # Lint & format uv run ruff check packages/ @@ -61,17 +71,26 @@ This is a **uv workspace** monorepo. The root `pyproject.toml` declares `package ### Dependency Graph ``` -gds-framework ← core engine (no GDS dependencies) +gds-framework ← core engine (pydantic only, no upstream deps) ↑ -gds-viz ← visualization (depends on gds-framework) -gds-games ← game theory DSL (depends on gds-framework) -gds-stockflow ← stock-flow DSL (depends on gds-framework) -gds-control ← control systems DSL (depends on gds-framework) -gds-software ← software architecture DSL (depends on gds-framework) + ├── gds-viz ← Mermaid diagrams + phase portraits [matplotlib] + ├── gds-games ← game theory DSL + Nash equilibrium [nashpy] + ├── gds-stockflow ← stock-flow DSL + ├── gds-control ← control systems DSL + ├── gds-software ← software architecture DSL + ├── gds-business ← business dynamics DSL (CLD, SCN, VSM) + └── gds-owl ← OWL/SHACL/SPARQL export (rdflib, pyshacl) + ↑ + gds-symbolic ← SymPy bridge (extends gds-control) [sympy] + ↑ + gds-examples ← tutorials (depends on most DSLs + viz) + +gds-sim ← discrete-time simulation (standalone, pydantic only) ↑ -gds-examples ← tutorials (depends on gds-framework + gds-viz) + ├── gds-analysis ← spec→sim bridge, reachability (gds-framework + gds-sim) + └── gds-psuu ← parameter sweep + Optuna (gds-sim) -gds-sim ← simulation engine (standalone — no gds-framework dep, only pydantic) +gds-continuous ← continuous-time ODE engine (standalone, pydantic only) [scipy] ``` ### gds-framework: Two-Layer Design @@ -86,7 +105,7 @@ These layers are loosely coupled — you can use the composition algebra without ### Domain DSL Pattern -Four domain DSLs (stockflow, control, games, software) compile to GDS. The stockflow, control, and software packages follow a shared pattern: +Five domain DSLs (stockflow, control, games, software, business) compile to GDS. The stockflow, control, software, and business packages follow a shared pattern: 1. **Elements** — frozen Pydantic models for user-facing declarations (not GDS blocks) 2. **Model** — mutable container with `@model_validator` construction-time validation diff --git a/docs/analysis/getting-started.md b/docs/analysis/getting-started.md new file mode 100644 index 0000000..13a934e --- /dev/null +++ b/docs/analysis/getting-started.md @@ -0,0 +1,129 @@ +# Getting Started + +## Installation + +```bash +uv add gds-analysis +``` + +For development (monorepo): + +```bash +git clone https://github.com/BlockScience/gds-core.git +cd gds-core +uv sync --all-packages +``` + +## From Specification to Simulation + +The typical workflow: build a GDS specification from a domain model, supply behavioral functions, then simulate. + +```python +from gds_control import ( + State, Input, Sensor, Controller, + ControlModel, compile_model, +) +from gds_analysis import spec_to_model + +# 1. Build a GDS specification from a control model +model = ControlModel( + name="Thermostat", + states=[State(name="temperature", initial=20.0)], + inputs=[Input(name="setpoint")], + sensors=[Sensor(name="thermometer", observes=["temperature"])], + controllers=[ + Controller(name="PID", reads=["thermometer", "setpoint"], drives=["temperature"]), + ], +) +spec = compile_model(model) + +# 2. Supply behavioral functions (R3 -- not in the spec) +sim_model = spec_to_model( + spec, + policies={ + "thermometer": lambda state, params, **kw: {"reading": state["temperature"]}, + "PID": lambda state, params, **kw: { + "command": params["Kp"] * (params["setpoint"] - state["temperature"]) + }, + }, + sufs={ + "temperature": lambda state, params, signal=None, **kw: ( + "temperature", + state["temperature"] + signal["command"] * 0.1, + ), + }, + initial_state={"temperature": 20.0}, + params={"setpoint": 22.0, "Kp": 0.3}, +) + +# 3. Run via gds-sim +from gds_sim import Simulation + +results = Simulation(model=sim_model, timesteps=50, runs=1).run() +print(f"Final temperature: {results['temperature'][-1]:.1f}") +``` + +## Guarded Policies + +Enforce `AdmissibleInputConstraint` at runtime: + +```python +from gds_analysis import guarded_policy + +# Wrap a policy to enforce admissibility +safe_policy = guarded_policy( + policy_fn=my_policy, + constraint=lambda state, action: action["power"] <= state["max_power"], + fallback=lambda state, params, **kw: {"power": 0.0}, +) +``` + +If the constraint predicate returns `False`, the fallback function is called instead. Without a fallback, `ConstraintViolationError` is raised. + +## Computing Reachable Sets + +Explore what states are reachable from an initial condition: + +```python +from gds_analysis import reachable_set, reachable_graph, configuration_space + +# R(x) = set of states reachable in one step from x +reached = reachable_set( + spec, + sim_model, + state={"temperature": 20.0}, + input_samples=[ + {"command": 0.0}, + {"command": 1.0}, + {"command": -1.0}, + ], +) +print(f"Reachable states: {len(reached)}") + +# Build graph over sampled states and extract configuration space +graph = reachable_graph(spec, sim_model, states=sampled_states, input_samples=inputs) +x_c = configuration_space(graph) # largest SCC -- mutually reachable states +``` + +## Trajectory Analysis + +Measure convergence along a simulation trajectory: + +```python +from gds_analysis import trajectory_distances + +distances = trajectory_distances( + results, + metric=lambda s1, s2: abs(s1["temperature"] - s2["temperature"]), +) + +# Check convergence: distances should decrease +print(f"Initial distance: {distances[0]:.3f}") +print(f"Final distance: {distances[-1]:.3f}") +``` + +## Next Steps + +- [Analysis Overview](index.md) -- architecture and key functions +- [Framework](../framework/index.md) -- GDS specification and structural annotations +- [PSUU](../psuu/index.md) -- systematic parameter exploration over simulations diff --git a/docs/analysis/index.md b/docs/analysis/index.md new file mode 100644 index 0000000..91e6caa --- /dev/null +++ b/docs/analysis/index.md @@ -0,0 +1,72 @@ +# gds-analysis + +[![PyPI](https://img.shields.io/pypi/v/gds-analysis)](https://pypi.org/project/gds-analysis/) +[![Python](https://img.shields.io/pypi/pyversions/gds-analysis)](https://pypi.org/project/gds-analysis/) +[![License](https://img.shields.io/github/license/BlockScience/gds-core)](https://github.com/BlockScience/gds-core/blob/main/LICENSE) + +**Bridge from GDS structural specifications to runtime simulation and analysis.** + +## What is this? + +`gds-analysis` closes the gap between `gds-framework`'s structural annotations (AdmissibleInputConstraint, TransitionSignature) and `gds-sim`'s runtime engine. It provides the behavioral layer that turns verified specifications into executable models. + +- **`spec_to_model()`** -- adapter that converts a `GDSSpec` + behavioral functions into a `gds_sim.Model` +- **`guarded_policy()`** -- wraps a policy function with `AdmissibleInputConstraint` enforcement at runtime +- **`reachable_set()`** -- computes the reachable set R(x) from an initial state by exploring the transition graph (Paper Def 4.1) +- **`reachable_graph()`** -- returns the full state transition graph as an adjacency structure +- **`configuration_space()`** -- finds the largest SCC of the reachability graph (Paper Def 4.2) +- **`trajectory_distances()`** -- computes metric distances along a trajectory for convergence analysis + +## Architecture + +``` +gds-framework gds-sim +| | +| GDSSpec, entities, | Model, StateUpdateBlock, +| blocks, constraints | Simulation, Results +| | ++-------+ gds-analysis +-------+ + | | + | spec_to_model(), guarded_policy(), + | reachable_set(), trajectory_distances() + | + +-- Your application + | + | Verified specs -> executable simulations, + | reachability analysis, convergence proofs. +``` + +## Key Functions + +| Function | Input | Output | +|----------|-------|--------| +| `spec_to_model(spec, policies, sufs, ...)` | `GDSSpec` + dict of callables | `gds_sim.Model` | +| `guarded_policy(policy_fn, constraint, fallback)` | callable + predicate + fallback | guarded callable | +| `reachable_set(spec, model, state, input_samples)` | spec + model + state + inputs | `list[dict]` | +| `reachable_graph(spec, model, states, input_samples)` | spec + model + states + inputs | adjacency dict | +| `configuration_space(graph)` | adjacency dict | largest SCC | +| `trajectory_distances(results, metric)` | `Results` + distance fn | `list[float]` | + +## The Behavioral Gap + +GDS specifications are structural -- they declare *what* blocks exist, how they wire together, and what constraints hold. But they do not contain *behavioral* functions (policies, state update functions). The adapter pattern separates structural (R1) from behavioral (R3): + +- **Structural (from GDSSpec)**: block topology, wiring, role assignments, entity/variable declarations +- **Behavioral (from user)**: policy functions, state update functions, initial conditions +- **Bridge**: `spec_to_model()` wires user-supplied callables into the structural skeleton + +## Constraint Enforcement + +`guarded_policy()` wraps a policy function so that `AdmissibleInputConstraint` predicates are checked at every timestep. If a constraint is violated, the guard invokes the fallback function or raises `ConstraintViolationError` with the failing constraint's name and the offending input values. + +## Quick Start + +```bash +uv add gds-analysis +``` + +See [Getting Started](getting-started.md) for a full walkthrough. + +## Credits + +Built on [gds-framework](../framework/index.md) and [gds-sim](https://pypi.org/project/gds-sim/) by [BlockScience](https://block.science). diff --git a/docs/continuous/getting-started.md b/docs/continuous/getting-started.md new file mode 100644 index 0000000..e2749c5 --- /dev/null +++ b/docs/continuous/getting-started.md @@ -0,0 +1,133 @@ +# Getting Started + +## Installation + +```bash +uv add "gds-continuous[scipy]" +``` + +For development (monorepo): + +```bash +git clone https://github.com/BlockScience/gds-core.git +cd gds-core +uv sync --all-packages +``` + +## Your First ODE: Exponential Decay + +Model the simplest continuous-time system: exponential decay `dx/dt = -kx`. + +```python +from gds_continuous import ODEModel, ODESimulation, ODEResults + +# 1. Define the right-hand side: dx/dt = f(t, x, params) +def decay_rhs(t, state, params): + k = params["k"] + return {"x": -k * state["x"]} + +# 2. Build the model +model = ODEModel( + state_names=["x"], + initial_state={"x": 10.0}, + rhs=decay_rhs, + params={"k": 0.5}, +) + +# 3. Configure and run the simulation +sim = ODESimulation(model=model, t_span=(0.0, 10.0), solver="RK45") +results: ODEResults = sim.run() + +# 4. Inspect results +print(f"t = {results.t[-1]:.1f}, x = {results['x'][-1]:.4f}") +# t = 10.0, x = 0.0067 +``` + +## Plotting Results + +```python +import matplotlib.pyplot as plt + +plt.plot(results.t, results["x"]) +plt.xlabel("Time") +plt.ylabel("x(t)") +plt.title("Exponential Decay: dx/dt = -0.5x") +plt.grid(True) +plt.show() +``` + +## A Two-State System: Lotka-Volterra + +Model predator-prey dynamics with coupled ODEs: + +```python +from gds_continuous import ODEModel, ODESimulation + +def lotka_volterra(t, state, params): + x, y = state["prey"], state["predator"] + a, b, c, d = params["a"], params["b"], params["c"], params["d"] + return { + "prey": a * x - b * x * y, + "predator": c * x * y - d * y, + } + +model = ODEModel( + state_names=["prey", "predator"], + initial_state={"prey": 10.0, "predator": 5.0}, + rhs=lotka_volterra, + params={"a": 1.1, "b": 0.4, "c": 0.1, "d": 0.4}, +) + +sim = ODESimulation(model=model, t_span=(0.0, 50.0), solver="RK45") +results = sim.run() +``` + +## Parameter Sweep + +Compare different decay rates by running multiple simulations: + +```python +import matplotlib.pyplot as plt +from gds_continuous import ODEModel, ODESimulation + +def decay_rhs(t, state, params): + return {"x": -params["k"] * state["x"]} + +fig, ax = plt.subplots() +for k in [0.1, 0.3, 0.5, 1.0, 2.0]: + model = ODEModel( + state_names=["x"], + initial_state={"x": 10.0}, + rhs=decay_rhs, + params={"k": k}, + ) + results = ODESimulation(model=model, t_span=(0.0, 10.0), solver="RK45").run() + ax.plot(results.t, results["x"], label=f"k={k}") + +ax.set_xlabel("Time") +ax.set_ylabel("x(t)") +ax.legend() +ax.set_title("Exponential Decay: Parameter Sweep") +plt.show() +``` + +## Choosing a Solver + +For most problems, the default `RK45` works well. Switch solvers when needed: + +```python +# Stiff system -- use an implicit solver +sim = ODESimulation(model=model, t_span=(0.0, 10.0), solver="Radau") + +# Unknown stiffness -- let LSODA auto-detect +sim = ODESimulation(model=model, t_span=(0.0, 10.0), solver="LSODA") + +# High accuracy -- use DOP853 with tight tolerances +sim = ODESimulation(model=model, t_span=(0.0, 10.0), solver="DOP853", rtol=1e-10, atol=1e-12) +``` + +## Next Steps + +- [Overview](index.md) -- solver comparison table and architecture +- [Symbolic Math](../symbolic/index.md) -- generate ODE right-hand sides from symbolic equations +- [Analysis](../analysis/index.md) -- bridge GDS specifications to continuous-time simulation diff --git a/docs/continuous/index.md b/docs/continuous/index.md new file mode 100644 index 0000000..749fe14 --- /dev/null +++ b/docs/continuous/index.md @@ -0,0 +1,68 @@ +# gds-continuous + +[![PyPI](https://img.shields.io/pypi/v/gds-continuous)](https://pypi.org/project/gds-continuous/) +[![Python](https://img.shields.io/pypi/pyversions/gds-continuous)](https://pypi.org/project/gds-continuous/) +[![License](https://img.shields.io/github/license/BlockScience/gds-core)](https://github.com/BlockScience/gds-core/blob/main/LICENSE) + +**Continuous-time ODE integration engine** -- the continuous-time counterpart to `gds-sim`. + +## What is this? + +`gds-continuous` provides an ODE simulation engine for continuous-time dynamical systems. It follows the same standalone architectural pattern as `gds-sim` -- minimal dependencies, Pydantic models, columnar results -- but integrates SciPy's ODE solvers instead of discrete timestep iteration. + +- **`ODEModel`** -- declares state variables, initial conditions, and a right-hand side function `dx/dt = f(t, x, params)` +- **`ODESimulation`** -- configures time span, solver method, tolerances, and evaluation points +- **`ODEResults`** -- columnar storage of time series with named state access +- **6 solver methods** -- `RK45`, `RK23`, `DOP853`, `Radau`, `BDF`, `LSODA` (all via `scipy.integrate.solve_ivp`) +- **Zero GDS dependency** -- standalone package, same as `gds-sim` + +## Architecture + +``` +scipy + numpy (optional deps) +| ++-- gds-continuous (uv add gds-continuous[scipy]) + | + | ODE engine: ODEModel, ODESimulation, ODEResults. + | 6 solver backends via scipy.integrate.solve_ivp. + | + +-- Your application + | + | Concrete ODE models, parameter studies, + | phase portraits, trajectory analysis. +``` + +## Relationship to gds-sim + +| | gds-sim | gds-continuous | +|---|---|---| +| **Time** | Discrete timesteps | Continuous `t_span` | +| **Update rule** | `f(state, params) -> state` | `dx/dt = f(t, x, params)` | +| **Solver** | Direct iteration | SciPy `solve_ivp` | +| **Results** | `Results` (timestep-indexed) | `ODEResults` (time-indexed) | +| **Dependencies** | pydantic only | pydantic + scipy + numpy | + +Both are standalone engines with no `gds-framework` dependency. They can be used independently or bridged via `gds-analysis`. + +## Solver Methods + +| Method | Type | Best for | +|--------|------|----------| +| `RK45` | Explicit Runge-Kutta (default) | General non-stiff problems | +| `RK23` | Explicit Runge-Kutta | Low-accuracy requirements | +| `DOP853` | Explicit Runge-Kutta | High-accuracy non-stiff problems | +| `Radau` | Implicit Runge-Kutta | Stiff problems | +| `BDF` | Implicit multi-step | Stiff problems | +| `LSODA` | Automatic stiff/non-stiff | Unknown stiffness | + +## Quick Start + +```bash +uv add "gds-continuous[scipy]" +``` + +See [Getting Started](getting-started.md) for a full walkthrough. + +## Credits + +Built by [BlockScience](https://block.science). diff --git a/docs/games/equilibrium.md b/docs/games/equilibrium.md new file mode 100644 index 0000000..ba031aa --- /dev/null +++ b/docs/games/equilibrium.md @@ -0,0 +1,118 @@ +# Equilibrium Analysis + +The `ogs.equilibrium` module computes Nash equilibria for two-player normal-form games using [Nashpy](https://nashpy.readthedocs.io/). + +## Installation + +```bash +uv add "gds-games[nash]" +``` + +The `[nash]` extra installs `nashpy` and `numpy`. + +## Key Types and Functions + +| Name | Purpose | +|------|---------| +| `extract_payoff_matrices(ir)` | Extract `(A, B)` payoff matrices from a two-player `PatternIR` | +| `compute_nash(ir, method)` | Compute Nash equilibria from a compiled `PatternIR` | +| `NashResult` | Container for equilibrium strategies with `support()` and `expected_payoffs()` | + +## Solver Methods + +| Method | Algorithm | Notes | +|--------|-----------|-------| +| `"support_enumeration"` (default) | Support enumeration | Exact, finds all Nash equilibria | +| `"vertex_enumeration"` | Vertex enumeration | Alternative exact enumeration | +| `"lemke_howson"` | Lemke-Howson pivoting | Fast, returns one equilibrium | + +## Example: Prisoner's Dilemma + +Define payoffs via `TerminalCondition` entries, then compute equilibria: + +```python +from ogs.dsl.pattern import TerminalCondition + +# Prisoner's Dilemma payoff structure +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}, + ), +] +``` + +Extract matrices and solve: + +```python +from ogs.equilibrium import extract_payoff_matrices, compute_nash + +# Extract payoff matrices from a compiled PatternIR +matrices = extract_payoff_matrices(pattern_ir) +print(matrices.A) # Player 1's payoff matrix +print(matrices.B) # Player 2's payoff matrix + +# Find Nash equilibria +equilibria = compute_nash(pattern_ir) +for ne in equilibria: + print(f"Player 1: {dict(zip(ne.actions1, ne.sigma1))}") + print(f"Player 2: {dict(zip(ne.actions2, ne.sigma2))}") + print(f"Support: {ne.support()}") + print(f"Expected payoffs: {ne.expected_payoffs(matrices)}") +``` + +## NashResult + +Each `NashResult` contains: + +- **`sigma1`** / **`sigma2`** -- mixed strategy vectors (numpy arrays) +- **`actions1`** / **`actions2`** -- action labels corresponding to each strategy index +- **`support()`** -- returns the set of actions played with positive probability for each player +- **`expected_payoffs(matrices)`** -- computes `(E[payoff1], E[payoff2])` under the equilibrium strategies + +## Direct Matrix Input + +You can also bypass IR extraction and supply payoff matrices directly: + +```python +import numpy as np +from ogs.equilibrium import compute_nash_from_matrices + +A = np.array([[3, 0], [5, 1]]) # Player 1 payoffs +B = np.array([[3, 5], [0, 1]]) # Player 2 payoffs + +equilibria = compute_nash_from_matrices(A, B, method="support_enumeration") +``` + +## Limitations + +- **2-player only** -- games with more than 2 action spaces raise `ValueError` +- **Complete information** -- all joint action profiles must have numeric payoffs +- **Normal form** -- extensive-form games must be converted to normal form first +- **Numerical precision** -- mixed strategy equilibria may have floating-point rounding + +## Next Steps + +- [Game Types](guide/game-types.md) -- all 6 atomic game types +- [Patterns & Composition](guide/patterns.md) -- composing complex multi-player games +- [Getting Started](getting-started.md) -- basic game definition workflow diff --git a/docs/index.md b/docs/index.md index 0a6a9bc..eafa59f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,36 +27,65 @@ Key guides include embedded [marimo](https://marimo.io) notebooks — run code, ## Packages -| PyPI Package | Import Name | Description | +Install just what you need: `uv add gds-core[control,continuous]` + +### Structural Specification + +| Package | Import | Description | |---|---|---| -| `gds-framework` | `gds` | Core engine — blocks, composition algebra, compiler, verification | -| `gds-viz` | `gds_viz` | Mermaid diagram renderers for GDS specifications | -| `gds-stockflow` | `stockflow` | Declarative stock-flow DSL over GDS semantics | -| `gds-control` | `gds_control` | State-space control DSL over GDS semantics | -| `gds-games` | `ogs` | Typed DSL for compositional game theory (Open Games) | -| `gds-software` | `gds_software` | Software architecture DSL (DFD, state machine, C4, ERD, etc.) | -| `gds-business` | `gds_business` | Business dynamics DSL (CLD, supply chain, value stream map) | -| `gds-sim` | `gds_sim` | Simulation engine — Model, Simulation, Results | -| `gds-psuu` | `gds_psuu` | Parameter space search under uncertainty for gds-sim | -| `gds-examples` | — | Tutorial models demonstrating framework features | +| [`gds-framework`](framework/index.md) | `gds` | Core engine -- composition algebra, compiler, verification | +| [`gds-viz`](viz/index.md) | `gds_viz` | Mermaid diagrams + [phase portraits](viz/index.md) `[phase]` | +| [`gds-owl`](owl/index.md) | `gds_owl` | OWL/SHACL/SPARQL export for formal representability | + +### Domain DSLs + +| Package | Import | Description | +|---|---|---| +| [`gds-stockflow`](stockflow/index.md) | `stockflow` | Declarative stock-flow DSL | +| [`gds-control`](control/index.md) | `gds_control` | State-space control DSL | +| [`gds-games`](games/index.md) | `ogs` | Compositional game theory + [Nash equilibrium](games/equilibrium.md) `[nash]` | +| [`gds-software`](software/index.md) | `gds_software` | Software architecture DSL (DFD, SM, C4, ERD) | +| [`gds-business`](business/index.md) | `gds_business` | Business dynamics DSL (CLD, SCN, VSM) | +| [`gds-symbolic`](symbolic/index.md) | `gds_symbolic` | SymPy bridge for control models `[sympy]` | + +### Simulation & Analysis + +| Package | Import | Description | +|---|---|---| +| [`gds-sim`](https://pypi.org/project/gds-sim/) | `gds_sim` | Discrete-time simulation engine (standalone) | +| [`gds-continuous`](continuous/index.md) | `gds_continuous` | Continuous-time ODE engine `[scipy]` | +| [`gds-analysis`](analysis/index.md) | `gds_analysis` | GDSSpec-to-gds-sim bridge, reachability | +| [`gds-psuu`](psuu/index.md) | `gds_psuu` | Parameter sweep + Optuna optimization | + +### Tutorials + +| Package | Description | +|---|---| +| `gds-examples` | [Tutorial models](examples/learning-path.md) + [Homicidal Chauffeur](continuous/getting-started.md) notebook | ## Architecture ``` -gds-framework ← core engine (no GDS dependencies) - ↑ -gds-viz ← visualization (depends on gds-framework) -gds-games ← game theory DSL (depends on gds-framework) -gds-stockflow ← stock-flow DSL (depends on gds-framework) -gds-control ← control systems DSL (depends on gds-framework) -gds-software ← software architecture DSL (depends on gds-framework) -gds-business ← business dynamics DSL (depends on gds-framework) +gds-framework ← core engine (pydantic only) ↑ -gds-examples ← tutorials (depends on gds-framework + gds-viz) + ├── gds-viz ← Mermaid diagrams + phase portraits [matplotlib] + ├── gds-games ← game theory DSL + Nash equilibrium [nashpy] + ├── gds-stockflow ← stock-flow DSL + ├── gds-control ← control systems DSL + ├── gds-software ← software architecture DSL + ├── gds-business ← business dynamics DSL (CLD, SCN, VSM) + └── gds-owl ← OWL/SHACL/SPARQL export (rdflib, pyshacl) + ↑ + gds-symbolic ← SymPy bridge (extends gds-control) [sympy] + ↑ + gds-examples ← tutorials (depends on most DSLs + viz) -gds-sim ← simulation engine (standalone — no gds-framework dep) +gds-sim ← discrete-time simulation (standalone, pydantic only) ↑ -gds-psuu ← parameter search under uncertainty (depends on gds-sim) + ├── gds-analysis ← spec→sim bridge, reachability + └── gds-psuu ← parameter sweep + Optuna + +gds-continuous ← continuous-time ODE engine (standalone) [scipy] ``` ## License diff --git a/docs/research/journal.md b/docs/research/journal.md index 50710ca..ddc69ee 100644 --- a/docs/research/journal.md +++ b/docs/research/journal.md @@ -616,3 +616,176 @@ gds-analysis now has 52 tests at 94% coverage. The API is cleaner (coverage metadata), and the float fingerprinting is robust. --- + +## Entry 008 — 2026-03-28 + +**Subject:** Session scorecard — full verification + analysis arc + +### What Landed (Single Session) + +**Verification Framework (Phases 1a, 2, 3):** +- 13 algebra PBT tests (interchange law, associativity, commutativity, + identity) with Hypothesis CI reproducibility profiles +- R3 undecidability proofs (6 propositions, R3-undecidable/R3-separation + sub-classification) + R1/R2 decidability bounds + Theorem C.3 partition + independence +- 26 OWL round-trip PBT tests (SHACL gate, SPARQL conformance, derived + property preservation, strategy invariants) + +**Bridge Proposal Steps 1-5:** +- Step 1: AdmissibleInputConstraint (prior) +- Step 2: TransitionSignature (prior) +- Step 3: StateMetric (this session) +- Step 4: `reachable_set()` with ReachabilityResult metadata +- Step 5: `configuration_space()` via iterative Tarjan SCC + +**gds-analysis Package (new):** +- `spec_to_model()` adapter: GDSSpec → gds_sim.Model +- `guarded_policy()`: runtime constraint enforcement with `depends_on` + projection +- `trajectory_distances()`: StateMetric computation on trajectories +- `reachable_set()` / `reachable_graph()` / `configuration_space()`: + reachability analysis with float tolerance and exhaustive/sampled + distinction +- 52 tests, 94% coverage + +**Ecosystem Extensions (closed today):** +- #77: Nashpy equilibrium computation (11 tests) +- #122: gds-continuous ODE engine (49 tests) +- #125: SymPy symbolic math (29 tests) +- #126: Phase portrait visualization (10 tests) + +**Integration Examples:** +- SIR epidemic: spec → simulate → conservation check → distances → + reachable set +- Crosswalk: spec → simulate → Markov transition verification → + reachability graph → SCC (configuration space) +- Heating-cooling example (9 tests) + +**Quality:** +- 3 rounds of code review on PBT tests +- 2 independent audits (software architecture + data science methodology) + with all Phase 1-2 fixes applied +- CI fix: gds-viz phase portrait tests now skip when gds-continuous + unavailable (was failing CI on main) + +**Research Documentation:** +- Verification plan (4 phases) +- Formal proofs (R3 undecidability + representability bounds) +- Research journal (8 entries) +- 5 GitHub issues created and closed, 3 new issues for future work + +### Issue Scorecard + +| Issue | Title | Status | +|---|---|---| +| #134 | Phase 1a: Interchange law PBT | Closed | +| #135 | Phase 1b-c: Coq mechanized proofs | Open (long-term) | +| #136 | Phase 2: R1/R2/R3 as theorem | Closed | +| #137 | Phase 3: Round-trip PBT | Closed | +| #138 | Phase 4: Dynamical invariants | Closed (superseded) | +| #139 | Verification + StateMetric PR | Merged to main | +| #140 | gds-analysis package | Closed | +| #141 | Reachable set R(x) + X_C | Closed | +| #142 | Contingent derivative (Steps 6-7) | Open (research) | +| #146 | gds-analysis + audits PR | Merged to main | + +### Remaining Open + +| Issue | Title | Blocker | +|---|---|---| +| #76 | Lean 4 export | Toolchain | +| #123 | Continuous-time differential games | Research | +| #124 | Optimal control / Hamiltonian | Research | +| #127 | Backward reachable set (gds-control) | gds-analysis | +| #135 | Coq mechanized proofs | Toolchain | +| #142 | Contingent derivative + controllability | Research | +| #143 | Package consolidation | Architecture decision | + +### Key Observations + +1. The bridge proposal (Steps 1-5) is now structurally complete. The + gap between the paper's mathematical definitions and the code is + closed for the non-research items. Steps 6-7 remain genuinely + research-level (contingent derivative, controllability). + +2. gds-analysis connects gds-framework to gds-sim without either + package knowing about the other. The adapter pattern preserves + the clean dependency graph. + +3. The verification framework provides three layers of confidence: + - PBT (Hypothesis): empirical confidence across random inputs + - Formal proofs (markdown + LaTeX): mathematical arguments + - SHACL/SPARQL validation: ontological consistency + +4. The R3-undecidable/R3-separation distinction is a genuine + contribution — it clarifies which GDS design choices create the + representability boundary and which could be moved by extending + the formalism. + +--- + +## Entry 009 — 2026-03-28 + +**Subject:** Final audit fixes + Hamiltonian mechanics (#124) + +### Audit Fixes + +Addressed final audit findings from the dev-vs-main diff review: + +- **F841 lint**: removed unused `r1` variable in float tolerance test +- **.hypothesis/ committed**: removed from tracking, added to `.gitignore` + (47 constants + example files were machine-specific cache) +- **ogs/equilibrium.py** (2 medium findings): + - `extract_payoff_matrices()` now raises `ValueError` on unrecognized + actions instead of silently skipping (→ zero payoff) + - Validates payoff matrix completeness — raises on missing action + profiles instead of silent zeros + - Numpy import guarded with helpful `ImportError` + - 3 new tests: incomplete TC, typo action, missing player + +### Hamiltonian Mechanics (#124) + +Added `hamiltonian.py` module to gds-symbolic, implementing Pontryagin's +Maximum Principle via symbolic differentiation: + +- **`HamiltonianSpec`**: Lagrangian `L(x, u, t)`, terminal cost, control + bounds, free-final-time flag +- **`derive_hamiltonian()`**: builds H = L + p^T f symbolically, computes + costate dynamics dp/dt = -dH/dx via `sympy.diff`, lambdifies the + augmented (x, p) system to a plain Python ODE callable +- **`derive_from_model()`**: convenience wrapper for `SymbolicControlModel` +- **`verify_conservation()`**: checks H = const along a trajectory (for + optimality verification) + +10 new tests: 1D LQR, 2D harmonic oscillator, parameterized dynamics, +missing state equations, SymbolicControlModel integration, conservation +checks, and end-to-end ODE integration (derive → integrate → verify). + +### Issue Status + +| Issue | Status | +|---|---| +| #124 Hamiltonian mechanics | **Closed** | +| #127 Backward reachable sets | Open (next) | +| #123 Continuous-time games | Open (needs design) | +| #143 Package consolidation | Open (architecture) | +| #135 Coq proofs | Open (tooling) | +| #142 Controllability | Open (research) | +| #76 Lean 4 export | Open (tooling) | + +### Session Totals (Full Day) + +| Metric | Count | +|---|---| +| Issues closed | 10 (#77, #122, #124, #125, #126, #134, #136, #137, #140, #141) | +| Issues superseded | 1 (#138) | +| New packages | 2 (gds-analysis, gds-continuous) | +| New modules | 3 (hamiltonian.py, reachability.py, equilibrium.py) | +| New tests written | ~160 | +| Formal proofs | 2 documents (R3 undecidability + representability bounds) | +| Audits completed | 5 (3 code reviews + 2 independent audits) | +| Journal entries | 9 | +| PRs merged to main | 4 (#133, #139, #146, pending) | + +--- diff --git a/docs/symbolic/getting-started.md b/docs/symbolic/getting-started.md new file mode 100644 index 0000000..1f967ab --- /dev/null +++ b/docs/symbolic/getting-started.md @@ -0,0 +1,135 @@ +# Getting Started + +## Installation + +```bash +uv add "gds-symbolic[sympy]" +``` + +For development (monorepo): + +```bash +git clone https://github.com/BlockScience/gds-core.git +cd gds-core +uv sync --all-packages +``` + +## Your First Symbolic Model + +Define a damped harmonic oscillator symbolically: two state variables (position and velocity), one input (external force). + +```python +from gds_symbolic import ( + SymbolicControlModel, + StateEquation, + OutputEquation, + compile_to_ode, +) + +# 1. Declare symbolic state equations +# dx1/dt = x2 (velocity) +# dx2/dt = -k*x1 - c*x2 + u (acceleration with damping) +model = SymbolicControlModel( + name="DampedOscillator", + state_equations=[ + StateEquation(state="x1", expr="x2"), + StateEquation(state="x2", expr="-k * x1 - c * x2 + u"), + ], + output_equations=[ + OutputEquation(output="position", expr="x1"), + ], + parameters={"k": 4.0, "c": 0.5}, +) + +# 2. Compile to a callable ODE function +ode_fn = compile_to_ode(model) + +# 3. Evaluate at a point +dx = ode_fn(t=0.0, state={"x1": 1.0, "x2": 0.0}, params={"k": 4.0, "c": 0.5, "u": 0.0}) +print(dx) # {"x1": 0.0, "x2": -4.0} +``` + +## Integration with gds-continuous + +Plug the compiled ODE function directly into `gds-continuous`: + +```python +from gds_continuous import ODEModel, ODESimulation + +ode_model = ODEModel( + state_names=["x1", "x2"], + initial_state={"x1": 1.0, "x2": 0.0}, + rhs=ode_fn, + params={"k": 4.0, "c": 0.5, "u": 0.0}, +) + +sim = ODESimulation(model=ode_model, t_span=(0.0, 20.0), solver="RK45") +results = sim.run() + +import matplotlib.pyplot as plt +plt.plot(results.t, results["x1"], label="position") +plt.plot(results.t, results["x2"], label="velocity") +plt.legend() +plt.title("Damped Harmonic Oscillator") +plt.xlabel("Time") +plt.grid(True) +plt.show() +``` + +## Linearization + +Compute Jacobian matrices at an operating point to get the standard state-space form `(A, B, C, D)`: + +```python +from gds_symbolic import linearize + +# Linearize around the equilibrium (x1=0, x2=0, u=0) +lin = linearize( + model, + operating_point={"x1": 0.0, "x2": 0.0}, + input_point={"u": 0.0}, +) + +print("A =", lin.A) # [[ 0. 1.], [-4. -0.5]] +print("B =", lin.B) # [[0.], [1.]] +print("C =", lin.C) # [[1. 0.]] +print("D =", lin.D) # [[0.]] +``` + +The `LinearSystem` object holds NumPy arrays for each matrix: + +```python +import numpy as np + +# Check eigenvalues for stability +eigenvalues = np.linalg.eigvals(lin.A) +print(f"Eigenvalues: {eigenvalues}") +print(f"Stable: {all(e.real < 0 for e in eigenvalues)}") +``` + +## Nonlinear Example: Van der Pol Oscillator + +A classic nonlinear system where linearization reveals local stability: + +```python +from gds_symbolic import SymbolicControlModel, StateEquation, linearize + +vdp = SymbolicControlModel( + name="VanDerPol", + state_equations=[ + StateEquation(state="x1", expr="x2"), + StateEquation(state="x2", expr="mu * (1 - x1**2) * x2 - x1"), + ], + parameters={"mu": 1.0}, +) + +# Linearize at the origin +lin = linearize(vdp, operating_point={"x1": 0.0, "x2": 0.0}) +print("A =", lin.A) # [[0, 1], [-1, mu]] -- unstable for mu > 0 +``` + +## Next Steps + +- [Symbolic Overview](index.md) -- architecture and key types +- [Continuous-Time](../continuous/index.md) -- ODE simulation engine for running compiled models +- [Control](../control/index.md) -- the underlying control DSL diff --git a/docs/symbolic/index.md b/docs/symbolic/index.md new file mode 100644 index 0000000..eea1eb3 --- /dev/null +++ b/docs/symbolic/index.md @@ -0,0 +1,76 @@ +# gds-symbolic + +[![PyPI](https://img.shields.io/pypi/v/gds-symbolic)](https://pypi.org/project/gds-symbolic/) +[![Python](https://img.shields.io/pypi/pyversions/gds-symbolic)](https://pypi.org/project/gds-symbolic/) +[![License](https://img.shields.io/github/license/BlockScience/gds-core)](https://github.com/BlockScience/gds-core/blob/main/LICENSE) + +**SymPy bridge for gds-control** -- symbolic state equations, automatic linearization, and ODE code generation. + +## What is this? + +`gds-symbolic` extends `gds-control`'s `ControlModel` with symbolic mathematics. Instead of writing numerical right-hand side functions by hand, you declare state and output equations as symbolic expressions and let the compiler do the rest. + +- **`StateEquation`** -- symbolic expression for `dx/dt` (e.g., `"-k * x + b * u"`) +- **`OutputEquation`** -- symbolic expression for sensor output `y` (e.g., `"x + noise"`) +- **`compile_to_ode()`** -- lambdifies symbolic equations into a callable `ODEFunction` compatible with `gds-continuous` +- **`linearize()`** -- computes Jacobian matrices (A, B, C, D) at an operating point +- **Safe expression parsing** -- uses `sympy.parsing.sympy_parser.parse_expr`, never `eval` + +## Architecture + +``` +gds-control (pip install gds-control) +| +| State-space control DSL: State, Input, Sensor, Controller. +| ++-- gds-symbolic (uv add gds-symbolic[sympy]) + | + | Symbolic layer: StateEquation, OutputEquation, + | compile_to_ode(), linearize(). + | + +-- gds-continuous (optional integration) + | + | ODE simulation engine: ODEModel, ODESimulation. +``` + +## Key Types + +| Type | Purpose | +|------|---------| +| `StateEquation` | Symbolic `dx_i/dt = expr(x, u, params)` | +| `OutputEquation` | Symbolic `y_i = expr(x, u, params)` | +| `SymbolicControlModel` | Extends `ControlModel` with symbolic equations | +| `ODEFunction` | Lambdified callable: `f(t, x, params) -> dx/dt` | +| `LinearSystem` | Matrices `(A, B, C, D)` from Jacobian linearization | + +## How It Works + +``` +Symbolic expressions (strings) + | + v +parse_expr() --> SymPy Expr objects + | + v +compile_to_ode() --> ODEFunction (lambdified, numpy-backed) + | | + v v +linearize() gds-continuous ODEModel + | + v +LinearSystem(A, B, C, D) --> eigenvalue analysis, controllability, etc. +``` + +All expression parsing uses `sympy.parsing.sympy_parser.parse_expr` with a restricted transformation set -- arbitrary code execution is not possible. + +## Quick Start + +```bash +uv add "gds-symbolic[sympy]" +``` + +See [Getting Started](getting-started.md) for a full walkthrough. + +## Credits + +Built on [gds-control](../control/index.md) by [BlockScience](https://block.science). diff --git a/mkdocs.yml b/mkdocs.yml index c2a4773..16dd1da 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ plugins: Games (gds-games / ogs): - {games/index.md: "Typed DSL for compositional game theory (Open Games)"} - {games/getting-started.md: "Model a Prisoner's Dilemma game"} + - {games/equilibrium.md: "Nash equilibrium computation via Nashpy"} - games/guide/*.md - games/design/*.md - games/api/*.md @@ -119,6 +120,15 @@ plugins: - {psuu/getting-started.md: "Installation + first parameter sweep"} - psuu/guide/*.md - psuu/api/*.md + Continuous-Time (gds-continuous): + - {continuous/index.md: "Continuous-time ODE integration engine — 6 SciPy solvers"} + - {continuous/getting-started.md: "Define and simulate an ODE system"} + Symbolic Math (gds-symbolic): + - {symbolic/index.md: "SymPy bridge for gds-control — symbolic equations and linearization"} + - {symbolic/getting-started.md: "Compile symbolic equations to ODE functions"} + Analysis (gds-analysis): + - {analysis/index.md: "Bridge GDSSpec structural annotations to gds-sim runtime"} + - {analysis/getting-started.md: "Convert a GDSSpec to a runnable simulation model"} Case Studies: - {case-studies/index.md: "Real-world projects built on the GDS ecosystem"} - {case-studies/axelrod.md: "Axelrod tournament — one model, many views with gds-games, gds-sim, gds-psuu, gds-viz"} @@ -224,6 +234,7 @@ nav: - Reports: games/guide/reports.md - Visualization: games/guide/visualization.md - CLI: games/guide/cli.md + - Equilibrium Analysis: games/equilibrium.md - Design: - Architecture Overview: games/design/architecture-overview.md - API Reference: @@ -354,6 +365,15 @@ nav: - gds_psuu.optimizers: psuu/api/optimizers.md - gds_psuu.sweep: psuu/api/sweep.md - gds_psuu.results: psuu/api/results.md + - Continuous-Time: + - Overview: continuous/index.md + - Getting Started: continuous/getting-started.md + - Symbolic Math: + - Overview: symbolic/index.md + - Getting Started: symbolic/getting-started.md + - Analysis: + - Overview: analysis/index.md + - Getting Started: analysis/getting-started.md - Case Studies: - case-studies/index.md - Axelrod Tournament: case-studies/axelrod.md diff --git a/packages/gds-analysis/CLAUDE.md b/packages/gds-analysis/CLAUDE.md new file mode 100644 index 0000000..c566c2f --- /dev/null +++ b/packages/gds-analysis/CLAUDE.md @@ -0,0 +1,40 @@ +# CLAUDE.md -- gds-analysis + +## Package Identity + +`gds-analysis` bridges `gds-framework` structural annotations to `gds-sim` runtime, enabling constraint enforcement, metric computation, and reachability analysis on concrete trajectories. + +- **Import**: `import gds_analysis` +- **Dependencies**: `gds-framework>=0.2.3`, `gds-sim>=0.1.0` + +## Architecture + +Four modules, each bridging one aspect of structural specification to runtime: + +| Module | Function | Paper reference | +|--------|----------|-----------------| +| `adapter.py` | `spec_to_model(spec, policies, sufs, ...)` → `gds_sim.Model` | — | +| `constraints.py` | `guarded_policy(policy_fn, constraint)` → wrapped policy | Def 2.5 | +| `metrics.py` | `trajectory_distances(results, spec)` → distance matrix | — | +| `reachability.py` | `reachable_set(spec, model, state, inputs)` → R(x) | Def 4.1, 4.2 | + +### spec_to_model adapter + +Maps GDS block roles to gds-sim execution primitives: +- `BoundaryAction` / `Policy` / `ControlAction` → policies dict +- `Mechanism` → SUFs dict (state update functions) +- Users supply the behavioral callables (R3); the adapter wires them using the structural skeleton (R1) + +If `enforce_constraints=True`, wraps BoundaryAction policies with `guarded_policy()` using any registered `AdmissibleInputConstraint`. + +### Reachability + +- `reachable_set(spec, model, state, input_samples)` — computes R(x) by running one timestep per input sample +- `reachable_graph(spec, model, states, input_samples)` — builds full reachability graph across multiple states +- `configuration_space(reachability_graph)` — extracts largest SCC (the configuration space X_C) + +## Commands + +```bash +uv run --package gds-analysis pytest packages/gds-analysis/tests -v +``` diff --git a/packages/gds-analysis/gds_analysis/__init__.py b/packages/gds-analysis/gds_analysis/__init__.py index 4c5dc0a..111c11a 100644 --- a/packages/gds-analysis/gds_analysis/__init__.py +++ b/packages/gds-analysis/gds_analysis/__init__.py @@ -8,6 +8,12 @@ __version__ = "0.1.0" from gds_analysis.adapter import spec_to_model +from gds_analysis.backward_reachability import ( + BackwardReachableSet, + Isochrone, + backward_reachable_set, + extract_isochrones, +) from gds_analysis.constraints import guarded_policy from gds_analysis.metrics import trajectory_distances from gds_analysis.reachability import ( @@ -18,8 +24,12 @@ ) __all__ = [ + "BackwardReachableSet", + "Isochrone", "ReachabilityResult", + "backward_reachable_set", "configuration_space", + "extract_isochrones", "guarded_policy", "reachable_graph", "reachable_set", diff --git a/packages/gds-analysis/gds_analysis/backward_reachability.py b/packages/gds-analysis/gds_analysis/backward_reachability.py new file mode 100644 index 0000000..80a890f --- /dev/null +++ b/packages/gds-analysis/gds_analysis/backward_reachability.py @@ -0,0 +1,189 @@ +"""Backward reachable set computation for continuous-time systems. + +Given a target set and continuous-time dynamics, computes the set of +states from which the target is reachable by integrating backward in +time. Uses gds-continuous's ODE engine for integration. + +The pattern follows the Homicidal Chauffeur approach: sample initial +conditions on the target set boundary, integrate backward for time T, +and collect the reached states as the backward reachable set B(T). + +Isochrones are level sets of the backward reachable tube: B(t) for +t in {t1, t2, ...} extracted from the trajectory data. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from gds_continuous import ODEModel, ODESimulation + +if TYPE_CHECKING: + from gds_continuous.types import ODEFunction + + +@dataclass +class BackwardReachableSet: + """Result of backward reachable set computation. + + Attributes + ---------- + trajectories + List of (times, states) for each initial condition on the + target set. States are dicts keyed by state_names. + target_points + Initial conditions on the target set boundary. + state_names + Ordered state variable names. + integration_time + Total backward integration time T. + n_trajectories + Number of trajectories computed. + """ + + trajectories: list[tuple[list[float], list[dict[str, float]]]] + target_points: list[dict[str, float]] + state_names: list[str] + integration_time: float + n_trajectories: int + + +@dataclass +class Isochrone: + """A level set of the backward reachable tube at time t. + + Attributes + ---------- + time + The backward time at which this isochrone is extracted. + points + State dicts at this time from each trajectory. + """ + + time: float + points: list[dict[str, float]] + + +def backward_reachable_set( + dynamics: ODEFunction, + state_names: list[str], + target_points: list[dict[str, float]], + integration_time: float, + params: dict[str, Any] | None = None, + *, + solver: str = "RK45", + rtol: float = 1e-8, + atol: float = 1e-10, + max_step: float = 0.05, + t_eval: list[float] | None = None, +) -> BackwardReachableSet: + """Compute the backward reachable set by integrating backward from + a target set. + + Parameters + ---------- + dynamics + Forward dynamics: (t, y, params) -> dy/dt. The function is + automatically negated for backward integration. + state_names + Ordered state variable names. + target_points + Initial conditions on the target set boundary. Each dict maps + state_names to float values. + integration_time + Total backward integration time T > 0. + params + Parameters passed to the dynamics function. + solver + ODE solver (RK45, RK23, DOP853, Radau, BDF, LSODA). + rtol, atol + Solver tolerances. + max_step + Maximum step size. + t_eval + Custom evaluation time points. If None, uses solver's adaptive + stepping. + + Returns + ------- + BackwardReachableSet with trajectories and metadata. + """ + params = params or {} + + def backward_rhs(t: float, y: list[float], p: dict[str, Any]) -> list[float]: + fwd = dynamics(t, y, p) + return [-v for v in fwd] + + trajectories: list[tuple[list[float], list[dict[str, float]]]] = [] + + for ic in target_points: + model = ODEModel( + state_names=state_names, + initial_state={n: ic[n] for n in state_names}, + rhs=backward_rhs, + params={k: [v] for k, v in params.items()}, + ) + sim = ODESimulation( + model=model, + t_span=(0.0, integration_time), + t_eval=t_eval, + solver=solver, + rtol=rtol, + atol=atol, + max_step=max_step, + ) + results = sim.run() + times = results.times + rows = results.to_list() + states = [{n: row[n] for n in state_names} for row in rows] + trajectories.append((times, states)) + + return BackwardReachableSet( + trajectories=trajectories, + target_points=target_points, + state_names=state_names, + integration_time=integration_time, + n_trajectories=len(target_points), + ) + + +def extract_isochrones( + brs: BackwardReachableSet, + times: list[float], + *, + tolerance: float = 0.05, +) -> list[Isochrone]: + """Extract isochrones (level sets) from backward reachable set. + + Parameters + ---------- + brs + Result of backward_reachable_set(). + times + Target times at which to extract isochrones. + tolerance + Time matching tolerance — a trajectory point at time t is + included in the isochrone for target time T if |t - T| < tol. + + Returns + ------- + List of Isochrone objects, one per requested time. + """ + isochrones: list[Isochrone] = [] + + for target_t in times: + points: list[dict[str, float]] = [] + for traj_times, traj_states in brs.trajectories: + best_idx = None + best_dist = float("inf") + for i, t in enumerate(traj_times): + dist = abs(t - target_t) + if dist < best_dist: + best_dist = dist + best_idx = i + if best_idx is not None and best_dist < tolerance: + points.append(traj_states[best_idx]) + isochrones.append(Isochrone(time=target_t, points=points)) + + return isochrones diff --git a/packages/gds-analysis/pyproject.toml b/packages/gds-analysis/pyproject.toml index faa7e25..42bc492 100644 --- a/packages/gds-analysis/pyproject.toml +++ b/packages/gds-analysis/pyproject.toml @@ -32,6 +32,9 @@ dependencies = [ "gds-sim>=0.1.0", ] +[project.optional-dependencies] +continuous = ["gds-continuous[scipy]>=0.1.0", "numpy>=1.26"] + [project.urls] Homepage = "https://github.com/BlockScience/gds-core" Repository = "https://github.com/BlockScience/gds-core" diff --git a/packages/gds-analysis/tests/test_backward_reachability.py b/packages/gds-analysis/tests/test_backward_reachability.py new file mode 100644 index 0000000..cde4b98 --- /dev/null +++ b/packages/gds-analysis/tests/test_backward_reachability.py @@ -0,0 +1,207 @@ +"""Tests for backward reachable set computation.""" + +from __future__ import annotations + +import math +from typing import Any + +import pytest + +gds_continuous = pytest.importorskip("gds_continuous") +numpy = pytest.importorskip("numpy") + +from gds_analysis.backward_reachability import ( # noqa: E402 + BackwardReachableSet, + backward_reachable_set, + extract_isochrones, +) + +# --------------------------------------------------------------------------- +# Test dynamics +# --------------------------------------------------------------------------- + + +def simple_decay(t: float, y: list[float], params: dict[str, Any]) -> list[float]: + """dx/dt = -x (exponential decay toward origin).""" + return [-y[0]] + + +def harmonic(t: float, y: list[float], params: dict[str, Any]) -> list[float]: + """2D harmonic oscillator: dx1/dt = x2, dx2/dt = -x1.""" + return [y[1], -y[0]] + + +def hc_forward(t: float, y: list[float], params: dict[str, Any]) -> list[float]: + """Homicidal Chauffeur forward dynamics (simplified 2D).""" + import numpy as np + + x1, x2, p1, p2 = y + w = params.get("w", 0.25) + norm_p = math.sqrt(p1**2 + p2**2) + if norm_p < 1e-15: + return [0.0, 0.0, 0.0, 0.0] + sigma = p2 * x1 - p1 * x2 + phi = -float(np.sign(sigma)) + return [ + float(-phi * x2 + w * p1 / norm_p), + float(phi * x1 + w * p2 / norm_p - 1.0), + float(-phi * p2), + float(phi * p1), + ] + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + + +class TestBackwardReachableSet: + def test_1d_decay(self) -> None: + """Backward from x=0.5 should reach x > 0.5 (decay runs backward).""" + brs = backward_reachable_set( + dynamics=simple_decay, + state_names=["x"], + target_points=[{"x": 0.5}], + integration_time=1.0, + params={}, + ) + assert isinstance(brs, BackwardReachableSet) + assert brs.n_trajectories == 1 + assert brs.state_names == ["x"] + + # Final backward state should be > 0.5 (since decay goes toward 0) + _, states = brs.trajectories[0] + final_x = states[-1]["x"] + assert final_x > 0.5 + + def test_multiple_target_points(self) -> None: + """Multiple targets produce multiple trajectories.""" + targets = [{"x": 0.3}, {"x": 0.5}, {"x": 0.7}] + brs = backward_reachable_set( + dynamics=simple_decay, + state_names=["x"], + target_points=targets, + integration_time=1.0, + ) + assert brs.n_trajectories == 3 + assert len(brs.trajectories) == 3 + + def test_2d_harmonic(self) -> None: + """2D backward reachable set from the unit circle.""" + targets = [ + {"x1": math.cos(a), "x2": math.sin(a)} + for a in [0, math.pi / 2, math.pi, 3 * math.pi / 2] + ] + brs = backward_reachable_set( + dynamics=harmonic, + state_names=["x1", "x2"], + target_points=targets, + integration_time=2.0, + ) + assert brs.n_trajectories == 4 + # Harmonic oscillator conserves energy — backward states + # should still be on the unit circle (approximately) + for _, states in brs.trajectories: + final = states[-1] + r = math.sqrt(final["x1"] ** 2 + final["x2"] ** 2) + assert r == pytest.approx(1.0, abs=0.01) + + def test_hc_capture_circle(self) -> None: + """HC backward from capture circle produces reasonable trajectories.""" + w = 0.25 + ell = 0.5 + alpha = math.pi / 2 + x1_T = ell * math.cos(alpha) + x2_T = ell * math.sin(alpha) + lam = -1.0 / (ell * (w - math.sin(alpha))) + target = { + "x1": x1_T, + "x2": x2_T, + "p1": lam * x1_T, + "p2": lam * x2_T, + } + + brs = backward_reachable_set( + dynamics=hc_forward, + state_names=["x1", "x2", "p1", "p2"], + target_points=[target], + integration_time=5.0, + params={"w": w}, + rtol=1e-10, + atol=1e-12, + ) + assert brs.n_trajectories == 1 + _, states = brs.trajectories[0] + # Should have moved away from capture circle + final = states[-1] + dist = math.sqrt(final["x1"] ** 2 + final["x2"] ** 2) + assert dist > ell # farther than capture radius + + def test_custom_t_eval(self) -> None: + """Custom evaluation points are respected.""" + brs = backward_reachable_set( + dynamics=simple_decay, + state_names=["x"], + target_points=[{"x": 1.0}], + integration_time=2.0, + t_eval=[0.0, 0.5, 1.0, 1.5, 2.0], + ) + _, states = brs.trajectories[0] + assert len(states) == 5 + + +class TestExtractIsochrones: + def test_extract_at_times(self) -> None: + """Isochrones extracted at requested times.""" + brs = backward_reachable_set( + dynamics=simple_decay, + state_names=["x"], + target_points=[{"x": 0.5}, {"x": 1.0}], + integration_time=2.0, + t_eval=[0.0, 0.5, 1.0, 1.5, 2.0], + ) + isos = extract_isochrones(brs, [0.0, 1.0, 2.0]) + assert len(isos) == 3 + # Each isochrone should have 2 points (one per trajectory) + for iso in isos: + assert len(iso.points) == 2 + + def test_isochrone_times_match(self) -> None: + brs = backward_reachable_set( + dynamics=simple_decay, + state_names=["x"], + target_points=[{"x": 1.0}], + integration_time=3.0, + t_eval=[0.0, 1.0, 2.0, 3.0], + ) + isos = extract_isochrones(brs, [1.0, 2.0]) + assert isos[0].time == 1.0 + assert isos[1].time == 2.0 + + def test_isochrone_values_increase_backward(self) -> None: + """For decay, backward isochrones at later times should be farther.""" + brs = backward_reachable_set( + dynamics=simple_decay, + state_names=["x"], + target_points=[{"x": 0.5}], + integration_time=3.0, + t_eval=[0.0, 1.0, 2.0, 3.0], + ) + isos = extract_isochrones(brs, [0.0, 1.0, 2.0, 3.0]) + xs = [iso.points[0]["x"] for iso in isos] + # Each successive isochrone should be farther from origin + for i in range(len(xs) - 1): + assert xs[i + 1] > xs[i] + + def test_tolerance_filtering(self) -> None: + """Points outside tolerance are excluded.""" + brs = backward_reachable_set( + dynamics=simple_decay, + state_names=["x"], + target_points=[{"x": 1.0}], + integration_time=1.0, + t_eval=[0.0, 1.0], + ) + # Request isochrone at t=0.5 — no eval point there + isos = extract_isochrones(brs, [0.5], tolerance=0.01) + assert isos[0].points == [] # nothing within tolerance diff --git a/packages/gds-analysis/tests/test_reachability.py b/packages/gds-analysis/tests/test_reachability.py index f81b406..e15af89 100644 --- a/packages/gds-analysis/tests/test_reachability.py +++ b/packages/gds-analysis/tests/test_reachability.py @@ -127,13 +127,6 @@ def test_float_tolerance(self, thermostat_spec: GDSSpec) -> None: {"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, diff --git a/packages/gds-games/ogs/equilibrium.py b/packages/gds-games/ogs/equilibrium.py index 7c8a494..7373615 100644 --- a/packages/gds-games/ogs/equilibrium.py +++ b/packages/gds-games/ogs/equilibrium.py @@ -94,7 +94,13 @@ def extract_payoff_matrices(ir: PatternIR) -> PayoffMatrices: ValueError If the PatternIR does not represent a valid 2-player normal-form game. """ - import numpy as np + try: + import numpy as np + except ImportError as exc: + raise ImportError( + "numpy is required for payoff matrix extraction. " + "Install with: uv add gds-games[nash]" + ) from exc if not ir.action_spaces or len(ir.action_spaces) != 2: msg = f"Expected exactly 2 action spaces, got {len(ir.action_spaces or [])}" @@ -116,13 +122,31 @@ def extract_payoff_matrices(ir: PatternIR) -> PayoffMatrices: B = np.zeros((m, n)) # Build lookup from (action1, action2) -> terminal condition + populated: set[tuple[int, int]] = set() for tc in ir.terminal_conditions: if player1 not in tc.actions or player2 not in tc.actions: - continue + msg = ( + f"Terminal condition '{tc.name}' missing actions " + f"for {player1} and/or {player2}. " + f"Got actions: {list(tc.actions.keys())}" + ) + raise ValueError(msg) a1 = tc.actions[player1] a2 = tc.actions[player2] - if a1 not in actions1 or a2 not in actions2: - continue + if a1 not in actions1: + msg = ( + f"Terminal condition '{tc.name}': " + f"unrecognized action '{a1}' for {player1}. " + f"Valid actions: {actions1}" + ) + raise ValueError(msg) + if a2 not in actions2: + msg = ( + f"Terminal condition '{tc.name}': " + f"unrecognized action '{a2}' for {player2}. " + f"Valid actions: {actions2}" + ) + raise ValueError(msg) i = actions1.index(a1) j = actions2.index(a2) @@ -137,6 +161,21 @@ def extract_payoff_matrices(ir: PatternIR) -> PayoffMatrices: A[i, j] = tc.payoffs[player1] B[i, j] = tc.payoffs[player2] + populated.add((i, j)) + + # Validate that all action profile entries are populated + expected = {(i, j) for i in range(m) for j in range(n)} + missing = expected - populated + if missing: + missing_profiles = [ + f"({actions1[i]}, {actions2[j]})" for i, j in sorted(missing) + ] + msg = ( + f"Incomplete payoff matrix: {len(missing)} of " + f"{m * n} action profiles have no terminal condition. " + f"Missing: {', '.join(missing_profiles)}" + ) + raise ValueError(msg) return PayoffMatrices( A=A, diff --git a/packages/gds-games/tests/test_equilibrium.py b/packages/gds-games/tests/test_equilibrium.py index 3362ed0..a4d981b 100644 --- a/packages/gds-games/tests/test_equilibrium.py +++ b/packages/gds-games/tests/test_equilibrium.py @@ -265,3 +265,45 @@ def test_vertex_enumeration(self) -> None: def test_unknown_method(self) -> None: with pytest.raises(ValueError, match="Unknown method"): compute_nash(_prisoners_dilemma_ir(), method="bogus") + + +class TestIncompleteGames: + """Validate errors on malformed games.""" + + def test_missing_terminal_condition(self) -> None: + """Incomplete payoff matrix (missing action profile) raises.""" + ir = _prisoners_dilemma_ir() + # Remove one terminal condition → incomplete matrix + ir.terminal_conditions = ir.terminal_conditions[:3] + with pytest.raises(ValueError, match="Incomplete payoff matrix"): + extract_payoff_matrices(ir) + + def test_unrecognized_action(self) -> None: + """Terminal condition with typo in action name raises.""" + ir = _prisoners_dilemma_ir() + tc = ir.terminal_conditions[0] + players = list(tc.actions.keys()) + bad_tc = type(tc)( + name=tc.name, + actions={players[0]: "TYPO_ACTION", players[1]: tc.actions[players[1]]}, + outcome=tc.outcome, + payoffs=tc.payoffs, + ) + ir.terminal_conditions[0] = bad_tc + with pytest.raises(ValueError, match="unrecognized action"): + extract_payoff_matrices(ir) + + def test_missing_player_in_actions(self) -> None: + """Terminal condition missing a player's action raises.""" + ir = _prisoners_dilemma_ir() + tc = ir.terminal_conditions[0] + players = list(tc.actions.keys()) + bad_tc = type(tc)( + name=tc.name, + actions={players[0]: tc.actions[players[0]]}, + outcome=tc.outcome, + payoffs=tc.payoffs, + ) + ir.terminal_conditions[0] = bad_tc + with pytest.raises(ValueError, match="missing actions"): + extract_payoff_matrices(ir) diff --git a/packages/gds-owl/CLAUDE.md b/packages/gds-owl/CLAUDE.md new file mode 100644 index 0000000..231ede3 --- /dev/null +++ b/packages/gds-owl/CLAUDE.md @@ -0,0 +1,43 @@ +# CLAUDE.md -- gds-owl + +## Package Identity + +`gds-owl` provides OWL/Turtle, SHACL validation, and SPARQL queries for `gds-framework` specifications. Implements the R1/R2 representability tiers from the formal analysis. + +- **Import**: `import gds_owl` +- **Dependencies**: `gds-framework>=0.2.3`, `rdflib>=7.0` +- **Optional**: `pyshacl>=0.27` for SHACL validation + +## Architecture + +| Module | Purpose | R-tier | +|--------|---------|--------| +| `export.py` | `spec_to_graph()`, `system_ir_to_graph()`, `canonical_to_graph()`, `report_to_graph()` | R1 | +| `import_.py` | `graph_to_spec()`, `graph_to_system_ir()`, `graph_to_canonical()`, `graph_to_report()` | R1 | +| `ontology.py` | `build_core_ontology()` — OWL class hierarchy | R1 | +| `shacl.py` | `validate_graph()`, `build_all_shapes()` — 13 SHACL shapes | R1 | +| `sparql.py` | `run_query(graph, template_name)` — pre-built SPARQL templates | R2 | +| `serialize.py` | `to_turtle()`, `to_jsonld()`, `to_ntriples()` | — | +| `_namespace.py` | `GDS`, `GDS_CORE`, `GDS_IR`, `GDS_VERIF`, `PREFIXES` | — | + +### Round-trip fidelity + +The `rho` mapping: `GDSSpec → RDF → GDSSpec` preserves all R1 fields (names, types, units, spaces, entities, blocks, roles, wirings, updates). R3 fields (`TypeDef.constraint`, `f_behav`) are correctly lossy — they become `None` after round-trip. + +### SHACL shapes + +13 shapes validating structural properties: +- `BlockIRShape`, `BoundaryActionShape`, `MechanismShape` — role constraints +- `WiringIRShape` — wire source/target +- `TypeDefShape`, `SpaceShape`, `EntityShape` — data model +- `AdmissibleInputConstraintShape`, `TransitionSignatureShape` — annotations + +### SPARQL templates + +Pre-built queries: `blocks_by_role`, `dependency_path`, `reachability`, `loop_detection`, `parameter_impact`, `entity_update_map`. + +## Commands + +```bash +uv run --package gds-owl pytest packages/gds-owl/tests -v +``` diff --git a/packages/gds-psuu/CLAUDE.md b/packages/gds-psuu/CLAUDE.md new file mode 100644 index 0000000..fa77dc8 --- /dev/null +++ b/packages/gds-psuu/CLAUDE.md @@ -0,0 +1,37 @@ +# CLAUDE.md -- gds-psuu + +## Package Identity + +`gds-psuu` provides parameter space search under uncertainty for the GDS ecosystem. Wraps Optuna for Bayesian optimization and supports grid/random search, sensitivity analysis (Morris, OAT), and KPI-based evaluation. + +- **Import**: `import gds_psuu` +- **Dependencies**: `gds-sim>=0.1.0`, `pydantic>=2.10`, `pandas>=2.0`, `optuna>=4.0` + +## Architecture + +| Module | Purpose | +|--------|---------| +| `space.py` | `ParameterSpace`, `Continuous`, `Discrete`, `Integer` — search domain definition | +| `kpi.py` | `KPI` — key performance indicators (`final_state_mean`, `time_average`, etc.) | +| `metric.py` | `Metric`, `Aggregation` — trajectory metrics (`final_value`, `max_value`, `probability_above`) | +| `objective.py` | `Objective`, `SingleKPI`, `WeightedSum` — optimization targets | +| `evaluation.py` | `Evaluator` — runs simulation, computes KPIs, aggregates | +| `sweep.py` | `Sweep` — orchestrates parameter sweep + evaluation | +| `sensitivity.py` | `MorrisAnalyzer`, `OATAnalyzer` — sensitivity analysis | +| `optimizers/` | `GridSearchOptimizer`, `RandomSearchOptimizer`, `BayesianOptimizer` | +| `results.py` | `SweepResults`, `EvaluationSummary` — structured output | + +### Pipeline + +``` +ParameterSpace → Sweep(model, space, kpis, objective) → sweep.run() + → evaluates each param point via gds-sim + → computes KPIs per trajectory + → returns SweepResults with best_point, all_results +``` + +## Commands + +```bash +uv run --package gds-psuu pytest packages/gds-psuu/tests -v +``` diff --git a/packages/gds-sim/CLAUDE.md b/packages/gds-sim/CLAUDE.md new file mode 100644 index 0000000..3dd5606 --- /dev/null +++ b/packages/gds-sim/CLAUDE.md @@ -0,0 +1,45 @@ +# CLAUDE.md -- gds-sim + +## Package Identity + +`gds-sim` is a high-performance discrete-time simulation engine for the GDS ecosystem. Standalone (pydantic-only dependency, no gds-framework import). cadCAD-compatible function signatures. + +- **Import**: `import gds_sim` +- **Dependencies**: `pydantic>=2.10` +- **Optional**: `[pandas]` for DataFrame conversion + +## Architecture + +| Module | Purpose | +|--------|---------| +| `types.py` | `State`, `Signal`, `Params`, `PolicyFn`, `SUFn`, `StateUpdateBlock`, `Hooks` | +| `model.py` | `Model` (initial state + blocks + params), `Simulation`, `Experiment` | +| `engine.py` | Hot-path execution loop — no deepcopy, no wrapping at runtime | +| `results.py` | `Results` — columnar dict-of-lists with pre-allocation | +| `compat.py` | cadCAD signature auto-detection (4-arg policy, 5-arg SUF → wrapped) | +| `parallel.py` | `ProcessPoolExecutor` for multi-subset/multi-run parallelism (fork) | + +### Execution model + +``` +for each (subset, run): + state = dict(initial_state) + for t in range(1, timesteps + 1): + for block in blocks: + signal = execute_policies(block, state, params) # all read same state + state = execute_sufs(block, state, signal) # simultaneous within block +``` + +- **Simultaneous within block**: All SUFs in one block read the pre-block state +- **Sequential across blocks**: Each block sees the state from the previous block +- **Shallow copy only**: `dict(state)` per block (~10ns), no deepcopy + +### Results pre-allocation + +`Results.preallocate(sim)` computes exact row count: `(1 + timesteps * n_blocks) * runs * n_subsets`. Columnar storage avoids per-row dict overhead. + +## Commands + +```bash +uv run --package gds-sim pytest packages/gds-sim/tests -v +``` diff --git a/packages/gds-symbolic/CLAUDE.md b/packages/gds-symbolic/CLAUDE.md new file mode 100644 index 0000000..de2302c --- /dev/null +++ b/packages/gds-symbolic/CLAUDE.md @@ -0,0 +1,40 @@ +# CLAUDE.md -- gds-symbolic + +## Package Identity + +`gds-symbolic` extends `gds-control`'s `ControlModel` with symbolic differential equations (SymPy). Compiles symbolic ODEs to plain Python callables via `sympy.lambdify` for use with `gds-continuous`. + +- **Import**: `import gds_symbolic` +- **Dependencies**: `gds-framework>=0.2.3`, `gds-control>=0.1.0`, `pydantic>=2.10` +- **Optional**: `[sympy]` for SymPy + numpy, `[continuous]` for gds-continuous + +## Architecture + +| Module | Purpose | +|--------|---------| +| `elements.py` | `StateEquation`, `OutputEquation` — frozen Pydantic models storing `expr_str` (R1-serializable) | +| `model.py` | `SymbolicControlModel(ControlModel)` — adds symbolic equations + validation | +| `compile.py` | `compile_to_ode(model)` → `(ODEFunction, state_order)` via `parse_expr` + `lambdify` | +| `linearize.py` | `linearize(model, x0, u0)` → `LinearizedSystem(A, B, C, D)` via Jacobians | +| `errors.py` | `SymbolicError(CSError)` — inherits control domain error hierarchy | + +### Security + +Expression parsing uses `sympy.parsing.sympy_parser.parse_expr` with a restricted `local_dict` — NOT `sympify` (which uses `eval`). This is safe for untrusted `expr_str` input. + +### R1/R2/R3 boundary + +- `StateEquation.expr_str` (string) → R1, serializable to OWL +- `sympy.Expr` (parsed) → R2, transient +- Lambdified callable → R3, opaque + +### Known limitations + +- Inputs resolved from constant `params` — no time-varying signals (see gds-continuous CLAUDE.md) +- `compile()` and `compile_system()` remain structural — symbolic equations don't appear in GDSSpec + +## Commands + +```bash +uv run --package gds-symbolic pytest packages/gds-symbolic/tests -v +``` diff --git a/packages/gds-symbolic/gds_symbolic/__init__.py b/packages/gds-symbolic/gds_symbolic/__init__.py index 6d6ab29..d5d952a 100644 --- a/packages/gds-symbolic/gds_symbolic/__init__.py +++ b/packages/gds-symbolic/gds_symbolic/__init__.py @@ -4,13 +4,25 @@ from gds_symbolic.elements import OutputEquation, StateEquation from gds_symbolic.errors import SymbolicError +from gds_symbolic.hamiltonian import ( + HamiltonianSpec, + HamiltonianSystem, + derive_from_model, + derive_hamiltonian, + verify_conservation, +) from gds_symbolic.linearize import LinearizedSystem from gds_symbolic.model import SymbolicControlModel __all__ = [ + "HamiltonianSpec", + "HamiltonianSystem", "LinearizedSystem", "OutputEquation", "StateEquation", "SymbolicControlModel", "SymbolicError", + "derive_from_model", + "derive_hamiltonian", + "verify_conservation", ] diff --git a/packages/gds-symbolic/gds_symbolic/hamiltonian.py b/packages/gds-symbolic/gds_symbolic/hamiltonian.py new file mode 100644 index 0000000..ddb37f8 --- /dev/null +++ b/packages/gds-symbolic/gds_symbolic/hamiltonian.py @@ -0,0 +1,239 @@ +"""Hamiltonian mechanics and Pontryagin's Maximum Principle. + +Derives the Hamiltonian H(x, p, u, t) = L(x, u) + p^T f(x, u) from +a SymbolicControlModel's state equations and a user-supplied Lagrangian. +Symbolically computes costate dynamics dp/dt = -dH/dx and produces an +augmented ODEModel for (x, p) integration. + +This module connects GDS structural specification (ControlModel) to +optimal control theory via symbolic differentiation (SymPy). +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from pydantic import BaseModel + +from gds_symbolic._compat import require_sympy + + +class HamiltonianSpec(BaseModel, frozen=True): + """Specification for optimal control via Pontryagin's principle. + + Parameters + ---------- + lagrangian + Running cost L(x, u, t) as a SymPy-parseable string. + terminal_cost + Terminal cost Phi(x(T)) as a SymPy-parseable string. + Empty string means no terminal cost. + control_bounds + Lower and upper bounds for each control input. + free_final_time + If True, H = 0 along optimal trajectories (transversality). + """ + + lagrangian: str + terminal_cost: str = "" + control_bounds: dict[str, tuple[float, float]] = {} + free_final_time: bool = False + + +@dataclass(frozen=True) +class HamiltonianSystem: + """Derived Hamiltonian system ready for integration. + + Produced by ``derive_hamiltonian()``. Contains the symbolic + Hamiltonian, costate dynamics, and a compiled augmented ODE. + + Attributes + ---------- + hamiltonian_expr + Symbolic Hamiltonian H(x, p, u) as a string. + costate_exprs + Costate dynamics dp_i/dt = -dH/dx_i as strings. + state_names + Ordered state variable names. + costate_names + Ordered costate variable names (p_x1, p_x2, ...). + augmented_ode + Compiled ODE function for the augmented (x, p) system. + augmented_names + Full state vector names [x1, ..., xn, p_x1, ..., p_xn]. + """ + + hamiltonian_expr: str + costate_exprs: dict[str, str] + state_names: list[str] + costate_names: list[str] + augmented_ode: Any # ODEFunction + augmented_names: list[str] + + +def derive_hamiltonian( + state_equations: dict[str, str], + state_names: list[str], + input_names: list[str], + param_names: list[str], + spec: HamiltonianSpec, +) -> HamiltonianSystem: + """Derive the Hamiltonian system from state dynamics and cost. + + Parameters + ---------- + state_equations + Map of state_name -> dx/dt expression string. + state_names + Ordered state variable names. + input_names + Control input variable names. + param_names + Parameter names (constants during integration). + spec + HamiltonianSpec with Lagrangian and constraints. + + Returns + ------- + HamiltonianSystem with symbolic expressions and compiled ODE. + """ + require_sympy() + import sympy + from sympy.parsing.sympy_parser import parse_expr + + # Build symbol tables + state_syms = {n: sympy.Symbol(n) for n in state_names} + costate_names = [f"p_{n}" for n in state_names] + costate_syms = {n: sympy.Symbol(n) for n in costate_names} + input_syms = {n: sympy.Symbol(n) for n in input_names} + param_syms = {n: sympy.Symbol(n) for n in param_names} + t_sym = sympy.Symbol("t") + + all_syms = { + **state_syms, + **costate_syms, + **input_syms, + **param_syms, + "t": t_sym, + } + + # Parse state dynamics f(x, u) + f_exprs = {} + for name in state_names: + if name in state_equations: + f_exprs[name] = parse_expr(state_equations[name], local_dict=all_syms) + else: + f_exprs[name] = sympy.Integer(0) + + # Parse Lagrangian L(x, u, t) + lagrangian = parse_expr(spec.lagrangian, local_dict=all_syms) + + # Hamiltonian: H = L + p^T f + hamiltonian = lagrangian + for i, name in enumerate(state_names): + hamiltonian += costate_syms[costate_names[i]] * f_exprs[name] + + # Costate dynamics: dp_i/dt = -dH/dx_i + costate_dynamics = {} + for i, name in enumerate(state_names): + dp_dt = -sympy.diff(hamiltonian, state_syms[name]) + costate_dynamics[costate_names[i]] = dp_dt + + # Compile augmented ODE: [dx/dt, dp/dt] + augmented_names = state_names + costate_names + # Build ordered expression vector + rhs_exprs = [] + for name in state_names: + rhs_exprs.append(f_exprs[name]) + for cname in costate_names: + rhs_exprs.append(costate_dynamics[cname]) + + # Lambdify + ordered_symbols = ( + [state_syms[n] for n in state_names] + + [costate_syms[n] for n in costate_names] + + [input_syms[n] for n in input_names] + + [param_syms[n] for n in param_names] + ) + rhs_lambda = sympy.lambdify(ordered_symbols, rhs_exprs, modules="math") + + def augmented_ode(t: float, y: list[float], params: dict[str, Any]) -> list[float]: + input_vals = [params.get(n, 0.0) for n in input_names] + param_vals = [params.get(n, 0.0) for n in param_names] + args = list(y) + input_vals + param_vals + result = rhs_lambda(*args) + if isinstance(result, (int, float)): + return [float(result)] + return [float(v) for v in result] + + return HamiltonianSystem( + hamiltonian_expr=str(hamiltonian), + costate_exprs={k: str(v) for k, v in costate_dynamics.items()}, + state_names=state_names, + costate_names=costate_names, + augmented_ode=augmented_ode, + augmented_names=augmented_names, + ) + + +def derive_from_model( + model: Any, + spec: HamiltonianSpec, +) -> HamiltonianSystem: + """Derive Hamiltonian from a SymbolicControlModel. + + Convenience wrapper that extracts state equations, names, and + parameters from the model. + """ + state_names = [s.name for s in model.states] + input_names = [i.name for i in model.inputs] + param_names = list(model.symbolic_params) + state_equations = {eq.state_name: eq.expr_str for eq in model.state_equations} + return derive_hamiltonian( + state_equations=state_equations, + state_names=state_names, + input_names=input_names, + param_names=param_names, + spec=spec, + ) + + +def verify_conservation( + times: list[float], + states: list[list[float]], + hamiltonian_fn: Any, + params: dict[str, Any], + *, + tolerance: float = 1e-4, +) -> tuple[bool, float]: + """Verify Hamiltonian conservation along a trajectory. + + For free-final-time problems, H should be 0. + For fixed-final-time problems, H should be constant. + + Parameters + ---------- + times + Time points. + states + State vectors at each time point (augmented: [x, p]). + hamiltonian_fn + Callable: (t, y, params) -> H value. + params + Parameter dict. + tolerance + Maximum allowed variation in H. + + Returns + ------- + (conserved, max_variation) + """ + h_values = [ + hamiltonian_fn(t, y, params) for t, y in zip(times, states, strict=True) + ] + if not h_values: + return True, 0.0 + h0 = h_values[0] + max_var = max(abs(h - h0) for h in h_values) + return max_var < tolerance, max_var diff --git a/packages/gds-symbolic/tests/test_hamiltonian.py b/packages/gds-symbolic/tests/test_hamiltonian.py new file mode 100644 index 0000000..e5ec13f --- /dev/null +++ b/packages/gds-symbolic/tests/test_hamiltonian.py @@ -0,0 +1,213 @@ +"""Tests for Hamiltonian mechanics and Pontryagin's Maximum Principle.""" + +from __future__ import annotations + +import pytest + +sympy = pytest.importorskip("sympy") + +from gds_symbolic.hamiltonian import ( # noqa: E402 + HamiltonianSpec, + HamiltonianSystem, + derive_hamiltonian, + verify_conservation, +) + + +class TestDeriveHamiltonian: + """Test symbolic Hamiltonian derivation.""" + + def test_1d_linear_quadratic(self) -> None: + """LQR: dx/dt = -x + u, L = x^2 + u^2. + + H = x^2 + u^2 + p*(-x + u) + dp/dt = -dH/dx = -2*x + p + """ + system = derive_hamiltonian( + state_equations={"x": "-x + u"}, + state_names=["x"], + input_names=["u"], + param_names=[], + spec=HamiltonianSpec(lagrangian="x**2 + u**2"), + ) + + assert isinstance(system, HamiltonianSystem) + assert system.state_names == ["x"] + assert system.costate_names == ["p_x"] + assert system.augmented_names == ["x", "p_x"] + assert len(system.costate_exprs) == 1 + assert "p_x" in system.costate_exprs + # dp/dt = -dH/dx = -(2x + p*(-1)) = -2x + p + # Check the costate expression contains the expected terms + expr = system.costate_exprs["p_x"] + assert "x" in expr + assert "p_x" in expr + + def test_2d_harmonic(self) -> None: + """Harmonic oscillator: dx1/dt = x2, dx2/dt = -x1. + + L = x1^2 + x2^2 + H = x1^2 + x2^2 + p1*x2 + p2*(-x1) + dp1/dt = -dH/dx1 = -2*x1 + p2 + dp2/dt = -dH/dx2 = -2*x2 - p1 + """ + system = derive_hamiltonian( + state_equations={"x1": "x2", "x2": "-x1"}, + state_names=["x1", "x2"], + input_names=[], + param_names=[], + spec=HamiltonianSpec(lagrangian="x1**2 + x2**2"), + ) + + assert system.state_names == ["x1", "x2"] + assert system.costate_names == ["p_x1", "p_x2"] + assert len(system.costate_exprs) == 2 + + def test_augmented_ode_callable(self) -> None: + """The augmented ODE should be callable.""" + system = derive_hamiltonian( + state_equations={"x": "-k*x"}, + state_names=["x"], + input_names=[], + param_names=["k"], + spec=HamiltonianSpec(lagrangian="x**2"), + ) + + # y = [x, p_x], params = {"k": 1.0} + dy = system.augmented_ode(0.0, [1.0, 0.5], {"k": 1.0}) + assert len(dy) == 2 + # dx/dt = -k*x = -1.0 + assert dy[0] == pytest.approx(-1.0) + + def test_with_params(self) -> None: + """Parameters pass through to the compiled ODE.""" + system = derive_hamiltonian( + state_equations={"x": "-alpha*x + u"}, + state_names=["x"], + input_names=["u"], + param_names=["alpha"], + spec=HamiltonianSpec(lagrangian="x**2 + u**2"), + ) + + # With alpha=2, u=0: dx/dt = -2*1 = -2 + dy = system.augmented_ode(0.0, [1.0, 0.0], {"alpha": 2.0, "u": 0.0}) + assert dy[0] == pytest.approx(-2.0) + + def test_missing_state_equation(self) -> None: + """State without equation gets dx/dt = 0.""" + system = derive_hamiltonian( + state_equations={"x1": "-x1"}, + state_names=["x1", "x2"], + input_names=[], + param_names=[], + spec=HamiltonianSpec(lagrangian="x1**2"), + ) + + dy = system.augmented_ode(0.0, [1.0, 2.0, 0.0, 0.0], {}) + # dx2/dt = 0 (no equation) + assert dy[1] == pytest.approx(0.0) + + +class TestDeriveFromModel: + """Test derive_from_model with SymbolicControlModel.""" + + def test_from_symbolic_model(self) -> None: + from gds_control.dsl.elements import Input, State + + from gds_symbolic import SymbolicControlModel + from gds_symbolic.elements import StateEquation + from gds_symbolic.hamiltonian import derive_from_model + + model = SymbolicControlModel( + name="LQR", + states=[State(name="x", initial=1.0)], + inputs=[Input(name="u")], + state_equations=[StateEquation(state_name="x", expr_str="-x + u")], + symbolic_params=["Q", "R"], + ) + + system = derive_from_model( + model, + HamiltonianSpec(lagrangian="Q*x**2 + R*u**2"), + ) + + assert system.state_names == ["x"] + assert system.costate_names == ["p_x"] + dy = system.augmented_ode(0.0, [1.0, 0.5], {"u": 0.0, "Q": 1.0, "R": 1.0}) + assert len(dy) == 2 + + +class TestVerifyConservation: + """Test Hamiltonian conservation checking.""" + + def test_constant_hamiltonian(self) -> None: + """H = const should be detected as conserved.""" + + def h_fn(t, y, params): + return 1.0 # constant + + times = [0.0, 0.1, 0.2, 0.3] + states = [[1.0, 0.5]] * 4 + conserved, max_var = verify_conservation(times, states, h_fn, {}) + assert conserved is True + assert max_var == pytest.approx(0.0) + + def test_varying_hamiltonian(self) -> None: + """H varying beyond tolerance should fail.""" + + def h_fn(t, y, params): + return t # varies with time + + times = [0.0, 0.5, 1.0] + states = [[1.0, 0.5]] * 3 + conserved, max_var = verify_conservation(times, states, h_fn, {}, tolerance=0.1) + assert conserved is False + assert max_var == pytest.approx(1.0) + + def test_empty_trajectory(self) -> None: + conserved, max_var = verify_conservation([], [], lambda t, y, p: 0.0, {}) + assert conserved is True + assert max_var == 0.0 + + def test_integration_with_ode(self) -> None: + """End-to-end: derive → integrate → verify conservation.""" + pytest.importorskip("numpy") + from gds_continuous import ODEModel, ODESimulation + + # Simple decay: dx/dt = -x, L = x^2 + system = derive_hamiltonian( + state_equations={"x": "-x"}, + state_names=["x"], + input_names=[], + param_names=[], + spec=HamiltonianSpec(lagrangian="x**2"), + ) + + model = ODEModel( + state_names=system.augmented_names, + initial_state={"x": 1.0, "p_x": 0.5}, + rhs=system.augmented_ode, + params={}, + ) + sim = ODESimulation(model=model, t_span=(0.0, 1.0)) + results = sim.run() + + # Build H function from the symbolic expression + import sympy as sp + from sympy.parsing.sympy_parser import parse_expr + + x, p_x = sp.Symbol("x"), sp.Symbol("p_x") + h_expr = parse_expr(system.hamiltonian_expr, local_dict={"x": x, "p_x": p_x}) + h_lambda = sp.lambdify([x, p_x], h_expr, modules="math") + + def h_fn(t, y, params): + return h_lambda(y[0], y[1]) + + times = results.times + rows = results.to_list() + states = [[row[n] for n in system.augmented_names] for row in rows] + + # Conservation won't be exact for non-optimal trajectories, + # but we can check the function runs without error + _, max_var = verify_conservation(times, states, h_fn, {}) + assert isinstance(max_var, float) diff --git a/packages/gds-viz/CLAUDE.md b/packages/gds-viz/CLAUDE.md new file mode 100644 index 0000000..5b29ccc --- /dev/null +++ b/packages/gds-viz/CLAUDE.md @@ -0,0 +1,40 @@ +# CLAUDE.md -- gds-viz + +## Package Identity + +`gds-viz` provides visualization for GDS specifications: Mermaid diagram renderers (structural, canonical, architecture, traceability) and phase portrait plotting for continuous-time systems. + +- **Import**: `import gds_viz` +- **Dependencies**: `gds-framework>=0.2.3` +- **Optional**: `[phase]` for matplotlib + numpy + gds-continuous (phase portraits) + +## Architecture + +### Mermaid renderers (no optional deps) + +| Function | Input | Output | +|----------|-------|--------| +| `system_to_mermaid(ir)` | SystemIR | Structural block diagram | +| `block_to_mermaid(block)` | Block | Single block diagram | +| `spec_to_mermaid(spec)` | GDSSpec | Architecture by role/domain | +| `canonical_to_mermaid(can)` | CanonicalGDS | h = f . g decomposition | +| `trace_to_mermaid(spec, entity, var)` | GDSSpec | Backward traceability | +| `params_to_mermaid(spec)` | GDSSpec | Parameter influence graph | + +### Phase portraits (`gds_viz.phase`, requires `[phase]`) + +| Function | Purpose | +|----------|---------| +| `phase_portrait(model, x_var, y_var, ...)` | Full portrait: vector field + trajectories + nullclines | +| `vector_field_plot(model, config)` | Quiver plot only | +| `trajectory_plot(results, x_var, y_var)` | Trajectories in phase space | +| `compute_vector_field(model, config)` | Raw (X, Y, dX, dY) arrays | +| `compute_trajectories(model, ics, ...)` | Integrate multiple ICs → list[ODEResults] | + +Supports >2D systems via `fixed_states` projection (e.g., Lorenz z=25 slice). + +## Commands + +```bash +uv run --package gds-viz pytest packages/gds-viz/tests -v +``` diff --git a/packages/gds-viz/tests/test_phase.py b/packages/gds-viz/tests/test_phase.py index 36c193b..c565bce 100644 --- a/packages/gds-viz/tests/test_phase.py +++ b/packages/gds-viz/tests/test_phase.py @@ -8,6 +8,7 @@ matplotlib = pytest.importorskip("matplotlib") numpy = pytest.importorskip("numpy") +gds_continuous = pytest.importorskip("gds_continuous") from gds_continuous import ODEModel # noqa: E402 from gds_viz.phase import ( # noqa: E402