Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ipe/v1/schema/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ class IOFieldSpec(BaseModel):
"구조적 해소 — 차원이 데이터 의존이라 정적 range 로 표현 불가하기 때문."
),
)
reference_kind: Literal["index", "cardinality"] = Field(
default="index",
description=(
"references 스칼라의 **의미** (서술 분기 — 생성 바이트는 두 경우 동일). "
"index=collection 의 특정 원소/정점을 가리키는 **위치 번호**(현행·기본; "
"graph 출발/도착 s·t, 질의 인덱스). cardinality=collection 크기에 묶인 "
"**개수/수량**으로, 위치 인덱스가 아니다(예: binary_search '적어도 K개' "
"에서 K). 둘 다 생성 범위는 [base, base+크기-1] 로 byte-identical 하나 "
"input_format/constraints 서술이 '가리키는 번호' ↔ '~ 이하의 개수' 로 "
"갈린다. references 미설정 시 무의미. narrative 가 K 를 '개수'로 서술하는데 "
"여기서 index 로 두면 'index↔count' 모순으로 QA reject 되던 결함의 해소."
),
)
cols_range: ConstraintRange | None = Field(
default=None,
description=(
Expand Down
29 changes: 22 additions & 7 deletions ipe/v2/generation/input_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,14 @@ def _int_array_format(field: IOFieldSpec) -> str:

def _render_field(field: IOFieldSpec, indexing: int) -> str:
if _is_reference(field):
# 참조 스칼라 — 가리키는 collection 의 원소/정점 번호 (indexing base).
# 참조 스칼라 — 가리키는 collection 의 크기에 묶인 값 (indexing base).
lo, bound = ("0", "크기 미만") if indexing == 0 else ("1", "크기 이하")
if field.reference_kind == "cardinality":
# 개수/수량 (위치 인덱스 아님) — narrative 의 '개수' 서술과 정합.
return (
f"{field.name}: 한 줄에 정수 하나 — {field.references} 의 크기에 묶인 "
f"개수/수량 ({lo} 이상 {field.references} 의 {bound}). 위치 인덱스가 아니다."
)
label = "0-indexed" if indexing == 0 else "1-indexed"
return (
f"{field.name}: 한 줄에 정수 하나 — {field.references} 의 원소/정점을 가리키는 "
Expand Down Expand Up @@ -231,7 +237,8 @@ def describe_io_field(field: IOFieldSpec) -> str:
"""
head = f"{field.name}:{field.type}"
if _is_reference(field):
return f"{head} →refs {field.references}(1..|{field.references}|)"
kind = "개수" if field.reference_kind == "cardinality" else "위치번호"
return f"{head} →refs {field.references}(1..|{field.references}|, {kind})"
rng = ""
if field.size_range is not None:
rng += f" size[{field.size_range.min_value}..{field.size_range.max_value}]"
Expand Down Expand Up @@ -284,18 +291,26 @@ def render_constraints(io_schema: IOSchema) -> list[ConstraintRange]:
)
if symbolic is not None and base == 0:
symbolic = f"{symbolic}-1" # 0-indexed → '크기 미만' = [0, V-1]
label = "0-indexed" if base == 0 else "1-indexed"
bound = "크기 미만" if base == 0 else "크기 이하"
if f.reference_kind == "cardinality":
# 개수/수량 (위치 인덱스 아님) — input_format·narrative 와 같은 의미.
desc = (
f"{f.references} 의 크기에 묶인 개수/수량 "
f"({base} 이상 {f.references} 의 {bound})"
)
else:
label = "0-indexed" if base == 0 else "1-indexed"
desc = (
f"{f.references} 의 {label} 번호 "
f"({base} 이상 {f.references} 의 {bound})"
)
out.append(
ConstraintRange(
name=f.name,
min_value=base,
max_value=base + size_hi - 1,
symbolic_max=symbolic,
description=(
f"{f.references} 의 {label} 번호 "
f"({base} 이상 {f.references} 의 {bound})"
),
description=desc,
)
)
continue
Expand Down
8 changes: 8 additions & 0 deletions ipe/v2/nodes/formalizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@
**trivial 퇴화**(QA difficulty reject), ``[1,V상한]`` 으로 잡으면 작은 그래프에서
**V 초과 범위밖 입력**(정해 IndexError → fail_synthesis)이 된다. ``references`` 가
둘 다 구조적으로 차단한다 (정점 질의는 거의 항상 이 방식).
- **reference_kind (의미 구분)**: 그 스칼라가 collection 의 **특정 원소/정점을 가리키는
위치 번호**이면 ``reference_kind="index"``(기본; graph s·t, 질의 인덱스). 반대로
collection **크기에 묶인 개수/수량**(위치가 아님)이면 반드시
``reference_kind="cardinality"`` 로 둔다 — 예: binary_search '적어도 K개를 만족하는
최소값' 에서 K(1≤K≤N 인 목표 **개수**, N번째 원소가 아님), '상위 K개 선택'의 K.
두 경우 생성 범위는 [1, 크기] 로 동일하지만, cardinality 인데 index(기본)로 두면
형식 계약이 'fields 를 가리키는 1-indexed 번호'로 서술돼 narrative 의 '목표 개수'
서술과 **'index↔count' 모순**을 일으켜 QA reject 된다.
- **중복 카운트 금지** (위 graph 규율을 모든 collection 으로 일반화): collection
필드(int_array/int_matrix/grid/weighted_edges/tree_edges)는 canonical 직렬화에
**자기 크기 헤더**(원소 개수 N / 행·열 R C / 정점·간선 V E)를 **자기접두**로 자체
Expand Down
69 changes: 69 additions & 0 deletions tests/v2/test_input_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,75 @@ def test_reference_into_int_array_bound_to_element_count() -> None:
assert 1 <= k <= 6 # 원소 개수 이내 1-indexed


# ---------- reference_kind: index(위치) vs cardinality(개수) 서술 분기 ----------


def _cardinality_schema(kind: str) -> IOSchema:
"""[int_array fields, int K→fields(reference_kind=kind)] — binary_search '적어도 K개' 형상."""
return IOSchema(
inputs=(
IOFieldSpec(
name="fields",
type="int_array",
size_range=ConstraintRange(name="fields", min_value=4, max_value=4),
value_range=ConstraintRange(name="v", min_value=1, max_value=9),
),
IOFieldSpec(
name="K", type="int", references="fields", reference_kind=kind # type: ignore[arg-type]
),
),
output_type="int",
output_format="x",
)


def test_cardinality_reference_renders_count_prose() -> None:
"""cardinality 참조는 '개수/수량'·'위치 인덱스가 아니다' 로 서술 — index 의 '가리키는
번호' 와 갈려 narrative '목표 개수' 서술과 정합(index↔count 모순 해소)."""
text = render_input_format(_cardinality_schema("cardinality"))
assert "개수" in text and "위치 인덱스가 아니다" in text
assert "가리키는" not in text # 위치 인덱스 단정 안 함
assert "fields 의 크기 이하" in text # 데이터 의존 범위는 보존


def test_index_reference_renders_pointer_prose_unchanged() -> None:
"""index(기본) 참조는 현행 '가리키는 1-indexed 번호' 서술 유지 — graph 무회귀."""
text = render_input_format(_cardinality_schema("index"))
assert "가리키는" in text and "1-indexed" in text
assert "위치 인덱스가 아니다" not in text


def test_cardinality_reference_constraint_description() -> None:
cons_card = {c.name: c for c in render_constraints(_cardinality_schema("cardinality"))}
cons_idx = {c.name: c for c in render_constraints(_cardinality_schema("index"))}
assert "개수" in cons_card["K"].description
assert "번호" not in cons_card["K"].description
assert "번호" in cons_idx["K"].description # index 는 현행 유지
# 숫자 범위·기호는 두 경우 동일 (의미만 갈림, 바인딩 동일)
assert cons_card["K"].min_value == cons_idx["K"].min_value == 1
assert cons_card["K"].symbolic_max == cons_idx["K"].symbolic_max == "N"


def test_cardinality_reference_generation_byte_identical_to_index() -> None:
"""reference_kind 는 서술만 가른다 — 생성 입력 바이트는 index 와 완전 동일."""
contract = GeneratorContract(scale_families=(ScaleFamily(name="s", case_count=12),))
idx = [c.input_text for c in generate_inputs(contract, _cardinality_schema("index"), seed=7)]
card = [
c.input_text
for c in generate_inputs(contract, _cardinality_schema("cardinality"), seed=7)
]
assert idx == card # byte-identical


def test_describe_io_field_marks_reference_kind() -> None:
card = describe_io_field(
IOFieldSpec(name="K", type="int", references="fields", reference_kind="cardinality")
)
idx = describe_io_field(IOFieldSpec(name="s", type="int", references="grid"))
assert "개수" in card # cardinality 마킹
assert "위치번호" in idx # index(기본) 마킹


def test_reference_resolves_regardless_of_field_order() -> None:
"""참조 스칼라가 collection 보다 **앞**에 선언돼도 실제 크기에 바인딩."""
schema = IOSchema(
Expand Down
Loading