๊ธฐ๋Šฅ ์ƒ์„ธ ๋ณด๊ณ 

06. ๋‹ค๋ชฉ์  ์Šค์ฝ”์–ด๋ง (NSGA-II Pareto + scalar, GNINA, ECR) ๊ธฐ๋Šฅ ๋ถ„์„

๋Œ€์ƒ ๋ชจ๋“ˆ - pyrosetta_flow/multiobjective.py โ€” ObjectiveWeights, cheap_objectives, multiobjective_scalar, screen_selectivity, ๋…์„ฑ ํŽ˜๋„ํ‹ฐ - pyrosetta_flow/pareto_ranking.py โ€” NSGA-II ๋น„์ง€๋ฐฐ ์ •๋ ฌ + crowding distance - pyrosetta_flow/gnina_rescoring.py โ€” GNINA CNN rescoring + ECR consensus - pyrosetta_flow/ranking.py โ€” ์‹คํ—˜ ๋กœ๊ทธ ๊ธฐ๋ฐ˜ ์ง‘๊ณ„ ๋žญํ‚น(๋ณด์กฐ) - pyrosetta_flow/scoring_pipeline.py โ€” 4๋ชฉ์  ํ†ตํ•ฉ ์ฒด์ธ(ECR consensus ํฌํ•จ)

๋ณธ ๋ณด๊ณ ์„œ์˜ ๋ชจ๋“  ์‚ฌ์‹ค ์ฃผ์žฅ์€ file_path:line ์œผ๋กœ ์ธ์šฉํ•œ๋‹ค. ๋ฏธํ™•์ธ ํ•ญ๋ชฉ์€ ๋ฏธ๊ฒ€์ฆ์œผ๋กœ ๋ช…์‹œํ•œ๋‹ค.


โ‘  ๋™์ž‘ ์›๋ฆฌ

1.1 4๊ฐœ ๋ชฉ์ ์˜ ์ •์˜ (ฮ”G + ๋ฐ˜๊ฐ๊ธฐ + ์„ ํƒ์„ฑ + ADMET)

๋‹ค๋ชฉ์  ํ”„๋ ˆ์ž„์€ SST-14(AGCKNFFWKTFTSC) ๋ณ€์ด์ฒด๋ฅผ 4์ถ•์œผ๋กœ ํ‰๊ฐ€ํ•œ๋‹ค (multiobjective.py:5-9):

๋ชฉ์  ๋ฐฉํ–ฅ ์‚ฐ์ถœ ์ถœ์ฒ˜ ์„ฑ๊ฒฉ
ddg โ†“ (๋‚ฎ์„์ˆ˜๋ก ๊ฒฐํ•ฉ ๊ฐ•ํ•จ) PyRosetta FlexPepDock (์‹ค์ธก REU/kcalยทmol) ์‹ค์ธก
selectivity_margin / delta_margin โ†‘ (์–‘์ˆ˜=SSTR2 ์„ ํƒ์ ) off-target PyRosetta ๋„ํ‚น ์‹ค์ธก
half_life_h โ†’ stability_norm โ†‘ ์„œ์—ด ๊ธฐ๋ฐ˜ ์•™์ƒ๋ธ” surrogate surrogate
admet_score โ†‘ ๋ฌผ์„ฑ(GRAVY/Boman/Instability/pI) surrogate surrogate

honest disclaimer (multiobjective.py:15-18): half_life_h, admet_score ๋Š” ๋žญํ‚น์šฉ surrogate ์ด๋ฉฐ ์ž„์ƒ ๋ฐ˜๊ฐ๊ธฐยท์ž„์ƒ ADMET ์ˆ˜์น˜๊ฐ€ ์•„๋‹ˆ๋‹ค. ddgยทselectivity_margin ์€ ์‹ค์ œ FlexPepDock ๊ฒฐ๊ณผ์ง€๋งŒ ์ ˆ๋Œ€ ์นœํ™”๋„(Ki/Kd)๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ด๋Š” VR-cycle-09 / H-06("๊ณ„์‚ฐ ๋ถˆ๊ฐ€๋Šฅ์„ ๊ณ„์‚ฐ ๊ฐ€๋Šฅํ•œ ์ฒ™ ํ•˜์ง€ ์•Š๊ธฐ") ์›์น™์˜ ์ฝ”๋“œ ๋ฐ˜์˜์ด๋‹ค.

1.2 ๋น„์šฉ ๊ณ„์ธตํ™” (cost-tiered)

์‹ค์ œ ๋„ํ‚น์ด ๋น„์‹ธ๋ฏ€๋กœ 2๊ณ„์ธต์œผ๋กœ ๋‚˜๋ˆˆ๋‹ค (multiobjective.py:11-13):

  • Layer 0 (๋ชจ๋“  ํ›„๋ณด, ์„œ์—ด๋งŒ, ฮผs๊ธ‰): ๋ฐ˜๊ฐ๊ธฐ + ADMET surrogate โ€” cheap_objectives() (multiobjective.py:101-155)
  • Layer 1 (top-K, ์‹ค์ œ PyRosetta, ์ˆ˜๋ถ„๊ธ‰): off-target ์„ ํƒ์„ฑ ๋„ํ‚น โ€” screen_selectivity() (multiobjective.py:361-458)

