agentevolve-optimizer 0.5.0__py3-none-any.whl
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.
- agent_evolve/__init__.py +722 -0
- agent_evolve/agentic.py +2800 -0
- agent_evolve/api.py +767 -0
- agent_evolve/application/__init__.py +1580 -0
- agent_evolve/application/action_allocation.py +744 -0
- agent_evolve/application/action_allocation_frame.py +347 -0
- agent_evolve/application/action_allocation_frame_commit.py +185 -0
- agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
- agent_evolve/application/action_allocation_frame_v3.py +338 -0
- agent_evolve/application/action_archive_value.py +497 -0
- agent_evolve/application/action_evidence_consistency.py +455 -0
- agent_evolve/application/action_forecast_partitioning.py +1471 -0
- agent_evolve/application/action_metric_projection.py +211 -0
- agent_evolve/application/action_role_value.py +680 -0
- agent_evolve/application/action_score_authorities.py +363 -0
- agent_evolve/application/action_structural_signature.py +116 -0
- agent_evolve/application/action_target_realization.py +402 -0
- agent_evolve/application/agentic_evolution.py +7734 -0
- agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
- agent_evolve/application/anchor_residual_identification.py +463 -0
- agent_evolve/application/archive_conditioned_action_target.py +208 -0
- agent_evolve/application/artifact_journal.py +246 -0
- agent_evolve/application/artifact_replay.py +347 -0
- agent_evolve/application/budgeted_optimizer.py +1828 -0
- agent_evolve/application/calibrated_campaign.py +485 -0
- agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
- agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
- agent_evolve/application/campaign_capacity_recourse.py +254 -0
- agent_evolve/application/campaign_contextual_outcomes.py +119 -0
- agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
- agent_evolve/application/campaign_evidence_registry.py +262 -0
- agent_evolve/application/campaign_execution.py +2537 -0
- agent_evolve/application/campaign_generation_audit.py +942 -0
- agent_evolve/application/campaign_learning.py +1812 -0
- agent_evolve/application/campaign_learning_runtime.py +1977 -0
- agent_evolve/application/campaign_search_phase.py +227 -0
- agent_evolve/application/campaign_selector_context_extension.py +220 -0
- agent_evolve/application/campaign_variation_envelope.py +649 -0
- agent_evolve/application/campaign_variation_trace.py +451 -0
- agent_evolve/application/candidate_archive_consequence.py +128 -0
- agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
- agent_evolve/application/composite_outcome_updater.py +145 -0
- agent_evolve/application/composition_portfolio_selection.py +363 -0
- agent_evolve/application/concurrent_stage.py +144 -0
- agent_evolve/application/contextual_action_allocation.py +181 -0
- agent_evolve/application/contextual_campaign_outcomes.py +267 -0
- agent_evolve/application/contextual_campaign_planning.py +1366 -0
- agent_evolve/application/contextual_delayed_credit.py +651 -0
- agent_evolve/application/contextual_search_controller.py +2374 -0
- agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
- agent_evolve/application/decision_metric_projection.py +112 -0
- agent_evolve/application/derived_action_semantics.py +129 -0
- agent_evolve/application/detailed_evaluation.py +449 -0
- agent_evolve/application/earned_lineage.py +1011 -0
- agent_evolve/application/effective_choice_audit.py +484 -0
- agent_evolve/application/empirical_consequence_calibration.py +908 -0
- agent_evolve/application/evaluation_accounting.py +325 -0
- agent_evolve/application/evaluation_cache.py +199 -0
- agent_evolve/application/evaluation_escrow.py +547 -0
- agent_evolve/application/evaluation_recourse.py +253 -0
- agent_evolve/application/event_recorder.py +151 -0
- agent_evolve/application/evolution_campaign.py +1840 -0
- agent_evolve/application/executable_hypothesis.py +323 -0
- agent_evolve/application/factorial_branch_pilot.py +772 -0
- agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
- agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
- agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
- agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
- agent_evolve/application/finite_action_selection.py +188 -0
- agent_evolve/application/finite_action_set.py +306 -0
- agent_evolve/application/finite_action_transition.py +537 -0
- agent_evolve/application/finite_variation_eligibility.py +296 -0
- agent_evolve/application/forecast_geometry_portfolio.py +799 -0
- agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
- agent_evolve/application/front_proximity_admission.py +311 -0
- agent_evolve/application/front_proximity_parent_basis.py +458 -0
- agent_evolve/application/frozen_hurdle_score.py +659 -0
- agent_evolve/application/g3_causal_screen.py +2257 -0
- agent_evolve/application/g3_causal_validation.py +1046 -0
- agent_evolve/application/g3_postseal_curation.py +818 -0
- agent_evolve/application/gated_agentic_generator.py +205 -0
- agent_evolve/application/generation_feedback.py +293 -0
- agent_evolve/application/generative_proposal_journal.py +185 -0
- agent_evolve/application/geometry_conditional_elasticity.py +453 -0
- agent_evolve/application/global_wave_action_allocation.py +1151 -0
- agent_evolve/application/head_mass_conditional_seat.py +268 -0
- agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
- agent_evolve/application/identifiable_reflection_learning.py +395 -0
- agent_evolve/application/identifiable_reflection_request.py +364 -0
- agent_evolve/application/in_memory_residual_archive.py +341 -0
- agent_evolve/application/insight_memory.py +1804 -0
- agent_evolve/application/live_runtime_manifest.py +758 -0
- agent_evolve/application/llm_task_queue.py +769 -0
- agent_evolve/application/matched_finite_action_block.py +409 -0
- agent_evolve/application/materialized_action_broker.py +2328 -0
- agent_evolve/application/materialized_action_constraints.py +83 -0
- agent_evolve/application/materialized_variation.py +211 -0
- agent_evolve/application/multi_option_evolution.py +1536 -0
- agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
- agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
- agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
- agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
- agent_evolve/application/outcome_relation.py +193 -0
- agent_evolve/application/paired_allocation_comparison.py +241 -0
- agent_evolve/application/paired_block_schedule.py +127 -0
- agent_evolve/application/parent_measurement.py +226 -0
- agent_evolve/application/pareto_archive.py +811 -0
- agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
- agent_evolve/application/portfolio_evolution.py +2950 -0
- agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
- agent_evolve/application/portfolio_memory_attribution.py +581 -0
- agent_evolve/application/portfolio_memory_dose.py +788 -0
- agent_evolve/application/portfolio_memory_matched_control.py +938 -0
- agent_evolve/application/portfolio_memory_transfer.py +297 -0
- agent_evolve/application/portfolio_optimization_memory.py +363 -0
- agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
- agent_evolve/application/portfolio_projection.py +335 -0
- agent_evolve/application/portfolio_recombination.py +2032 -0
- agent_evolve/application/post_evolution_reflection.py +834 -0
- agent_evolve/application/postcommit_rank_authority.py +245 -0
- agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
- agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
- agent_evolve/application/prequential_residual_exploration.py +343 -0
- agent_evolve/application/prequential_score_portfolio.py +954 -0
- agent_evolve/application/projections.py +292 -0
- agent_evolve/application/protected_action_committee.py +1027 -0
- agent_evolve/application/protected_branch_pilot.py +376 -0
- agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
- agent_evolve/application/provider_replay.py +910 -0
- agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
- agent_evolve/application/recombination_residual_expert.py +403 -0
- agent_evolve/application/reflection_workflow.py +571 -0
- agent_evolve/application/region_conditional_credit.py +911 -0
- agent_evolve/application/residual_campaign_runtime.py +531 -0
- agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
- agent_evolve/application/residual_headroom_ledger.py +1544 -0
- agent_evolve/application/residual_learning_transaction.py +396 -0
- agent_evolve/application/residual_portfolio_evolution.py +1228 -0
- agent_evolve/application/residual_reachability.py +749 -0
- agent_evolve/application/residual_stage_credit.py +499 -0
- agent_evolve/application/same_prefix_paired_audit.py +1580 -0
- agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
- agent_evolve/application/sequential_lineage_allocation.py +1017 -0
- agent_evolve/application/sequential_market_replay.py +1395 -0
- agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
- agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
- agent_evolve/application/single_score_action_allocation.py +299 -0
- agent_evolve/application/source_exposure_allocation.py +906 -0
- agent_evolve/application/staged_memory.py +210 -0
- agent_evolve/application/stratified_cold_start_allocation.py +732 -0
- agent_evolve/application/support_guarded_hurdle_score.py +549 -0
- agent_evolve/application/target_conditioned_action_forecast.py +595 -0
- agent_evolve/application/target_conditioned_campaign.py +566 -0
- agent_evolve/application/treatment_assignment.py +201 -0
- agent_evolve/application/trusted_objective_evidence.py +217 -0
- agent_evolve/application/two_stage_action_evolution.py +1131 -0
- agent_evolve/application/v8lite_allocation_policy.py +1083 -0
- agent_evolve/application/v9_candidate_policy.py +1303 -0
- agent_evolve/bootstrap.py +108 -0
- agent_evolve/campaign_presets.py +517 -0
- agent_evolve/campaign_profiles.py +452 -0
- agent_evolve/campaign_variation_topology.py +288 -0
- agent_evolve/campaign_workload.py +950 -0
- agent_evolve/cli.py +797 -0
- agent_evolve/contract.py +241 -0
- agent_evolve/core/__init__.py +91 -0
- agent_evolve/core/action_semantics.py +411 -0
- agent_evolve/core/authored.py +105 -0
- agent_evolve/core/formatting.py +286 -0
- agent_evolve/core/optimization_semantics.py +324 -0
- agent_evolve/core/problem.py +167 -0
- agent_evolve/core/results.py +323 -0
- agent_evolve/core/stats.py +70 -0
- agent_evolve/core/telemetry.py +100 -0
- agent_evolve/domain/__init__.py +89 -0
- agent_evolve/domain/artifact.py +162 -0
- agent_evolve/domain/durable_text.py +68 -0
- agent_evolve/domain/event.py +1454 -0
- agent_evolve/domain/finite_action_set.py +426 -0
- agent_evolve/domain/finite_variation.py +526 -0
- agent_evolve/domain/generative_emission.py +559 -0
- agent_evolve/domain/ids.py +163 -0
- agent_evolve/domain/inline_text.py +106 -0
- agent_evolve/domain/insight.py +27 -0
- agent_evolve/domain/lineage.py +737 -0
- agent_evolve/domain/llm_task_queue.py +960 -0
- agent_evolve/domain/outcome.py +96 -0
- agent_evolve/domain/patch.py +854 -0
- agent_evolve/domain/typed_json.py +542 -0
- agent_evolve/domain/variation_space.py +158 -0
- agent_evolve/driver.py +1014 -0
- agent_evolve/harness/__init__.py +29 -0
- agent_evolve/harness/base.py +242 -0
- agent_evolve/harness/directives.py +163 -0
- agent_evolve/harness/generative_seal.py +479 -0
- agent_evolve/harness/registry.py +41 -0
- agent_evolve/infrastructure/__init__.py +39 -0
- agent_evolve/infrastructure/artifacts/__init__.py +6 -0
- agent_evolve/infrastructure/artifacts/_verification.py +67 -0
- agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
- agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
- agent_evolve/infrastructure/asyncio_runtime.py +109 -0
- agent_evolve/infrastructure/authored_runtime.py +188 -0
- agent_evolve/infrastructure/authored_worker.py +171 -0
- agent_evolve/infrastructure/clock.py +53 -0
- agent_evolve/infrastructure/events/__init__.py +6 -0
- agent_evolve/infrastructure/events/_validation.py +89 -0
- agent_evolve/infrastructure/events/in_memory.py +56 -0
- agent_evolve/infrastructure/events/jsonl.py +193 -0
- agent_evolve/infrastructure/exception_provenance.py +215 -0
- agent_evolve/infrastructure/ids.py +118 -0
- agent_evolve/infrastructure/lineage_codec.py +1836 -0
- agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
- agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
- agent_evolve/infrastructure/resource_lease.py +370 -0
- agent_evolve/infrastructure/sanitization/__init__.py +8 -0
- agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
- agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
- agent_evolve/infrastructure/stream_liveness.py +383 -0
- agent_evolve/infrastructure/subprocess_boundary.py +136 -0
- agent_evolve/integrations/__init__.py +1 -0
- agent_evolve/integrations/botorch/__init__.py +28 -0
- agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
- agent_evolve/integrations/completion.py +242 -0
- agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
- agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
- agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
- agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
- agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
- agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
- agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
- agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
- agent_evolve/integrations/pydantic_ai/harness.py +159 -0
- agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
- agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
- agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
- agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
- agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
- agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
- agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
- agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
- agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
- agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
- agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
- agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
- agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
- agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
- agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
- agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
- agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
- agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
- agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
- agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
- agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
- agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
- agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
- agent_evolve/integrations/pymoo_adapter.py +242 -0
- agent_evolve/policies/__init__.py +17 -0
- agent_evolve/policies/check.py +469 -0
- agent_evolve/policies/emit_scaffold.py +451 -0
- agent_evolve/policies/feedback/__init__.py +37 -0
- agent_evolve/policies/feedback/held_out_asn.py +1325 -0
- agent_evolve/policies/genetic.py +607 -0
- agent_evolve/policies/llm_backoff.py +183 -0
- agent_evolve/policies/llm_chooser.py +226 -0
- agent_evolve/policies/llm_generator.py +1760 -0
- agent_evolve/policies/llm_init.py +267 -0
- agent_evolve/policies/llm_operator.py +109 -0
- agent_evolve/policies/llm_prior.py +194 -0
- agent_evolve/policies/llm_surrogate.py +334 -0
- agent_evolve/policies/measurement_evidence.py +704 -0
- agent_evolve/policies/memory/__init__.py +223 -0
- agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
- agent_evolve/policies/memory/compatibility_matching.py +593 -0
- agent_evolve/policies/memory/global_falsification.py +1841 -0
- agent_evolve/policies/memory/prompt_shape.py +503 -0
- agent_evolve/policies/memory/randomized_subset.py +714 -0
- agent_evolve/policies/memory/staged_causal.py +1270 -0
- agent_evolve/policies/memory/treatment_compliance.py +759 -0
- agent_evolve/policies/objective_resolution/__init__.py +17 -0
- agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
- agent_evolve/policies/operator_portfolio.py +407 -0
- agent_evolve/policies/reguidance.py +1133 -0
- agent_evolve/policies/reward/__init__.py +83 -0
- agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
- agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
- agent_evolve/policies/reward/affine_hypervolume.py +490 -0
- agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
- agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
- agent_evolve/policies/reward/frozen_archive.py +360 -0
- agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
- agent_evolve/policies/search_state.py +208 -0
- agent_evolve/policies/selection/__init__.py +345 -0
- agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
- agent_evolve/policies/selection/affine_frontier_context.py +330 -0
- agent_evolve/policies/selection/affine_frontier_target.py +473 -0
- agent_evolve/policies/selection/archive_elite.py +1346 -0
- agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
- agent_evolve/policies/selection/calibrated_slate.py +1394 -0
- agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
- agent_evolve/policies/selection/common_candidate_pool.py +685 -0
- agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
- agent_evolve/policies/selection/disjoint_pairs.py +479 -0
- agent_evolve/policies/selection/elite_explorer.py +719 -0
- agent_evolve/policies/selection/finite_action.py +187 -0
- agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
- agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
- agent_evolve/policies/selection/forecast_calibration.py +922 -0
- agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
- agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
- agent_evolve/policies/selection/full_support_slate.py +91 -0
- agent_evolve/policies/selection/meaningful_direction.py +240 -0
- agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
- agent_evolve/policies/selection/model_anchored_slate.py +826 -0
- agent_evolve/policies/selection/phenotype_recourse.py +979 -0
- agent_evolve/policies/selection/proposal_support.py +368 -0
- agent_evolve/policies/selection/random_portfolio.py +254 -0
- agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
- agent_evolve/policies/selection/residual_frontier.py +463 -0
- agent_evolve/policies/selection/residual_frontier_target.py +605 -0
- agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
- agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
- agent_evolve/policies/selection/target_conditioned_features.py +812 -0
- agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
- agent_evolve/policies/selection/task_keyed_palette.py +906 -0
- agent_evolve/policies/semantics.py +147 -0
- agent_evolve/policies/structure.py +362 -0
- agent_evolve/policies/structured_output_budget.py +62 -0
- agent_evolve/policies/surrogate.py +696 -0
- agent_evolve/policies/variation/__init__.py +1 -0
- agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
- agent_evolve/policies/variation/crossover_inheritance.py +575 -0
- agent_evolve/policies/variation/disjoint_recombination.py +611 -0
- agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
- agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
- agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
- agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
- agent_evolve/policies/variation/typed_patch.py +1981 -0
- agent_evolve/policies/weighted_prior.py +394 -0
- agent_evolve/ports/__init__.py +383 -0
- agent_evolve/ports/action_allocation.py +733 -0
- agent_evolve/ports/action_allocation_frame.py +1153 -0
- agent_evolve/ports/action_allocation_frame_commit.py +294 -0
- agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
- agent_evolve/ports/action_allocation_frame_v3.py +995 -0
- agent_evolve/ports/action_forecast.py +1568 -0
- agent_evolve/ports/action_metric_projection.py +165 -0
- agent_evolve/ports/agentic_generator.py +1561 -0
- agent_evolve/ports/archive_context.py +136 -0
- agent_evolve/ports/artifact_sanitizer.py +44 -0
- agent_evolve/ports/artifact_store.py +225 -0
- agent_evolve/ports/clock.py +13 -0
- agent_evolve/ports/contextual_search_allocation.py +827 -0
- agent_evolve/ports/decision_metric_projection.py +258 -0
- agent_evolve/ports/event_store.py +55 -0
- agent_evolve/ports/executable_hypothesis.py +557 -0
- agent_evolve/ports/finite_acquisition.py +377 -0
- agent_evolve/ports/finite_acquisition_batch.py +296 -0
- agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
- agent_evolve/ports/finite_acquisition_json.py +247 -0
- agent_evolve/ports/finite_acquisition_space.py +168 -0
- agent_evolve/ports/finite_action_selection.py +348 -0
- agent_evolve/ports/finite_action_set.py +256 -0
- agent_evolve/ports/frontier_target.py +396 -0
- agent_evolve/ports/generation_failure.py +43 -0
- agent_evolve/ports/hard_feasibility.py +233 -0
- agent_evolve/ports/id_factory.py +34 -0
- agent_evolve/ports/llm_task_queue.py +93 -0
- agent_evolve/ports/objective_resolution.py +419 -0
- agent_evolve/ports/paired_allocation_comparison.py +401 -0
- agent_evolve/ports/paired_block_schedule.py +475 -0
- agent_evolve/ports/parent_measurement.py +336 -0
- agent_evolve/ports/portfolio_memory_dose.py +643 -0
- agent_evolve/ports/portfolio_selection.py +3169 -0
- agent_evolve/ports/postcommit_rank_authority.py +467 -0
- agent_evolve/ports/presented_action_evidence.py +794 -0
- agent_evolve/ports/resource_lease.py +162 -0
- agent_evolve/ports/structured_generator.py +734 -0
- agent_evolve/ports/structured_output_budget.py +120 -0
- agent_evolve/ports/subprocess_boundary.py +138 -0
- agent_evolve/ports/treatment_assignment.py +466 -0
- agent_evolve/ports/variation_catalog.py +76 -0
- agent_evolve/ports/variation_source.py +226 -0
- agent_evolve/proposal_mode.py +157 -0
- agent_evolve/proposers/__init__.py +10 -0
- agent_evolve/proposers/random_proposer.py +188 -0
- agent_evolve/provider_accounting.py +163 -0
- agent_evolve/py.typed +0 -0
- agent_evolve/reference_method.py +1570 -0
- agent_evolve/session/__init__.py +11 -0
- agent_evolve/session/authorship.py +864 -0
- agent_evolve/session/evaluate.py +236 -0
- agent_evolve/session/fidelity.py +237 -0
- agent_evolve/session/genetic_loop.py +742 -0
- agent_evolve/session/loop.py +803 -0
- agent_evolve/session/screening.py +671 -0
- agent_evolve/settings.py +376 -0
- agent_evolve/workload_kit.py +368 -0
- agent_evolve/workload_prompt.py +398 -0
- agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
- agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
- agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
- agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
- agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
- agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
"""Generic genetic operators over a typed candidate model.
|
|
2
|
+
|
|
3
|
+
The loop this package shipped with had no genetic operators at all: it rendered
|
|
4
|
+
the Pareto front as text and asked the model to author whole configurations.
|
|
5
|
+
That is whole-artifact rewrite, and it is measurably worse than uniform random
|
|
6
|
+
sampling on every genome length we have tested (advantage_theory sweep,
|
|
7
|
+
2026-08-03: greedy authoring -0.086 to -0.531 excess capture, population with
|
|
8
|
+
recombination +0.0042 to +0.1798).
|
|
9
|
+
|
|
10
|
+
Everything here is workload-agnostic. A locus is an addressable position inside a
|
|
11
|
+
candidate, derived from the problem's own ``candidate_model``; no module in this
|
|
12
|
+
package knows what a locus *means*. Sequence-valued fields expand element-wise so
|
|
13
|
+
that recombination is defined where the structure actually varies, which is the
|
|
14
|
+
whole reason a genome differs from a paragraph of text.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
import random
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from typing import Any, Iterable, Mapping, Protocol, Sequence
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"Locus",
|
|
26
|
+
"loci_of",
|
|
27
|
+
"read_locus",
|
|
28
|
+
"write_locus",
|
|
29
|
+
"locus_domain",
|
|
30
|
+
"locus_is_projected",
|
|
31
|
+
"SamplingPrior",
|
|
32
|
+
"DomainRestriction",
|
|
33
|
+
"crossover",
|
|
34
|
+
"mutate",
|
|
35
|
+
"uniform_candidate",
|
|
36
|
+
"tournament",
|
|
37
|
+
"truncation_survival",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True, slots=True)
|
|
42
|
+
class Locus:
|
|
43
|
+
"""An addressable position in a candidate.
|
|
44
|
+
|
|
45
|
+
``field`` names a top-level key; ``index`` is set only for sequence-valued
|
|
46
|
+
fields, where each element is independently heritable.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
field: str
|
|
50
|
+
index: int | None = None
|
|
51
|
+
|
|
52
|
+
def __str__(self) -> str: # pragma: no cover - display only
|
|
53
|
+
return self.field if self.index is None else f"{self.field}[{self.index}]"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def loci_of(config: Mapping[str, Any]) -> tuple[Locus, ...]:
|
|
57
|
+
"""Every heritable position in *config*, derived from the value shapes.
|
|
58
|
+
|
|
59
|
+
Scalars contribute one locus. Sequences contribute one per element, because
|
|
60
|
+
a genome whose only locus is "the whole list" cannot be recombined -- which
|
|
61
|
+
is exactly the degenerate case that makes whole-artifact rewrite the only
|
|
62
|
+
available move.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
out: list[Locus] = []
|
|
66
|
+
for field, value in config.items():
|
|
67
|
+
if isinstance(value, (list, tuple)) and not isinstance(value, (str, bytes)):
|
|
68
|
+
out.extend(Locus(field, i) for i in range(len(value)))
|
|
69
|
+
else:
|
|
70
|
+
out.append(Locus(field))
|
|
71
|
+
return tuple(out)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def read_locus(config: Mapping[str, Any], locus: Locus) -> Any:
|
|
75
|
+
value = config[locus.field]
|
|
76
|
+
return value if locus.index is None else value[locus.index]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def write_locus(config: Mapping[str, Any], locus: Locus, value: Any) -> dict[str, Any]:
|
|
80
|
+
"""Return a copy of *config* with *locus* set to *value*. Never mutates."""
|
|
81
|
+
|
|
82
|
+
out = {k: (list(v) if isinstance(v, (list, tuple)) and not isinstance(v, (str, bytes)) else v)
|
|
83
|
+
for k, v in config.items()}
|
|
84
|
+
if locus.index is None:
|
|
85
|
+
out[locus.field] = value
|
|
86
|
+
else:
|
|
87
|
+
out[locus.field][locus.index] = value
|
|
88
|
+
return out
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class SamplingPrior(Protocol):
|
|
92
|
+
"""What a sampler needs from a prior over WHERE to sample.
|
|
93
|
+
|
|
94
|
+
``narrow`` confines a declared domain -- always to a subset, never to
|
|
95
|
+
nothing (see :class:`DomainRestriction` for the enforced invariants).
|
|
96
|
+
``weights_for`` grades the values a draw is about to choose among;
|
|
97
|
+
returning ``None`` means "uniform", and the sampler then draws through
|
|
98
|
+
exactly the call it used before graded priors existed, so a prior with
|
|
99
|
+
nothing to say costs nothing and changes nothing.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def narrow(self, locus: "Locus", domain: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
def weights_for(
|
|
106
|
+
self, locus: "Locus", values: Sequence[Any]
|
|
107
|
+
) -> "tuple[float, ...] | None":
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass(frozen=True)
|
|
112
|
+
class DomainRestriction:
|
|
113
|
+
"""A narrowing of declared locus domains -- a prior over where to sample.
|
|
114
|
+
|
|
115
|
+
Every operator that draws a value reads its domain through
|
|
116
|
+
:func:`locus_domain`, so restricting there restricts initialization and
|
|
117
|
+
mutation together without either operator knowing a restriction exists.
|
|
118
|
+
|
|
119
|
+
Two invariants, both enforced rather than documented. A restriction can only
|
|
120
|
+
ever *narrow*: the result is always a subset of what the problem declared,
|
|
121
|
+
because a caller that could widen a domain would be authoring values the
|
|
122
|
+
problem never allowed. And it can never empty a domain: an empty
|
|
123
|
+
intersection means the restriction is about a schema it does not match, so
|
|
124
|
+
the declared domain is returned unchanged and the miss is counted in
|
|
125
|
+
:attr:`misses` for the caller to act on. Silently sampling nothing, or
|
|
126
|
+
sampling outside the schema, are both worse than ignoring a bad prior.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
allowed: Mapping[str, Sequence[Any]] = field(default_factory=dict)
|
|
130
|
+
misses: list[str] = field(default_factory=list, compare=False, repr=False)
|
|
131
|
+
|
|
132
|
+
def narrow(self, locus: "Locus", domain: tuple[Any, ...]) -> tuple[Any, ...]:
|
|
133
|
+
want = self.allowed.get(locus.field)
|
|
134
|
+
if want is None or not domain:
|
|
135
|
+
return domain
|
|
136
|
+
kept = tuple(v for v in domain if v in tuple(want))
|
|
137
|
+
if not kept:
|
|
138
|
+
self.misses.append(locus.field)
|
|
139
|
+
return domain
|
|
140
|
+
return kept
|
|
141
|
+
|
|
142
|
+
def weights_for(self, locus: "Locus", values: Sequence[Any]) -> None:
|
|
143
|
+
"""Uniform over the narrowed support: the hard 0/1 special case."""
|
|
144
|
+
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def locus_domain(
|
|
149
|
+
candidate_model: Any,
|
|
150
|
+
locus: Locus,
|
|
151
|
+
*,
|
|
152
|
+
restriction: "SamplingPrior | None" = None,
|
|
153
|
+
) -> tuple[Any, ...]:
|
|
154
|
+
"""Allowed values for *locus*, read from the problem's own schema.
|
|
155
|
+
|
|
156
|
+
Returns ``()`` when the schema does not constrain the value to a finite set;
|
|
157
|
+
callers fall back to recombination-only for that locus rather than inventing
|
|
158
|
+
a domain the problem never declared.
|
|
159
|
+
|
|
160
|
+
*restriction* narrows the declared domain (see :class:`DomainRestriction`).
|
|
161
|
+
Omitted, the result is exactly what the schema declares.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
domain = _declared_domain(candidate_model, locus)
|
|
165
|
+
if restriction is None:
|
|
166
|
+
return domain
|
|
167
|
+
return restriction.narrow(locus, domain)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
#: Points a bounded numeric axis is projected onto when the schema says nothing
|
|
171
|
+
#: else. Integers get 64: an axis like ``ge=0, le=1000`` keeps a tenth of a
|
|
172
|
+
#: percent of resolution while its domain still renders in a prompt. Numbers get
|
|
173
|
+
#: 16, which is the grid ``from_pymoo`` has quantized continuous decision
|
|
174
|
+
#: variables onto since it shipped -- one resolution rule for the package, not
|
|
175
|
+
#: two. Both are overridable per field (see :func:`_grid_override`).
|
|
176
|
+
_INTEGER_GRID = 64
|
|
177
|
+
_NUMBER_GRID = 16
|
|
178
|
+
|
|
179
|
+
#: A per-field grid outside this range is not a search space: one point is a
|
|
180
|
+
#: constant, and a domain nobody can render or enumerate cheaply is not a
|
|
181
|
+
#: domain an operator can draw from.
|
|
182
|
+
_GRID_MIN, _GRID_MAX = 2, 256
|
|
183
|
+
|
|
184
|
+
#: Slack for reading multiples off a float bound, whose decimal literal in the
|
|
185
|
+
#: schema (0.3) is not the binary value the arithmetic produces.
|
|
186
|
+
_TOL = 1e-12
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _quantum(value: float) -> float:
|
|
190
|
+
"""A grid float rendered the way every reader will see it.
|
|
191
|
+
|
|
192
|
+
Ten significant digits: enough that no two points of a 256-point grid
|
|
193
|
+
collide, few enough that ``repr`` is stable, so a value in a prompt, a value
|
|
194
|
+
in a card and a value compared against a domain are the same string and the
|
|
195
|
+
same float.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
return float(f"{value:.10g}")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _bound(node: Mapping[str, Any], key: str) -> float | None:
|
|
202
|
+
"""A real numeric bound under *key*, or ``None``. ``True`` is not a bound."""
|
|
203
|
+
|
|
204
|
+
value = node.get(key)
|
|
205
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
206
|
+
return None
|
|
207
|
+
return float(value) if math.isfinite(float(value)) else None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _grid_override(node: Mapping[str, Any]) -> int | None:
|
|
211
|
+
"""The field's own point count, from ``{"agent_evolve": {"grid": N}}``.
|
|
212
|
+
|
|
213
|
+
Written by the problem author as
|
|
214
|
+
``Field(json_schema_extra={"agent_evolve": {"grid": N}})``: the one place a
|
|
215
|
+
schema can say how finely *this* axis deserves to be searched.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
extra = node.get("agent_evolve")
|
|
219
|
+
if isinstance(extra, Mapping):
|
|
220
|
+
wanted = extra.get("grid")
|
|
221
|
+
if isinstance(wanted, int) and not isinstance(wanted, bool):
|
|
222
|
+
return max(_GRID_MIN, min(_GRID_MAX, wanted))
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _spread(count: int, points: int) -> tuple[int, ...]:
|
|
227
|
+
"""*points* indices spread over ``range(count)``, both extremes included.
|
|
228
|
+
|
|
229
|
+
Fewer values than points means every value: a projection never invents
|
|
230
|
+
resolution the range does not have.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
if count <= points:
|
|
234
|
+
return tuple(range(count))
|
|
235
|
+
return tuple(dict.fromkeys(
|
|
236
|
+
round(i * (count - 1) / (points - 1)) for i in range(points)
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _multiple_indices(
|
|
241
|
+
lo: float, hi: float, step: float, *, open_lo: bool, open_hi: bool
|
|
242
|
+
) -> tuple[int, int]:
|
|
243
|
+
"""First and last multiple-of-*step* indices inside the interval."""
|
|
244
|
+
|
|
245
|
+
if isinstance(lo, int) and isinstance(hi, int) and isinstance(step, int):
|
|
246
|
+
first, last = -((-lo) // step), hi // step # exact for ints of any size
|
|
247
|
+
else:
|
|
248
|
+
first = math.ceil(lo / step - _TOL)
|
|
249
|
+
last = math.floor(hi / step + _TOL)
|
|
250
|
+
if open_lo and first * step <= lo:
|
|
251
|
+
first += 1
|
|
252
|
+
if open_hi and last * step >= hi:
|
|
253
|
+
last -= 1
|
|
254
|
+
return first, last
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _integer_domain(node: Mapping[str, Any], points: int) -> tuple[int, ...]:
|
|
258
|
+
"""Every integer the bounds allow, or *points* of them evenly spaced.
|
|
259
|
+
|
|
260
|
+
``exclusiveMinimum``/``exclusiveMaximum`` tighten by one, which is what they
|
|
261
|
+
mean on an integer axis. ``multipleOf`` restricts to multiples, so the
|
|
262
|
+
emitted values are ones the schema would actually accept.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
lows: list[int] = []
|
|
266
|
+
highs: list[int] = []
|
|
267
|
+
closed_lo = _bound(node, "minimum")
|
|
268
|
+
if closed_lo is not None:
|
|
269
|
+
lows.append(math.ceil(closed_lo))
|
|
270
|
+
open_lo = _bound(node, "exclusiveMinimum")
|
|
271
|
+
if open_lo is not None:
|
|
272
|
+
lows.append(math.floor(open_lo) + 1)
|
|
273
|
+
closed_hi = _bound(node, "maximum")
|
|
274
|
+
if closed_hi is not None:
|
|
275
|
+
highs.append(math.floor(closed_hi))
|
|
276
|
+
open_hi = _bound(node, "exclusiveMaximum")
|
|
277
|
+
if open_hi is not None:
|
|
278
|
+
highs.append(math.ceil(open_hi) - 1)
|
|
279
|
+
if not lows or not highs:
|
|
280
|
+
return () # unbounded on one side: no finite reading
|
|
281
|
+
lo, hi = max(lows), min(highs)
|
|
282
|
+
step = 1
|
|
283
|
+
multiple = _bound(node, "multipleOf")
|
|
284
|
+
if multiple is not None:
|
|
285
|
+
if multiple <= 0 or not float(multiple).is_integer():
|
|
286
|
+
return () # no integer axis reading of this step
|
|
287
|
+
step = int(multiple)
|
|
288
|
+
first, last = _multiple_indices(lo, hi, step, open_lo=False, open_hi=False)
|
|
289
|
+
if first > last:
|
|
290
|
+
return () # the bounds admit nothing
|
|
291
|
+
return tuple((first + i) * step for i in _spread(last - first + 1, points))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _number_domain(node: Mapping[str, Any], points: int) -> tuple[float, ...]:
|
|
295
|
+
"""A *points*-point grid across a doubly bounded continuous axis.
|
|
296
|
+
|
|
297
|
+
Inclusive bounds put a point on each endpoint. An excluded endpoint is not
|
|
298
|
+
a legal value, so it becomes one more interval instead: an open interval of
|
|
299
|
+
``points`` interior points sits on ``points + 1`` intervals.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
lo, open_lo = _bound(node, "minimum"), False
|
|
303
|
+
tighter = _bound(node, "exclusiveMinimum")
|
|
304
|
+
if tighter is not None and (lo is None or tighter >= lo):
|
|
305
|
+
lo, open_lo = tighter, True
|
|
306
|
+
hi, open_hi = _bound(node, "maximum"), False
|
|
307
|
+
tighter = _bound(node, "exclusiveMaximum")
|
|
308
|
+
if tighter is not None and (hi is None or tighter <= hi):
|
|
309
|
+
hi, open_hi = tighter, True
|
|
310
|
+
if lo is None or hi is None or hi < lo:
|
|
311
|
+
return () # one-sided or empty: nothing finite
|
|
312
|
+
multiple = _bound(node, "multipleOf")
|
|
313
|
+
if multiple is not None:
|
|
314
|
+
if multiple <= 0:
|
|
315
|
+
return ()
|
|
316
|
+
first, last = _multiple_indices(lo, hi, multiple,
|
|
317
|
+
open_lo=open_lo, open_hi=open_hi)
|
|
318
|
+
if first > last:
|
|
319
|
+
return ()
|
|
320
|
+
return tuple(dict.fromkeys(
|
|
321
|
+
_quantum((first + i) * multiple)
|
|
322
|
+
for i in _spread(last - first + 1, points)
|
|
323
|
+
))
|
|
324
|
+
if lo == hi:
|
|
325
|
+
return () if (open_lo or open_hi) else (_quantum(lo),)
|
|
326
|
+
edges = points + int(open_lo) + int(open_hi)
|
|
327
|
+
start = 1 if open_lo else 0
|
|
328
|
+
return tuple(dict.fromkeys(
|
|
329
|
+
_quantum(lo + (hi - lo) * i / (edges - 1))
|
|
330
|
+
for i in range(start, start + points)
|
|
331
|
+
))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _node_domain(
|
|
335
|
+
node: Mapping[str, Any], override: int | None
|
|
336
|
+
) -> tuple[tuple[Any, ...], bool]:
|
|
337
|
+
"""One schema node's finite domain, and whether it was projected.
|
|
338
|
+
|
|
339
|
+
Projected means "read off bounds": the schema declared a range, not a set of
|
|
340
|
+
values, and what comes back is a finite sample of that range. Enumerations,
|
|
341
|
+
booleans and constants are declared outright and are not projections.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
if "enum" in node:
|
|
345
|
+
return tuple(node["enum"]), False
|
|
346
|
+
if "const" in node:
|
|
347
|
+
# A one-value domain is still a domain. Read as nothing, a Literal of
|
|
348
|
+
# length one freezes its locus and every reader calls it undeclared.
|
|
349
|
+
return (node["const"],), False
|
|
350
|
+
declared = node.get("type")
|
|
351
|
+
types = tuple(declared) if isinstance(declared, list) else (declared,)
|
|
352
|
+
if "boolean" in types:
|
|
353
|
+
# A boolean is a finite domain. Without this, a bool field has no
|
|
354
|
+
# declared values, mutate() leaves it alone, and a real design axis is
|
|
355
|
+
# silently frozen at whatever the seed happened to carry.
|
|
356
|
+
return (False, True), False
|
|
357
|
+
points = _grid_override(node) or override
|
|
358
|
+
if "integer" in types:
|
|
359
|
+
return _integer_domain(node, points or _INTEGER_GRID), True
|
|
360
|
+
if "number" in types:
|
|
361
|
+
return _number_domain(node, points or _NUMBER_GRID), True
|
|
362
|
+
return (), False
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _declared_domain_detail(
|
|
366
|
+
candidate_model: Any, locus: Locus
|
|
367
|
+
) -> tuple[tuple[Any, ...], bool]:
|
|
368
|
+
"""The schema's own domain for *locus*, and whether it is a projection."""
|
|
369
|
+
|
|
370
|
+
if candidate_model is None:
|
|
371
|
+
return (), False
|
|
372
|
+
try:
|
|
373
|
+
schema = candidate_model.model_json_schema()
|
|
374
|
+
except Exception: # not a pydantic model
|
|
375
|
+
return (), False
|
|
376
|
+
defs = schema.get("$defs", {}) or schema.get("definitions", {}) or {}
|
|
377
|
+
node = (schema.get("properties", {}) or {}).get(locus.field)
|
|
378
|
+
if node is None:
|
|
379
|
+
return (), False
|
|
380
|
+
|
|
381
|
+
def resolve(n: Any) -> Any:
|
|
382
|
+
seen = 0
|
|
383
|
+
while isinstance(n, dict) and "$ref" in n and seen < 10:
|
|
384
|
+
ref = n["$ref"].rsplit("/", 1)[-1]
|
|
385
|
+
n = defs.get(ref, {})
|
|
386
|
+
seen += 1
|
|
387
|
+
return n
|
|
388
|
+
|
|
389
|
+
node = resolve(node)
|
|
390
|
+
if locus.index is not None:
|
|
391
|
+
node = resolve(node.get("items", {}) if isinstance(node, dict) else {})
|
|
392
|
+
elif isinstance(node, dict) and node.get("type") == "array":
|
|
393
|
+
# A bare Locus("field") naming a sequence field means the field's
|
|
394
|
+
# shared per-element vocabulary -- what pooled attribution and
|
|
395
|
+
# field-keyed priors reason over. Indexed loci resolve above.
|
|
396
|
+
node = resolve(node.get("items", {}))
|
|
397
|
+
if not isinstance(node, dict):
|
|
398
|
+
return (), False
|
|
399
|
+
override = _grid_override(node) # a wrapper carries the field's own grid
|
|
400
|
+
domain, projected = _node_domain(node, override)
|
|
401
|
+
if domain:
|
|
402
|
+
return domain, projected
|
|
403
|
+
for key in ("anyOf", "oneOf"):
|
|
404
|
+
for branch in node.get(key, ()) or ():
|
|
405
|
+
branch = resolve(branch)
|
|
406
|
+
if isinstance(branch, dict) and "enum" in branch:
|
|
407
|
+
return tuple(branch["enum"]), False
|
|
408
|
+
for key in ("anyOf", "oneOf"):
|
|
409
|
+
for branch in node.get(key, ()) or ():
|
|
410
|
+
branch = resolve(branch)
|
|
411
|
+
# A "null" branch is Optional's absence marker, not a value to
|
|
412
|
+
# sample: writing None where the problem declared a range is how a
|
|
413
|
+
# sampler produces candidates the problem never allowed.
|
|
414
|
+
if not isinstance(branch, dict) or branch.get("type") == "null":
|
|
415
|
+
continue
|
|
416
|
+
domain, projected = _node_domain(branch, override)
|
|
417
|
+
if domain:
|
|
418
|
+
return domain, projected
|
|
419
|
+
return (), False
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _declared_domain(candidate_model: Any, locus: Locus) -> tuple[Any, ...]:
|
|
423
|
+
"""The schema's own domain for *locus*, before any narrowing."""
|
|
424
|
+
|
|
425
|
+
return _declared_domain_detail(candidate_model, locus)[0]
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def locus_is_projected(candidate_model: Any, locus: Locus) -> bool:
|
|
429
|
+
"""True when *locus*'s domain is a finite projection of a declared range.
|
|
430
|
+
|
|
431
|
+
A projected domain is searchable but lossy: the optimizer moves on the grid,
|
|
432
|
+
not the continuum. Reports that name domain sizes say so, because "16
|
|
433
|
+
values" means something different when the schema declared 16 values than
|
|
434
|
+
when it declared an interval.
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
domain, projected = _declared_domain_detail(candidate_model, locus)
|
|
438
|
+
return bool(domain) and projected
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _draw(
|
|
442
|
+
r: random.Random,
|
|
443
|
+
values: tuple[Any, ...],
|
|
444
|
+
locus: Locus,
|
|
445
|
+
restriction: "SamplingPrior | None",
|
|
446
|
+
) -> Any:
|
|
447
|
+
"""Draw one of *values*, honouring the prior's weights when it has any.
|
|
448
|
+
|
|
449
|
+
The unweighted path is literally ``r.choice`` -- same call, same RNG
|
|
450
|
+
stream -- which is what keeps every run without a graded prior
|
|
451
|
+
byte-identical to the pre-weights seam (the fossil test holds this).
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
weights = None
|
|
455
|
+
if restriction is not None:
|
|
456
|
+
weigher = getattr(restriction, "weights_for", None)
|
|
457
|
+
if callable(weigher):
|
|
458
|
+
weights = weigher(locus, values)
|
|
459
|
+
if weights is None:
|
|
460
|
+
return r.choice(values)
|
|
461
|
+
return r.choices(values, weights=weights, k=1)[0]
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def crossover(
|
|
465
|
+
parent_a: Mapping[str, Any],
|
|
466
|
+
parent_b: Mapping[str, Any],
|
|
467
|
+
*,
|
|
468
|
+
mask: Sequence[bool] | None = None,
|
|
469
|
+
rng: random.Random | None = None,
|
|
470
|
+
) -> dict[str, Any]:
|
|
471
|
+
"""Recombine two parents locus by locus.
|
|
472
|
+
|
|
473
|
+
``mask[i]`` true takes locus *i* from *parent_b*. A caller-supplied mask is
|
|
474
|
+
how an external chooser -- a model, a heuristic -- expresses *where to cut*
|
|
475
|
+
without ever authoring a candidate. With no mask, each locus is drawn
|
|
476
|
+
independently at random, which is the unguided control.
|
|
477
|
+
|
|
478
|
+
The mask is indexed by *parent_a*'s loci, and its length must match them:
|
|
479
|
+
a mask of the wrong length is a caller bug and is refused by name rather
|
|
480
|
+
than silently reinterpreted.
|
|
481
|
+
|
|
482
|
+
**Ragged genomes.** When a field holds a sequence, each element is a locus,
|
|
483
|
+
so two candidates of the same problem may have different genome lengths.
|
|
484
|
+
A locus that *parent_b* does not have cannot be inherited from it, so the
|
|
485
|
+
child keeps *parent_a*'s value there. Recombination is therefore defined on
|
|
486
|
+
the loci the parents share, which reduces exactly to the fixed-length case
|
|
487
|
+
when the shapes match.
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
loci = loci_of(parent_a)
|
|
491
|
+
if mask is None:
|
|
492
|
+
r = rng or random.Random()
|
|
493
|
+
mask = [bool(r.getrandbits(1)) for _ in loci]
|
|
494
|
+
if len(mask) != len(loci):
|
|
495
|
+
raise ValueError(
|
|
496
|
+
f"mask has {len(mask)} bits but the candidate has {len(loci)} loci"
|
|
497
|
+
)
|
|
498
|
+
donor = frozenset(loci_of(parent_b))
|
|
499
|
+
child = dict(parent_a)
|
|
500
|
+
for locus, take_b in zip(loci, mask):
|
|
501
|
+
if take_b and locus in donor:
|
|
502
|
+
child = write_locus(child, locus, read_locus(parent_b, locus))
|
|
503
|
+
return child
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def mutate(
|
|
507
|
+
config: Mapping[str, Any],
|
|
508
|
+
candidate_model: Any,
|
|
509
|
+
*,
|
|
510
|
+
rate: float | None = None,
|
|
511
|
+
loci: Iterable[Locus] | None = None,
|
|
512
|
+
rng: random.Random | None = None,
|
|
513
|
+
restriction: SamplingPrior | None = None,
|
|
514
|
+
) -> dict[str, Any]:
|
|
515
|
+
"""Resample loci from their declared domains.
|
|
516
|
+
|
|
517
|
+
*loci* names exactly which positions to change -- how an external chooser
|
|
518
|
+
expresses *which loci* without authoring a candidate. Otherwise every locus
|
|
519
|
+
is resampled independently with probability *rate*, defaulting to 1/n so a
|
|
520
|
+
candidate changes in about one place.
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
r = rng or random.Random()
|
|
524
|
+
all_loci = loci_of(config)
|
|
525
|
+
targets = tuple(loci) if loci is not None else tuple(
|
|
526
|
+
lc for lc in all_loci
|
|
527
|
+
if r.random() < (rate if rate is not None else 1.0 / max(1, len(all_loci)))
|
|
528
|
+
)
|
|
529
|
+
out = dict(config)
|
|
530
|
+
for locus in targets:
|
|
531
|
+
domain = locus_domain(candidate_model, locus, restriction=restriction)
|
|
532
|
+
if not domain:
|
|
533
|
+
continue # undeclared domain: leave it alone
|
|
534
|
+
current = read_locus(out, locus)
|
|
535
|
+
choices = tuple(v for v in domain if v != current) or domain
|
|
536
|
+
out = write_locus(out, locus, _draw(r, choices, locus, restriction))
|
|
537
|
+
return out
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def uniform_candidate(
|
|
541
|
+
template: Mapping[str, Any],
|
|
542
|
+
candidate_model: Any,
|
|
543
|
+
*,
|
|
544
|
+
rng: random.Random | None = None,
|
|
545
|
+
restriction: SamplingPrior | None = None,
|
|
546
|
+
) -> dict[str, Any]:
|
|
547
|
+
"""A fresh draw over every locus with a declared domain.
|
|
548
|
+
|
|
549
|
+
Loci the schema does not constrain keep the template's value: inventing
|
|
550
|
+
values the problem never declared is not sampling, it is guessing. When no
|
|
551
|
+
locus declares a domain at all, fall back to :func:`mutate` so the caller
|
|
552
|
+
still gets diversity rather than a copy.
|
|
553
|
+
|
|
554
|
+
This exists because filling a population with *mutants of one seed* builds
|
|
555
|
+
an anchored cloud around that seed. Measured on a third-party optimizer,
|
|
556
|
+
correcting exactly this anchor moved its result from +0.095 (loses badly to
|
|
557
|
+
uniform) to +0.0066 (parity) -- and the standard seed on at least one of
|
|
558
|
+
our own workloads scores worse than a typical uniform draw.
|
|
559
|
+
"""
|
|
560
|
+
|
|
561
|
+
r = rng or random.Random()
|
|
562
|
+
out = dict(template)
|
|
563
|
+
drew = False
|
|
564
|
+
for locus in loci_of(template):
|
|
565
|
+
domain = locus_domain(candidate_model, locus, restriction=restriction)
|
|
566
|
+
if domain:
|
|
567
|
+
out = write_locus(out, locus, _draw(r, domain, locus, restriction))
|
|
568
|
+
drew = True
|
|
569
|
+
if not drew:
|
|
570
|
+
return mutate(template, candidate_model, rng=r, restriction=restriction)
|
|
571
|
+
return out
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def tournament(
|
|
575
|
+
population: Sequence[tuple[Mapping[str, Any], float]],
|
|
576
|
+
*,
|
|
577
|
+
size: int = 2,
|
|
578
|
+
rng: random.Random | None = None,
|
|
579
|
+
) -> Mapping[str, Any]:
|
|
580
|
+
"""Pick one parent by a *size*-way tournament on the ranking value (lower wins)."""
|
|
581
|
+
|
|
582
|
+
if not population:
|
|
583
|
+
raise ValueError("cannot select a parent from an empty population")
|
|
584
|
+
r = rng or random.Random()
|
|
585
|
+
contenders = r.sample(list(population), min(size, len(population)))
|
|
586
|
+
return min(contenders, key=lambda pair: pair[1])[0]
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def truncation_survival(
|
|
590
|
+
population: Sequence[tuple[Mapping[str, Any], float]],
|
|
591
|
+
*,
|
|
592
|
+
keep: int,
|
|
593
|
+
key_of,
|
|
594
|
+
) -> list[tuple[Mapping[str, Any], float]]:
|
|
595
|
+
"""Deduplicate by candidate identity, then keep the *keep* best.
|
|
596
|
+
|
|
597
|
+
Deduplication is not cosmetic: without it a population collapses onto copies
|
|
598
|
+
of one genome and recombination stops producing anything new, which is the
|
|
599
|
+
failure mode a diversity-free loop reaches within a couple of generations.
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
best: dict[str, tuple[Mapping[str, Any], float]] = {}
|
|
603
|
+
for config, value in population:
|
|
604
|
+
k = key_of(config)
|
|
605
|
+
if k not in best or value < best[k][1]:
|
|
606
|
+
best[k] = (config, value)
|
|
607
|
+
return sorted(best.values(), key=lambda pair: pair[1])[:keep]
|