factorforge-cds 3.4.5__tar.gz → 3.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {factorforge_cds-3.4.5/src/factorforge_cds.egg-info → factorforge_cds-3.5.2}/PKG-INFO +49 -5
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/README.md +44 -4
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/pyproject.toml +7 -2
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/__init__.py +3 -1
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/analysis/metrics.py +69 -0
- factorforge_cds-3.5.2/src/factorforge/analysis/sequence_risks.py +97 -0
- factorforge_cds-3.5.2/src/factorforge/benchmark/models.py +62 -0
- factorforge_cds-3.5.2/src/factorforge/benchmark/runner.py +144 -0
- factorforge_cds-3.5.2/src/factorforge/cli/benchmark_cli.py +124 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/cli/main.py +461 -18
- factorforge_cds-3.5.2/src/factorforge/constraints/__init__.py +5 -0
- factorforge_cds-3.5.2/src/factorforge/constraints/type_iis.py +22 -0
- factorforge_cds-3.5.2/src/factorforge/core/host_model.py +155 -0
- factorforge_cds-3.5.2/src/factorforge/core/reranker.py +353 -0
- factorforge_cds-3.5.2/src/factorforge/core/slate_engine.py +241 -0
- factorforge_cds-3.5.2/src/factorforge/core/slate_generator.py +363 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/database.py +1 -1
- factorforge_cds-3.5.2/src/factorforge/db/__init__.py +5 -0
- factorforge_cds-3.5.2/src/factorforge/db/connector.py +514 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/__init__.py +48 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/acquisition.py +236 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/acquisition_logger.py +263 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/filter.py +165 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/generator.py +431 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/panel_builder.py +512 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/schemas.py +129 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/slate.py +310 -0
- factorforge_cds-3.5.2/src/factorforge/discovery/traits.py +95 -0
- factorforge_cds-3.5.2/src/factorforge/engines/__init__.py +63 -0
- factorforge_cds-3.5.2/src/factorforge/engines/balanced_optimizer.py +153 -0
- factorforge_cds-3.5.2/src/factorforge/engines/dp_adapter.py +80 -0
- factorforge_cds-3.5.2/src/factorforge/engines/dp_v2.py +203 -0
- factorforge_cds-3.5.2/src/factorforge/engines/dp_v2_1.py +312 -0
- factorforge_cds-3.5.2/src/factorforge/engines/dp_v2_1_1.py +547 -0
- factorforge_cds-3.5.2/src/factorforge/engines/lm/__init__.py +9 -0
- factorforge_cds-3.5.2/src/factorforge/engines/lm/adapter.py +103 -0
- factorforge_cds-3.5.2/src/factorforge/engines/lm/diagnostics.py +42 -0
- factorforge_cds-3.5.2/src/factorforge/engines/lm/inference.py +323 -0
- factorforge_cds-3.5.2/src/factorforge/engines/lm/models/__init__.py +5 -0
- factorforge_cds-3.5.2/src/factorforge/engines/lm/models/mbart_codon.py +146 -0
- factorforge_cds-3.5.2/src/factorforge/engines/lm/tokenizer/control_tokenizer.py +203 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/__init__.py +3 -1
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/optimizer.py +43 -15
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/scoring.py +58 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/registry.py +7 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/adapters.py +193 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/automaton.py +95 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/compiler.py +115 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/data/__init__.py +28 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/data/corpus_builder.py +314 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/data/tokenizer.py +166 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/decoder.py +522 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/interfaces.py +375 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/model/__init__.py +35 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/model/arch.py +179 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/model/baselines.py +138 -0
- factorforge_cds-3.5.2/src/factorforge/engines/sllm/model/export_onnx.py +110 -0
- factorforge_cds-3.5.2/src/factorforge/engines/targeted_remediator.py +93 -0
- factorforge_cds-3.5.2/src/factorforge/evaluation/evaluator.py +219 -0
- factorforge_cds-3.5.2/src/factorforge/evaluation/evidence_logger.py +120 -0
- factorforge_cds-3.5.2/src/factorforge/evaluation/metrics.py +29 -0
- factorforge_cds-3.5.2/src/factorforge/evaluation/models.py +78 -0
- factorforge_cds-3.5.2/src/factorforge/learning/__init__.py +14 -0
- factorforge_cds-3.5.2/src/factorforge/learning/ranker.py +272 -0
- factorforge_cds-3.5.2/src/factorforge/profiles/profile_snapshot.py +86 -0
- factorforge_cds-3.5.2/src/factorforge/registry/method_registry.py +61 -0
- factorforge_cds-3.5.2/src/factorforge/registry/model_registry.py +190 -0
- factorforge_cds-3.5.2/src/factorforge/registry/version_manifest.json +65 -0
- factorforge_cds-3.5.2/src/factorforge/registry/versioning.py +46 -0
- factorforge_cds-3.5.2/src/factorforge/review/evaluation_integrity.py +91 -0
- factorforge_cds-3.5.2/src/factorforge/rules/models.py +93 -0
- factorforge_cds-3.5.2/src/factorforge/rules/registry.py +474 -0
- factorforge_cds-3.5.2/src/factorforge/schemas/evidence.py +68 -0
- factorforge_cds-3.5.2/src/factorforge/schemas/run_config.schema.json +29 -0
- factorforge_cds-3.5.2/src/factorforge/scoring/quantization.py +98 -0
- factorforge_cds-3.5.2/src/factorforge/sops/config.py +125 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/utils/exceptions.py +5 -0
- factorforge_cds-3.5.2/src/factorforge/utils/sequence_identity.py +43 -0
- factorforge_cds-3.5.2/src/factorforge/validation/hub.py +303 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2/src/factorforge_cds.egg-info}/PKG-INFO +49 -5
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge_cds.egg-info/SOURCES.txt +87 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge_cds.egg-info/requires.txt +5 -0
- factorforge_cds-3.5.2/src/factorforge_cds.egg-info/top_level.txt +3 -0
- factorforge_cds-3.5.2/src/scratch/integrity_test.py +100 -0
- factorforge_cds-3.5.2/src/scripts/seed_fixtures.py +107 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_benchmark_codon_table_metadata.py +1 -0
- factorforge_cds-3.5.2/tests/test_benchmark_harness.py +45 -0
- factorforge_cds-3.5.2/tests/test_cross_engine_evaluation.py +69 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_database.py +4 -0
- factorforge_cds-3.5.2/tests/test_db_connector.py +83 -0
- factorforge_cds-3.5.2/tests/test_dp_v2_1_1.py +176 -0
- factorforge_cds-3.5.2/tests/test_dp_v2_1_initiation.py +250 -0
- factorforge_cds-3.5.2/tests/test_lm_alpha_contract.py +69 -0
- factorforge_cds-3.5.2/tests/test_math_v2_dp_optimality.py +145 -0
- factorforge_cds-3.5.2/tests/test_multi_engine_governance.py +86 -0
- factorforge_cds-3.5.2/tests/test_optional_postgres.py +36 -0
- factorforge_cds-3.5.2/tests/test_partial_dp_rescue.py +198 -0
- factorforge_cds-3.5.2/tests/test_prepare_lm_dataset.py +50 -0
- factorforge_cds-3.5.2/tests/test_quantized_scoring.py +58 -0
- factorforge_cds-3.5.2/tests/test_rule_registry.py +112 -0
- factorforge_cds-3.5.2/tests/test_sllm_biological_pipeline.py +177 -0
- factorforge_cds-3.5.2/tests/test_slm_decoder.py +220 -0
- factorforge_cds-3.5.2/tests/test_slm_interface.py +80 -0
- factorforge_cds-3.5.2/tests/test_sop_profile.py +66 -0
- factorforge_cds-3.5.2/tests/test_suite_capability_compat.py +62 -0
- factorforge_cds-3.5.2/tests/test_version_manifest.py +21 -0
- factorforge_cds-3.4.5/src/factorforge/engines/__init__.py +0 -24
- factorforge_cds-3.4.5/src/factorforge_cds.egg-info/top_level.txt +0 -1
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/LICENSE +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/setup.cfg +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/__main__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/analysis/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/analysis/feasibility.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/cli/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/cli/legacy_cli.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/core/interfaces/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/core/interfaces/exporter.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/core/interfaces/optimizer.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/core/interfaces/validator.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/nbenthamiana_codons.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/nbenthamiana_golden_set.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/ntabacum_codons.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/nbev11_cds_all_derived_codons.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/nbev11_cds_all_derived_filtered_stats.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/nbev11_cds_all_derived_manifest.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/nbev11_cds_hc_derived_codons.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/nbev11_cds_hc_derived_filtered_stats.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/nbev11_cds_hc_derived_manifest.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/qld183_v103_derived_codons.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/qld183_v103_derived_manifest.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/profiles/qld183_v103_filtered_stats.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/reference/reference_policy_manifest.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/templates/high_expression.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/templates/standard_expression.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/data/wolffia_globosa_codons.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/design_review.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/codon_table_builder.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/construct_builder.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/exporter.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/pipeline.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/rules/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/rules/domesticator.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/rules/reverse_translator.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/rules/rule_engine.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/scoring_ml.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/utils.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/engines/profile/validator.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/io/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/io/fasta.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/io/validation.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/protein_risk/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/protein_risk/annotate.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/protein_risk/kd_scale.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/protein_risk/risk_classifier.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/protein_risk/sp_predict.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/protein_risk/tm_predict.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/registry/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/registry/registry_loader.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/review/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/schemas/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/schemas/design_package.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/schemas/design_package.schema.json +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/utils/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/utils/construct_id.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/utils/restriction_sites.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/utils/sequence_validator.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/utils/validation.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/validation/__init__.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/validation/cli.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/validation/package_generator.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/validation_registry.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge/validation_report.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge_cds.egg-info/dependency_links.txt +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/src/factorforge_cds.egg-info/entry_points.txt +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_baselines.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_benchmark_cli_output_guard.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_benchmark_regression.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_benchmark_scoring.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_benchmark_smoke.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_cai.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_codon_table_manifest.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_design_package_schema.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_design_package_semantics.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_design_package_serialization.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_design_review.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_docs_consistency.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_fasta_io.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_gc_content.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_host_profile_metadata.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_iupac_validation.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_legacy_cli.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_no_raw_sequence_logging.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_openbio_missing_metric_contract.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_parameter_registry.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_protein_risk.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_registry_production_sync.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_restriction_sites.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_sequence_validator.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_translation_integrity.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_validation_contract_compat.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_validation_registry.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_validation_report.py +0 -0
- {factorforge_cds-3.4.5 → factorforge_cds-3.5.2}/tests/test_worked_example.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: factorforge-cds
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.2
|
|
4
4
|
Summary: FactorForge - open-source CDS design and pre-synthesis sequence review by Eijex.
|
|
5
5
|
Author-email: Eijex <eijex.lab@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-only
|
|
@@ -19,6 +19,9 @@ Requires-Dist: biopython>=1.81
|
|
|
19
19
|
Requires-Dist: requests>=2.31
|
|
20
20
|
Requires-Dist: click>=8.4.1
|
|
21
21
|
Requires-Dist: pydantic>=2.0
|
|
22
|
+
Provides-Extra: postgres
|
|
23
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "postgres"
|
|
24
|
+
Requires-Dist: sqlalchemy>=2.0; extra == "postgres"
|
|
22
25
|
Provides-Extra: dev
|
|
23
26
|
Requires-Dist: jsonschema>=4.0; extra == "dev"
|
|
24
27
|
Requires-Dist: pandas>=2.0; extra == "dev"
|
|
@@ -29,6 +32,7 @@ Requires-Dist: pyyaml>=6.0; extra == "dev"
|
|
|
29
32
|
Provides-Extra: ml
|
|
30
33
|
Requires-Dist: transformers>=4.40; extra == "ml"
|
|
31
34
|
Requires-Dist: torch>=2.0; extra == "ml"
|
|
35
|
+
Requires-Dist: onnxruntime>=1.16; extra == "ml"
|
|
32
36
|
Dynamic: license-file
|
|
33
37
|
|
|
34
38
|
# FactorForge
|
|
@@ -42,10 +46,20 @@ Dynamic: license-file
|
|
|
42
46
|
[](https://codecov.io/gh/eijex/factorforge-cds)
|
|
43
47
|
[](https://doi.org/10.5281/zenodo.20407330)
|
|
44
48
|
[](https://factorforge.eijex.com)
|
|
45
|
-
[](https://www.buymeacoffee.com/eijex)
|
|
46
50
|
|
|
47
51
|
|
|
48
|
-
FactorForge performs
|
|
52
|
+
FactorForge performs deterministic CDS design with CAI/GC metrics, PolyA-signal screening, and Golden Gate/MoClo-aware checks. It is positioned as a pre-synthesis review harness: it helps teams generate reproducible CDS candidates, inspect assembly-relevant sequence constraints, and package design metadata before downstream synthesis, cloning, or experimental review. Primary support: *N. benthamiana* (agroinfiltration). Experimental host context: Tobacco BY-2 (`--host by2`).
|
|
53
|
+
|
|
54
|
+
FactorForge v3.5.1 uses independently versioned engines:
|
|
55
|
+
|
|
56
|
+
| Generation | Engine | Version | Availability |
|
|
57
|
+
|---|---|---:|---|
|
|
58
|
+
| Gen 1 | Rule/profile | 1.0.0 | Stable, public |
|
|
59
|
+
| Gen 2 | DP v2 | 2.0.1 | Stable, public default feasibility path |
|
|
60
|
+
| Gen 2 | DP v2.1.1 | 2.1.1 | Explicit local-guard path; computational evidence only |
|
|
61
|
+
| Gen 3 | sLLM Hybrid | 0.2.0-preview.1 | Feature-gated constrained-generation research preview |
|
|
62
|
+
| Rescue | Adaptive partial DP | 1.0.0 | Exact suffix rescue conditioned on a verified prefix |
|
|
49
63
|
|
|
50
64
|
**→ [Full Documentation](https://eijex.github.io/factorforge-cds/)** · **[Roadmap](https://eijex.github.io/factorforge-cds/roadmap/)**
|
|
51
65
|
|
|
@@ -75,12 +89,30 @@ Or use the **[web app](https://factorforge.eijex.com)** — no installation requ
|
|
|
75
89
|
|
|
76
90
|
## Repository Structure
|
|
77
91
|
|
|
78
|
-
The supported
|
|
92
|
+
The supported deterministic engines are the profile engine, stable DP v2, and
|
|
93
|
+
the explicit DP v2.1.1 development candidate under:
|
|
79
94
|
|
|
80
95
|
```text
|
|
81
96
|
src/factorforge/engines/profile/
|
|
97
|
+
src/factorforge/engines/dp_v2.py
|
|
98
|
+
src/factorforge/engines/dp_v2_1_1.py
|
|
99
|
+
src/factorforge/engines/sllm/
|
|
100
|
+
src/factorforge/discovery/
|
|
82
101
|
```
|
|
83
102
|
|
|
103
|
+
DP v2.1.1 adds an exact active-layer 5′ GC guard and Aho-Corasick rejection of
|
|
104
|
+
homopolymers of 6 nt or longer to the v2.1 initiation-aware objective. It emits
|
|
105
|
+
local-composition metrics and, when ViennaRNA and sufficient transcript context
|
|
106
|
+
are available, a separately evaluated 5′ MFE value. It is not the default; its
|
|
107
|
+
single-target calibration does not establish holdout generalization or biological
|
|
108
|
+
performance.
|
|
109
|
+
|
|
110
|
+
The v3.5.1 discovery-slate surface generates versioned Top-K research candidates,
|
|
111
|
+
applies a shared deterministic hard-constraint filter, and records generator and
|
|
112
|
+
fallback lineage. The sLLM path is disabled by default. A partial-DP rescue solves
|
|
113
|
+
an exact suffix conditioned on the retained prefix; it is not a claim of global
|
|
114
|
+
optimality and does not establish biological performance.
|
|
115
|
+
|
|
84
116
|
Historical implementation tracks are preserved under `archive/` for provenance
|
|
85
117
|
and are not imported by the installed package or exposed as supported engines.
|
|
86
118
|
|
|
@@ -95,7 +127,7 @@ FactorForge outputs are **in-silico only** and have not been experimentally vali
|
|
|
95
127
|
## Citing
|
|
96
128
|
|
|
97
129
|
```
|
|
98
|
-
FactorForge v3.
|
|
130
|
+
FactorForge v3.5.2 (2026). Open-source constraint-based CDS design and sequence review.
|
|
99
131
|
Eijex. https://github.com/eijex/factorforge-cds
|
|
100
132
|
```
|
|
101
133
|
|
|
@@ -105,6 +137,12 @@ Eijex. https://github.com/eijex/factorforge-cds
|
|
|
105
137
|
|
|
106
138
|
Mun-Kyu Kim ([@eijex](https://github.com/eijex))
|
|
107
139
|
|
|
140
|
+
## Optional PostgreSQL support
|
|
141
|
+
|
|
142
|
+
Install `pip install "factorforge-cds[postgres]"` only when using the PostgreSQL
|
|
143
|
+
connector. Explicit local SQLite checkpoints do not require a PostgreSQL driver.
|
|
144
|
+
Database integration tests require an explicitly configured `DATABASE_URL`.
|
|
145
|
+
|
|
108
146
|
## License
|
|
109
147
|
|
|
110
148
|
GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
|
|
@@ -118,6 +156,12 @@ GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
|
|
|
118
156
|
- **Sponsor** — Support our research via [GitHub Sponsors](https://github.com/sponsors/eijex) or [PayPal](https://paypal.me/munkyukim)
|
|
119
157
|
- **Docs** — [eijex.github.io/factorforge-cds](https://eijex.github.io/factorforge-cds/)
|
|
120
158
|
- **Wet-lab Feedback** — Public-safe feedback summaries are welcome via [Share Wet-lab Feedback (GitHub)](https://github.com/eijex/factorforge-cds/issues/new?template=wet_lab_result.yml). Do not submit raw sequences, confidential construct details, internal batch IDs, patient data, private contact information, exact process parameters, or confidential partner/customer data. Email `eijex.lab@gmail.com` for private or sensitive summaries. See [VALIDATION.md](VALIDATION.md) before submitting.
|
|
159
|
+
- **Sequence Policy Profiles** — FactorForge applies the sequence-review policy
|
|
160
|
+
chosen by the user; it does not define or approve a laboratory SOP. The web
|
|
161
|
+
app provides an illustrative YAML example and active-profile export.
|
|
162
|
+
Sequence-free, non-confidential suggestions can be submitted through the
|
|
163
|
+
public policy-template form; use `eijex.lab@gmail.com` for private or
|
|
164
|
+
laboratory-specific profiles. See the [sequence policy boundary](docs/sequence-policy.md).
|
|
121
165
|
- **GitHub Issues** — bugs, features: [github.com/eijex/factorforge-cds/issues](https://github.com/eijex/factorforge-cds/issues)
|
|
122
166
|
- **Email** — eijex.lab@gmail.com
|
|
123
167
|
- **FactorForge** — [factorforge.eijex.com](https://factorforge.eijex.com)
|
|
@@ -9,10 +9,20 @@
|
|
|
9
9
|
[](https://codecov.io/gh/eijex/factorforge-cds)
|
|
10
10
|
[](https://doi.org/10.5281/zenodo.20407330)
|
|
11
11
|
[](https://factorforge.eijex.com)
|
|
12
|
-
[](https://www.buymeacoffee.com/eijex)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
FactorForge performs
|
|
15
|
+
FactorForge performs deterministic CDS design with CAI/GC metrics, PolyA-signal screening, and Golden Gate/MoClo-aware checks. It is positioned as a pre-synthesis review harness: it helps teams generate reproducible CDS candidates, inspect assembly-relevant sequence constraints, and package design metadata before downstream synthesis, cloning, or experimental review. Primary support: *N. benthamiana* (agroinfiltration). Experimental host context: Tobacco BY-2 (`--host by2`).
|
|
16
|
+
|
|
17
|
+
FactorForge v3.5.1 uses independently versioned engines:
|
|
18
|
+
|
|
19
|
+
| Generation | Engine | Version | Availability |
|
|
20
|
+
|---|---|---:|---|
|
|
21
|
+
| Gen 1 | Rule/profile | 1.0.0 | Stable, public |
|
|
22
|
+
| Gen 2 | DP v2 | 2.0.1 | Stable, public default feasibility path |
|
|
23
|
+
| Gen 2 | DP v2.1.1 | 2.1.1 | Explicit local-guard path; computational evidence only |
|
|
24
|
+
| Gen 3 | sLLM Hybrid | 0.2.0-preview.1 | Feature-gated constrained-generation research preview |
|
|
25
|
+
| Rescue | Adaptive partial DP | 1.0.0 | Exact suffix rescue conditioned on a verified prefix |
|
|
16
26
|
|
|
17
27
|
**→ [Full Documentation](https://eijex.github.io/factorforge-cds/)** · **[Roadmap](https://eijex.github.io/factorforge-cds/roadmap/)**
|
|
18
28
|
|
|
@@ -42,12 +52,30 @@ Or use the **[web app](https://factorforge.eijex.com)** — no installation requ
|
|
|
42
52
|
|
|
43
53
|
## Repository Structure
|
|
44
54
|
|
|
45
|
-
The supported
|
|
55
|
+
The supported deterministic engines are the profile engine, stable DP v2, and
|
|
56
|
+
the explicit DP v2.1.1 development candidate under:
|
|
46
57
|
|
|
47
58
|
```text
|
|
48
59
|
src/factorforge/engines/profile/
|
|
60
|
+
src/factorforge/engines/dp_v2.py
|
|
61
|
+
src/factorforge/engines/dp_v2_1_1.py
|
|
62
|
+
src/factorforge/engines/sllm/
|
|
63
|
+
src/factorforge/discovery/
|
|
49
64
|
```
|
|
50
65
|
|
|
66
|
+
DP v2.1.1 adds an exact active-layer 5′ GC guard and Aho-Corasick rejection of
|
|
67
|
+
homopolymers of 6 nt or longer to the v2.1 initiation-aware objective. It emits
|
|
68
|
+
local-composition metrics and, when ViennaRNA and sufficient transcript context
|
|
69
|
+
are available, a separately evaluated 5′ MFE value. It is not the default; its
|
|
70
|
+
single-target calibration does not establish holdout generalization or biological
|
|
71
|
+
performance.
|
|
72
|
+
|
|
73
|
+
The v3.5.1 discovery-slate surface generates versioned Top-K research candidates,
|
|
74
|
+
applies a shared deterministic hard-constraint filter, and records generator and
|
|
75
|
+
fallback lineage. The sLLM path is disabled by default. A partial-DP rescue solves
|
|
76
|
+
an exact suffix conditioned on the retained prefix; it is not a claim of global
|
|
77
|
+
optimality and does not establish biological performance.
|
|
78
|
+
|
|
51
79
|
Historical implementation tracks are preserved under `archive/` for provenance
|
|
52
80
|
and are not imported by the installed package or exposed as supported engines.
|
|
53
81
|
|
|
@@ -62,7 +90,7 @@ FactorForge outputs are **in-silico only** and have not been experimentally vali
|
|
|
62
90
|
## Citing
|
|
63
91
|
|
|
64
92
|
```
|
|
65
|
-
FactorForge v3.
|
|
93
|
+
FactorForge v3.5.2 (2026). Open-source constraint-based CDS design and sequence review.
|
|
66
94
|
Eijex. https://github.com/eijex/factorforge-cds
|
|
67
95
|
```
|
|
68
96
|
|
|
@@ -72,6 +100,12 @@ Eijex. https://github.com/eijex/factorforge-cds
|
|
|
72
100
|
|
|
73
101
|
Mun-Kyu Kim ([@eijex](https://github.com/eijex))
|
|
74
102
|
|
|
103
|
+
## Optional PostgreSQL support
|
|
104
|
+
|
|
105
|
+
Install `pip install "factorforge-cds[postgres]"` only when using the PostgreSQL
|
|
106
|
+
connector. Explicit local SQLite checkpoints do not require a PostgreSQL driver.
|
|
107
|
+
Database integration tests require an explicitly configured `DATABASE_URL`.
|
|
108
|
+
|
|
75
109
|
## License
|
|
76
110
|
|
|
77
111
|
GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
|
|
@@ -85,6 +119,12 @@ GNU Affero General Public License v3.0 — see [LICENSE](LICENSE).
|
|
|
85
119
|
- **Sponsor** — Support our research via [GitHub Sponsors](https://github.com/sponsors/eijex) or [PayPal](https://paypal.me/munkyukim)
|
|
86
120
|
- **Docs** — [eijex.github.io/factorforge-cds](https://eijex.github.io/factorforge-cds/)
|
|
87
121
|
- **Wet-lab Feedback** — Public-safe feedback summaries are welcome via [Share Wet-lab Feedback (GitHub)](https://github.com/eijex/factorforge-cds/issues/new?template=wet_lab_result.yml). Do not submit raw sequences, confidential construct details, internal batch IDs, patient data, private contact information, exact process parameters, or confidential partner/customer data. Email `eijex.lab@gmail.com` for private or sensitive summaries. See [VALIDATION.md](VALIDATION.md) before submitting.
|
|
122
|
+
- **Sequence Policy Profiles** — FactorForge applies the sequence-review policy
|
|
123
|
+
chosen by the user; it does not define or approve a laboratory SOP. The web
|
|
124
|
+
app provides an illustrative YAML example and active-profile export.
|
|
125
|
+
Sequence-free, non-confidential suggestions can be submitted through the
|
|
126
|
+
public policy-template form; use `eijex.lab@gmail.com` for private or
|
|
127
|
+
laboratory-specific profiles. See the [sequence policy boundary](docs/sequence-policy.md).
|
|
88
128
|
- **GitHub Issues** — bugs, features: [github.com/eijex/factorforge-cds/issues](https://github.com/eijex/factorforge-cds/issues)
|
|
89
129
|
- **Email** — eijex.lab@gmail.com
|
|
90
130
|
- **FactorForge** — [factorforge.eijex.com](https://factorforge.eijex.com)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "factorforge-cds"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.5.2"
|
|
8
8
|
description = "FactorForge - open-source CDS design and pre-synthesis sequence review by Eijex."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-only"
|
|
@@ -27,6 +27,10 @@ dependencies = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.optional-dependencies]
|
|
30
|
+
postgres = [
|
|
31
|
+
"psycopg2-binary>=2.9",
|
|
32
|
+
"sqlalchemy>=2.0",
|
|
33
|
+
]
|
|
30
34
|
dev = [
|
|
31
35
|
"jsonschema>=4.0",
|
|
32
36
|
"pandas>=2.0",
|
|
@@ -38,6 +42,7 @@ dev = [
|
|
|
38
42
|
ml = [
|
|
39
43
|
"transformers>=4.40",
|
|
40
44
|
"torch>=2.0",
|
|
45
|
+
"onnxruntime>=1.16",
|
|
41
46
|
]
|
|
42
47
|
|
|
43
48
|
[project.urls]
|
|
@@ -53,7 +58,7 @@ factorforge-validate = "factorforge.validation.cli:main"
|
|
|
53
58
|
where = ["src"]
|
|
54
59
|
|
|
55
60
|
[tool.setuptools.package-data]
|
|
56
|
-
"factorforge" = ["data/*.json", "data/templates/*.json", "data/profiles/*.json", "data/reference/*.json", "schemas/*.json"]
|
|
61
|
+
"factorforge" = ["data/*.json", "data/templates/*.json", "data/profiles/*.json", "data/reference/*.json", "schemas/*.json", "registry/*.json"]
|
|
57
62
|
|
|
58
63
|
[tool.pytest.ini_options]
|
|
59
64
|
testpaths = ["tests"]
|
|
@@ -4,7 +4,9 @@ FactorForge - Codon Optimization Platform
|
|
|
4
4
|
profile: constraint-aware rule/profile engine
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from factorforge.registry.versioning import product_version
|
|
8
|
+
|
|
9
|
+
__version__ = product_version()
|
|
8
10
|
__author__ = "Eijex"
|
|
9
11
|
|
|
10
12
|
# Auto-register engines (safe when running from source tree)
|
|
@@ -434,3 +434,72 @@ def detect_invalid_codons(sequence: str) -> list[dict[str, Any]]:
|
|
|
434
434
|
elif codon not in STANDARD_GENETIC_CODE:
|
|
435
435
|
findings.append({"start": start, "end": start + 3, "codon": codon, "reason": "unknown"})
|
|
436
436
|
return findings
|
|
437
|
+
import math
|
|
438
|
+
import collections
|
|
439
|
+
|
|
440
|
+
def calculate_synonymous_entropy(sequence, codon_to_aa):
|
|
441
|
+
from factorforge.analysis.metrics import _codons
|
|
442
|
+
codons = _codons(sequence)
|
|
443
|
+
aa_to_codons = collections.defaultdict(list)
|
|
444
|
+
for c in codons:
|
|
445
|
+
aa = codon_to_aa.get(c, 'X')
|
|
446
|
+
aa_to_codons[aa].append(c)
|
|
447
|
+
|
|
448
|
+
entropies = {}
|
|
449
|
+
for aa, used_codons in aa_to_codons.items():
|
|
450
|
+
if aa in ['M', 'W', 'X']:
|
|
451
|
+
entropies[aa] = 0.0
|
|
452
|
+
continue
|
|
453
|
+
total = len(used_codons)
|
|
454
|
+
counts = collections.Counter(used_codons)
|
|
455
|
+
h = 0.0
|
|
456
|
+
for c, count in counts.items():
|
|
457
|
+
p = count / total
|
|
458
|
+
h -= p * math.log2(p)
|
|
459
|
+
|
|
460
|
+
# Max entropy for this AA
|
|
461
|
+
possible_codons = [k for k, v in codon_to_aa.items() if v == aa]
|
|
462
|
+
k = len(possible_codons)
|
|
463
|
+
h_max = math.log2(k) if k > 1 else 1.0
|
|
464
|
+
entropies[aa] = h / h_max if h_max > 0 else 0.0
|
|
465
|
+
|
|
466
|
+
if not entropies:
|
|
467
|
+
return 0.0
|
|
468
|
+
return sum(entropies.values()) / len(entropies)
|
|
469
|
+
|
|
470
|
+
def calculate_max_synonymous_share(sequence, codon_to_aa):
|
|
471
|
+
from factorforge.analysis.metrics import _codons
|
|
472
|
+
codons = _codons(sequence)
|
|
473
|
+
aa_to_codons = collections.defaultdict(list)
|
|
474
|
+
for c in codons:
|
|
475
|
+
aa = codon_to_aa.get(c, 'X')
|
|
476
|
+
aa_to_codons[aa].append(c)
|
|
477
|
+
|
|
478
|
+
max_shares = {}
|
|
479
|
+
for aa, used_codons in aa_to_codons.items():
|
|
480
|
+
if aa in ['M', 'W', 'X']:
|
|
481
|
+
continue
|
|
482
|
+
total = len(used_codons)
|
|
483
|
+
counts = collections.Counter(used_codons)
|
|
484
|
+
if total == 0:
|
|
485
|
+
continue
|
|
486
|
+
max_share = max(counts.values()) / total
|
|
487
|
+
max_shares[aa] = max_share
|
|
488
|
+
return max_shares
|
|
489
|
+
|
|
490
|
+
def calculate_reference_distance(sequence, reference_weights, codon_to_aa):
|
|
491
|
+
from factorforge.analysis.metrics import _codons
|
|
492
|
+
codons = _codons(sequence)
|
|
493
|
+
counts = collections.Counter(codons)
|
|
494
|
+
|
|
495
|
+
distance = 0.0
|
|
496
|
+
for codon, ref_freq in reference_weights.items():
|
|
497
|
+
aa = codon_to_aa.get(codon, 'X')
|
|
498
|
+
# find sequence frequency for this codon among its synonymous peers
|
|
499
|
+
syn_codons = [c for c, a in codon_to_aa.items() if a == aa]
|
|
500
|
+
total_aa = sum(counts.get(c, 0) for c in syn_codons)
|
|
501
|
+
|
|
502
|
+
seq_freq = counts.get(codon, 0) / total_aa if total_aa > 0 else 0.0
|
|
503
|
+
# simple absolute distance
|
|
504
|
+
distance += abs(seq_freq - ref_freq)
|
|
505
|
+
return distance
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Sequence risk scanners (PolyA, Repeats, Homopolymers, etc.)
|
|
2
|
+
|
|
3
|
+
Extracted from legacy RuleEngine to serve as shared evaluation metrics.
|
|
4
|
+
"""
|
|
5
|
+
import re
|
|
6
|
+
from typing import Dict, Any, List, Set, Tuple
|
|
7
|
+
|
|
8
|
+
# Polyadenylation signals (true biological motifs)
|
|
9
|
+
POLYA_PATTERNS = {
|
|
10
|
+
# Tier 1 (canonical, highest efficiency in plants/eukaryotes)
|
|
11
|
+
"AATAAA": "canonical",
|
|
12
|
+
"ATTAAA": "variant_1",
|
|
13
|
+
"AGTAAA": "variant_2",
|
|
14
|
+
# Tier 2 (lower-frequency but functional in plants)
|
|
15
|
+
"AATACA": "tier_2",
|
|
16
|
+
"AAGAAA": "tier_2",
|
|
17
|
+
"AATGAA": "tier_2",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
UNSTABLE_MOTIFS = {
|
|
21
|
+
"ATTTA": "ARE (AU-rich element)",
|
|
22
|
+
"WWWWWW": "W=A/T, 6+ in a row"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def scan_polya(seq: str) -> List[Dict[str, Any]]:
|
|
26
|
+
"""Detect biological PolyA signal motifs (e.g. AATAAA)."""
|
|
27
|
+
seq_upper = seq.upper()
|
|
28
|
+
violations: List[Dict[str, Any]] = []
|
|
29
|
+
|
|
30
|
+
for pattern, pattern_type in POLYA_PATTERNS.items():
|
|
31
|
+
pos = 0
|
|
32
|
+
while True:
|
|
33
|
+
idx = seq_upper.find(pattern, pos)
|
|
34
|
+
if idx == -1:
|
|
35
|
+
break
|
|
36
|
+
violations.append({
|
|
37
|
+
"type": "polya_signal",
|
|
38
|
+
"pattern": pattern,
|
|
39
|
+
"pattern_type": pattern_type,
|
|
40
|
+
"position": idx,
|
|
41
|
+
"context": seq_upper[max(0, idx - 10):min(len(seq_upper), idx + len(pattern) + 10)]
|
|
42
|
+
})
|
|
43
|
+
pos = idx + 1
|
|
44
|
+
|
|
45
|
+
return violations
|
|
46
|
+
|
|
47
|
+
def scan_homopolymers(seq: str, threshold: int = 6) -> List[Dict[str, Any]]:
|
|
48
|
+
"""Find homopolymers (runs of any single nucleotide)."""
|
|
49
|
+
seq_upper = seq.upper()
|
|
50
|
+
violations: List[Dict[str, Any]] = []
|
|
51
|
+
for nt in "ATGC":
|
|
52
|
+
pattern = nt * threshold
|
|
53
|
+
pos = 0
|
|
54
|
+
while True:
|
|
55
|
+
idx = seq_upper.find(pattern, pos)
|
|
56
|
+
if idx == -1:
|
|
57
|
+
break
|
|
58
|
+
match = re.match(f"{nt}+", seq_upper[idx:])
|
|
59
|
+
length = match.end() if match else threshold
|
|
60
|
+
violations.append({
|
|
61
|
+
"type": "homopolymer",
|
|
62
|
+
"base": nt,
|
|
63
|
+
"length": length,
|
|
64
|
+
"position": idx,
|
|
65
|
+
"context": seq_upper[max(0, idx - 10):min(len(seq_upper), idx + length + 10)]
|
|
66
|
+
})
|
|
67
|
+
pos = idx + length
|
|
68
|
+
return violations
|
|
69
|
+
|
|
70
|
+
def scan_repeats(seq: str, min_length: int = 20, max_distance: int = 1000) -> List[Dict[str, Any]]:
|
|
71
|
+
"""Detect direct or inverted repeats causing assembly/stability issues.
|
|
72
|
+
|
|
73
|
+
Direct repeats (exact k-mer matches) >= 20 bp are known to cause issues with
|
|
74
|
+
DNA synthesis (e.g. Twist/IDT) and homologous recombination in host organisms.
|
|
75
|
+
"""
|
|
76
|
+
seq_upper = seq.upper()
|
|
77
|
+
violations: List[Dict[str, Any]] = []
|
|
78
|
+
|
|
79
|
+
# Naive k-mer search for direct repeats
|
|
80
|
+
# For a real implementation, a suffix tree or kmer hash map is better.
|
|
81
|
+
# We will use a fast localized search.
|
|
82
|
+
seen_kmers = {}
|
|
83
|
+
for i in range(len(seq_upper) - min_length + 1):
|
|
84
|
+
kmer = seq_upper[i:i + min_length]
|
|
85
|
+
if kmer in seen_kmers:
|
|
86
|
+
dist = i - seen_kmers[kmer]
|
|
87
|
+
if dist <= max_distance:
|
|
88
|
+
violations.append({
|
|
89
|
+
"type": "direct_repeat",
|
|
90
|
+
"pattern": kmer,
|
|
91
|
+
"position1": seen_kmers[kmer],
|
|
92
|
+
"position2": i,
|
|
93
|
+
"distance": dist
|
|
94
|
+
})
|
|
95
|
+
seen_kmers[kmer] = i
|
|
96
|
+
|
|
97
|
+
return violations
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Dict, Any, List, Optional
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from factorforge.evaluation.models import EvaluationResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BenchmarkProvenance(BaseModel):
|
|
9
|
+
"""Immutable provenance tracking for benchmark runs."""
|
|
10
|
+
input_sequence_hash: str
|
|
11
|
+
input_type: str = Field(description="protein or CDS")
|
|
12
|
+
terminal_stop_policy: str
|
|
13
|
+
codon_reference_id: str
|
|
14
|
+
constraint_profile_id: str
|
|
15
|
+
evaluator_version: str
|
|
16
|
+
method_version: str
|
|
17
|
+
git_commit: str
|
|
18
|
+
seed: Optional[int] = None
|
|
19
|
+
inference_mode: str
|
|
20
|
+
runtime_environment: str
|
|
21
|
+
timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BenchmarkTarget(BaseModel):
|
|
25
|
+
"""A target sequence for benchmarking."""
|
|
26
|
+
target_id: str
|
|
27
|
+
protein_sequence: str
|
|
28
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BenchmarkRunConfig(BaseModel):
|
|
32
|
+
"""Configuration for a benchmark execution."""
|
|
33
|
+
suite_name: str = Field(..., description="e.g., Engineering Benchmark Suite")
|
|
34
|
+
host: str = "nbenthamiana"
|
|
35
|
+
target_gc_min_percent: float
|
|
36
|
+
target_gc_max_percent: float
|
|
37
|
+
forbidden_type_iis: List[str] = Field(default_factory=list)
|
|
38
|
+
terminal_stop_policy: str = "append"
|
|
39
|
+
seed: int = 42
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EngineBenchmarkResult(BaseModel):
|
|
43
|
+
"""Benchmark result for a single engine on a single target."""
|
|
44
|
+
engine_name: str
|
|
45
|
+
target_id: str
|
|
46
|
+
provenance: BenchmarkProvenance
|
|
47
|
+
optimized_sequence: str
|
|
48
|
+
evaluation: EvaluationResult
|
|
49
|
+
runtime_seconds: float
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class BenchmarkSuiteReport(BaseModel):
|
|
53
|
+
"""Aggregated benchmark report across all targets and engines."""
|
|
54
|
+
suite_name: str
|
|
55
|
+
run_timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
|
|
56
|
+
config: BenchmarkRunConfig
|
|
57
|
+
results: List[EngineBenchmarkResult]
|
|
58
|
+
|
|
59
|
+
# Summary statistics mapped by engine
|
|
60
|
+
pass_counts: Dict[str, str] = Field(description="Raw pass counts e.g., '3/4'")
|
|
61
|
+
average_cai: Dict[str, Optional[float]]
|
|
62
|
+
average_gc: Dict[str, float]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Type
|
|
2
|
+
import time
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
from factorforge.engines import EngineRegistry, register_builtin_engines
|
|
6
|
+
from factorforge.evaluation.evaluator import SharedEvaluator
|
|
7
|
+
from factorforge.core.interfaces import OptimizerEngine
|
|
8
|
+
from factorforge.benchmark.models import (
|
|
9
|
+
BenchmarkTarget,
|
|
10
|
+
BenchmarkRunConfig,
|
|
11
|
+
EngineBenchmarkResult,
|
|
12
|
+
BenchmarkSuiteReport,
|
|
13
|
+
BenchmarkProvenance
|
|
14
|
+
)
|
|
15
|
+
from factorforge.analysis.metrics import load_codon_usage_table
|
|
16
|
+
|
|
17
|
+
# Initialize engines
|
|
18
|
+
register_builtin_engines()
|
|
19
|
+
|
|
20
|
+
class BenchmarkRunner:
|
|
21
|
+
"""AgentOS Benchmark Harness for evaluating sequence generation methods."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: BenchmarkRunConfig):
|
|
24
|
+
self.config = config
|
|
25
|
+
|
|
26
|
+
# Load canonical host table to enable CAI in the SharedEvaluator
|
|
27
|
+
# Default internal path loads N. benthamiana by default, which is our benchmark host.
|
|
28
|
+
table = load_codon_usage_table()
|
|
29
|
+
|
|
30
|
+
self.evaluator = SharedEvaluator(
|
|
31
|
+
version="1.0.1",
|
|
32
|
+
codon_weights=table.codon_weights
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def run_suite(self, targets: List[BenchmarkTarget], engine_names: List[str]) -> BenchmarkSuiteReport:
|
|
36
|
+
"""Execute the benchmark suite across requested engines and targets."""
|
|
37
|
+
|
|
38
|
+
engines: Dict[str, OptimizerEngine] = {}
|
|
39
|
+
for name in engine_names:
|
|
40
|
+
engine_instance = EngineRegistry.get(name)
|
|
41
|
+
if not engine_instance:
|
|
42
|
+
raise ValueError(f"Engine '{name}' not found in registry.")
|
|
43
|
+
engines[name] = engine_instance
|
|
44
|
+
|
|
45
|
+
results: List[EngineBenchmarkResult] = []
|
|
46
|
+
|
|
47
|
+
for target in targets:
|
|
48
|
+
input_hash = hashlib.sha256(target.protein_sequence.encode()).hexdigest()
|
|
49
|
+
|
|
50
|
+
for engine_name, engine in engines.items():
|
|
51
|
+
|
|
52
|
+
mode = getattr(engine, "inference_mode", "standard")
|
|
53
|
+
|
|
54
|
+
provenance = BenchmarkProvenance(
|
|
55
|
+
input_sequence_hash=input_hash,
|
|
56
|
+
input_type="protein",
|
|
57
|
+
terminal_stop_policy=self.config.terminal_stop_policy,
|
|
58
|
+
codon_reference_id=f"host_{self.config.host}",
|
|
59
|
+
constraint_profile_id="default_gc",
|
|
60
|
+
evaluator_version=self.evaluator.version,
|
|
61
|
+
method_version=engine.version,
|
|
62
|
+
git_commit="current", # In real usage, this would be git rev-parse HEAD
|
|
63
|
+
seed=self.config.seed,
|
|
64
|
+
inference_mode=mode,
|
|
65
|
+
runtime_environment="default"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
start_time = time.time()
|
|
69
|
+
|
|
70
|
+
# Execute generation
|
|
71
|
+
opt_result = engine.optimize(
|
|
72
|
+
sequence=target.protein_sequence,
|
|
73
|
+
host=self.config.host,
|
|
74
|
+
target_gc_min=self.config.target_gc_min_percent / 100.0, # Some engines still expect fraction
|
|
75
|
+
target_gc_max=self.config.target_gc_max_percent / 100.0,
|
|
76
|
+
forbidden_type_iis=set(self.config.forbidden_type_iis),
|
|
77
|
+
terminal_stop_policy=self.config.terminal_stop_policy,
|
|
78
|
+
seed=self.config.seed
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
runtime = time.time() - start_time
|
|
82
|
+
|
|
83
|
+
# ONE SharedEvaluator for all generated candidates
|
|
84
|
+
eval_result_obj = self.evaluator.evaluate_candidate(
|
|
85
|
+
candidate_dna=opt_result.sequence,
|
|
86
|
+
expected_protein=target.protein_sequence,
|
|
87
|
+
candidate_id=f"{engine_name}-{target.target_id}",
|
|
88
|
+
target_gc_min_percent=self.config.target_gc_min_percent,
|
|
89
|
+
target_gc_max_percent=self.config.target_gc_max_percent,
|
|
90
|
+
forbidden_type_iis=set(self.config.forbidden_type_iis)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
res = EngineBenchmarkResult(
|
|
94
|
+
engine_name=engine_name,
|
|
95
|
+
target_id=target.target_id,
|
|
96
|
+
provenance=provenance,
|
|
97
|
+
optimized_sequence=opt_result.sequence,
|
|
98
|
+
evaluation=eval_result_obj,
|
|
99
|
+
runtime_seconds=runtime
|
|
100
|
+
)
|
|
101
|
+
results.append(res)
|
|
102
|
+
|
|
103
|
+
return self._aggregate_results(results)
|
|
104
|
+
|
|
105
|
+
def _aggregate_results(self, results: List[EngineBenchmarkResult]) -> BenchmarkSuiteReport:
|
|
106
|
+
"""Aggregate results into a full BenchmarkSuiteReport."""
|
|
107
|
+
|
|
108
|
+
pass_counts = {}
|
|
109
|
+
total_targets = len(set(r.target_id for r in results))
|
|
110
|
+
|
|
111
|
+
cai_sums = {}
|
|
112
|
+
cai_vals = {}
|
|
113
|
+
gc_sums = {}
|
|
114
|
+
engine_counts = {}
|
|
115
|
+
|
|
116
|
+
for r in results:
|
|
117
|
+
eng = r.engine_name
|
|
118
|
+
if eng not in pass_counts:
|
|
119
|
+
pass_counts[eng] = 0
|
|
120
|
+
cai_vals[eng] = []
|
|
121
|
+
gc_sums[eng] = 0.0
|
|
122
|
+
engine_counts[eng] = 0
|
|
123
|
+
|
|
124
|
+
if r.evaluation.passed:
|
|
125
|
+
pass_counts[eng] += 1
|
|
126
|
+
|
|
127
|
+
if r.evaluation.metrics.cai is not None:
|
|
128
|
+
cai_vals[eng].append(r.evaluation.metrics.cai)
|
|
129
|
+
|
|
130
|
+
gc_sums[eng] += r.evaluation.metrics.gc_percent
|
|
131
|
+
engine_counts[eng] += 1
|
|
132
|
+
|
|
133
|
+
pass_counts_str = {e: f"{count}/{total_targets}" for e, count in pass_counts.items()}
|
|
134
|
+
avg_cai = {e: (sum(cai_vals[e]) / len(cai_vals[e]) if cai_vals[e] else None) for e in pass_counts}
|
|
135
|
+
avg_gc = {e: (gc_sums[e] / engine_counts[e]) for e in pass_counts}
|
|
136
|
+
|
|
137
|
+
return BenchmarkSuiteReport(
|
|
138
|
+
suite_name=self.config.suite_name,
|
|
139
|
+
config=self.config,
|
|
140
|
+
results=results,
|
|
141
|
+
pass_counts=pass_counts_str,
|
|
142
|
+
average_cai=avg_cai,
|
|
143
|
+
average_gc=avg_gc
|
|
144
|
+
)
|