Layer 0 โ€” cheap_objectives

  • ๋ฐ˜๊ฐ๊ธฐ: halflife_ensemble.ensemble_halflife (ํœด๋ฆฌ์Šคํ‹ฑ A + RandomForest C ๊ฒฐํ•ฉ)์„ ํ˜ธ์ถœ, ์‹คํŒจ ์‹œ ๋ ˆ๊ฑฐ์‹œ ํœด๋ฆฌ์Šคํ‹ฑ ๋‹จ๋…์œผ๋กœ ํด๋ฐฑ (multiobjective.py:113-129). stability_norm ์€ log10 ์ •๊ทœํ™”๋กœ 0~1 ๋ณ€ํ™˜ (multiobjective.py:127-129).
  • ADMET: PharmaProperties ๋กœ GRAVY/Boman/Instability/aliphatic/pI ๊ณ„์‚ฐ (multiobjective.py:135-153). Cys3-Cys14 SS bond ๋ฅผ pI ๊ณ„์‚ฐ์—์„œ ์ œ์™ธํ•œ๋‹ค (multiobjective.py:139-140, 0-indexed ์ตœ์†Œยท์ตœ๋Œ€ Cys).
  • admet_reasonableness() ๊ฐ€ 4๊ฐœ ๋ฌผ์„ฑ์„ 0~1 ๋ถ€๋ถ„์ ์ˆ˜๋กœ ๋ณ€ํ™˜ ํ›„ ๊ฐ€์ค‘ ํ‰๊ท  (multiobjective.py:64-90):
  • ๊ฐ€์ค‘์น˜: Instability 0.35 + GRAVY 0.30 + Boman 0.15 + pI 0.20 (multiobjective.py:89)
  • Instability <40 ๋งŒ์ , 40~80 ์„ ํ˜• ๊ฐ์  (multiobjective.py:75)
  • GRAVY โ‰ค-1 ๋งŒ์ (์นœ์ˆ˜), โ‰ฅ1 0์  (multiobjective.py:77)
  • Boman <1.0 ์•ฝํ•จ, 1.0~4.0 ์„ ํ˜•, >4.0 ๋งŒ์  (multiobjective.py:79-84)
  • pI 6~8 ๋งŒ์  (multiobjective.py:86)

Layer 0.5 โ€” pepADMET ๋…์„ฑ ํŽ˜๋„ํ‹ฐ

  • predict_toxicity_for_sequences() ๊ฐ€ pepADMET GNN ์„ ๋ณ„๋„ conda env subprocess ๋กœ ๋ฐฐ์น˜ ์ถ”๋ก  (multiobjective.py:188-203). ๋ฏธ์„ค์น˜/์‹คํŒจ ์‹œ ๋นˆ dict ๋ฐ˜ํ™˜(graceful).
  • apply_toxicity_to_extra() ๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ admet_score ์— ๊ณฑ์…ˆ ํŽ˜๋„ํ‹ฐ๋กœ ๋ฐ˜์˜ (multiobjective.py:206-241):
  • binary is_toxic ๋Š” ๋น„๋ณ€๋ณ„์ (oxytocinยทnative ๊นŒ์ง€ ์ „๋ถ€ toxic ํŒ์ •)์ด๋ผ ๊ฒŒ์ดํŠธ์— ์“ฐ์ง€ ์•Š๊ณ  ๊ธฐ๋ก๋งŒ ํ•จ (multiobjective.py:211-219)
  • ๋Œ€์‹  hc50 ์—ฐ์†๊ฐ’์„ native SST-14 ๊ธฐ์ค€์„  ๋Œ€๋น„(home-advantage) ๋กœ ํ‰๊ฐ€ (multiobjective.py:222-241). native ยฑ5.0 ๋ฐด๋“œ๋Š” "๋™๊ธ‰" ๋ฌดํŽ˜๋„ํ‹ฐ (_HC50_NATIVE_TOLERANCE=5.0, multiobjective.py:166), ์ดˆ๊ณผ๋ถ„์— ์„ ํ˜• ๋น„๋ก€ ํŽ˜๋„ํ‹ฐ(ํ•˜ํ•œ _TOXIC_ADMET_PENALTY=0.4, multiobjective.py:163,238).
  • 2026-06-17 (VR-A2) ์ถ”๊ฐ€: SMILES ํŒŒ์‹ฑ ์‹คํŒจ๋กœ ์„ ํ˜• ํด๋ฐฑ๋œ ๊ฒฝ์šฐ cyclic ๊ตฌ์กฐ ์†Œ์‹ค๋กœ hc50 ์‹ ๋ขฐ ๋ถˆ๊ฐ€ โ†’ ๊ฒŒ์ดํŠธ skip(fail-open), ํ”Œ๋ž˜๊ทธ๋งŒ ๊ธฐ๋ก (multiobjective.py:225-228).
  • available=False(์ถ”๋ก  ๋ถˆ๊ฐ€)๋ฉด admet_score ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ โ€” fail-closed, ๊ฐ€์งœ ์•ˆ์ „ ํŒ์ • ๋ฐฉ์ง€ (multiobjective.py:209,217).

Layer 1 โ€” screen_selectivity

  • top-K ํ›„๋ณด์˜ SSTR2 ์ •๋ฐ€ํ™” ๋ณตํ•ฉ์ฒด๋ฅผ SSTR1/3/4/5 ์— off-target ๋„ํ‚น (multiobjective.py:361-458).
  • SSTR2(๋™์ผ ํ”„๋กœํ† ์ฝœ baseline) + off-target 4์ข…์„ ThreadPoolExecutor ๋กœ ๋ณ‘๋ ฌ ๋„ํ‚น (์ˆœ์ฐจ ~25๋ถ„ โ†’ ๋ณ‘๋ ฌ ~6๋ถ„/ํ›„๋ณด) (multiobjective.py:401-425).
  • selectivity_margin = min(offtarget_ddg) - baseline (์–‘์ˆ˜=SSTR2 ๋” ๊ฐ•ํ•จ=์„ ํƒ์ , G-2 SSOT) (multiobjective.py:434-435).
  • home-advantage ๋ณด์ •: native ๋„ ๋™์ผ ํ”„๋กœํ† ์ฝœ์—์„œ +margin ํŽธํ–ฅ์ด ์žˆ์œผ๋ฏ€๋กœ native baseline ๋Œ€๋น„ delta_margin = margin - nat_margin ์ด ์ง„์งœ ์„ ํƒ์„ฑ ์‹ ํ˜ธ (multiobjective.py:436-447). more_selective_than_native = delta_margin > 0 (multiobjective.py:448).
  • NaN ๋„ํ‚น ๊ฒฐ๊ณผ๋Š” fail-closed ๋กœ None ์ฒ˜๋ฆฌ (multiobjective.py:419), ์ „๋ถ€ ์‹คํŒจ ์‹œ selectivity_margin=None (multiobjective.py:429-430).
  • top-K ์„ ๋ณ„: clash ๊ฒŒ์ดํŠธ(clash_max=10.0) ํ†ต๊ณผ ํ›„ ddg ์˜ค๋ฆ„์ฐจ์ˆœ ์ƒ์œ„ K (select_topk_for_selectivity, multiobjective.py:308-323).

