this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add polynomials, schematic matrix, multi-paragraph, and mixed list+para bodies

generate_typeset.py:
- _polynomial(): explicit (a x^3 + b x^2 + c x + d), indexed (a_0 + a_1 x + ... + a_n x^n),
monic general form; variable pool includes operator letters T, A, D, L, X, S
- _schematic_matrix(): generic n×m with dots.c / dots.v / dots.down ellipsis
- New 0.73–0.76 probability slots (stole 3% from product/df/dx/partial)

generate_mixed.py:
- _multi_paragraph(): 2–4 inline seqs separated by blank lines (~15% of bodies)
- _para_then_list() / _list_then_para(): intro-para+list and list+outro-para (~10%)
- All reflowable bodies (multi-para, lists, mixed) get random fixed width from
_PARA_WIDTHS = [200, 240, 280, 320, 360, 420, 480]pt to cover narrow two-column
journal through wide single-column; tables stay width: auto
- generate_body() returns (body, page_width) tuple; _CONTENT_TEMPLATE parameterized
with {width}; render_content() accepts page_width arg
- Image hash keyed on "width:body" to avoid collisions across reflow variants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+105 -19
+51 -15
src/generate_mixed.py
··· 222 222 return f"#table(columns: {ncols}, inset: (x: 5pt, y: 7pt), {', '.join(cells)})" 223 223 224 224 225 - def generate_body(rng: random.Random) -> str: 225 + def _multi_paragraph(rng: random.Random) -> str: 226 + """2-4 inline sequences separated by blank lines (Typst paragraph breaks).""" 227 + n = rng.choices([2, 3, 4], weights=[4, 3, 2])[0] 228 + paras: list[str] = [] 229 + for _ in range(n): 230 + n_tok = rng.choices([3, 4, 5, 6, 7], weights=[3, 5, 5, 4, 2])[0] 231 + paras.append(_inline_seq(rng, n_tok, require_math=True)) 232 + return "\n\n".join(paras) 233 + 234 + 235 + def _para_then_list(rng: random.Random) -> str: 236 + n_tok = rng.choices([2, 3, 4], weights=[3, 5, 3])[0] 237 + intro = _inline_seq(rng, n_tok, require_math=True) 238 + return intro + "\n\n" + "\n".join(_list_body(rng)) 239 + 240 + 241 + def _list_then_para(rng: random.Random) -> str: 242 + n_tok = rng.choices([2, 3, 4], weights=[3, 5, 3])[0] 243 + outro = _inline_seq(rng, n_tok, require_math=True) 244 + return "\n".join(_list_body(rng)) + "\n\n" + outro 245 + 246 + 247 + def generate_body(rng: random.Random) -> tuple[str, str]: 248 + """Returns (body, page_width) where page_width is 'auto' or e.g. '280pt'.""" 226 249 r = rng.random() 227 250 if r < 0.15: 228 - return _generate_table(rng) 229 - elif r < 0.42: # ~27% lists (was 30%) 230 - return "\n".join(_list_body(rng)) 251 + return _generate_table(rng), "auto" 252 + elif r < 0.30: # ~15% multi-paragraph 253 + width = rng.choice(_PARA_WIDTHS) 254 + return _multi_paragraph(rng), f"{width}pt" 255 + elif r < 0.40: # ~10% para→list or list→para 256 + width = rng.choice(_PARA_WIDTHS) 257 + body = (_para_then_list if rng.random() < 0.5 else _list_then_para)(rng) 258 + return body, f"{width}pt" 259 + elif r < 0.58: # ~18% plain lists 260 + width = rng.choice(_PARA_WIDTHS) 261 + return "\n".join(_list_body(rng)), f"{width}pt" 231 262 n = rng.choices([2, 3, 4, 5, 6, 7], weights=[4, 8, 7, 5, 3, 1])[0] 232 - return _inline_seq(rng, n, require_math=True) 263 + return _inline_seq(rng, n, require_math=True), "auto" 233 264 234 265 235 266 # ── Rendering ───────────────────────────────────────────────────────────────── 236 267 268 + # Page widths (pt) used for multi-paragraph bodies; sampled uniformly. 269 + # Range covers narrow two-column journal (~200pt) to wide single-column (~480pt). 270 + _PARA_WIDTHS = [200, 240, 280, 320, 360, 420, 480] 271 + 237 272 _CONTENT_TEMPLATE = ( 238 - "#set page(width: auto, height: auto, " 273 + "#set page(width: {width}, height: auto, " 239 274 "margin: (x: 10pt, y: 8pt), fill: white)\n" 240 275 # Tall math (sqrt of integrals, underbrace, etc.) can exceed the default 241 276 # em-based spacing and overlap adjacent list items -- give more room. ··· 245 280 ) 246 281 247 282 248 - def render_content(body: str, out_path: Path) -> tuple[bool, str]: 283 + def render_content(body: str, out_path: Path, page_width: str = "auto") -> tuple[bool, str]: 249 284 """Returns (success, stderr).""" 250 - src = _CONTENT_TEMPLATE.format(body=body) 285 + src = _CONTENT_TEMPLATE.format(body=body, width=page_width) 251 286 with tempfile.NamedTemporaryFile(suffix=".typ", mode="w", delete=False) as f: 252 287 f.write(src) 253 288 typ_path = Path(f.name) ··· 287 322 288 323 print(f"Generating {args.count:,} unique bodies ...") 289 324 seen: set[str] = set() 290 - candidates: list[str] = [] 325 + candidates: list[tuple[str, str]] = [] 291 326 attempts = 0 292 327 293 328 with tqdm(total=args.count, unit="body") as pbar: 294 329 while len(candidates) < args.count: 295 330 attempts += 1 296 - body = generate_body(rng) 331 + body, page_width = generate_body(rng) 297 332 if body in seen: 298 333 continue 299 334 seen.add(body) 300 - candidates.append(body) 335 + candidates.append((body, page_width)) 301 336 pbar.update(1) 302 337 303 338 print(f" {attempts:,} attempts ({attempts / len(candidates):.1f}x overhead)") ··· 308 343 309 344 shown_failures = 0 310 345 311 - def _task(body: str) -> tuple[str, str, bool, str]: 312 - h = hashlib.sha1(body.encode()).hexdigest()[:16] 346 + def _task(body: str, page_width: str) -> tuple[str, str, bool, str]: 347 + # Include page_width in hash -- same body at different widths -> different images 348 + h = hashlib.sha1(f"{page_width}:{body}".encode()).hexdigest()[:16] 313 349 out_path = img_dir / f"{h}.png" 314 - ok, err = render_content(body, out_path) 350 + ok, err = render_content(body, out_path, page_width) 315 351 return body, f"images/{h}.png", ok, err 316 352 317 353 with ThreadPoolExecutor(max_workers=args.jobs) as pool: 318 - futs = {pool.submit(_task, b): b for b in candidates} 354 + futs = {pool.submit(_task, b, pw): b for b, pw in candidates} 319 355 with tqdm(total=len(candidates), unit="img") as pbar: 320 356 for fut in as_completed(futs): 321 357 body, rel_path, ok, err = fut.result()
+54 -4
src/generate_typeset.py
··· 72 72 _CALLIGRAPHIC = ["A", "B", "C", "F", "G", "H", "L", "M", "N", "P", "R", "S", "T"] 73 73 74 74 75 + def _polynomial(rng: random.Random) -> str: 76 + """Explicit polynomial in one variable: a x^d + ... + c, indexed, or monic general.""" 77 + # Capital letters give operator polynomials: p(T), q(A), etc. 78 + var = rng.choice(["x", "t", "z", "lambda", "T", "A", "D", "L", "X", "S"]) 79 + r = rng.random() 80 + if r < 0.45: 81 + # Explicit coefficients: a x^3 + b x^2 + c x + d 82 + degree = rng.randint(2, 4) 83 + pool = ["a", "b", "c", "p", "q", "alpha", "beta"] 84 + coeffs = rng.sample(pool, k=min(degree + 1, len(pool))) 85 + while len(coeffs) < degree + 1: 86 + coeffs.append(rng.choice(pool)) 87 + terms: list[str] = [] 88 + for k in range(degree, -1, -1): 89 + coeff = coeffs[degree - k] 90 + if k == 0: 91 + terms.append(coeff) 92 + elif k == 1: 93 + terms.append(f"{coeff} {var}") 94 + else: 95 + terms.append(f"{coeff} {var}^{k}") 96 + return " + ".join(terms) 97 + elif r < 0.75: 98 + # Indexed form: a_0 + a_1 x + dots.c + a_n x^n 99 + letter = rng.choice(["a", "b", "c"]) 100 + n_sym = rng.choice(["n", "m", "N"]) 101 + return f"{letter}_0 + {letter}_1 {var} + dots.c + {letter}_{n_sym} {var}^{n_sym}" 102 + else: 103 + # General monic: x^n + a_(n-1) x^(n-1) + dots.c + a_0 104 + n_sym = rng.choice(["n", "m"]) 105 + letter = rng.choice(["a", "b", "c"]) 106 + return (f"{var}^{n_sym} + {letter}_({n_sym}-1) {var}^({n_sym}-1) " 107 + f"+ dots.c + {letter}_0") 108 + 109 + 110 + def _schematic_matrix(rng: random.Random) -> str: 111 + """Generic n×m matrix with ellipsis dots showing structure.""" 112 + letter = rng.choice(["a", "b", "c", "m"]) 113 + row_sym = rng.choice(["m", "p", "r"]) 114 + col_sym = rng.choice(["n", "k", "q"]) 115 + r1 = f"{letter}_(1,1), dots.c, {letter}_(1,{col_sym})" 116 + r2 = "dots.v, dots.down, dots.v" 117 + r3 = f"{letter}_({row_sym},1), dots.c, {letter}_({row_sym},{col_sym})" 118 + return f"mat({r1}; {r2}; {r3})" 119 + 120 + 75 121 def _atom(rng: random.Random) -> str: 76 122 r = rng.random() 77 123 if r < 0.36: ··· 131 177 elif c < 0.63: 132 178 rel = rng.choice(_RELS) 133 179 return f"{_expr(rng, depth+1)} {rel} {_expr(rng, depth+1)}" 134 - elif c < 0.67: 180 + elif c < 0.66: 135 181 v, lo, hi = _atom(rng), _atom(rng), _atom(rng) 136 182 return f"product_({v} = {lo})^{hi} {_expr(rng, depth+1)}" 137 - elif c < 0.70: 183 + elif c < 0.69: 138 184 # Matrix / row-vector shapes; column vectors handled by vec() instead 139 185 nrows, ncols = rng.choices( 140 186 [(1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (3, 2), (3, 3)], ··· 143 189 rows = [", ".join(_atom(rng) for _ in range(ncols)) 144 190 for _ in range(nrows)] 145 191 return f"mat({'; '.join(rows)})" 146 - elif c < 0.73: 192 + elif c < 0.71: 147 193 f_var = rng.choice(_VARS) 148 194 x_var = rng.choice(_VARS) 149 195 return f"(dif {f_var}) / (dif {x_var})" 150 - elif c < 0.76: 196 + elif c < 0.73: 151 197 f_var = rng.choice(_VARS) 152 198 x_var = rng.choice(_VARS) 153 199 return f"(partial {f_var}) / (partial {x_var})" 200 + elif c < 0.75: 201 + return _polynomial(rng) 202 + elif c < 0.76: 203 + return _schematic_matrix(rng) 154 204 elif c < 0.79: 155 205 return f"norm({_expr(rng, depth+1)})" 156 206