From cf626495a2b26ce72a74ff7c7ab62b59c625d84a Mon Sep 17 00:00:00 2001 From: rohanrog Date: Wed, 4 Mar 2026 16:05:09 +0530 Subject: [PATCH] feat: add Pattern.compile() and Pattern.compile_system() convenience methods Aligns OGS API with other DSLs where Model.compile() and Model.compile_system() are the standard entry points. Also fix lint/format issues from merged PRs. Closes #90 --- .../gds_business/supplychain/model.py | 6 +- .../gds/verification/generic_checks.py | 5 +- packages/gds-games/ogs/dsl/pattern.py | 19 +++ .../gds-games/tests/test_pattern_compile.py | 108 ++++++++++++++++++ 4 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 packages/gds-games/tests/test_pattern_compile.py diff --git a/packages/gds-business/gds_business/supplychain/model.py b/packages/gds-business/gds_business/supplychain/model.py index 5614da6..5e1329c 100644 --- a/packages/gds-business/gds_business/supplychain/model.py +++ b/packages/gds-business/gds_business/supplychain/model.py @@ -64,13 +64,11 @@ def _validate_structure(self) -> Self: for s in self.shipments: if s.source not in node_names: errors.append( - f"Shipment {s.name!r} source {s.source!r} " - f"is not a declared node" + f"Shipment {s.name!r} source {s.source!r} is not a declared node" ) if s.target not in node_names: errors.append( - f"Shipment {s.name!r} target {s.target!r} " - f"is not a declared node" + f"Shipment {s.name!r} target {s.target!r} is not a declared node" ) # 4. Demand target references a declared node diff --git a/packages/gds-framework/gds/verification/generic_checks.py b/packages/gds-framework/gds/verification/generic_checks.py index 08ad623..f616a0c 100644 --- a/packages/gds-framework/gds/verification/generic_checks.py +++ b/packages/gds-framework/gds/verification/generic_checks.py @@ -78,10 +78,7 @@ def check_g002_signature_completeness(system: SystemIR) -> list[Finding]: # BoundaryAction blocks have no inputs by design — only check outputs is_boundary = block.block_type == "boundary" - if is_boundary: - has_required = has_output - else: - has_required = has_input and has_output + has_required = has_output if is_boundary else has_input and has_output missing = [] if not has_input: diff --git a/packages/gds-games/ogs/dsl/pattern.py b/packages/gds-games/ogs/dsl/pattern.py index 33b647a..ec81004 100644 --- a/packages/gds-games/ogs/dsl/pattern.py +++ b/packages/gds-games/ogs/dsl/pattern.py @@ -3,6 +3,11 @@ """ from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from gds.ir.models import SystemIR + from gds.spec import GDSSpec from pydantic import BaseModel, Field @@ -140,3 +145,17 @@ def specialize( else self.initializations, source=source if source is not None else self.source, ) + + # ── Compilation ───────────────────────────────────────── + + def compile(self) -> GDSSpec: + """Compile this pattern to a GDS specification.""" + from ogs.dsl.spec_bridge import compile_pattern_to_spec + + return compile_pattern_to_spec(self) + + def compile_system(self) -> SystemIR: + """Compile this pattern to a flat SystemIR for verification + visualization.""" + from ogs.dsl.compile import compile_to_ir + + return compile_to_ir(self).to_system_ir() diff --git a/packages/gds-games/tests/test_pattern_compile.py b/packages/gds-games/tests/test_pattern_compile.py new file mode 100644 index 0000000..6cd8baf --- /dev/null +++ b/packages/gds-games/tests/test_pattern_compile.py @@ -0,0 +1,108 @@ +"""Tests for Pattern.compile() and Pattern.compile_system() convenience methods.""" + +from gds.ir.models import SystemIR +from gds.spec import GDSSpec +from gds.types.interface import port +from ogs.dsl.games import CovariantFunction, DecisionGame +from ogs.dsl.pattern import Pattern, PatternInput +from ogs.dsl.types import CompositionType, InputType, Signature + + +def _sequential_pattern() -> Pattern: + a = CovariantFunction( + name="Transform A", + signature=Signature( + x=(port("Raw Input"),), + y=(port("Intermediate"),), + ), + ) + b = CovariantFunction( + name="Transform B", + signature=Signature( + x=(port("Intermediate"),), + y=(port("Final Output"),), + ), + ) + return Pattern( + name="Simple Sequential", + game=a >> b, + composition_type=CompositionType.SEQUENTIAL, + ) + + +def _pattern_with_inputs() -> Pattern: + agent = DecisionGame( + name="Agent", + signature=Signature( + x=(port("Observation"),), + y=(port("Action"),), + r=(port("Reward"),), + ), + ) + return Pattern( + name="Agent With Input", + game=agent, + inputs=[ + PatternInput( + name="External Signal", + input_type=InputType.EXTERNAL_WORLD, + target_game="Agent", + flow_label="Observation", + ), + ], + composition_type=CompositionType.SEQUENTIAL, + ) + + +class TestPatternCompile: + """Pattern.compile() returns a GDSSpec.""" + + def test_returns_gds_spec(self): + spec = _sequential_pattern().compile() + assert isinstance(spec, GDSSpec) + + def test_spec_name_matches_pattern(self): + spec = _sequential_pattern().compile() + assert spec.name == "Simple Sequential" + + def test_spec_contains_blocks(self): + spec = _sequential_pattern().compile() + assert len(spec.blocks) >= 2 + + def test_compile_with_inputs(self): + spec = _pattern_with_inputs().compile() + assert isinstance(spec, GDSSpec) + assert "External Signal" in spec.blocks + + def test_matches_standalone_function(self): + """compile() produces the same result as calling the standalone function.""" + from ogs.dsl.spec_bridge import compile_pattern_to_spec + + pattern = _sequential_pattern() + assert pattern.compile().name == compile_pattern_to_spec(pattern).name + + +class TestPatternCompileSystem: + """Pattern.compile_system() returns a SystemIR.""" + + def test_returns_system_ir(self): + sir = _sequential_pattern().compile_system() + assert isinstance(sir, SystemIR) + + def test_system_ir_has_blocks(self): + sir = _sequential_pattern().compile_system() + assert len(sir.blocks) >= 2 + + def test_compile_system_with_inputs(self): + sir = _pattern_with_inputs().compile_system() + assert isinstance(sir, SystemIR) + + def test_matches_standalone_pipeline(self): + """compile_system() produces the same result as the standalone pipeline.""" + from ogs.dsl.compile import compile_to_ir + + pattern = _sequential_pattern() + expected = compile_to_ir(pattern).to_system_ir() + result = pattern.compile_system() + assert len(result.blocks) == len(expected.blocks) + assert len(result.wirings) == len(expected.wirings)