1.3 NSGA-II Pareto front (pareto_ranking.py)

๋ ˆ๊ฑฐ์‹œ ๊ฐ€์ค‘ํ•ฉ(0.45/0.20/0.15/0.10/0.10)์„ pymoo ๊ธฐ๋ฐ˜ ๋น„์ง€๋ฐฐ ์ •๋ ฌ + crowding distance ๋กœ ๋Œ€์ฒด (pareto_ranking.py:3-4).

  • 4๋ชฉ์  ๋ชจ๋‘ ์ตœ์†Œํ™”๋กœ ๋ณ€ํ™˜ (_extract_objectives, pareto_ranking.py:35-53): [ddG, -stability, -druggability, -diversity] โ€” stability/druggability/diversity ๋Š” ๋ถ€ํ˜ธ ๋ฐ˜์ „(pareto_ranking.py:53).
  • ์ œ์•ฝ (_extract_constraints, pareto_ranking.py:56-77): hard_violations โ‰ค 0, clash_score - threshold โ‰ค 0 (๊ธฐ๋ณธ ์ž„๊ณ„ 10.0, pareto_ranking.py:32).
  • pareto_rank_candidates() ํ๋ฆ„ (pareto_ranking.py:125-190): 1. ๋ชฉ์  ํ–‰๋ ฌ F(nร—4) + ์ œ์•ฝ ์œ„๋ฐ˜ ๋ฒกํ„ฐ cv(์–‘์˜ ์œ„๋ฐ˜ ํ•ฉ) ๊ตฌ์„ฑ (pareto_ranking.py:158-166) 2. NonDominatedSorting().do(F) ๋กœ front ๋ถ„๋ฆฌ (pareto_ranking.py:169-170) 3. _penalise_infeasible() ๋กœ infeasible ํ›„๋ณด๋ฅผ ๋ชจ๋“  feasible front ๋’ค๋กœ ๋ฐ€์–ด๋‚ด๊ณ , ์œ„๋ฐ˜๋Ÿ‰ ์˜ค๋ฆ„์ฐจ์ˆœ ๋‹จ์ผ ํŽ˜๋„ํ‹ฐ front ๋กœ ๋ถ€์ฐฉ (pareto_ranking.py:80-117,173) 4. front๋ณ„ calc_crowding_distance ๊ณ„์‚ฐ(๊ฒฝ๊ณ„์ =inf), ํ›„๋ณด๋‹น 2๊ฐœ ๊ธธ์ด โ‰ค2 ์ธ front ๋Š” ์ „๋ถ€ inf (pareto_ranking.py:180-188) 5. pareto_rank(0=์ตœ์šฐ์„  front), crowding_distance(๋†’์„์ˆ˜๋ก ๊ณ ๋ฆฝ/๋‹ค์–‘) ๋ถ€์—ฌ (pareto_ranking.py:186-188)
  • select_from_pareto_front(): rank ์˜ค๋ฆ„์ฐจ์ˆœ โ†’ crowding distance ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ ํ›„ ์ƒ์œ„ n (pareto_ranking.py:193-231).
  • ์ฃผ์˜: diversity ๋Š” ํ˜„์žฌ ํ•ญ์ƒ 0.0 ์œผ๋กœ ์ž…๋ ฅ๋˜์–ด(scoring_pipeline.py:218) 4๋ชฉ์  ์ค‘ 1๊ฐœ๊ฐ€ ์‚ฌ์‹ค์ƒ ๋น„ํ™œ์„ฑ ์ƒํƒœ๋‹ค(๋ฏธ๊ฒ€์ฆ: ๋‹ค๋ฅธ ํ˜ธ์ถœ ๊ฒฝ๋กœ์—์„œ ์ฑ„์›Œ์งˆ ๊ฐ€๋Šฅ์„ฑ).

1.4 GNINA CNN rescoring (gnina_rescoring.py)

FlexPepDock ์ถœ๋ ฅ PDB ๋ฅผ GNINA --score_only ๋ชจ๋“œ๋กœ ์žฌ์ฑ„์  (gnina_rescoring.py:1-9).

  • dry-run mock ์—ฌ๋ถ€ โ€” ํ™•์ธ๋จ: gnina ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ PATH ์— ์—†์œผ๋ฉด ๊ฒฝ๊ณ  ๋กœ๊ทธ + ๊ฒฐ์ •๋ก ์  mock ์ ์ˆ˜ ๋ฐ˜ํ™˜ (gnina_rescoring.py:7-9,30,45-47,205-210). mock ๊ฐ’์€ gnina_cnn_score/affinity/vina_score = 0.0, gnina_dry_run = 1.0 (gnina_rescoring.py:32-37).
  • ํ˜„ ํ™˜๊ฒฝ์—์„œ ์‹ค์ธก ํ™•์ธ: which gnina โ†’ not found. ๋”ฐ๋ผ์„œ ํ˜„์žฌ GNINA ๋‹จ๊ณ„๋Š” dry-run mock ์œผ๋กœ ๋™์ž‘ ์ค‘์ด๋ฉฐ ์‹ค์ œ CNN ์ ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋‹ค.
  • ๋ณตํ•ฉ์ฒด PDB ๋ฅผ chain ๊ธฐ์ค€์œผ๋กœ receptor(A)/peptide(B) ์ž„์‹œํŒŒ์ผ ๋ถ„๋ฆฌ (split_receptor_peptide, gnina_rescoring.py:97-176).
  • ๋‹จ์ผ rescore: subprocess ํ˜ธ์ถœ, rcโ‰ 0/timeout ์‹œ NaN + ์—๋Ÿฌ ํ”Œ๋ž˜๊ทธ ๋ฐ˜ํ™˜ (gnina_rescore, gnina_rescoring.py:179-265).
  • ๋ฐฐ์น˜: ThreadPoolExecutor ๋ณ‘๋ ฌ, ์ž…๋ ฅ ์ˆœ์„œ ๋ณด์กด (batch_gnina_rescore, gnina_rescoring.py:268-324).
  • stdout ํŒŒ์‹ฑ: CNNscore ํ—ค๋” ๋‹ค์Œ ์ค„์—์„œ 3ํ† ํฐ ์ถ”์ถœ, ์‹คํŒจ ์‹œ NaN (_parse_gnina_output, gnina_rescoring.py:50-89).

