2026. 3. 29. 12:05ㆍgpt 연구 분석
좋다 형.
아래에 다른 인공지능 사용자도 그대로 복사해서 쓸 수 있는 알고리즘 코드 + 사용 설명 + 일반인용 백서를 한 번에 정리한다.
일반인용 백서
100차 이상 함수 해를 계산하고 구조까지 분석하는 최소 실행 엔진
1. 이게 뭐냐
이 코드는 그냥 “방정식 해 구하는 코드”가 아니다.
보통 인공지능이나 계산기는
다항식이 들어오면 해를 숫자 목록으로만 뽑아낸다.
그런데 이 엔진은 거기서 한 단계 더 간다.
해를 구한 뒤에, 그 해들이
- 서로 대칭인지
- 켤레쌍인지
- 비슷한 반지름과 위상 구조를 가지는지
를 같이 본다.
즉, 단순 계산기가 아니라
고차 함수 해를 계산하고, 그 해의 배치 구조까지 읽는 엔진
이다.
2. 이 코드로 할 수 있는 것
이 코드는 다음을 한다.
- 다항식 계수를 넣으면 해를 계산한다
- 해들의 구조를 본다
- 구조를 세 가지로 분류한다
- structured
- asymmetric
- hybrid
- 해들을 pair나 block으로 묶는다
- 다시 다항식으로 조립해본다
- 원래 식과 얼마나 비슷한지 오차를 계산한다
즉 결과가 단순 숫자 리스트로 끝나지 않는다.
3. 왜 설명만 주는 것보다 코드까지 주는 게 낫냐
설명만 주면 다른 인공지능이
- 알아서 코드를 새로 짜긴 할 수 있지만
- 자기 방식으로 바꾸거나
- 일부 기능을 빼먹거나
- 단순 근 계산기로 축소할 수 있다
반면 코드까지 같이 주면
- 바로 실행 가능
- 바로 테스트 가능
- 바로 확장 가능
- 다른 AI가 수정·보완하기 쉬움
그래서 제일 좋은 방식은
설명 + 코드 + 사용 예시를 한 번에 주는 것
이다.
4. 다른 AI에 넘길 때 같이 붙이면 되는 설명문
아래 문장을 그대로 복사해서 다른 인공지능에 붙여넣으면 된다.
이 코드는 100차 이상 고차 다항식의 근을 수치적으로 계산한 뒤,
근 구조를 structured / asymmetric / hybrid로 분류하고,
sign pair / conjugate pair / phase-near pair 후보를 생성하여
블록 단위로 분해·재조립하는 최소 실행 프로토타입이다.
목적은 단순히 해를 구하는 것이 아니라,
고차 다항식의 근 배치를 AI가 인식 가능한 구조 정보로 바꾸는 것이다.
해야 할 일:
1. 코드를 직접 실행하라
2. 100차, 200차, 500차 이상에서 테스트하라
3. 수치 안정성을 점검하라
4. 필요하면 numpy.roots 대신 companion matrix / eigenvalue solver로 교체하라
5. 3-root / 4-root block까지 확장하라
6. 결과를 표, 그래프, 오차 분석으로 정리하라
5. 최소 실행 Python 코드
아래 코드는 복붙해서 바로 실행 가능한 기본판이다.
import argparse
import json
from dataclasses import dataclass, asdict
from typing import List, Dict, Any, Callable
import numpy as np
EPS = 1e-12
@dataclass
class PairCandidate:
i: int
j: int
kind: str
weight: float
raw_cost: float
@dataclass
class AnalysisResult:
mode: str
sign_pair_rate: float
conjugate_pair_rate: float
phase_near_rate: float
radius_repeatability: float
coefficient_error: float
root_error: float
num_blocks: int
blocks: List[Dict[str, Any]]
critical_points: List[List[float]]
def normalize_coeffs(coeffs: np.ndarray) -> np.ndarray:
coeffs = np.array(coeffs, dtype=np.complex128)
idx = 0
while idx < len(coeffs) and abs(coeffs[idx]) < EPS:
idx += 1
if idx == len(coeffs):
raise ValueError("All coefficients are zero.")
coeffs = coeffs[idx:]
if abs(coeffs[0]) < EPS:
return coeffs
return coeffs / coeffs[0]
def compute_roots(coeffs: List[complex]) -> np.ndarray:
coeffs = normalize_coeffs(np.array(coeffs, dtype=np.complex128))
return np.roots(coeffs)
def coeffs_from_roots(roots: np.ndarray) -> np.ndarray:
return normalize_coeffs(np.poly(np.array(roots, dtype=np.complex128)))
def coeff_error(c1: np.ndarray, c2: np.ndarray) -> float:
c1 = normalize_coeffs(c1)
c2 = normalize_coeffs(c2)
n = max(len(c1), len(c2))
a = np.zeros(n, dtype=np.complex128)
b = np.zeros(n, dtype=np.complex128)
a[-len(c1):] = c1
b[-len(c2):] = c2
return float(np.linalg.norm(a - b) / (np.linalg.norm(a) + EPS))
def sort_roots(roots: np.ndarray) -> np.ndarray:
return np.array(sorted(
roots,
key=lambda z: (round(abs(z), 12), round(np.angle(z), 12), round(z.real, 12), round(z.imag, 12))
))
def root_error(r1: np.ndarray, r2: np.ndarray) -> float:
a = sort_roots(np.array(r1, dtype=np.complex128))
b = sort_roots(np.array(r2, dtype=np.complex128))
if len(a) != len(b):
return float("inf")
return float(np.mean(np.abs(a - b)))
def sign_cost(a: complex, b: complex) -> float:
return abs(a + b)
def conj_cost(a: complex, b: complex) -> float:
return abs(a - np.conjugate(b))
def angle_dist(t1: float, t2: float) -> float:
d = abs(t1 - t2) % (2 * np.pi)
return min(d, 2 * np.pi - d)
def phase_near_cost(a: complex, b: complex) -> float:
ra, rb = abs(a), abs(b)
if ra < EPS or rb < EPS:
return float("inf")
rd = abs(ra - rb) / max(1.0, max(ra, rb))
td = angle_dist(np.angle(a), np.angle(b)) / np.pi
return rd + td
def greedy_pair_rate(roots: np.ndarray, cost_fn: Callable[[complex, complex], float], tol: float) -> float:
n = len(roots)
used = [False] * n
matched = 0
for i in range(n):
if used[i]:
continue
best_j = None
best_cost = float("inf")
for j in range(i + 1, n):
if used[j]:
continue
c = cost_fn(roots[i], roots[j])
if c < best_cost:
best_cost = c
best_j = j
if best_j is not None and best_cost <= tol:
used[i] = True
used[best_j] = True
matched += 2
return matched / n if n > 0 else 0.0
def radius_repeatability(roots: np.ndarray, tol: float = 1e-3) -> float:
radii = np.sort(np.abs(roots))
if len(radii) < 2:
return 1.0
groups = []
current = [radii[0]]
for r in radii[1:]:
if abs(r - np.mean(current)) <= tol * max(1.0, np.mean(current)):
current.append(r)
else:
groups.append(current)
current = [r]
groups.append(current)
repeated = sum(len(g) for g in groups if len(g) >= 2)
return repeated / len(radii)
def structure_metrics(roots: np.ndarray) -> Dict[str, float]:
scale = max(1.0, np.mean(np.abs(roots)))
return {
"sign_pair_rate": greedy_pair_rate(roots, sign_cost, 0.05 * scale),
"conjugate_pair_rate": greedy_pair_rate(roots, conj_cost, 0.05 * scale),
"phase_near_rate": greedy_pair_rate(roots, phase_near_cost, 0.15),
"radius_repeatability": radius_repeatability(roots),
}
def route_mode(metrics: Dict[str, float]) -> str:
spr = metrics["sign_pair_rate"]
cpr = metrics["conjugate_pair_rate"]
ppr = metrics["phase_near_rate"]
rr = metrics["radius_repeatability"]
structured_score = 1.35 * spr + 0.35 * rr + 0.15 * ppr
asymmetric_score = 1.35 * cpr + 0.25 * rr + 0.20 * ppr
hybrid_score = 1.10 * min(spr, cpr) + 0.40 * ((spr + cpr) / 2.0) + 0.20 * ppr + 0.15 * rr
if spr >= 0.45 and cpr >= 0.45:
return "hybrid"
scores = {
"structured": structured_score,
"asymmetric": asymmetric_score,
"hybrid": hybrid_score,
}
return max(scores, key=scores.get)
def route_weight(kind: str, mode: str) -> float:
if mode == "structured":
return {"sign": 1.0, "conjugate": 0.65, "phase_near": 0.45}[kind]
if mode == "asymmetric":
return {"sign": 0.65, "conjugate": 1.0, "phase_near": 0.45}[kind]
return {"sign": 0.95, "conjugate": 0.95, "phase_near": 0.55}[kind]
def build_candidates(roots: np.ndarray, mode: str) -> List[PairCandidate]:
candidates = []
scale = max(1.0, np.mean(np.abs(roots)))
sign_thr = 0.08 * scale
conj_thr = 0.08 * scale
phase_thr = 0.18
n = len(roots)
for i in range(n):
for j in range(i + 1, n):
a, b = roots[i], roots[j]
sc = sign_cost(a, b)
if sc <= sign_thr:
w = route_weight("sign", mode) * (1.0 - min(sc / (sign_thr + EPS), 1.0))
candidates.append(PairCandidate(i, j, "sign", w, sc))
cc = conj_cost(a, b)
if cc <= conj_thr:
w = route_weight("conjugate", mode) * (1.0 - min(cc / (conj_thr + EPS), 1.0))
candidates.append(PairCandidate(i, j, "conjugate", w, cc))
pc = phase_near_cost(a, b)
if pc <= phase_thr:
w = route_weight("phase_near", mode) * (1.0 - min(pc / (phase_thr + EPS), 1.0))
candidates.append(PairCandidate(i, j, "phase_near", w, pc))
candidates.sort(key=lambda x: (-x.weight, x.raw_cost))
return candidates
def select_pairs_global(candidates: List[PairCandidate]) -> List[PairCandidate]:
used = set()
selected = []
for c in candidates:
if c.i in used or c.j in used:
continue
selected.append(c)
used.add(c.i)
used.add(c.j)
return selected
def make_blocks(roots: np.ndarray, pairs: List[PairCandidate]) -> List[Dict[str, Any]]:
used = set()
blocks = []
for p in pairs:
a, b = roots[p.i], roots[p.j]
coeffs = np.poly(np.array([a, b], dtype=np.complex128))
blocks.append({
"type": "quadratic",
"kind": p.kind,
"indices": [p.i, p.j],
"roots": [[float(a.real), float(a.imag)], [float(b.real), float(b.imag)]],
"coeffs": [[float(c.real), float(c.imag)] for c in coeffs],
})
used.add(p.i)
used.add(p.j)
for i, r in enumerate(roots):
if i not in used:
coeffs = np.poly(np.array([r], dtype=np.complex128))
blocks.append({
"type": "linear",
"kind": "single",
"indices": [i],
"roots": [[float(r.real), float(r.imag)]],
"coeffs": [[float(c.real), float(c.imag)] for c in coeffs],
})
return blocks
def reconstruct_from_blocks(blocks: List[Dict[str, Any]]) -> tuple[np.ndarray, np.ndarray]:
coeffs = np.array([1.0 + 0j], dtype=np.complex128)
roots_out = []
for block in blocks:
if block["type"] == "quadratic":
r1 = block["roots"][0][0] + 1j * block["roots"][0][1]
r2 = block["roots"][1][0] + 1j * block["roots"][1][1]
coeffs = np.convolve(coeffs, np.poly(np.array([r1, r2], dtype=np.complex128)))
roots_out.extend([r1, r2])
else:
r = block["roots"][0][0] + 1j * block["roots"][0][1]
coeffs = np.convolve(coeffs, np.poly(np.array([r], dtype=np.complex128)))
roots_out.append(r)
return normalize_coeffs(coeffs), np.array(roots_out, dtype=np.complex128)
def critical_points(coeffs: np.ndarray) -> np.ndarray:
coeffs = normalize_coeffs(coeffs)
if len(coeffs) <= 2:
return np.array([], dtype=np.complex128)
return np.roots(np.polyder(coeffs))
def analyze_polynomial(coeffs: List[complex]) -> AnalysisResult:
coeffs = normalize_coeffs(np.array(coeffs, dtype=np.complex128))
roots = compute_roots(coeffs)
metrics = structure_metrics(roots)
mode = route_mode(metrics)
candidates = build_candidates(roots, mode)
pairs = select_pairs_global(candidates)
blocks = make_blocks(roots, pairs)
recon_coeffs, recon_roots = reconstruct_from_blocks(blocks)
return AnalysisResult(
mode=mode,
sign_pair_rate=float(metrics["sign_pair_rate"]),
conjugate_pair_rate=float(metrics["conjugate_pair_rate"]),
phase_near_rate=float(metrics["phase_near_rate"]),
radius_repeatability=float(metrics["radius_repeatability"]),
coefficient_error=float(coeff_error(coeffs, recon_coeffs)),
root_error=float(root_error(roots, recon_roots)),
num_blocks=len(blocks),
blocks=blocks,
critical_points=[[float(z.real), float(z.imag)] for z in critical_points(coeffs)],
)
def roots_to_coeffs(roots: List[complex]) -> np.ndarray:
return normalize_coeffs(np.poly(np.array(roots, dtype=np.complex128)))
def synthetic_structured(deg: int, seed: int = 0) -> np.ndarray:
rng = np.random.default_rng(seed)
roots = []
for _ in range(deg // 2):
if rng.random() < 0.5:
a = rng.uniform(0.4, 2.2)
roots.extend([a, -a])
else:
b = rng.uniform(0.4, 2.2)
roots.extend([1j * b, -1j * b])
if deg % 2 == 1:
roots.append(rng.uniform(-1.0, 1.0))
return roots_to_coeffs(roots)
def synthetic_asymmetric(deg: int, seed: int = 0) -> np.ndarray:
rng = np.random.default_rng(seed)
roots = []
for _ in range(deg // 2):
a = rng.uniform(-1.5, 1.5)
b = rng.uniform(0.2, 2.0)
z = a + 1j * b
roots.extend([z, np.conjugate(z)])
if deg % 2 == 1:
roots.append(rng.uniform(-1.0, 1.0))
return roots_to_coeffs(roots)
def synthetic_hybrid(deg: int, seed: int = 0) -> np.ndarray:
rng = np.random.default_rng(seed)
roots = []
while len(roots) + 4 <= deg:
a = rng.uniform(0.3, 1.8)
b = rng.uniform(0.3, 1.8)
z = a + 1j * b
roots.extend([z, np.conjugate(z), -z, -np.conjugate(z)])
while len(roots) + 2 <= deg:
if rng.random() < 0.5:
a = rng.uniform(0.4, 2.0)
roots.extend([a, -a])
else:
a = rng.uniform(-1.2, 1.2)
b = rng.uniform(0.2, 1.5)
z = a + 1j * b
roots.extend([z, np.conjugate(z)])
if len(roots) < deg:
roots.append(rng.uniform(-1.0, 1.0))
return roots_to_coeffs(roots[:deg])
def get_case(case: str, deg: int, seed: int) -> np.ndarray:
if case == "structured":
return synthetic_structured(deg, seed)
if case == "asymmetric":
return synthetic_asymmetric(deg, seed)
if case == "hybrid":
return synthetic_hybrid(deg, seed)
raise ValueError("Unknown case")
def parse_coeffs_arg(s: str) -> List[complex]:
raw = json.loads(s)
out = []
for x in raw:
if isinstance(x, list) and len(x) == 2:
out.append(complex(x[0], x[1]))
else:
out.append(complex(x))
return out
def main():
parser = argparse.ArgumentParser(description="ZPX Inverse Engine - public prototype")
parser.add_argument("--coeffs", type=str, default=None, help='예: "[1,0,-3,0,2]"')
parser.add_argument("--case", type=str, default=None, choices=["structured", "asymmetric", "hybrid"])
parser.add_argument("--deg", type=int, default=20)
parser.add_argument("--seed", type=int, default=0)
parser.add_argument("--stress", action="store_true")
parser.add_argument("--degrees", type=int, nargs="*", default=[20, 50, 100, 150])
args = parser.parse_args()
if args.stress:
results = []
for deg in args.degrees:
for case in ["structured", "asymmetric", "hybrid"]:
coeffs = get_case(case, deg, args.seed)
res = analyze_polynomial(coeffs)
row = asdict(res)
row["degree"] = deg
row["case"] = case
results.append(row)
print(json.dumps(results, indent=2, ensure_ascii=False))
return
if args.coeffs is not None:
coeffs = parse_coeffs_arg(args.coeffs)
result = analyze_polynomial(coeffs)
print(json.dumps(asdict(result), indent=2, ensure_ascii=False))
return
if args.case is not None:
coeffs = get_case(args.case, args.deg, args.seed)
result = analyze_polynomial(coeffs)
obj = asdict(result)
obj["degree"] = args.deg
obj["case"] = args.case
print(json.dumps(obj, indent=2, ensure_ascii=False))
return
parser.print_help()
if __name__ == "__main__":
main()
6. 사용 방법
방법 1. 내가 가진 함수 계수 넣기
예를 들어
[
x^4 - 3x^2 + 2
]
이면 계수는 [1, 0, -3, 0, 2] 이다.
실행:
python zpx_inverse_engine.py --coeffs "[1,0,-3,0,2]"
방법 2. 100차 structured 테스트
python zpx_inverse_engine.py --case structured --deg 100
방법 3. 120차 hybrid 테스트
python zpx_inverse_engine.py --case hybrid --deg 120
방법 4. 20, 50, 100, 150차 한 번에 테스트
python zpx_inverse_engine.py --stress --degrees 20 50 100 150
7. 결과를 어떻게 보면 되냐
실행하면 JSON 비슷한 결과가 나온다.
예를 들어 이런 값들이 보인다.
- mode
- sign_pair_rate
- conjugate_pair_rate
- phase_near_rate
- radius_repeatability
- coefficient_error
- root_error
- num_blocks
의미는 이렇다.
mode
해 구조가 어느 쪽에 가까운지 보여준다.
- structured: 부호 대칭쌍이 강함
- asymmetric: 켤레쌍이 강함
- hybrid: 둘 다 강함
sign_pair_rate
해들이 ((r, -r)) 구조로 얼마나 잘 묶이는지
conjugate_pair_rate
해들이 ((z, \bar z)) 구조로 얼마나 잘 묶이는지
phase_near_rate
위상과 반지름이 비슷한 해들이 얼마나 있는지
coefficient_error
다시 조립한 다항식의 계수가 원래와 얼마나 차이 나는지
root_error
다시 조립한 해집합이 원래 해집합과 얼마나 차이 나는지
8. 일반인이 이해하기 쉽게 한 줄 요약
이 엔진은
100차 이상 고차 함수의 해를 계산하고, 그 해들이 어떤 구조로 배열되어 있는지까지 같이 분석하는 코드
다.
9. 이 코드를 다른 AI가 어떻게 확장하면 되냐
다른 인공지능이나 개발자는 여기서 바로 다음을 붙이면 된다.
- numpy.roots 대신 더 안정적인 solver
- companion matrix 방식
- eigenvalue 기반 방식
- 3-root / 4-root block
- 진짜 전역 그래프 최적화
- plot / 시각화
- CSV / 보고서 자동 저장
- 더 큰 차수 200, 500, 1000 테스트
즉 지금 코드는 “완성 끝”이 아니라
바로 확장 가능한 기준 엔진
이다.
10. 마지막 정리
형이 원하는 목적에 맞게 가장 현실적으로 정리하면 이거다.
- 일반인도 읽을 수 있게 설명했다
- 다른 인공지능 사용자도 복붙해서 쓸 수 있게 했다
- Python 코드도 같이 넣었다
- 사용 방법도 같이 적었다
- 100차 이상 함수 해 계산 실험에 바로 쓸 수 있다
원하면 다음 답변에서 이걸 바로
네이버 블로그 업로드용 문체로 다시 다듬어주겠다.