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,742 @@
|
|
|
1
|
+
"""A search loop that actually maintains a population and recombines it.
|
|
2
|
+
|
|
3
|
+
The loop in :mod:`agent_evolve.session.loop` renders the Pareto front as text
|
|
4
|
+
and asks a model to author whole configurations. That is whole-artifact rewrite;
|
|
5
|
+
it has no parent selection, no recombination, no mutation and no survival rule,
|
|
6
|
+
and it is measurably worse than uniform random sampling on every genome length
|
|
7
|
+
measured (advantage_theory sweep, 2026-08-03).
|
|
8
|
+
|
|
9
|
+
This loop supplies those operators. The seam that matters is
|
|
10
|
+
:class:`OperatorChooser`: something outside the loop decides *which parents* and
|
|
11
|
+
*where to cut*, and the loop never learns what made that decision. A random
|
|
12
|
+
chooser is the unguided control; a model-backed chooser is guidance. Neither can
|
|
13
|
+
author a candidate, because the chooser's return type cannot express one -- the
|
|
14
|
+
distinction is enforced by the type, not by convention.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
import random
|
|
21
|
+
from dataclasses import dataclass, field, replace
|
|
22
|
+
from typing import (
|
|
23
|
+
Any, Callable, Dict, List, Mapping, Optional, Sequence, Set)
|
|
24
|
+
|
|
25
|
+
from agent_evolve.core.problem import ObjectiveSpec
|
|
26
|
+
from agent_evolve.core.results import SearchResult, dominates
|
|
27
|
+
from agent_evolve.core.telemetry import harvest_telemetry
|
|
28
|
+
from agent_evolve.policies.genetic import (
|
|
29
|
+
Locus,
|
|
30
|
+
crossover,
|
|
31
|
+
loci_of,
|
|
32
|
+
mutate,
|
|
33
|
+
truncation_survival,
|
|
34
|
+
uniform_candidate,
|
|
35
|
+
)
|
|
36
|
+
from agent_evolve.session.evaluate import EvaluationCache, evaluate_batch
|
|
37
|
+
|
|
38
|
+
__all__ = ["OperatorChoice", "OperatorChooser", "GeneticConfig", "run_genetic_loop",
|
|
39
|
+
"random_chooser", "domination_rank"]
|
|
40
|
+
|
|
41
|
+
Config = Dict[str, Any]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class OperatorChoice:
|
|
46
|
+
"""One offspring, expressed as operator arguments rather than a candidate.
|
|
47
|
+
|
|
48
|
+
There is deliberately no field that can hold a configuration. A chooser that
|
|
49
|
+
wanted to author a genome could not say so through this type.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
parent_a: int
|
|
53
|
+
parent_b: int
|
|
54
|
+
mask: tuple[bool, ...]
|
|
55
|
+
mutate_loci: Optional[tuple[Locus, ...]] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
#: Given the ranked population, how many offspring are wanted, and the search
|
|
59
|
+
#: state accumulated so far, return that many operator choices. Indices address
|
|
60
|
+
#: the population list. The state argument is explicit rather than captured so
|
|
61
|
+
#: that a chooser's inputs are visible at the call site and an ablation can be
|
|
62
|
+
#: read off the signature.
|
|
63
|
+
OperatorChooser = Callable[
|
|
64
|
+
[Sequence[tuple[Config, float]], int, Any], Sequence[OperatorChoice]
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(slots=True)
|
|
69
|
+
class GeneticConfig:
|
|
70
|
+
population_size: int = 8
|
|
71
|
+
offspring_per_generation: int = 6
|
|
72
|
+
generations: int = 5
|
|
73
|
+
mutation_rate: Optional[float] = None # None -> 1/n_loci
|
|
74
|
+
seed: Optional[int] = None
|
|
75
|
+
seeds: tuple = ()
|
|
76
|
+
evaluation_budget: Optional[int] = None
|
|
77
|
+
evaluation_cache: EvaluationCache = field(default_factory=EvaluationCache)
|
|
78
|
+
#: What the chooser may reason over. Left None for the score-only baseline.
|
|
79
|
+
state: Any = None
|
|
80
|
+
#: Evaluations to spend on a screening design before the population is
|
|
81
|
+
#: built. 0 leaves the loop byte-identical to the pre-structure seam. The
|
|
82
|
+
#: screen is charged against the same budget as everything else: a phase
|
|
83
|
+
#: that spent free evaluations would not be a real operating point.
|
|
84
|
+
structure_budget: int = 0
|
|
85
|
+
#: Turns the screen's evidence into a prior. Defaults to the credential-free
|
|
86
|
+
#: statistical rule, so the phase is useful with no model at all -- and so a
|
|
87
|
+
#: model-proposed prior always has a rule to beat.
|
|
88
|
+
prior_proposer: Any = None
|
|
89
|
+
#: Pool sequence positions by field in the structure phase: the screen
|
|
90
|
+
#: becomes per-value pure/spiked designs and attribution counts every
|
|
91
|
+
#: (candidate, position) pair as one observation. The exchangeability
|
|
92
|
+
#: bet this makes is checked by the same unwind test as any prior.
|
|
93
|
+
structure_pooled: bool = False
|
|
94
|
+
#: A prior over WHERE to sample: narrows the declared domain of any locus it
|
|
95
|
+
#: names, for initialization and mutation alike. Left None, every sampler
|
|
96
|
+
#: sees exactly the schema's own domains, so the loop is byte-identical to
|
|
97
|
+
#: the pre-restriction seam. This is guidance over the sampling
|
|
98
|
+
#: distribution rather than over operator choice within a fixed one.
|
|
99
|
+
restriction: Any = None
|
|
100
|
+
#: Virtual pre-screening (a session.screening.Screening). Each generation
|
|
101
|
+
#: the loop builds pool_factor times the offspring it can afford, asks the
|
|
102
|
+
#: screen's validated surrogate to order the pool, and measures the
|
|
103
|
+
#: exploration floor plus the top of the order. None -- the default --
|
|
104
|
+
#: leaves the loop byte-identical to the pre-screening seam; the pool's
|
|
105
|
+
#: extra construction runs on its own RNG stream for the same reason.
|
|
106
|
+
screening: Any = None
|
|
107
|
+
#: Model-proposed initial population members, validated value-by-value
|
|
108
|
+
#: upstream. Inserted AFTER the caller's seeds and before schema-uniform
|
|
109
|
+
#: fill; () -- the default -- is byte-identical to the pre-init seam.
|
|
110
|
+
initial_proposals: tuple = ()
|
|
111
|
+
#: Variation-arm portfolio (a policies.operator_portfolio
|
|
112
|
+
#: .OperatorPortfolio). When present it constructs the generation's
|
|
113
|
+
#: offspring -- classical, rule, and authored arms under survival credit
|
|
114
|
+
#: -- and the loop reports each measured child's fate back to it. None
|
|
115
|
+
#: leaves offspring construction byte-identical to the classical path.
|
|
116
|
+
portfolio: Any = None
|
|
117
|
+
#: A model-authored SAMPLER (a policies.llm_generator.AuthoredGenerator).
|
|
118
|
+
#: When present it draws the whole generation's candidate POOL -- many
|
|
119
|
+
#: times the offspring the budget can afford -- and the pool then goes
|
|
120
|
+
#: through the same screening path as any other pool, or is taken from
|
|
121
|
+
#: the top when there is no screen. Mass generation charges nothing:
|
|
122
|
+
#: the generator never sees the problem or the cache, and only the
|
|
123
|
+
#: `want` candidates handed to measure() can reach the budget. None --
|
|
124
|
+
#: the default -- leaves the loop byte-identical to the pre-generator
|
|
125
|
+
#: seam. It replaces offspring construction, so it does not compose with
|
|
126
|
+
#: `portfolio`; asking for both is refused rather than silently ignored.
|
|
127
|
+
generator: Any = None
|
|
128
|
+
#: Whether the screen may spend the problem's CHEAPER evaluation fidelity
|
|
129
|
+
#: (``Problem.evaluate_proxy``), and how. "off" leaves the loop
|
|
130
|
+
#: byte-identical to the pre-proxy seam and is what every problem without
|
|
131
|
+
#: a cheap fidelity gets regardless. "rows" buys gate evidence at the
|
|
132
|
+
#: cheap fidelity, "screen" lets the cheap fidelity compete as a
|
|
133
|
+
#: surrogate under the gate, "both" does both. Proxy evaluations are
|
|
134
|
+
#: counted in their own ledger and are NEVER charged to
|
|
135
|
+
#: ``evaluation_budget``: the budget the claims are denominated in counts
|
|
136
|
+
#: real evaluations, and a cheap one is not one of them.
|
|
137
|
+
#:
|
|
138
|
+
#: The default is the MEASURED arm: "screen" won 23/24 seeds against the
|
|
139
|
+
#: no-proxy screen pooled over both budgets (12/0 at B=24, p = 0.00049;
|
|
140
|
+
#: 11/12 at B=16, p = 0.006; wave-K aug14_multifidelity.md), with the
|
|
141
|
+
#: shuffled-proxy control identical to no-proxy in all 12 runs -- the
|
|
142
|
+
#: gain is the cheap MEASUREMENT, not the machinery. The seam is active
|
|
143
|
+
#: iff the problem exposes ``evaluate_proxy``: ``ProxySource.for_problem``
|
|
144
|
+
#: returns None otherwise and nothing attaches, so every problem without
|
|
145
|
+
#: a cheap fidelity keeps the pre-proxy behaviour bit for bit.
|
|
146
|
+
proxy_fidelity: str = "screen"
|
|
147
|
+
#: A hard ceiling on proxy evaluations for the whole run. None -- no
|
|
148
|
+
#: ceiling. A consumer that exhausts it degrades to "no proxy".
|
|
149
|
+
proxy_ceiling: Optional[int] = None
|
|
150
|
+
#: MEASUREMENT-CONDITIONED REVISION of the sampling prior (a
|
|
151
|
+
#: policies.reguidance.Reguidance). Once per generation the policy is
|
|
152
|
+
#: asked whether its declared cadence has come round; when it has, one
|
|
153
|
+
#: model call reads what the run measured and returns a re-weighted prior
|
|
154
|
+
#: -- damped into the one in force, so a revision can tilt the draws and
|
|
155
|
+
#: cannot exclude anything -- plus, when it is bought, a few complete
|
|
156
|
+
#: configurations to measure next. None -- the default -- is
|
|
157
|
+
#: byte-identical to the pre-seam loop: no call fires and no counter
|
|
158
|
+
#: moves. The prior it revises is the same one initialization and
|
|
159
|
+
#: mutation already consult, so one revision reshapes every subsequent
|
|
160
|
+
#: draw with no further calls.
|
|
161
|
+
reguidance: Any = None
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def domination_rank(
|
|
165
|
+
objectives_of: Sequence[Mapping[str, float]],
|
|
166
|
+
specs: Sequence[ObjectiveSpec],
|
|
167
|
+
) -> List[float]:
|
|
168
|
+
"""How many population members dominate each one. Lower is better.
|
|
169
|
+
|
|
170
|
+
Pareto-correct and weight-free. A scalarization would need weights nobody
|
|
171
|
+
declared, and an undeclared weight is precisely the kind of hidden choice
|
|
172
|
+
that has silently decided results in this project before.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def dominates(x: Mapping[str, float], y: Mapping[str, float]) -> bool:
|
|
176
|
+
better_anywhere = False
|
|
177
|
+
for spec in specs:
|
|
178
|
+
# spec.goal, read directly rather than via a default: a missing goal
|
|
179
|
+
# is a contract violation, and defaulting it to "min" would silently
|
|
180
|
+
# invert every maximised objective instead of failing.
|
|
181
|
+
sign = 1.0 if spec.goal == "min" else -1.0
|
|
182
|
+
xi, yi = sign * float(x[spec.name]), sign * float(y[spec.name])
|
|
183
|
+
if xi > yi:
|
|
184
|
+
return False
|
|
185
|
+
if xi < yi:
|
|
186
|
+
better_anywhere = True
|
|
187
|
+
return better_anywhere
|
|
188
|
+
|
|
189
|
+
return [
|
|
190
|
+
float(sum(1 for other in objectives_of if dominates(other, this)))
|
|
191
|
+
for this in objectives_of
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def random_chooser(rng: random.Random, n_loci: int) -> OperatorChooser:
|
|
196
|
+
"""The unguided control: tournament parents, uniform mask, random loci."""
|
|
197
|
+
|
|
198
|
+
def choose(population: Sequence[tuple[Config, float]], count: int,
|
|
199
|
+
state: Any = None):
|
|
200
|
+
del state # the unguided control reasons over nothing
|
|
201
|
+
out: List[OperatorChoice] = []
|
|
202
|
+
for _ in range(count):
|
|
203
|
+
def pick() -> int:
|
|
204
|
+
a, b = rng.sample(range(len(population)), min(2, len(population))) \
|
|
205
|
+
if len(population) >= 2 else (0, 0)
|
|
206
|
+
return a if population[a][1] <= population[b][1] else b
|
|
207
|
+
|
|
208
|
+
out.append(
|
|
209
|
+
OperatorChoice(
|
|
210
|
+
parent_a=pick(),
|
|
211
|
+
parent_b=pick(),
|
|
212
|
+
mask=tuple(bool(rng.getrandbits(1)) for _ in range(n_loci)),
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
return out
|
|
216
|
+
|
|
217
|
+
return choose
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def run_genetic_loop(
|
|
221
|
+
*,
|
|
222
|
+
problem: Any,
|
|
223
|
+
config: GeneticConfig,
|
|
224
|
+
chooser: Optional[OperatorChooser] = None,
|
|
225
|
+
log: Callable[[str], None] = lambda _m: None,
|
|
226
|
+
) -> SearchResult:
|
|
227
|
+
"""Evolve a population under *problem*, spending at most the budget."""
|
|
228
|
+
|
|
229
|
+
from agent_evolve.session.loop import _build_search_result, _default_candidate_key
|
|
230
|
+
|
|
231
|
+
if config.generator is not None and config.portfolio is not None:
|
|
232
|
+
raise ValueError(
|
|
233
|
+
"generator and portfolio both construct the generation's "
|
|
234
|
+
"candidates: the generator draws the pool, the portfolio "
|
|
235
|
+
"recombines parents into it. Run one or the other."
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
specs = list(problem.objectives)
|
|
239
|
+
candidate_model = getattr(problem, "candidate_model", None)
|
|
240
|
+
rng = random.Random(config.seed)
|
|
241
|
+
cache = config.evaluation_cache
|
|
242
|
+
if config.evaluation_budget is not None:
|
|
243
|
+
cache.budget = config.evaluation_budget
|
|
244
|
+
|
|
245
|
+
seeds = [dict(c) for c in (config.seeds or tuple(problem.seeds()))]
|
|
246
|
+
if not seeds:
|
|
247
|
+
raise ValueError(
|
|
248
|
+
"the genetic loop needs at least one seed to know the shape of a "
|
|
249
|
+
"candidate. Give Problem.seeds() one configuration, or use "
|
|
250
|
+
"proposer='llm' with the authoring loop."
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def spent() -> int:
|
|
254
|
+
return int(getattr(cache, "misses", 0))
|
|
255
|
+
|
|
256
|
+
def budget_left() -> int:
|
|
257
|
+
if config.evaluation_budget is None:
|
|
258
|
+
return 1 << 30
|
|
259
|
+
return max(0, config.evaluation_budget - spent())
|
|
260
|
+
|
|
261
|
+
from agent_evolve.policies.search_state import SearchState
|
|
262
|
+
|
|
263
|
+
# --- the cheaper evaluation fidelity, if the problem has one -----------
|
|
264
|
+
# The source holds the problem's `evaluate_proxy` bound method and nothing
|
|
265
|
+
# else -- no problem, no cache, no budget -- so a proxy evaluation has no
|
|
266
|
+
# route to a charge. It is attached to the SCREEN, which is the only
|
|
267
|
+
# consumer that can use cheap evidence without putting it in the archive.
|
|
268
|
+
proxy_source: Any = None
|
|
269
|
+
if config.proxy_fidelity != "off" and config.screening is not None:
|
|
270
|
+
from agent_evolve.session.fidelity import ProxySource
|
|
271
|
+
|
|
272
|
+
proxy_source = ProxySource.for_problem(
|
|
273
|
+
problem, ceiling=config.proxy_ceiling)
|
|
274
|
+
if proxy_source is not None:
|
|
275
|
+
config.screening.attach_proxy(
|
|
276
|
+
proxy_source, mode=config.proxy_fidelity)
|
|
277
|
+
|
|
278
|
+
all_valid: List[Any] = []
|
|
279
|
+
all_meta: List[tuple] = []
|
|
280
|
+
history: List[Dict[str, Any]] = []
|
|
281
|
+
# The state accumulates across generations and is handed to the chooser
|
|
282
|
+
# each time. The unguided chooser ignores it, which is what makes the two
|
|
283
|
+
# arms differ in exactly one thing.
|
|
284
|
+
state = config.state if config.state is not None else SearchState()
|
|
285
|
+
state.history = history
|
|
286
|
+
|
|
287
|
+
def measure(configs: List[Config], gen: int) -> List[Any]:
|
|
288
|
+
room = budget_left()
|
|
289
|
+
if room <= 0:
|
|
290
|
+
return []
|
|
291
|
+
valid, failed, _ordered = evaluate_batch(
|
|
292
|
+
problem, configs[:room], specs, cache=cache
|
|
293
|
+
)
|
|
294
|
+
all_valid.extend(valid)
|
|
295
|
+
for result in list(valid) + list(failed):
|
|
296
|
+
all_meta.append((result, {"generation": gen}))
|
|
297
|
+
# Every measured candidate feeds the per-locus table, including ones the
|
|
298
|
+
# population does not keep: what was tried and rejected is exactly the
|
|
299
|
+
# evidence that a locus is saturated.
|
|
300
|
+
state.evaluated.extend((r.configuration, dict(r.objectives)) for r in valid)
|
|
301
|
+
# --- actionable side information ----------------------------------
|
|
302
|
+
# Failures verbatim: what the validator or evaluator said is exactly
|
|
303
|
+
# the diagnostic the score-only condition throws away.
|
|
304
|
+
for result in failed:
|
|
305
|
+
message = getattr(result, "error_message", None)
|
|
306
|
+
if message:
|
|
307
|
+
state.side_information.append(f"rejected: {message}")
|
|
308
|
+
# Optional problem hook -- an opt-in sixth obligation, never required.
|
|
309
|
+
hook = getattr(problem, "side_information", None)
|
|
310
|
+
if callable(hook):
|
|
311
|
+
for result in valid:
|
|
312
|
+
try:
|
|
313
|
+
text = hook(result.configuration, dict(result.objectives))
|
|
314
|
+
except Exception as exc: # a hook must not kill a run,
|
|
315
|
+
state.side_information.append( # but must not vanish either
|
|
316
|
+
f"side_information hook raised {type(exc).__name__}: {exc}"
|
|
317
|
+
)
|
|
318
|
+
break
|
|
319
|
+
if text:
|
|
320
|
+
state.side_information.append(str(text))
|
|
321
|
+
del state.side_information[:-64] # bounded, newest kept
|
|
322
|
+
return valid
|
|
323
|
+
|
|
324
|
+
def remember_measured(results: Sequence[Any],
|
|
325
|
+
surviving: Optional[Set[str]] = None) -> None:
|
|
326
|
+
"""Report charged measurements to the generator as EVIDENCE.
|
|
327
|
+
|
|
328
|
+
Every charge the run makes, whoever proposed it. A generator that
|
|
329
|
+
reasons over measurements is reasoning about the SPACE, and the space
|
|
330
|
+
does not care which component produced the point: the initial
|
|
331
|
+
population and the structure screen are measurements this run paid
|
|
332
|
+
for, and withholding them leaves the channel blind until the loop has
|
|
333
|
+
spent two generations reproducing evidence it already had. That was
|
|
334
|
+
W11, measured: the locus prior could not be authored before a median
|
|
335
|
+
charge of 40 on a venue whose dominant knob is legible by charge 19.
|
|
336
|
+
|
|
337
|
+
Attribution is the separate call: a generator is credited only with
|
|
338
|
+
the children it drew, so its survival counters -- and the revision and
|
|
339
|
+
unwind rules that read them -- are untouched by what it is shown.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
generator = config.generator
|
|
343
|
+
note = getattr(generator, "note_measured", None)
|
|
344
|
+
if generator is None or not results or note is None:
|
|
345
|
+
return
|
|
346
|
+
kept = surviving or set()
|
|
347
|
+
for result in results:
|
|
348
|
+
note(result.configuration,
|
|
349
|
+
objectives=dict(result.objectives),
|
|
350
|
+
survived=_default_candidate_key(result.configuration) in kept)
|
|
351
|
+
|
|
352
|
+
# --- initial population: the seeds, then SCHEMA-UNIFORM draws -----------
|
|
353
|
+
# Not mutants of the seed. A population of near-copies of one
|
|
354
|
+
# configuration is an anchored cloud around it; measured on a third-party
|
|
355
|
+
# optimizer, correcting exactly this anchor moved its result from +0.095
|
|
356
|
+
# (loses badly to uniform) to +0.0066 (parity), and the standard seed on
|
|
357
|
+
# log2 scores worse than a typical uniform draw.
|
|
358
|
+
n_loci = len(loci_of(seeds[0]))
|
|
359
|
+
|
|
360
|
+
# --- optional structure phase: buy a model of the landscape first -------
|
|
361
|
+
# Operator choice cannot escape the distribution it samples from, so before
|
|
362
|
+
# building a population the loop can spend a few evaluations on a CROSSED
|
|
363
|
+
# screen, read which locus values the evidence refutes, and narrow the
|
|
364
|
+
# domains every later draw sees. The screen costs real budget and the prior
|
|
365
|
+
# can be wrong, so both are recorded and the bet is checked below.
|
|
366
|
+
restriction = config.restriction
|
|
367
|
+
screen_front: List[Mapping[str, float]] = []
|
|
368
|
+
structure_record: Dict[str, Any] = {}
|
|
369
|
+
prior_proposer_used: Any = None
|
|
370
|
+
if config.structure_budget > 0 and restriction is None:
|
|
371
|
+
from agent_evolve.policies.structure import (
|
|
372
|
+
attribute, crossed_screen, statistical_prior)
|
|
373
|
+
|
|
374
|
+
screened = crossed_screen(seeds[0], candidate_model,
|
|
375
|
+
size=config.structure_budget, rng=rng,
|
|
376
|
+
pool_by_field=config.structure_pooled)
|
|
377
|
+
screen_valid = measure(screened, 0)
|
|
378
|
+
# Charged, therefore evidence. The screen's points never enter a
|
|
379
|
+
# population, so none of them is marked survived -- an absent verdict
|
|
380
|
+
# reported as absent rather than invented.
|
|
381
|
+
remember_measured(screen_valid)
|
|
382
|
+
if screen_valid:
|
|
383
|
+
attr = attribute(
|
|
384
|
+
[(r.configuration, dict(r.objectives)) for r in screen_valid],
|
|
385
|
+
specs, candidate_model,
|
|
386
|
+
pool_by_field=config.structure_pooled)
|
|
387
|
+
propose = config.prior_proposer or statistical_prior
|
|
388
|
+
prior_proposer_used = propose
|
|
389
|
+
try:
|
|
390
|
+
restriction = propose(attr, candidate_model)
|
|
391
|
+
except Exception as exc: # a proposer must not kill a run
|
|
392
|
+
structure_record["proposer_error"] = f"{type(exc).__name__}: {exc}"
|
|
393
|
+
restriction = None
|
|
394
|
+
# Keep what the screen itself achieved, as objective vectors: the
|
|
395
|
+
# unwind test below asks whether the restricted search can still
|
|
396
|
+
# match it, and domination is the only comparison that means
|
|
397
|
+
# anything across several objectives.
|
|
398
|
+
# Keep the screen's non-dominated points that the prior EXCLUDES.
|
|
399
|
+
# Those are exactly the claims the prior makes: it asserts this
|
|
400
|
+
# region is not worth sampling. If the restricted search cannot
|
|
401
|
+
# beat even one of them, the assertion is unsupported and the bet
|
|
402
|
+
# comes off. Pooled rank-0 would be the weaker test and a useless
|
|
403
|
+
# one -- a restricted region always holds points that are
|
|
404
|
+
# non-dominated along some other objective, so it could never fire.
|
|
405
|
+
screen_ranks = domination_rank(
|
|
406
|
+
[dict(r.objectives) for r in screen_valid], specs)
|
|
407
|
+
allowed = dict(getattr(restriction, "allowed", {}) or {})
|
|
408
|
+
|
|
409
|
+
def _excluded(cfg: Mapping[str, Any]) -> bool:
|
|
410
|
+
return any(cfg.get(k) not in tuple(vals)
|
|
411
|
+
for k, vals in allowed.items())
|
|
412
|
+
|
|
413
|
+
screen_front = [dict(r.objectives)
|
|
414
|
+
for r, rank in zip(screen_valid, screen_ranks)
|
|
415
|
+
if rank == 0 and _excluded(r.configuration)]
|
|
416
|
+
structure_record.update(
|
|
417
|
+
screened=len(screened), evaluated=len(screen_valid),
|
|
418
|
+
allowed=dict(getattr(restriction, "allowed", {}) or {}),
|
|
419
|
+
misses=list(getattr(restriction, "misses", []) or []),
|
|
420
|
+
)
|
|
421
|
+
log(f"structure: screened {len(screen_valid)}, prior "
|
|
422
|
+
f"{structure_record.get('allowed') or 'none'}")
|
|
423
|
+
|
|
424
|
+
initial = list(seeds)
|
|
425
|
+
for proposal in config.initial_proposals:
|
|
426
|
+
if len(initial) < config.population_size:
|
|
427
|
+
initial.append(dict(proposal))
|
|
428
|
+
while len(initial) < config.population_size:
|
|
429
|
+
template = initial[rng.randrange(len(initial))]
|
|
430
|
+
initial.append(uniform_candidate(template, candidate_model, rng=rng,
|
|
431
|
+
restriction=restriction))
|
|
432
|
+
valid = measure(initial, 0)
|
|
433
|
+
if not valid:
|
|
434
|
+
raise RuntimeError(
|
|
435
|
+
"no seed evaluated successfully, so there is nothing to evolve from"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# The population carries OBJECTIVES, not ranks: a rank is only meaningful
|
|
439
|
+
# relative to the set it was computed in, so it must be recomputed whenever
|
|
440
|
+
# the set changes rather than carried forward from a previous generation.
|
|
441
|
+
def survive(
|
|
442
|
+
pool: List[tuple[Config, Mapping[str, float]]]
|
|
443
|
+
) -> List[tuple[Config, Mapping[str, float]]]:
|
|
444
|
+
ranks = domination_rank([obj for _c, obj in pool], specs)
|
|
445
|
+
kept = truncation_survival(
|
|
446
|
+
[((c, o), r) for (c, o), r in zip(pool, ranks)],
|
|
447
|
+
keep=config.population_size,
|
|
448
|
+
key_of=lambda pair: _default_candidate_key(pair[0]),
|
|
449
|
+
)
|
|
450
|
+
return [pair for pair, _rank in kept]
|
|
451
|
+
|
|
452
|
+
population = survive([(r.configuration, dict(r.objectives)) for r in valid])
|
|
453
|
+
history.append({"gen": 0, "valid_count": len(valid), "pop": len(population)})
|
|
454
|
+
log(f"generation 0: {len(valid)} evaluated, population {len(population)}")
|
|
455
|
+
if config.generator is not None:
|
|
456
|
+
# What the run has already measured, so the novelty guard can tell a
|
|
457
|
+
# candidate that is new from one the generator is re-proposing.
|
|
458
|
+
config.generator.note_archive([r.configuration for r in valid])
|
|
459
|
+
# ... and what those measurements SAID. The initial population is the only
|
|
460
|
+
# evidence in existence when the first pool is drawn; a channel that cannot
|
|
461
|
+
# see it cannot speak until generation 2, which is the W11 defect.
|
|
462
|
+
remember_measured(valid,
|
|
463
|
+
{_default_candidate_key(c) for c, _o in population})
|
|
464
|
+
|
|
465
|
+
pick = chooser or random_chooser(rng, n_loci)
|
|
466
|
+
# Guidance picks the revision channel returned last generation. They are
|
|
467
|
+
# authored members, like the initial proposals, so they wait for a
|
|
468
|
+
# generation of their own rather than displacing offspring already built.
|
|
469
|
+
immigrants: List[Config] = []
|
|
470
|
+
# Pool extras draw from their own stream: the main stream must spend
|
|
471
|
+
# exactly the same draws whether or not screening is on, or "off" stops
|
|
472
|
+
# being byte-identical to the pre-screening seam.
|
|
473
|
+
rng_pool = random.Random(0 if config.seed is None else (config.seed ^ 0x5CEE11))
|
|
474
|
+
|
|
475
|
+
for gen in range(1, config.generations + 1):
|
|
476
|
+
if budget_left() <= 0:
|
|
477
|
+
log(f"budget exhausted after {spent()} evaluations; stopping at gen {gen}")
|
|
478
|
+
break
|
|
479
|
+
want = min(config.offspring_per_generation, budget_left())
|
|
480
|
+
# The chooser sees ranks, not raw objectives: it decides which parents to
|
|
481
|
+
# combine, and a rank is the comparable form of "how good is this one".
|
|
482
|
+
ranks = domination_rank([obj for _c, obj in population], specs)
|
|
483
|
+
ranked = [(c, r) for (c, _o), r in zip(population, ranks)]
|
|
484
|
+
filled = 0
|
|
485
|
+
choices: Sequence[OperatorChoice] = ()
|
|
486
|
+
# An authored generator draws the whole generation, so there is no
|
|
487
|
+
# parent choice to make and the chooser is not consulted -- calling it
|
|
488
|
+
# and discarding the answer would spend a model call on nothing.
|
|
489
|
+
if config.generator is None:
|
|
490
|
+
choices = list(pick(ranked, want, state))[:want]
|
|
491
|
+
# A chooser that returns too few must not silently shrink the
|
|
492
|
+
# generation: the arm would then spend less budget than the control
|
|
493
|
+
# it is compared against. Top up at random and record how many, so
|
|
494
|
+
# the shortfall shows up in the result instead of in the conclusion.
|
|
495
|
+
if len(choices) < want:
|
|
496
|
+
filled = want - len(choices)
|
|
497
|
+
choices = list(choices) + list(
|
|
498
|
+
random_chooser(rng, n_loci)(ranked, filled, None)
|
|
499
|
+
)
|
|
500
|
+
def build_kid(choice: OperatorChoice, r: random.Random) -> Config:
|
|
501
|
+
a = population[choice.parent_a % len(population)][0]
|
|
502
|
+
b = population[choice.parent_b % len(population)][0]
|
|
503
|
+
mask = choice.mask
|
|
504
|
+
# The mask is fitted to the parent it is about to be applied to,
|
|
505
|
+
# NOT to `n_loci`. A locus count is a property of a candidate, not
|
|
506
|
+
# of a problem: a field holding a sequence contributes one locus per
|
|
507
|
+
# element, so two candidates of the same problem legitimately have
|
|
508
|
+
# different genome lengths. `n_loci` is read once from seeds[0], and
|
|
509
|
+
# using it here made the shipped knapsack example -- whose seeds are
|
|
510
|
+
# a 1-item and a 3-item selection -- crash deterministically on the
|
|
511
|
+
# first generation, because a 1-bit mask met a 3-locus parent.
|
|
512
|
+
want_bits = len(loci_of(a))
|
|
513
|
+
if len(mask) != want_bits: # a chooser may be wrong; the
|
|
514
|
+
mask = mask[:want_bits] + tuple( # loop must not crash on it
|
|
515
|
+
bool(r.getrandbits(1)) for _ in range(want_bits - len(mask))
|
|
516
|
+
)
|
|
517
|
+
kid = crossover(a, b, mask=mask)
|
|
518
|
+
return mutate(kid, candidate_model, rate=config.mutation_rate,
|
|
519
|
+
restriction=restriction,
|
|
520
|
+
loci=choice.mutate_loci, rng=r)
|
|
521
|
+
|
|
522
|
+
kid_origins: Optional[List[str]] = None
|
|
523
|
+
# --- mass generation: the model wrote the sampler, not the samples --
|
|
524
|
+
# The pool is many times what the budget can afford, and costs the
|
|
525
|
+
# budget nothing: the generator is handed a template, the domains, and
|
|
526
|
+
# the archive -- never the problem and never the cache -- so the only
|
|
527
|
+
# candidates that can become evaluations are the `want` below.
|
|
528
|
+
pool_kids: Optional[List[Config]] = None
|
|
529
|
+
if config.generator is not None:
|
|
530
|
+
pool_kids = config.generator.propose(
|
|
531
|
+
template=seeds[0], candidate_model=candidate_model,
|
|
532
|
+
restriction=restriction,
|
|
533
|
+
archive=[c for c, _o in population],
|
|
534
|
+
want=want, rng=rng_pool,
|
|
535
|
+
seed=(config.seed or 0) * 1000 + gen)
|
|
536
|
+
kids = [dict(kid) for kid in pool_kids[:want]]
|
|
537
|
+
elif config.portfolio is not None:
|
|
538
|
+
pairs = [
|
|
539
|
+
(population[choice.parent_a % len(population)][0],
|
|
540
|
+
population[choice.parent_b % len(population)][0])
|
|
541
|
+
for choice in choices
|
|
542
|
+
]
|
|
543
|
+
kids, kid_origins = config.portfolio.construct_generation(
|
|
544
|
+
pairs, candidate_model, restriction, rng, generation=gen)
|
|
545
|
+
else:
|
|
546
|
+
kids = [build_kid(choice, rng) for choice in choices]
|
|
547
|
+
|
|
548
|
+
# --- virtual pre-screening: build more than we can afford, pay for
|
|
549
|
+
# the promising. The surrogate is re-validated on today's data before
|
|
550
|
+
# it may order anything (the gate is the arbitration), a floor of the
|
|
551
|
+
# chooser's own picks is always measured unscreened (the screen must
|
|
552
|
+
# never own the whole generation), and only measure() below touches
|
|
553
|
+
# the budget -- the screen has no route to it by construction.
|
|
554
|
+
screen_note: Optional[Dict[str, Any]] = None
|
|
555
|
+
if config.screening is not None and kids:
|
|
556
|
+
# Cheap-fidelity evidence FIRST, so the gate this generation sees
|
|
557
|
+
# the rows the campaign could not afford. It buys evidence about
|
|
558
|
+
# THIS generation's candidates, which is the distribution the
|
|
559
|
+
# screen is about to rank, and it charges nothing.
|
|
560
|
+
if proxy_source is not None:
|
|
561
|
+
config.screening.prime(
|
|
562
|
+
kids if pool_kids is None else pool_kids,
|
|
563
|
+
[proxy_source.key(c) for c, _o in state.evaluated])
|
|
564
|
+
active = config.screening.refresh(
|
|
565
|
+
list(state.evaluated), specs,
|
|
566
|
+
seed=(config.seed or 0) + gen)
|
|
567
|
+
screen_note = {"pool": len(kids), "held_out": len(kids),
|
|
568
|
+
"advanced": 0, "active": bool(active)}
|
|
569
|
+
if active:
|
|
570
|
+
if pool_kids is None:
|
|
571
|
+
extra_n = (config.screening.pool_factor - 1) * len(kids)
|
|
572
|
+
extra_choices = random_chooser(rng_pool, n_loci)(
|
|
573
|
+
ranked, extra_n, None)
|
|
574
|
+
pool_kids = kids + [build_kid(c, rng_pool)
|
|
575
|
+
for c in extra_choices]
|
|
576
|
+
pool_origins = (None if kid_origins is None else
|
|
577
|
+
list(kid_origins)
|
|
578
|
+
+ ["pool"] * (len(pool_kids) - len(kids)))
|
|
579
|
+
report = config.screening.screen(
|
|
580
|
+
pool_kids, [obj for _c, obj in population], specs)
|
|
581
|
+
if report is not None:
|
|
582
|
+
# The floor is the screen's own, not a constant: a screen
|
|
583
|
+
# certified on some of the objectives is biased against
|
|
584
|
+
# the ones it cannot see, so it reserves more of the
|
|
585
|
+
# generation for the chooser's unscreened picks.
|
|
586
|
+
floor_n = min(len(kids), max(1, math.ceil(
|
|
587
|
+
config.screening.exploration_floor_for(report) * want)))
|
|
588
|
+
keep = list(range(floor_n))
|
|
589
|
+
for index in report.order:
|
|
590
|
+
if len(keep) >= want:
|
|
591
|
+
break
|
|
592
|
+
if index >= floor_n:
|
|
593
|
+
keep.append(index)
|
|
594
|
+
kids = [pool_kids[index] for index in keep[:want]]
|
|
595
|
+
if pool_origins is not None:
|
|
596
|
+
kid_origins = [pool_origins[index]
|
|
597
|
+
for index in keep[:want]]
|
|
598
|
+
screen_note = {
|
|
599
|
+
"pool": len(pool_kids), "held_out": floor_n,
|
|
600
|
+
"advanced": len(kids) - floor_n, "active": True,
|
|
601
|
+
"surrogate": report.surrogate_name,
|
|
602
|
+
# Which objectives this order was actually computed
|
|
603
|
+
# over. A generation that screened on a subset must
|
|
604
|
+
# not be readable as one that screened on the whole
|
|
605
|
+
# problem, so the scope travels in the record beside
|
|
606
|
+
# the count of what it advanced.
|
|
607
|
+
"objectives": list(report.screened_objectives),
|
|
608
|
+
"objectives_declared": list(report.declared_objectives),
|
|
609
|
+
"partial": bool(report.partial),
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
# --- immigrants: authored members, ahead of the offspring -----------
|
|
613
|
+
# They bypass the screen exactly as the initial proposals do: the
|
|
614
|
+
# screen orders what the loop CONSTRUCTED, and a member the model
|
|
615
|
+
# authored from the run's measurements is a guidance pick whose bet is
|
|
616
|
+
# settled by the evaluator, not by a surrogate.
|
|
617
|
+
injected = 0
|
|
618
|
+
if immigrants:
|
|
619
|
+
kids = [dict(member) for member in immigrants] + list(kids)
|
|
620
|
+
kids = kids[:want]
|
|
621
|
+
injected = min(len(immigrants), len(kids))
|
|
622
|
+
immigrants = []
|
|
623
|
+
|
|
624
|
+
valid = measure(kids, gen)
|
|
625
|
+
# Survivors compete against this generation's offspring on equal terms;
|
|
626
|
+
# both carry their own objectives, so the rank is recomputed over the
|
|
627
|
+
# merged set rather than inherited from the set it was measured in.
|
|
628
|
+
pool: List[tuple[Config, Mapping[str, float]]] = list(population)
|
|
629
|
+
pool.extend((r.configuration, dict(r.objectives)) for r in valid)
|
|
630
|
+
population = survive(pool)
|
|
631
|
+
|
|
632
|
+
# --- survival credit: a mechanism is what its children survive ------
|
|
633
|
+
if config.generator is not None and valid:
|
|
634
|
+
surviving = {_default_candidate_key(c) for c, _o in population}
|
|
635
|
+
for result in valid:
|
|
636
|
+
config.generator.record_measured(
|
|
637
|
+
result.configuration,
|
|
638
|
+
survived=(_default_candidate_key(result.configuration)
|
|
639
|
+
in surviving),
|
|
640
|
+
objectives=dict(result.objectives))
|
|
641
|
+
|
|
642
|
+
if config.portfolio is not None and kid_origins is not None and valid:
|
|
643
|
+
surviving = {_default_candidate_key(c) for c, _o in population}
|
|
644
|
+
origin_by_key: Dict[str, str] = {}
|
|
645
|
+
for kid, origin in zip(kids, kid_origins):
|
|
646
|
+
origin_by_key.setdefault(_default_candidate_key(kid), origin)
|
|
647
|
+
for result in valid:
|
|
648
|
+
key = _default_candidate_key(result.configuration)
|
|
649
|
+
origin = origin_by_key.get(key)
|
|
650
|
+
if origin and origin != "pool":
|
|
651
|
+
config.portfolio.record_measured(
|
|
652
|
+
origin, survived=key in surviving)
|
|
653
|
+
for name in config.portfolio.review():
|
|
654
|
+
log(f"generation {gen}: operator arm {name!r} retired -- no "
|
|
655
|
+
"survivors in at least 4 measured children while the "
|
|
656
|
+
"classical arm has some")
|
|
657
|
+
|
|
658
|
+
# --- the prior is a bet, and a bet must be checkable -----------------
|
|
659
|
+
# A restriction that removed the good region cannot recover on its own,
|
|
660
|
+
# so once the restricted search has had a generation to show something,
|
|
661
|
+
# ask whether it can still match what the screen already found. If not,
|
|
662
|
+
# drop the restriction for the remainder and say so. Unwinding a prior
|
|
663
|
+
# that is merely unlucky costs less than holding one that is wrong.
|
|
664
|
+
if restriction is not None and screen_front and not structure_record.get("unwound"):
|
|
665
|
+
beat_something = any(
|
|
666
|
+
dominates(obj, excluded, specs)
|
|
667
|
+
for _c, obj in population
|
|
668
|
+
for excluded in screen_front)
|
|
669
|
+
if not beat_something:
|
|
670
|
+
restriction = None
|
|
671
|
+
structure_record["unwound"] = gen
|
|
672
|
+
log(f"generation {gen}: the prior stopped paying; restriction "
|
|
673
|
+
"dropped for the remainder")
|
|
674
|
+
|
|
675
|
+
# --- the prior is also REVISABLE ------------------------------------
|
|
676
|
+
# The unwind above can only drop a prior; it cannot correct one. A
|
|
677
|
+
# prior authored before the run measured anything is right about the
|
|
678
|
+
# rows it was authored from and says nothing about the rows that came
|
|
679
|
+
# after, so the channel that reads those rows runs here, after
|
|
680
|
+
# survival, on its own declared cadence.
|
|
681
|
+
reguide_note: Optional[Dict[str, Any]] = None
|
|
682
|
+
if config.reguidance is not None:
|
|
683
|
+
outcome = config.reguidance.maybe_revise(
|
|
684
|
+
rows=list(state.evaluated), population=list(population),
|
|
685
|
+
specs=specs, restriction=restriction, charges=spent(), gen=gen)
|
|
686
|
+
if outcome.restriction is not None:
|
|
687
|
+
restriction = outcome.restriction
|
|
688
|
+
if outcome.immigrants:
|
|
689
|
+
immigrants = [dict(member) for member in outcome.immigrants]
|
|
690
|
+
reguide_note = outcome.note
|
|
691
|
+
if reguide_note is not None:
|
|
692
|
+
log(f"generation {gen}: the sampling prior was revisited at "
|
|
693
|
+
f"{spent()} charged evaluations")
|
|
694
|
+
|
|
695
|
+
entry = {"gen": gen, "valid_count": len(valid),
|
|
696
|
+
"pop": len(population),
|
|
697
|
+
"choices_filled_at_random": filled}
|
|
698
|
+
if reguide_note is not None:
|
|
699
|
+
entry["reguide"] = reguide_note
|
|
700
|
+
if injected:
|
|
701
|
+
entry["reguide_immigrants"] = injected
|
|
702
|
+
if screen_note is not None:
|
|
703
|
+
entry["screen"] = screen_note
|
|
704
|
+
if config.generator is not None:
|
|
705
|
+
entry["generate"] = config.generator.note()
|
|
706
|
+
if config.portfolio is not None:
|
|
707
|
+
entry["portfolio"] = config.portfolio.summary()
|
|
708
|
+
history.append(entry)
|
|
709
|
+
if filled:
|
|
710
|
+
log(f"generation {gen}: the chooser supplied {want - filled} of "
|
|
711
|
+
f"{want} choices; {filled} were filled at random")
|
|
712
|
+
log(f"generation {gen}: {len(valid)} evaluated, {spent()} of "
|
|
713
|
+
f"{config.evaluation_budget} budget used")
|
|
714
|
+
|
|
715
|
+
if structure_record:
|
|
716
|
+
history.append({"structure": dict(structure_record)})
|
|
717
|
+
result = _build_search_result(
|
|
718
|
+
all_valid, all_meta, specs, history,
|
|
719
|
+
evaluations=spent(), candidate_key=_default_candidate_key,
|
|
720
|
+
)
|
|
721
|
+
# Telemetry is attached even when every mechanism was counter-free: an
|
|
722
|
+
# empty mechanism list on a random run is a measured zero, and a guided
|
|
723
|
+
# run's counters are the difference between "guidance did not help" and
|
|
724
|
+
# "guidance never arrived".
|
|
725
|
+
virtual = 0
|
|
726
|
+
if config.screening is not None:
|
|
727
|
+
virtual = int(getattr(
|
|
728
|
+
config.screening.telemetry, "virtual_evaluations", 0))
|
|
729
|
+
# Proxy evaluations are reported BESIDE the charged count, never inside
|
|
730
|
+
# it: `real_evaluations` is what the budget bought and what any
|
|
731
|
+
# evaluation-efficiency claim is denominated in.
|
|
732
|
+
proxy_evaluations = (0 if proxy_source is None
|
|
733
|
+
else int(proxy_source.ledger.evaluations))
|
|
734
|
+
return replace(result, telemetry=harvest_telemetry(
|
|
735
|
+
(pick, prior_proposer_used, config.screening,
|
|
736
|
+
getattr(config.screening, "author", None),
|
|
737
|
+
config.portfolio, getattr(config.portfolio, "author", None),
|
|
738
|
+
config.generator, getattr(config.generator, "author", None),
|
|
739
|
+
config.reguidance, proxy_source),
|
|
740
|
+
real_evaluations=spent(), virtual_evaluations=virtual,
|
|
741
|
+
proxy_evaluations=proxy_evaluations,
|
|
742
|
+
))
|