1.5 ECR (Exponential Rank Consensus)

์—ฌ๋Ÿฌ ์ ์ˆ˜ํ•ญ์„ ์ˆœ์œ„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ†ตํ•ฉ (exponential_rank_consensus, gnina_rescoring.py:327-395).

  • ๊ณต์‹: ECR_i = ฮฃ_k exp(-rank_{i,k} / N), N=ํ›„๋ณด ์ˆ˜, ๋†’์„์ˆ˜๋ก ์ข‹์Œ (gnina_rescoring.py:333-337,388).
  • ๋ชจ๋“  ์ง€์› ์ ์ˆ˜ํ•ญ์€ ๋‚ฎ์„์ˆ˜๋ก ์ข‹์Œ โ†’ ์˜ค๋ฆ„์ฐจ์ˆœ 1-based ์ˆœ์œ„ ๋ถ€์—ฌ (gnina_rescoring.py:367-382). NaN/๋น„์ˆ˜์น˜๋Š” inf ๋กœ ๋ฐ€์–ด ์ตœ์•… ์ˆœ์œ„ (gnina_rescoring.py:374-375).
  • ์ž…๋ ฅ dict ๋ฅผ ๋ณ€ํ˜•ํ•˜์ง€ ์•Š๊ณ  ecr_score + ecr_ranks ๋ฅผ ์ถ”๊ฐ€ํ•œ ์‚ฌ๋ณธ ๋ฐ˜ํ™˜, ecr_score ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ (gnina_rescoring.py:385-395).
  • ๊ธฐ๋ณธ score_keys: gnina_cnn_score, gnina_cnn_affinity, gnina_vina_score (gnina_rescoring.py:359-360). ๋‹จ, ํŒŒ์ดํ”„๋ผ์ธ์—์„œ๋Š” ddg ๋ฅผ ์ถ”๊ฐ€ํ•ด 4๊ฐœ ํ•ญ์œผ๋กœ ํ˜ธ์ถœ (scoring_pipeline.py:177).

1.6 ์Šค์นผ๋ผ ๊ฐ€์ค‘ (multiobjective_scalar)

UI ์ •๋ ฌ ๋ณด์กฐ์šฉ ๋‹จ์ผ ์ ์ˆ˜ (multiobjective.py:281-305).

  • ๊ฐ€์ค‘์น˜ (์ฝ”๋“œ ์ง์ ‘ ํ™•์ธ, ObjectiveWeights, multiobjective.py:273-278):
  • ddg = 0.40 (๊ฒฐํ•ฉ, ์ตœ์šฐ์„ )
  • selectivity = 0.25
  • stability = 0.20
  • admet = 0.15
  • ํ•ฉ = 1.00
  • ์ •๊ทœํ™” (multiobjective.py:293-298): ddg ๋Š” (ddg_ref - ddg)/ddg_scale (๊ธฐ๋ณธ ref=0, scale=50, multiobjective.py:284-285), selectivity_margin ์€ /20.0 ํฌํ™”, stability/admet ์€ ์ด๋ฏธ 0~1.
  • ์ตœ์ข… ์ ์ˆ˜ = ๊ฐ€์ค‘ ์„ ํ˜•ํ•ฉ (multiobjective.py:299-305). runner ๋Š” _mo_scalar() ๋กœ UI ํ‘œ์‹œ์šฉ์œผ๋กœ๋งŒ ์‚ฌ์šฉ (runner.py:64-73).

1.7 ํ†ตํ•ฉ ์ฒด์ธ ๋ฐฐ์„  (scoring_pipeline.py)

_apply_alternative_scoring() ๊ฐ€ FlexPepDock ๊ฒฐ๊ณผ์— 5๋‹จ๊ณ„๋ฅผ ์ˆœ์ฐจ ์ ์šฉ, ๊ฐ ๋‹จ๊ณ„ graceful skip (scoring_pipeline.py:42-281):

๋‹จ๊ณ„ ๋‚ด์šฉ ์œ„์น˜ skip ์กฐ๊ฑด
0 cheap objectives (๋ฐ˜๊ฐ๊ธฐ+ADMET) scoring_pipeline.py:79-95 ์˜ˆ์™ธ ์‹œ non-fatal
0.5 pepADMET ๋…์„ฑ ํŽ˜๋„ํ‹ฐ scoring_pipeline.py:102-120 SST_DISABLE_PEPADMET_TOX env / ๋ฏธ์„ค์น˜
1 GNINA rescore (dry-run fallback) scoring_pipeline.py:125-157 _HAS_GNINA + PDB ์กด์žฌ
2 ECR consensus (ddg+GNINA 4ํ•ญ) scoring_pipeline.py:162-189 GNINA ๊ฒฐ๊ณผ ์žˆ์„ ๋•Œ๋งŒ
3 Pareto ranking (NSGA-II) scoring_pipeline.py:201-243 _HAS_PARETO(pymoo)
4 BO suggest (๋กœ๊ทธ๋งŒ, ๋ถ€์ˆ˜ํšจ๊ณผ ์—†์Œ) scoring_pipeline.py:249-279 bo_optimizer ์ „๋‹ฌ + ๊ด€์ธก โ‰ฅ2

Pareto ์ž…๋ ฅ ํด๋ฐฑ (scoring_pipeline.py:204-221): stability_norm ๋ถ€์žฌ ์‹œ clash ๊ธฐ๋ฐ˜ proxy((40-clash)/40), admet_score ๋ถ€์žฌ ์‹œ ECR ํด๋ฐฑ, diversity=0.0 ๊ณ ์ •.


