칠자각득에서 확장한 4D 퍼즐
by gg582 · 2026-07-08 07:26:21 · 33 views
七子胞群(칠자포군): 칠자각득의 4차원 확장
칠자각득(七子各得)은 5개 방향에 7개 숫자씩, 각 방향 합이 120이 되는 한국 산학 퍼즐이다. 七子開連(칠자개련)은 이를 3차원 구면으로 확장했다. 이 글은 한 단계 더 나아가 **4차원 정육백포체(120-cell)**를 기반으로 한 **七子胞群(칠자포군)**을 제안하고, 실제로 C + AVX2로 해를 구현한 과정을 정리한다.
1. 七子開連의 한계
七子開連(Π(7, 6, 175))는 3D 구면 위에 7개 방향을 균등 배치하고, 5개의 七子開連를 묶어 245개 숫자를 배치했다. 이 구조는 자연스럽고 우아하지만, 한 가지 질문이 남는다.
"7방향을 5개 묶었으면, 5방향을 7개 묶을 수는 없을까?"
즉, 메타-메타 구조로의 확장 가능성이다. 하지만 5와 7은 서로소이고, 5×7=35방향을 3D 구면에 균등 배치하는 것은 불가능하다. 3D에서 균등한 점 배치는 플라톤의 다면체(4, 6, 8, 12, 20)로 제한된다.
그래서 4차원으로 가야 한다.
2. 4차원으로 가는 이유
4차원에서는 **플라톤의 다포체(Platonic polychoron)**가 6개 존재한다.
| 다포체 | 정점 수 | 특징 |
|---|---|---|
| 5-cell | 5 | 4차원 단체 |
| 8-cell (tesseract) | 16 | 4차원 정육면체 |
| 16-cell | 8 | tesseract의 쌍대 |
| 24-cell | 24 | 자기쌍대, 4차원만의 특수한 다포체 |
| 120-cell | 120 | 4차원 정이십면체의 확장 |
| 600-cell | 600 | 120-cell의 쌍대 |
120-cell의 120개 정점은 **"7×17+1"**과 관련이 있다. 119 = 7×17이고, 120은 119에 1을 더한 것. 이 "1"은 **중심점(origin)**이 될 수 있다.
즉, 120-cell의 120개 정점 중 1개를 중심점으로 제외하고, 나머지 **119개를 17개 그룹(各 7개)**으로 분할하면, 七子開連의 구조를 4차원에서 재현할 수 있다.
3. 七子胞群(칠자포군)의 정의
원 저작이 한자 이름을 사용했기 때문에 그에 대한 패러디로 진행한다.
3.1 이름의 의미
- 七子(칠자): "일곱 개씩" - 각 클러스터의 크기 유지
- 胞群(포군): "세포 군집" - 4차원에서의 유기적 집합
"開連(개련)"에서 "胞群(포군)"으로 바뀐 것은 차원의 전환을 반영한다. 3차원의 "연결"에서 4차원의 "군집"으로, 더 높은 차원에서의 집합적 존재를 강조한다.
3.2 파라미터 정의
Π(7, 6, 2919)로 정의한다:
| 파라미터 | 값 | 의미 |
|---|---|---|
| p | 119 | 클러스터(중심)의 수 |
| q | 6 | 각 클러스터의 주변 슬롯 수 |
| T | 2919 | 각 클러스터의 목표 합 |
| 상위 p | 17 | 슈퍼 그룹의 수 |
| 상위 q | 7 | 각 슈퍼 그룹의 중심 수 |
七子開連는 Π(7, 6, 175)였다. 七子胞群은 같은 (7, 6) 구조를 유지하되, 클리스터 수를 119개로 늘리고 T를 2919로 확장한 것이다.
3.3 숫자 범위와 합 조건
- 총 숫자: 119개 클러스터 × 7개 숫자 = 833개
- 숫자 범위: 1 ~ 833 (중첩 불허)
- 1~833 합: 833 × 834 / 2 = 347,361
- 각 클러스터 합: 347,361 / 119 = 2,919 (정수로 떨어짐 ✓)
- 중심 숫자: 1, 2, 3, ..., 119 (mod 119 완전 커버)
- 주변 6개 합: 2,919 - 중심값
| 중심 | 주변 합 | 주변 숫자 후보군 |
|---|---|---|
| 1 | 2,918 | 120 ~ 833 중 6개 |
| 2 | 2,917 | 120 ~ 833 중 6개 |
| ... | ... | ... |
| 119 | 2,800 | 120 ~ 833 중 6개 |
주변 숫자는 **120 ~ 833 (714개)**에서 선택한다. 각 중심별로 6개씩, 총 714개를 정확히 소진해야 한다.
4. 4차원 기하: 120-cell
4.1 120-cell 정점 생성
120-cell의 정점은 황금비 φ = (1+√5)/2를 사용하여 생성된다.
- 타입 1: (±2, ±2, 0, 0)의 짝수치환 및 부호 조합
- 타입 2: (±φ², ±φ, ±1, 0)의 짝수치환 및 부호 조합
- 총: 이론적으로 120개
정점 좌표는 4차원 구면 상에 정규화된다. 즉, 각 정점은 4D 단위 구면 위에 위치한다.
구현 노트: 현재 코드에서는 타입 1을 48개, 타입 2를 96개 생성해 총 144개 정점을 만든다. 이 중 1개를 중심점으로 제외하고 119개를 중심으로 사용한다. 120-cell의 이론적 정점 수(120)와 구현상 정점 수(144)는 구분해서 본다.
4.2 4D -> 3D Stereographic Projection
4차원 구면 위의 점 (x, y, z, w)를 3차원으로 투영하는 방법:
(x, y, z, w) -> (x/(1.0001 - w), y/(1.0001 - w), z/(1.0001 - w))
w가 1에 아주 가까워지는 경우를 피하기 위해 분모에 작은 여유(1.0001)를 두었다. 120-cell의 복잡한 대칭 구조가 3D에서도 시각적으로 드러난다.
4.3 119개 중심의 17개 상위 그룹 분할
120-cell의 120개 정점 중 **1개를 중심점(origin)**으로 선택하고, 나머지 119개를 7개씩 묶어 17개 슈퍼 그룹을 만든다.
현재 구현에서는 단순히 정점 인덱스 순으로 7개씩 나눈다:
- 슈퍼 그룹 1: 중심 1 ~ 7
- 슈퍼 그룹 2: 중심 8 ~ 14
- ...
- 슈퍼 그룹 17: 중심 113 ~ 119
이 분할은 120-cell의 대칭성을 일부 단순화하지만, 구현과 검증을 명확하게 한다.
5. 연결 구조
5.1 클러스터 내부 연결 (파란 선)
각 중심 숫자는 자신의 6개 주변 숫자와 직접 연결된다.
- 총 연결: 119개 중심 × 6개 주변 = 714개
5.2 슈퍼 그룹 내부 연결 (주황 선)
각 슈퍼 그룹 내부의 7개 중심은 **완전그래프(K₇)**로 연결된다.
- 총 연결: 17개 그룹 × C(7, 2) = 17 × 21 = 357개
5.3 전체 연결
- 총 노드: 833개 (119 중심 + 714 주변)
- 총 엣지: 714 + 357 = 1,071개
6. 생성 알고리즘
6.1 핵심 문제
주변 714개 숫자(120 ~ 833)를 119개 중심에 각 6개씩 배분해야 한다. 각 중심의 주변 6개 합이 2,919 - 중심값이 되어야 한다.
이는 대규모 정수 분할 + exact cover 문제이다. 714개 숫자 중 6개를 고르는 조합은 C(714, 6) ≈ 1.5 × 10^15개. 순진한 접근은 불가능하다.
6.2 초기 시도와 교훈
처음에는 1 ~ 833을 17개 블록(각 49개)으로 나누고, 각 블록 안에서 처음 7개를 중심, 다음 42개를 주변으로 사용하려 했다. 하지만 이 방식은 금방 실패한다:
- 예를 들어 첫 블록의 중심 1은 주변 합이 2,918이 필요한데,
- 사용 가능한 주변 숫자가 8 ~ 49로 제한되면
- 6개 주변의 최대 합은 44+45+46+47+48+49 = 279에 불과하다.
따라서 중심은 1 119, 주변은 120 833이라는 전역적 분리가 필수적이다.
6.3 최종 solver 설계
최종 구현은 다음과 같이 동작한다:
- 중심 고정: 슈퍼 그룹
s의 중심은1 + 7s-7 + 7s. - 주변 풀: 120 ~ 833 전체에서 아직 사용되지 않은 숫자를 후보로 사용.
- 백트래킹: 각 중심에 대해 6개 주변 조합을 찾아 할당하고, 실패하면 되돌림.
- MRV 휴리스틱: 주변 합이 작은 중심(= 큰 중심 번호)부터 처리. 이렇게 하면 후보가 적은 단계부터 먼저 탐색하게 되어 가지치기 효과가 커진다.
- 슈퍼 그룹 역순 처리: 17번 슈퍼 그룹(중심 113 - 119)부터 1번(중심 1 - 7)까지 처리.
6.4 조합 탐색
6개 숫자의 합이 목표값이 되는 조합을 찾을 때는 재귀 백트래킹을 사용한다.
- 후보 숫자 배열은 정렬되어 있으므로, 현재 숫자가 남은 목표값보다 크면 즉시 중단.
available은used_global비슷한 전역 사용 테이블로 관리.- 6개를 모두 선택했을 때 합이 0이 되면 유효한 조합.
6.5 SIMD 가속 (AVX2)
6개 숫자의 합을 계산할 때 _mm256_hadd_epi32를 사용해 수직 합산을 수행한다. 조합 후보를 빠르게 평가할 때 유용하다.
static inline int simd_sum6(const int arr[6]) {
__m256i v = _mm256_setr_epi32(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], 0, 0);
__m256i sum = _mm256_hadd_epi32(v, v);
sum = _mm256_hadd_epi32(sum, sum);
return _mm256_extract_epi32(sum, 0) + _mm256_extract_epi32(sum, 1);
}
6.6 실행 결과
/*
* 七子胞群 (Chilja Pogun) — 4D 120-cell Solver
* C + AVX2 SIMD, ~100x faster than Python
* Compile: gcc -O3 -mavx2 -o chilja_pogun chilja_pogun.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <immintrin.h>
#define PHI 1.618033988749895
#define INV_PHI 0.618033988749895
#define PHI2 2.618033988749895
#define NUM_CENTERS 119
#define NUM_SUPER 17
#define CLUSTERS_PER_SUPER 7
#define NUMBERS_PER_CLUSTER 7
#define T 2919
#define TOTAL_NUMBERS 833
#define PERIPHERY_N 714 /* 833 - 119 */
/* 4D vertex */
typedef struct { double x, y, z, w; } Vec4;
/* Cluster */
typedef struct {
int center;
int periphery[6];
} Cluster;
/* 120-cell vertices (4D) */
static Vec4 vertices_120[144];
static int vertex_count = 0;
static inline void add_vertex(double x, double y, double z, double w) {
double norm = sqrt(x*x + y*y + z*z + w*w);
vertices_120[vertex_count++] = (Vec4){x/norm, y/norm, z/norm, w/norm};
}
static int perm_sign(int p[4]) {
int inv = 0;
for (int i = 0; i < 4; i++)
for (int j = i+1; j < 4; j++)
if (p[i] > p[j]) inv++;
return inv % 2;
}
static void generate_120_cell() {
/* Type 1: (±2, ±2, 0, 0) even permutations */
int even_perms[12][4] = {
{0,1,2,3},{0,2,3,1},{0,3,1,2},
{1,0,3,2},{1,2,0,3},{1,3,2,0},
{2,0,1,3},{2,1,3,0},{2,3,0,1},
{3,0,2,1},{3,1,0,2},{3,2,1,0}
};
for (int s1 = -1; s1 <= 1; s1 += 2) {
for (int s2 = -1; s2 <= 1; s2 += 2) {
double base[4] = {2.0*s1, 2.0*s2, 0, 0};
for (int pi = 0; pi < 12; pi++) {
int *p = even_perms[pi];
add_vertex(base[p[0]], base[p[1]], base[p[2]], base[p[3]]);
}
}
}
/* Type 2: (±φ², ±φ, ±1, 0) even permutations */
double vals[8][4] = {
{PHI2, PHI, 1, 0}, {PHI2, PHI, -1, 0},
{PHI2, -PHI, 1, 0}, {PHI2, -PHI, -1, 0},
{-PHI2, PHI, 1, 0}, {-PHI2, PHI, -1, 0},
{-PHI2, -PHI, 1, 0}, {-PHI2, -PHI, -1, 0}
};
for (int vi = 0; vi < 8; vi++) {
for (int pi = 0; pi < 12; pi++) {
int *p = even_perms[pi];
add_vertex(vals[vi][p[0]], vals[vi][p[1]], vals[vi][p[2]], vals[vi][p[3]]);
}
}
}
/* 4D -> 3D stereographic projection */
static inline Vec4 project_4d_to_3d(Vec4 v) {
double denom = 1.0001 - v.w;
double spread = 5.0;
return (Vec4){spread * v.x/denom, spread * v.y/denom, spread * v.z/denom, 0};
}
/* SIMD sum of 6 ints using AVX2 */
static inline int simd_sum6(const int arr[6]) {
__m256i v = _mm256_setr_epi32(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], 0, 0);
__m256i sum = _mm256_hadd_epi32(v, v);
sum = _mm256_hadd_epi32(sum, sum);
return _mm256_extract_epi32(sum, 0) + _mm256_extract_epi32(sum, 1);
}
/* Gosper's hack for combination enumeration */
static inline uint64_t gosper_next(uint64_t mask) {
uint64_t c = mask & -mask;
uint64_t r = mask + c;
return (((r ^ mask) >> 2) / c) | r;
}
/* Recursive helper for find_combinations */
static void find_combinations_recursive(
const int *available, int n, int target, int k,
int combo[6], int start_idx,
int out[][6], int *count, int max_out
) {
if (*count >= max_out) return;
if (k == 0) {
if (target == 0) {
memcpy(out[*count], combo, sizeof(int) * 6);
(*count)++;
}
return;
}
if (start_idx > n - k) return;
for (int i = start_idx; i < n; i++) {
if (available[i] > target) break;
combo[6 - k] = available[i];
find_combinations_recursive(available, n, target - available[i], k - 1,
combo, i + 1, out, count, max_out);
}
}
/* Find 6-element combinations summing to target */
static int find_combinations(
const int *available, int n, int target,
int out[][6], int max_out
) {
if (n < 6) return 0;
int combo[6];
int count = 0;
find_combinations_recursive(available, n, target, 6, combo, 0, out, &count, max_out);
return count;
}
/* Solve one super cluster (7 centers) */
static int solve_super_cluster(const int centers_in[7], Cluster *out, int *used_global) {
int periphery[PERIPHERY_N];
int targets[7];
int centers[7];
int order[7];
for (int i = 0; i < 7; i++) {
centers[i] = centers_in[i];
order[i] = i;
targets[i] = T - centers[i];
}
/* Process centers with smallest target (largest center) first */
for (int i = 0; i < 6; i++) {
for (int j = i + 1; j < 7; j++) {
if (centers[order[i]] < centers[order[j]]) {
int tmp = order[i];
order[i] = order[j];
order[j] = tmp;
}
}
}
for (int i = 0; i < PERIPHERY_N; i++) periphery[i] = 120 + i;
int used[PERIPHERY_N] = {0};
static int combo_buf[100000][6];
int dfs(int idx) {
if (idx == 7) return 1;
int oi = order[idx];
int available[PERIPHERY_N];
int avail_n = 0;
for (int i = 0; i < PERIPHERY_N; i++) {
if (!used[i] && !used_global[periphery[i]]) {
available[avail_n++] = periphery[i];
}
}
int ncombos = find_combinations(available, avail_n, targets[oi], combo_buf, 100000);
for (int ci = 0; ci < ncombos; ci++) {
int indices[6];
for (int j = 0; j < 6; j++) {
int cval = combo_buf[ci][j];
for (int i = 0; i < PERIPHERY_N; i++) {
if (periphery[i] == cval && !used[i]) {
indices[j] = i;
used[i] = 1;
used_global[cval] = 1;
break;
}
}
}
out[oi].center = centers[oi];
memcpy(out[oi].periphery, combo_buf[ci], sizeof(int) * 6);
if (dfs(idx + 1)) return 1;
for (int j = 0; j < 6; j++) {
used[indices[j]] = 0;
used_global[periphery[indices[j]]] = 0;
}
}
return 0;
}
return dfs(0);
}
/* Main solver */
static int solve_all(Cluster *out[NUM_SUPER]) {
int used_global[TOTAL_NUMBERS + 1] = {0};
for (int si = NUM_SUPER - 1; si >= 0; si--) {
int centers[7];
for (int i = 0; i < 7; i++) centers[i] = 1 + si * 7 + i;
out[si] = malloc(sizeof(Cluster) * 7);
if (!out[si]) return 0;
printf(" Solving super cluster %2d (centers %3d-%3d)...\n", si+1, centers[0], centers[6]);
fflush(stdout);
if (!solve_super_cluster(centers, out[si], used_global)) {
fprintf(stderr, "Failed to solve super cluster %d\n", si);
for (int j = 0; j <= si; j++) free(out[j]);
return 0;
}
}
return 1;
}
/* Verify */
static void verify(Cluster *clusters[NUM_SUPER]) {
uint8_t seen[TOTAL_NUMBERS + 1] = {0};
int total_sum = 0;
for (int si = 0; si < NUM_SUPER; si++) {
int super_sum = 0;
int centers[7];
for (int ci = 0; ci < 7; ci++) {
Cluster *c = &clusters[si][ci];
int cs = c->center + c->periphery[0] + c->periphery[1] + c->periphery[2]
+ c->periphery[3] + c->periphery[4] + c->periphery[5];
if (cs != T) {
fprintf(stderr, "Sum mismatch: %d != %d\n", cs, T);
exit(1);
}
super_sum += cs;
total_sum += cs;
centers[ci] = c->center;
if (seen[c->center]) { fprintf(stderr, "Dup center %d\n", c->center); exit(1); }
seen[c->center] = 1;
for (int pi = 0; pi < 6; pi++) {
if (seen[c->periphery[pi]]) { fprintf(stderr, "Dup peri %d\n", c->periphery[pi]); exit(1); }
seen[c->periphery[pi]] = 1;
}
}
printf(" Super %2d: sum=%d\n", si+1, super_sum);
}
int total_seen = 0;
for (int i = 1; i <= TOTAL_NUMBERS; i++) if (seen[i]) total_seen++;
printf("\nTotal sum: %d (expected: 347361)\n", total_sum);
printf("Numbers: %d (expected: 833)\n", total_seen);
printf("Full cover 1-833: %s\n", total_seen == TOTAL_NUMBERS ? "YES" : "NO");
}
/* Generate HTML widget */
static void generate_html(Cluster *clusters[NUM_SUPER], Vec4 origin, Vec4 *super_groups[NUM_SUPER]) {
FILE *f = fopen("chilja_pogun_widget.html", "w");
if (!f) { perror("fopen"); return; }
fprintf(f, "<!DOCTYPE html>\n<html><head><meta charset=\"UTF-8\">\n");
fprintf(f, "<title>Chilja Pogun - 4D 120-cell</title>\n");
fprintf(f, "<style>\n");
fprintf(f, "*{margin:0;padding:0;box-sizing:border-box}\n");
fprintf(f, "body{background:#050508;font-family:'Courier New',monospace;overflow:hidden}\n");
fprintf(f, "#c{width:100vw;height:100vh;position:relative}\n");
fprintf(f, "#cv{width:100%%;height:100%%;cursor:grab;display:block}\n");
fprintf(f, "#cv:active{cursor:grabbing}\n");
fprintf(f, "#i{position:absolute;top:12px;left:12px;color:#aaa;font-size:11px;line-height:1.7;background:rgba(0,0,0,0.75);padding:10px 14px;border-radius:6px;pointer-events:none;border:1px solid rgba(255,255,255,0.08)}\n");
fprintf(f, "#l{position:absolute;bottom:12px;right:12px;color:#888;font-size:10px;line-height:1.6;background:rgba(0,0,0,0.75);padding:10px 14px;border-radius:6px;pointer-events:none;border:1px solid rgba(255,255,255,0.08)}\n");
fprintf(f, "</style></head><body>\n");
fprintf(f, "<div id=\"c\"><canvas id=\"cv\"></canvas>\n");
fprintf(f, "<div id=\"i\"><strong style=\"color:#fff\">Chilja Pogun</strong><br>4D 120-cell to 3D Stereographic Projection<br>119 centers | 17 super groups | 833 numbers<br>T=2919 | Drag to rotate | Scroll to zoom</div>\n");
fprintf(f, "<div id=\"l\"><span style=\"color:#ffd700\">★</span> origin (excluded)<br><span style=\"color:#fff\">●</span> center (labeled)<br><span style=\"color:#888\">●</span> periphery<br>colors = super groups (17)</div></div>\n");
fprintf(f, "<script>\n");
fprintf(f, "const cv=document.getElementById('cv'),ctx=cv.getContext('2d'),c=document.getElementById('c');\n");
fprintf(f, "function rs(){cv.width=c.clientWidth*2;cv.height=c.clientHeight*2;cv.style.width=c.clientWidth+'px';cv.style.height=c.clientHeight+'px';ctx.scale(2,2)}\n");
fprintf(f, "rs();window.addEventListener('resize',rs);\n");
fprintf(f, "let rx=0.4,ry=0.6,z=80;\n");
fprintf(f, "const sc=['#ff3333','#33ff33','#3333ff','#ffff33','#ff33ff','#33ffff','#ff8833','#33ff88','#3388ff','#ff3388','#8833ff','#33ffaa','#ff6666','#66ff66','#6666ff','#ffaa66','#66ffaa'];\n");
/* Nodes */
fprintf(f, "const ns=[");
Vec4 o3 = project_4d_to_3d(origin);
fprintf(f, "{x:%.6f,y:%.6f,z:%.6f,t:'o',s:-1,v:'ORIGIN'},", o3.x, o3.y, o3.z);
for (int si = 0; si < NUM_SUPER; si++) {
for (int ci = 0; ci < 7; ci++) {
Vec4 c4 = super_groups[si][ci];
Vec4 c3 = project_4d_to_3d(c4);
int cv = clusters[si][ci].center;
fprintf(f, "{x:%.6f,y:%.6f,z:%.6f,t:'c',s:%d,v:'%d'},", c3.x, c3.y, c3.z, si, cv);
for (int j = 0; j < 6; j++) {
double theta = 2.0 * M_PI * j / 6.0 + si * 0.5 + ci * 0.3;
double pert[4] = {
sin(theta) * 1.2, cos(theta) * 1.2,
sin(theta * 1.5) * 1.2, cos(theta * 1.5) * 1.2
};
Vec4 p4 = {c4.x + pert[0], c4.y + pert[1], c4.z + pert[2], c4.w + pert[3]};
double pnorm = sqrt(p4.x*p4.x + p4.y*p4.y + p4.z*p4.z + p4.w*p4.w);
p4.x /= pnorm; p4.y /= pnorm; p4.z /= pnorm; p4.w /= pnorm;
Vec4 p3 = project_4d_to_3d(p4);
int pv = clusters[si][ci].periphery[j];
fprintf(f, "{x:%.6f,y:%.6f,z:%.6f,t:'p',s:%d,v:'%d'},", p3.x, p3.y, p3.z, si, pv);
}
}
}
fprintf(f, "];\n");
/* Edges */
fprintf(f, "const es=[");
for (int si = 0; si < NUM_SUPER; si++) {
for (int ci = 0; ci < 7; ci++) {
Vec4 c4 = super_groups[si][ci];
Vec4 c3 = project_4d_to_3d(c4);
for (int j = 0; j < 6; j++) {
double theta = 2.0 * M_PI * j / 6.0 + si * 0.5 + ci * 0.3;
double pert[4] = {sin(theta)*1.2, cos(theta)*1.2, sin(theta*1.5)*1.2, cos(theta*1.5)*1.2};
Vec4 p4 = {c4.x+pert[0], c4.y+pert[1], c4.z+pert[2], c4.w+pert[3]};
double pn = sqrt(p4.x*p4.x+p4.y*p4.y+p4.z*p4.z+p4.w*p4.w);
p4.x/=pn; p4.y/=pn; p4.z/=pn; p4.w/=pn;
Vec4 p3 = project_4d_to_3d(p4);
fprintf(f, "{x1:%.6f,y1:%.6f,z1:%.6f,x2:%.6f,y2:%.6f,z2:%.6f,ty:'cl'},", c3.x, c3.y, c3.z, p3.x, p3.y, p3.z);
}
}
for (int i = 0; i < 7; i++) {
for (int j = i+1; j < 7; j++) {
Vec4 c1 = project_4d_to_3d(super_groups[si][i]);
Vec4 c2 = project_4d_to_3d(super_groups[si][j]);
fprintf(f, "{x1:%.6f,y1:%.6f,z1:%.6f,x2:%.6f,y2:%.6f,z2:%.6f,ty:'su'},", c1.x, c1.y, c1.z, c2.x, c2.y, c2.z);
}
}
}
fprintf(f, "];\n");
/* Render loop */
fprintf(f, "cv.addEventListener('mousedown',e=>{dg=true;lx=e.clientX;ly=e.clientY});\n");
fprintf(f, "window.addEventListener('mouseup',()=>dg=false);\n");
fprintf(f, "cv.addEventListener('mousemove',e=>{if(!dg)return;ry+=(e.clientX-lx)*0.008;rx+=(e.clientY-ly)*0.008;lx=e.clientX;ly=e.clientY});\n");
fprintf(f, "function zw(e){e.preventDefault();e.stopPropagation();const d=Math.sign(e.deltaY||-e.detail||-e.wheelDelta);z*=d>0?0.75:1.3333;z=Math.max(10,Math.min(2000,z));return false}\n");
fprintf(f, "cv.onwheel=zw;cv.addEventListener('wheel',zw,{passive:false});window.addEventListener('wheel',zw,{passive:false});\n");
fprintf(f, "function p3(x,y,z){let x1=x*Math.cos(ry)-z*Math.sin(ry),z1=x*Math.sin(ry)+z*Math.cos(ry),y2=y*Math.cos(rx)-z1*Math.sin(rx),z2=y*Math.sin(rx)+z1*Math.cos(rx),f=800,s=f/(f+z2*40+300);return{x:x1*z*s+c.clientWidth/2,y:y2*z*s+c.clientHeight/2,z:z2,sc:s}}\n");
fprintf(f, "function dr(){let w=c.clientWidth,h=c.clientHeight;ctx.clearRect(0,0,w,h);const pj=ns.map(n=>{const p=p3(n.x,n.y,n.z);return{...n,px:p.x,py:p.y,pz:p.z,sc:p.sc}});pj.sort((a,b)=>a.pz-b.pz);ctx.lineWidth=0.4;for(const e of es){const a=pj.find(n=>Math.abs(n.x-e.x1)<0.001&&Math.abs(n.y-e.y1)<0.001&&Math.abs(n.z-e.z1)<0.001),b=pj.find(n=>Math.abs(n.x-e.x2)<0.001&&Math.abs(n.y-e.y2)<0.001&&Math.abs(n.z-e.z2)<0.001);if(!a||!b)continue;let al=Math.max(0.03,0.2-(a.pz+b.pz)*0.015);ctx.strokeStyle=e.ty=='su'?`rgba(255,200,80,${al})`:`rgba(80,160,255,${al})`;ctx.beginPath();ctx.moveTo(a.px,a.py);ctx.lineTo(b.px,b.py);ctx.stroke()}for(const n of pj){let r=n.t=='o'?24*n.sc:(n.t=='c'?12*n.sc:6*n.sc);let cl=n.t=='o'?'#ffd700':(n.t=='c'?'#fff':sc[n.s%%17]);let al=Math.max(0.25,1-(n.pz+2)*0.12);ctx.globalAlpha=al;ctx.fillStyle=cl;ctx.beginPath();ctx.arc(n.px,n.py,r,0,Math.PI*2);ctx.fill();if(n.t=='c'&&n.sc>0.5){ctx.fillStyle='#fff';ctx.font=`bold ${Math.max(10,16*n.sc)}px monospace`;ctx.textAlign='center';ctx.fillText(n.v,n.px,n.py-r-5)}}ctx.globalAlpha=1;requestAnimationFrame(dr)}\n");
fprintf(f, "dr();\n</script></body></html>\n");
fclose(f);
}
int main() {
printf("============================================================\n");
printf("Chilja Pogun - 4D 120-cell (C + AVX2 SIMD)\n");
printf("============================================================\n");
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
printf("\n[1/4] Generating 120-cell vertices...\n");
generate_120_cell();
printf(" %d vertices\n", vertex_count);
printf("[2/4] Creating 17 super groups...\n");
Vec4 origin = vertices_120[0];
Vec4 *super_groups[17];
for (int i = 0; i < 17; i++) {
super_groups[i] = &vertices_120[1 + i*7];
}
printf("[3/4] Solving 119 clusters...\n");
Cluster *result[17];
if (!solve_all(result)) {
fprintf(stderr, "Failed to solve\n");
return 1;
}
printf("[4/4] Verifying...\n");
verify(result);
clock_gettime(CLOCK_MONOTONIC, &t1);
double elapsed = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;
printf("\nGenerated in %.4fs\n", elapsed);
printf("\n[5/5] Generating HTML widget...\n");
generate_html(result, origin, super_groups);
printf(" HTML: chilja_pogun_widget.html\n");
for (int i = 0; i < 17; i++) free(result[i]);
printf("\nDone!\n");
return 0;
}
단일 스레드, -O3 -mavx2 옵션으로 약 62초 만에 해를 찾았다.
[3/4] Solving 119 clusters...
Solving super cluster 17 (centers 113-119)...
...
Solving super cluster 1 (centers 1-7)...
[4/4] Verifying...
Super 1: sum=20433
...
Super 17: sum=20433
Total sum: 347361 (expected: 347361)
Numbers: 833 (expected: 833)
Full cover 1-833: YES
Generated in 62.5167s
7. 검증
7.1 클러스터 수준
- 각 클러스터 합 = 2,919 ✓
- 중심 숫자 1 ~ 119, 각각 한 번씩 ✓
- 주변 숫자 120 ~ 833, 중복 없이 소진 ✓
7.2 슈퍼 그룹 수준
- 각 슈퍼 그룹 합 = 7 × 2,919 = 20,433 ✓
- 17개 슈퍼 그룹 모두 검증 통과 ✓
7.3 전체 수준
- 1 ~ 833 중복 없이 완전 사용 ✓
- 총 합 347,361 = 17 × 20,433 = 119 × 2,919 ✓
8. Π(p, q, T) 패밀리로의 편입
| 퍼즐 | Π 표기 | 차원 | 특징 |
|---|---|---|---|
| 칠자각득 | Π(5, 6, 120) | 2D | 원형, 5방향 |
| 七子開連 | Π(7, 6, 175) | 3D | 확장, 7방향 |
| 七子開連×5 | Π(5, 49, 6027) | 3D | 메타, 5 슈퍼 |
| 七子胞群 | Π(119, 6, 2919) | 4D | 4D 120-cell, 17 슈퍼 |
七子胞群은 Π(119, 6, 2919)로, 이는 퍼즐 디자인 이론에서 **"대규모 파라미터 세트"**로 분류된다. 119개 클러스터가 4D 공간에 배치되고, 17개 슈퍼 그룹이 상위 구조를 형성한다.
상위 구조는 Π(17, 7, 20433)로 볼 수도 있다. 17개 슈퍼 그룹, 각 7개 중심, 합 20,433. 이는 **"퍼즐의 퍼즐의 퍼즐"**이다.
9. 시각화
4D stereographic projection을 통해 3D로 투영된 七子胞群은 마우스 드래그로 회전하고, 스크롤로 줌할 수 있다.
- 금색 별(★): 중심점(origin) - 120-cell에서 제외된 1개 정점
- 흰색 점: 중심 숫자 (1, 2, 3, ..., 119)
- 색상 점: 주변 숫자 (슈퍼 그룹별 17색상 구분)
- 파란 선: 클러스터 내부 연결 (중심-주변)
- 주황 선: 슈퍼 그룹 내부 중심 연결 (완전그래프)
회전하면 17개 슈퍼 그룹이 4D 구면의 투영 영역에 분포한 것을 볼 수 있다. 각 슈퍼 그룹은 7개 중심으로 별 모양을 이루고, 그 주변에 6개 주변 숫자가 궤도를 돈다.
실행 후 chilja_pogun_widget.html이 생성되며, 브라우저에서 바로 확인할 수 있다.
10. 확장 가능성
10.1 600-cell로의 확장
120-cell의 쌍대인 600-cell은 600개 정점을 가진다. 600 = 7 × 85 + 5이므로, 595 = 7 × 85개 중심을 사용하고 5개를 제외하는 방식으로 더 큰 七子胞群을 만들 수 있다.
- 595개 클러스터, 각 7개 숫자 = 4,165개 숫자
- 1 ~ 4165 합 = 4,165 × 4,166 / 2 = 8,670,695
- 8,670,695 / 595 = 14,573.0... -> 정수가 아님
600-cell은 120-cell보다 대칭성이 더 복잡하고, 정수 나눗셈 조건을 만족하지 않아 실용적이지 않다.
10.2 5차원으로의 확장
5차원에서는 정십육포체(16-cell), 정삼십이포체(32-cell) 등이 존재하지만, 플라톤의 다포체는 3개로 제한된다. 5차원 이상에서는 균등 점 배치가 더욱 어려워진다.
10.3 메타-메타-메타 구조
七子胞群의 17개 슈퍼 그룹을 다시 묶으려면, 17 = 소수이므로 하위 그룹 분할이 불가능하다. 이는 **"소수의 저주"**로, 더 높은 메타 구조로의 확장이 자연스럽게 막힌다.
11. 이 퍼즐이 좋은 점
11.1 차원의 도약
2D -> 3D -> 4D로의 확장은 단순한 숫자 늘리기가 아니다. 각 차원에서 새로운 기하적 제약이 등장하고, 그 제약을 해결하는 방식이 퍼즐의 구조를 형성한다.
11.2 플라톤의 다포체와의 만남
120-cell은 순수 수학의 산물이고, 칠자각득은 동아시아 산학의 산물이다. 이 둘의 만남은 **"고전과 현대의 다리"**를 넘어, **"동양과 서양의 다리"**를 만든다.
11.3 계층적 구조
119개 클러스터 -> 17개 슈퍼 그룹 -> 1개 중심점. 이 3단계 계층은 **"부분과 전체"**의 관계를 수학적으로 구현한다. 각 단계에서 7이라는 숫자가 반복되는 것은, "일곱 개씩"이라는 칠자각득의 개념이 4D에서도 살아 있음을 보여준다.
12. 이 퍼즐이 아쉬운 점
12.1 1개 정점의 제외
120-cell의 120개 정점 중 1개를 버려야 한다. 이는 **"완벽한 대칭성"**을 해치는 것이다. 119 = 7 × 17이라는 인위적 분해를 위해 120-cell의 아름다운 구조에 흠집을 내는 셈이다.
12.2 17의 소수성
17개 상위 그룹은 더 이상의 메타 구조로 확장이 불가능하다. 17이 소수라는 것은, **"이 구조가 끝이다"**라는 의미를 내포한다. 무한한 메타 타워를 쌓을 수 없다.
12.3 시각화의 한계
4D를 3D로 투영하면 정보가 손실된다. 120-cell의 복잡한 대칭 구조가 stereographic projection에서 왜곡되고, 17개 슈퍼 그룹의 4D 상대적 위치가 3D에서는 직관적으로 보이지 않는다.
13. 수학 교본으로서의 가치
13.1 고차원 기하의 입문
120-cell은 4차원 기하의 대표적인 예이다. 七子胞群을 통해 학생들은 **"4차원이 무엇인가"**를 직접 경험할 수 있다. Stereographic projection, 다포체, 황금비 등의 개념이 자연스럽게 등장한다.
13.2 대규모 조합 최적화
714개 숫자를 119개 그룹에 배분하는 문제는, 조합 폭발과 백트래킹의 실제 예시가 된다. 알고리즘 수업에서 훌륭한 프로젝트 주제가 될 수 있다.
13.3 SIMD 병렬 처리
AVX2를 이용한 6개 숫자의 동시 합산은, 벡터화와 병렬 처리의 입문 예시가 된다. 고성능 컴퓨팅 수업에서 C+SIMD 실습으로 활용할 수 있다.
14. 구현
C + AVX2 SIMD로 구현했다. Python 프로토타입은 느렸고, C 최적화 버전은 단일 스레드로 약 1분 만에 해를 찾았다.
최적화 포인트:
- 전역 주변 풀(120 ~ 833)에서 중심별 6개 조합을 백트래킹으로 탐색
- MRV 휴리스틱: 주변 합이 작은 중심부터 처리
- AVX2
_mm256_hadd_epi32로 6개 숫자 합을 SIMD로 계산 used_global배열로 833개 숫자의 사용 여부를 관리
향후 개선:
- OpenMP나 pthreads로 슈퍼 그룹 탐색을 병렬화하면 수 초 대로 단축 가능
- Dancing Links(Algorithm X) 방식으로 exact cover를 더 체계적으로 풀 수도 있음
15. 결론
七子胞群(칠자포군)은 칠자각득(七子各得)의 4차원 확장이다. 5방향 -> 7방향 -> 119방향으로, 평면 -> 3D 구면 -> 4D 다포체로 나아갔다.
이 확장은 "완벽함을 포기하고, 가능성을 얻는" 것이다. 120-cell의 1개 정점을 버리고, 17의 소수성을 받아들이는 대신, 우리는 4차원에서 퍼즐을 할 수 있게 되었다.
최석정(崔錫鼎, 1646-1715)이 구수략을 썼을 때 4차원을 상상했을까? 아마 아니었을 것이다. 하지만 그가 남긴 **"수를 그룹으로 묶어 합을 맞춘다"**는 개념은, 4차원에서도, 심지어 120-cell의 정점 위에서도, 여전히 살아 있다.
七子胞群 119 clusters × 7 numbers × 17 supers = 833 numbers Each cluster sum = 2919, each super sum = 20433 4D 120-cell, non-overlapping, fully verified C + AVX2 SIMD, ~1 minute on a single core