โ‘ก ์˜ํ–ฅ

  • ์„ ํƒ ํ’ˆ์งˆ: ๊ฐ€์ค‘ํ•ฉ ๋‹จ์ผ ์ ์ˆ˜์˜ ์ž„์˜ ๊ฐ€์ค‘์น˜ ์˜์กด์„ NSGA-II Pareto front ๋กœ ๋Œ€์ฒดํ•ด, ๋ชฉ์  ๊ฐ„ trade-off ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋ณด์กดํ•œ๋‹ค(front-0 ํ›„๋ณด ์ง‘ํ•ฉ). ๋‹จ์ผ ์Šค์นผ๋ผ๋Š” UI ์ •๋ ฌ ๋ณด์กฐ๋กœ ๊ฒฉํ•˜ (runner.py:64-73).
  • ๋น„์šฉ ํšจ์œจ: cost-tiered ๊ตฌ์กฐ๋กœ ๋น„์‹ผ off-target ๋„ํ‚น์„ top-K ์—๋งŒ ์ ์šฉ(multiobjective.py:11-13, runner.py:1414-1418), ์ˆ˜์šฉ์ฒด ๋ณ‘๋ ฌํ™”๋กœ ํ›„๋ณด๋‹น ~25๋ถ„โ†’~6๋ถ„ (multiobjective.py:401-403).
  • ์ •์ง์„ฑ(๊ฐ€๋“œ): surrogate/์‹ค์ธก ๊ตฌ๋ถ„ ๋ช…์‹œ(multiobjective.py:15-18), fail-closed ๋…์„ฑ ๊ฒŒ์ดํŠธ(multiobjective.py:209), home-advantage ๋ณด์ •์œผ๋กœ native ํŽธํ–ฅ ์ œ๊ฑฐ(multiobjective.py:436-447). ํ™˜๊ฐ ์ ์ˆ˜ ์ฐจ๋‹จ์ด ์„ค๊ณ„์— ๋‚ด์žฅ.
  • ๊ฒฌ๊ณ ์„ฑ: ๋ชจ๋“  ์˜ต์…”๋„ ์˜์กด(GNINA/pymoo/pepADMET)์„ graceful skip ์œผ๋กœ ์ฒ˜๋ฆฌ, ๋ถ€์žฌ ํ™˜๊ฒฝ์—์„œ๋„ ํŒŒ์ดํ”„๋ผ์ธ ์ง„ํ–‰(scoring_pipeline.py ์ „๋ฐ˜).

โ‘ข ๊ด€๋ จ Action Item (๋‹ค๋ชฉ์  ํ†ตํ•ฉ)

CLAUDE.md ์˜ Stage ์ ์šฉ ์ด๋ ฅ ๋ฐ ์ง„ํ–‰ ๋ฉ”๋ชจ(MEMORY.md)์™€ ์—ฐ๊ณ„๋˜๋Š” ํ•ญ๋ชฉ:

  • ๋‹ค๋ชฉ์  ํ†ตํ•ฉ ๋ณธ์ฒด: ฮ”G+๋ฐ˜๊ฐ๊ธฐ+์„ ํƒ์„ฑ+ADMET 4์ถ• ํ†ตํ•ฉ (multiobjective.py:5-9) โ€” sstr2-selectivity-goal(2026-06-10) ์˜ ฮ”margin home-advantage ๋ณด์ •๊ณผ ์ง๊ฒฐ (multiobjective.py:436-447).
  • ๋ฐ˜๊ฐ๊ธฐ ์•™์ƒ๋ธ” ํ†ตํ•ฉ (2026-06-09): ํœด๋ฆฌ์Šคํ‹ฑ A + RF C ๊ฒฐํ•ฉ (multiobjective.py:111-121) โ€” MEMORY ์˜ "ensemble" ์ง„ํ–‰ ํ•ญ๋ชฉ.
  • pepADMET ๋…์„ฑ ํ†ตํ•ฉ (B, 2026-06-09 / VR-A2 2026-06-17): binary ๋น„๋ณ€๋ณ„์„ฑ ๋ฐœ๊ฒฌ โ†’ hc50 home-advantage ๊ฒŒ์ดํŠธ ์ „ํ™˜ (multiobjective.py:164-241).
  • VR-cycle-09 / H-06 ๊ฐ€๋“œ: surrogate honest disclaimer ์˜ ์ฝ”๋“œ ๋ฐ˜์˜ (multiobjective.py:15-18), CLAUDE.md Stage 8h ์™€ ์ง๊ฒฐ.
  • god-object ๋ถ„๋ฆฌ (P1, 2026-06-09): runner.py ์˜ ์Šค์ฝ”์–ด๋ง ์ฒด์ธ์„ scoring_pipeline.py ๋กœ ์ถ”์ถœ (scoring_pipeline.py:3).
  • ์ž”์—ฌ ๊ฐญ(๊ถŒ๊ณ ): diversity ๋ชฉ์ ์ด ํ•ญ์ƒ 0.0 ์œผ๋กœ ๋น„ํ™œ์„ฑ (scoring_pipeline.py:218) โ€” NSGA-II 4๋ชฉ์  ์ค‘ 1๊ฐœ ๋ฏธํ™œ์šฉ. ๋‹ค์–‘์„ฑ ๋ฉ”ํŠธ๋ฆญ(์„œ์—ด ๊ฑฐ๋ฆฌ ๋“ฑ) ์—ฐ๊ฒฐ์ด ๋ฏธ์™„ Action Item ํ›„๋ณด.

โ‘ฃ ์™„์„ฑ๋„ ๋ฐ ๊ทผ๊ฑฐ

์™„์„ฑ๋„: ์•ฝ 80%

์˜์—ญ ์™„์„ฑ๋„ ๊ทผ๊ฑฐ
cheap_objectives / ADMET surrogate 95% ์™„์ „ ๊ตฌํ˜„ + ํด๋ฐฑ + SS bond ์ฒ˜๋ฆฌ (multiobjective.py:64-155), ํ…Œ์ŠคํŠธ ๋ณด์œ  (tests/test_multiobjective.py)
๋…์„ฑ ํŽ˜๋„ํ‹ฐ (hc50 home-adv) 90% fail-closed/fail-open ๋ถ„๊ธฐ ์™„๋น„ (multiobjective.py:206-241), ๋‹จ pepADMET ๋ฏธ์„ค์น˜ ์‹œ skip
screen_selectivity (Layer 1) 85% ๋ณ‘๋ ฌ ๋„ํ‚น+home-adv ๋ณด์ • ์™„๋น„ (multiobjective.py:361-458), config-gated(enable_selectivity)
NSGA-II Pareto 90% ๋น„์ง€๋ฐฐ ์ •๋ ฌ+crowding+์ œ์•ฝ ์ฒ˜๋ฆฌ ์™„๋น„ (pareto_ranking.py ์ „์ฒด), ๋‹จ diversity ๋ฏธ์ž…๋ ฅ
GNINA rescoring 60% ํ˜„ ํ™˜๊ฒฝ dry-run mock ์œผ๋กœ๋งŒ ๋™์ž‘(๋ฐ”์ด๋„ˆ๋ฆฌ ๋ถ€์žฌ ํ™•์ธ), ์‹ค์ธก ๊ฒฝ๋กœ ๋ฏธ๊ฒ€์ฆ (gnina_rescoring.py:205-210)
ECR consensus 95% ๊ณต์‹ ๊ตฌํ˜„ + NaN ์ฒ˜๋ฆฌ ์™„๋น„ (gnina_rescoring.py:327-395)
scalar ํ†ตํ•ฉ 90% ๊ฐ€์ค‘์น˜ ์ •์˜+์ •๊ทœํ™” ์™„๋น„ (multiobjective.py:273-305), UI ๋ณด์กฐ ์—ญํ•  ๋ช…ํ™•
diversity ๋ชฉ์  20% ํ•ญ์ƒ 0.0 ์ž…๋ ฅ์œผ๋กœ ๋น„ํ™œ์„ฑ (scoring_pipeline.py:218)

๋ฏธ์™„/๋ฏธ๊ฒ€์ฆ: - GNINA ์‹ค์ธก(live) ๊ฒฝ๋กœ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ถ€์žฌ๋กœ ๋ณธ ํ™˜๊ฒฝ์—์„œ ๋ฏธ๊ฒ€์ฆ โ€” dry-run ๋งŒ ํ™•์ธ. - diversity ๋ชฉ์  ๋ฏธ์—ฐ๊ฒฐ. - pepADMET/halflife RF ์˜ ์‹ค์ œ ๋ชจ๋ธ ์ •ํ™•๋„๋Š” ๋ณธ ๋ณด๊ณ  ๋ฒ”์œ„ ๋ฐ–(surrogate ๋ฉด์ฑ… ๋ช…์‹œ๋จ).


โ‘ค ํ•™์ˆ  ๊ฐ€์น˜ (NSGA-II ๋‹ค๋ชฉ์  ์ตœ์ ํ™”์˜ ์˜์˜)

  • Pareto-optimal trade-off ๋ณด์กด: ์•ฝ๋ฌผ ํ›„๋ณด๋Š” ๊ฒฐํ•ฉ๋ ฅโ†‘ยท๋ฐ˜๊ฐ๊ธฐโ†‘ยท์„ ํƒ์„ฑโ†‘ยทADMETโ†‘ ๊ฐ€ ์„œ๋กœ ์ƒ์ถฉํ•˜๋Š” ์ „ํ˜•์  ๋‹ค๋ชฉ์  ๋ฌธ์ œ๋‹ค. ๊ฐ€์ค‘ํ•ฉ์€ ๊ฐ€์ค‘์น˜ ์„ ํƒ์— ๋”ฐ๋ผ ๋น„๋ณผ๋ก(non-convex) front ์˜ ์ผ๋ถ€ ํ•ด๋ฅผ ์˜๊ตฌํžˆ ๋ฐฐ์ œํ•˜์ง€๋งŒ, NSGA-II ๋น„์ง€๋ฐฐ ์ •๋ ฌ์€ trade-off surface ์ „์ฒด๋ฅผ ๋ณด์กดํ•œ๋‹ค (pareto_ranking.py:3-11). ์ด๋Š” ์ž„์˜ ๊ฐ€์ค‘์น˜ ๊ฐ€์ • ์—†์ด ์˜์‚ฌ๊ฒฐ์ •์ž์—๊ฒŒ ๋‹ค์–‘ํ•œ ํ›„๋ณด ์ง‘ํ•ฉ์„ ์ œ๊ณตํ•œ๋‹ค.
  • Crowding distance ๋กœ ๋‹ค์–‘์„ฑ ์œ ์ง€: ๋™์ผ front ๋‚ด์—์„œ ๊ณ ๋ฆฝ๋œ(๋‹ค์–‘ํ•œ) ํ•ด๋ฅผ ์šฐ์„  ์„ ํƒํ•ด ํ™”ํ•™์  ๊ณต๊ฐ„ ํƒ์ƒ‰์˜ ์กฐ๊ธฐ ์ˆ˜๋ ด์„ ๋ฐฉ์ง€ (pareto_ranking.py:180-188,224-228).
  • ์ œ์•ฝ ์ฒ˜๋ฆฌ: clash/hard_violation infeasible ํ•ด๋ฅผ feasible front ๋’ค๋กœ relegate ํ•˜๋Š” constraint-domination ๋ฐฉ์‹์€ Deb ์˜ NSGA-II ํ‘œ์ค€ ์ œ์•ฝ ์ฒ˜๋ฆฌ์™€ ์ •ํ•ฉ์  (pareto_ranking.py:80-117).
  • ECR consensus: ์ ˆ๋Œ€ ์ ์ˆ˜ ์Šค์ผ€์ผ์ด ์ด์งˆ์ ์ธ ๋‹ค์–‘ํ•œ ์ฑ„์ ๊ธฐ(ddG REU vs GNINA CNN vs Vina kcal)๋ฅผ ์ˆœ์œ„ ๊ณต๊ฐ„์—์„œ ํ†ตํ•ฉํ•˜๋Š” rank-based consensus ๋Š” ์Šค์ผ€์ผ ๋ถˆ๋ณ€์„ฑ์„ ํ™•๋ณด, ๋‹จ์ผ ์ฑ„์ ๊ธฐ ํŽธํ–ฅ์„ ์™„ํ™” (gnina_rescoring.py:333-337).
  • ์ •์งํ•œ surrogate ๋ถ„๋ฆฌ: surrogate(๋ฐ˜๊ฐ๊ธฐ/ADMET)์™€ ์‹ค์ธก(ddG/์„ ํƒ์„ฑ)์„ ๋ช…์‹œ ๊ตฌ๋ถ„ํ•˜๊ณ  home-advantage ๋กœ baseline ํŽธํ–ฅ์„ ๋ณด์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•๋ก ์€ AI ํ›„๋ณด ๋ฐœ๊ตด์˜ ์žฌํ˜„์„ฑยท์‹ ๋ขฐ์„ฑ ์ธก๋ฉด์—์„œ ํ•™์ˆ ์ ์œผ๋กœ ์˜๋ฏธ ์žˆ๋Š” ๊ฐ€๋“œ๋‹ค (multiobjective.py:15-18,436-447).

โ‘ฅ ์‚ฌ์šฉ๋ฒ•

ํ†ตํ•ฉ ์ฒด์ธ (์ž๋™, runner ๊ฒฝ์œ )

runner.py ๊ฐ€ FlexPepDock ๊ฒฐ๊ณผ์— ์ž๋™ ์ ์šฉ (runner.py:190,1123, scoring_pipeline.py:42):

from pyrosetta_flow.scoring_pipeline import _apply_alternative_scoring
candidates = _apply_alternative_scoring(candidates, iter_dir, iteration, bo_optimizer)
# ๊ฐ cand.extra_scores ์— half_life_h, admet_score, gnina_*, ecr_score,
# pareto_rank, crowding_distance ์ฑ„์›Œ์ง

cheap objectives ๋‹จ๋…

from pyrosetta_flow.multiobjective import cheap_objectives, enrich_candidates
obj = cheap_objectives("AGCKNFFWKTFTSC")          # half_life_h, admet_score, stability_norm ...
enrich_candidates(cands)                            # cand dict ์— stability/druggability ๋งคํ•‘ ์ฃผ์ž…

NSGA-II Pareto ๋žญํ‚น ๋‹จ๋…

from pyrosetta_flow.pareto_ranking import pareto_rank_candidates, select_from_pareto_front
ranked = pareto_rank_candidates(cands, clash_threshold=10.0)  # pareto_rank, crowding_distance ๋ถ€์—ฌ
best = select_from_pareto_front(ranked, n=5)                  # front-0 ์šฐ์„ , crowding ๋‚ด๋ฆผ์ฐจ์ˆœ

GNINA + ECR ๋‹จ๋…

from pyrosetta_flow.gnina_rescoring import batch_gnina_rescore, exponential_rank_consensus
scores = batch_gnina_rescore(pdb_paths, max_workers=4)        # ๋ฐ”์ด๋„ˆ๋ฆฌ ์—†์œผ๋ฉด dry-run mock
consensus = exponential_rank_consensus(cands, score_keys=["ddg","gnina_cnn_score",...])

์Šค์นผ๋ผ ์ ์ˆ˜ (UI ์ •๋ ฌ)

from pyrosetta_flow.multiobjective import multiobjective_scalar, ObjectiveWeights
s = multiobjective_scalar(cand, ObjectiveWeights(ddg=0.40, selectivity=0.25,
                                                  stability=0.20, admet=0.15))

์„ ํƒ์„ฑ (top-K, ๋น„์Œˆ, config-gated)

config.enable_selectivity=True + config.selectivity_top_k ์„ค์ • ์‹œ runner ๊ฐ€ top-K ์— ์ž๋™ ํ˜ธ์ถœ (runner.py:1414-1445). pepADMET ๋…์„ฑ์€ SST_DISABLE_PEPADMET_TOX=1 ๋กœ ๋น„ํ™œ์„ฑ ๊ฐ€๋Šฅ (scoring_pipeline.py:104-105).


โ‘ฆ ํ•„์š”ํ•œ ์ด์œ 

  • ๋‹จ์ผ ์ ์ˆ˜์˜ ํ•œ๊ณ„ ๊ทน๋ณต: SSTR2 ๋ฐฉ์‚ฌ์„ฑ์˜์•ฝํ’ˆ ํ›„๋ณด๋Š” ๊ฒฐํ•ฉยท๋ฐ˜๊ฐ๊ธฐยท์„ ํƒ์„ฑยท์•ˆ์ „์„ฑ์ด ๋™์‹œ ์ตœ์ ์ผ ์ˆ˜ ์—†๋‹ค(์ƒ์ถฉ). ๊ฐ€์ค‘ํ•ฉ ๋‹จ์ผ ๋žญํ‚น์€ ๊ฐ€์ค‘์น˜ ์„ ํƒ์ž์˜ ์ฃผ๊ด€์— ๊ฒฐ๊ณผ๊ฐ€ ์ข…์†๋˜๋ฏ€๋กœ, Pareto front ๋กœ ๊ฐ๊ด€์  trade-off ์ง‘ํ•ฉ์„ ํ™•๋ณดํ•ด์•ผ ํ•œ๋‹ค (pareto_ranking.py:3-11).
  • ๊ณ„์‚ฐ ๋น„์šฉ ํ†ต์ œ: ์‹ค์ œ PyRosetta off-target ๋„ํ‚น์€ ํ›„๋ณด๋‹น ์ˆ˜๋ถ„์ด ๋“ค์–ด ์ „์ˆ˜ ์ ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. cost-tiered ๊ตฌ์กฐ๋กœ ์ €๋น„์šฉ surrogate ๊ฐ€ ์ „์ˆ˜ 1์ฐจ ํ•„ํ„ฐ, ๋น„์‹ผ ๋„ํ‚น์€ top-K ์—๋งŒ ์ ์šฉํ•ด์•ผ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ํ™•๋ณดํ•œ๋‹ค (multiobjective.py:11-13).
  • ์ด์งˆ์  ์ฑ„์ ๊ธฐ ํ†ตํ•ฉ: ddG(REU)ยทGNINA(CNN ํ™•๋ฅ )ยทVina(kcal)๋Š” ์Šค์ผ€์ผ์ด ๋‹ฌ๋ผ ์ง์ ‘ ํ•ฉ์‚ฐ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ECR ์ˆœ์œ„ ํ•ฉ์˜๊ฐ€ ์Šค์ผ€์ผ ๋ถˆ๋ณ€ ํ†ตํ•ฉ์„ ์ œ๊ณตํ•œ๋‹ค (gnina_rescoring.py:333-337).
  • ํ™˜๊ฐ ์ฐจ๋‹จ / ์ •์ง์„ฑ: AI ๋ฐœ๊ตด ํŒŒ์ดํ”„๋ผ์ธ์˜ ์‹ ๋ขฐ์„ฑ์€ "๋ชจ๋ฅด๋Š” ๊ฒƒ์„ ์•„๋Š” ์ฒ™ํ•˜์ง€ ์•Š์Œ"์— ๋‹ฌ๋ ค์žˆ๋‹ค. surrogate ๋ฉด์ฑ… ๋ช…์‹œ, fail-closed ๋…์„ฑ ๊ฒŒ์ดํŠธ, home-advantage baseline ๋ณด์ •์ด ๊ฐ€์งœ ์ ์ˆ˜์˜ ์˜์‚ฌ๊ฒฐ์ • ์˜ค์—ผ์„ ๋ง‰๋Š”๋‹ค (multiobjective.py:15-18,209,436-447).
  • ์„ ํƒ์„ฑ ์šฐ์„  ๋ชฉํ‘œ: SSTR1/3/4/5 ๋Œ€๋น„ SSTR2 ์„ ํƒ์„ฑ์€ off-target ๋ถ€์ž‘์šฉ ํšŒํ”ผ์˜ ํ•ต์‹ฌ์ด๋‹ค. delta_margin(home-advantage ๋ณด์ •) ์ด native ๋Œ€๋น„ ์ง„์งœ ์„ ํƒ์„ฑ ์‹ ํ˜ธ๋ฅผ ์ œ๊ณตํ•œ๋‹ค (multiobjective.py:447-448).

๊ฒ€์ฆ ์ธ์šฉ ๋ชฉ๋ก

์ฃผ์žฅ ์ธ์šฉ
4๋ชฉ์  ์ •์˜(ddgโ†“/half_lifeโ†‘/selectivityโ†‘/admetโ†‘) multiobjective.py:5-9
surrogate vs ์‹ค์ธก honest disclaimer multiobjective.py:15-18
cost-tiered 2๊ณ„์ธต ๊ตฌ์กฐ multiobjective.py:11-13
admet_reasonableness ๊ฐ€์ค‘์น˜(0.35/0.30/0.15/0.20) multiobjective.py:89
cheap_objectives ๋ฐ˜๊ฐ๊ธฐ ์•™์ƒ๋ธ”+ํด๋ฐฑ multiobjective.py:111-129
SS bond pI ์ œ์™ธ ์ฒ˜๋ฆฌ multiobjective.py:139-140
๋…์„ฑ ํŽ˜๋„ํ‹ฐ ์ƒ์ˆ˜(0.4 / ยฑ5.0 / 200.0) multiobjective.py:163-168
binary is_toxic ๋น„๋ณ€๋ณ„ โ†’ hc50 ๊ฒŒ์ดํŠธ multiobjective.py:211-219
hc50 home-advantage ํŽ˜๋„ํ‹ฐ multiobjective.py:232-241
VR-A2 ์„ ํ˜• ํด๋ฐฑ hc50 skip multiobjective.py:225-228
fail-closed(available=False ๋ฌด์ฒ˜๋ฆฌ) multiobjective.py:209,217
ObjectiveWeights ๊ฐ’(0.40/0.25/0.20/0.15) multiobjective.py:273-278
multiobjective_scalar ์ •๊ทœํ™”ยท์„ ํ˜•ํ•ฉ multiobjective.py:293-305
select_topk_for_selectivity clash ๊ฒŒ์ดํŠธ multiobjective.py:308-323
screen_selectivity ๋ณ‘๋ ฌ ๋„ํ‚น multiobjective.py:401-425
selectivity_margin ์ •์˜ multiobjective.py:434-435
delta_margin home-advantage ๋ณด์ • multiobjective.py:436-448
NSGA-II ๊ฐ€์ค‘ํ•ฉ ๋Œ€์ฒด pareto_ranking.py:3-4
4๋ชฉ์  ์ตœ์†Œํ™” ๋ณ€ํ™˜ pareto_ranking.py:35-53
์ œ์•ฝ(hard_violations/clash) pareto_ranking.py:56-77
infeasible relegation pareto_ranking.py:80-117
๋น„์ง€๋ฐฐ ์ •๋ ฌ+crowding pareto_ranking.py:169-188
select_from_pareto_front ์ •๋ ฌํ‚ค pareto_ranking.py:224-231
GNINA dry-run mock ๋™์ž‘ gnina_rescoring.py:7-9,30,45-47,205-210
dry-run mock ๊ฐ’ gnina_rescoring.py:32-37
GNINA ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ถ€์žฌ(ํ™˜๊ฒฝ ์‹ค์ธก) which gnina โ†’ not found
ECR ๊ณต์‹ gnina_rescoring.py:333-337,388
ECR NaNโ†’inf ์ฒ˜๋ฆฌ gnina_rescoring.py:374-375
ECR ๊ธฐ๋ณธ score_keys gnina_rescoring.py:359-360
ํŒŒ์ดํ”„๋ผ์ธ 5๋‹จ๊ณ„ ์ฒด์ธ scoring_pipeline.py:42-281
ECR ํ˜ธ์ถœ ์‹œ ddg ์ถ”๊ฐ€(4ํ•ญ) scoring_pipeline.py:177
Pareto diversity=0.0 ๊ณ ์ • scoring_pipeline.py:218
Pareto ์ž…๋ ฅ ํด๋ฐฑ(clash proxy/ECR) scoring_pipeline.py:204-212
pepADMET env ๋น„ํ™œ์„ฑ ํ”Œ๋ž˜๊ทธ scoring_pipeline.py:104-105
runner scalar UI ๋ณด์กฐ ํ˜ธ์ถœ runner.py:64-73
runner ์„ ํƒ์„ฑ top-K config-gated runner.py:1414-1445