crca 1.4.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.
- .github/ISSUE_TEMPLATE/bug_report.md +65 -0
- .github/ISSUE_TEMPLATE/feature_request.md +41 -0
- .github/PULL_REQUEST_TEMPLATE.md +20 -0
- .github/workflows/publish-manual.yml +61 -0
- .github/workflows/publish.yml +64 -0
- .gitignore +214 -0
- CRCA.py +4156 -0
- LICENSE +201 -0
- MANIFEST.in +43 -0
- PKG-INFO +5035 -0
- README.md +4959 -0
- __init__.py +17 -0
- branches/CRCA-Q.py +2728 -0
- branches/crca_cg/corposwarm.py +9065 -0
- branches/crca_cg/fix_rancher_docker_creds.ps1 +155 -0
- branches/crca_cg/package.json +5 -0
- branches/crca_cg/test_bolt_integration.py +446 -0
- branches/crca_cg/test_corposwarm_comprehensive.py +773 -0
- branches/crca_cg/test_new_features.py +163 -0
- branches/crca_sd/__init__.py +149 -0
- branches/crca_sd/crca_sd_core.py +770 -0
- branches/crca_sd/crca_sd_governance.py +1325 -0
- branches/crca_sd/crca_sd_mpc.py +1130 -0
- branches/crca_sd/crca_sd_realtime.py +1844 -0
- branches/crca_sd/crca_sd_tui.py +1133 -0
- crca-1.4.0.dist-info/METADATA +5035 -0
- crca-1.4.0.dist-info/RECORD +501 -0
- crca-1.4.0.dist-info/WHEEL +4 -0
- crca-1.4.0.dist-info/licenses/LICENSE +201 -0
- docs/CRCA-Q.md +2333 -0
- examples/config.yaml.example +25 -0
- examples/crca_sd_example.py +513 -0
- examples/data_broker_example.py +294 -0
- examples/logistics_corporation.py +861 -0
- examples/palantir_example.py +299 -0
- examples/policy_bench.py +934 -0
- examples/pridnestrovia-sd.py +705 -0
- examples/pridnestrovia_realtime.py +1902 -0
- prompts/__init__.py +10 -0
- prompts/default_crca.py +101 -0
- pyproject.toml +151 -0
- requirements.txt +76 -0
- schemas/__init__.py +43 -0
- schemas/mcpSchemas.py +51 -0
- schemas/policy.py +458 -0
- templates/__init__.py +38 -0
- templates/base_specialized_agent.py +195 -0
- templates/drift_detection.py +325 -0
- templates/examples/causal_agent_template.py +309 -0
- templates/examples/drag_drop_example.py +213 -0
- templates/examples/logistics_agent_template.py +207 -0
- templates/examples/trading_agent_template.py +206 -0
- templates/feature_mixins.py +253 -0
- templates/graph_management.py +442 -0
- templates/llm_integration.py +194 -0
- templates/module_registry.py +276 -0
- templates/mpc_planner.py +280 -0
- templates/policy_loop.py +1168 -0
- templates/prediction_framework.py +448 -0
- templates/statistical_methods.py +778 -0
- tests/sanity.yml +31 -0
- tests/sanity_check +406 -0
- tests/test_core.py +47 -0
- tests/test_crca_excel.py +166 -0
- tests/test_crca_sd.py +780 -0
- tests/test_data_broker.py +424 -0
- tests/test_palantir.py +349 -0
- tools/__init__.py +38 -0
- tools/actuators.py +437 -0
- tools/bolt.diy/Dockerfile +103 -0
- tools/bolt.diy/app/components/@settings/core/AvatarDropdown.tsx +175 -0
- tools/bolt.diy/app/components/@settings/core/ControlPanel.tsx +345 -0
- tools/bolt.diy/app/components/@settings/core/constants.tsx +108 -0
- tools/bolt.diy/app/components/@settings/core/types.ts +114 -0
- tools/bolt.diy/app/components/@settings/index.ts +12 -0
- tools/bolt.diy/app/components/@settings/shared/components/TabTile.tsx +151 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionForm.tsx +193 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ConnectionTestIndicator.tsx +60 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ErrorState.tsx +102 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/LoadingState.tsx +94 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/ServiceHeader.tsx +72 -0
- tools/bolt.diy/app/components/@settings/shared/service-integration/index.ts +6 -0
- tools/bolt.diy/app/components/@settings/tabs/data/DataTab.tsx +721 -0
- tools/bolt.diy/app/components/@settings/tabs/data/DataVisualization.tsx +384 -0
- tools/bolt.diy/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +1013 -0
- tools/bolt.diy/app/components/@settings/tabs/features/FeaturesTab.tsx +295 -0
- tools/bolt.diy/app/components/@settings/tabs/github/GitHubTab.tsx +281 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubAuthDialog.tsx +173 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubCacheManager.tsx +367 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubConnection.tsx +233 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubErrorBoundary.tsx +105 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubProgressiveLoader.tsx +266 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositoryCard.tsx +121 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubRepositorySelector.tsx +312 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubStats.tsx +291 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/GitHubUserProfile.tsx +46 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/GitHubStateIndicators.tsx +264 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/RepositoryCard.tsx +361 -0
- tools/bolt.diy/app/components/@settings/tabs/github/components/shared/index.ts +11 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/GitLabTab.tsx +305 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabAuthDialog.tsx +186 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabConnection.tsx +253 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +358 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryCard.tsx +79 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/RepositoryList.tsx +142 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/StatsDisplay.tsx +91 -0
- tools/bolt.diy/app/components/@settings/tabs/gitlab/components/index.ts +4 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerList.tsx +99 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpServerListItem.tsx +70 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpStatusBadge.tsx +37 -0
- tools/bolt.diy/app/components/@settings/tabs/mcp/McpTab.tsx +239 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/NetlifyTab.tsx +1393 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/components/NetlifyConnection.tsx +990 -0
- tools/bolt.diy/app/components/@settings/tabs/netlify/components/index.ts +1 -0
- tools/bolt.diy/app/components/@settings/tabs/notifications/NotificationsTab.tsx +300 -0
- tools/bolt.diy/app/components/@settings/tabs/profile/ProfileTab.tsx +181 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/cloud/CloudProvidersTab.tsx +308 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ErrorBoundary.tsx +68 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/HealthStatusBadge.tsx +64 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/LoadingSkeleton.tsx +107 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/LocalProvidersTab.tsx +556 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ModelCard.tsx +106 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/ProviderCard.tsx +120 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/SetupGuide.tsx +671 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/StatusDashboard.tsx +91 -0
- tools/bolt.diy/app/components/@settings/tabs/providers/local/types.ts +44 -0
- tools/bolt.diy/app/components/@settings/tabs/settings/SettingsTab.tsx +215 -0
- tools/bolt.diy/app/components/@settings/tabs/supabase/SupabaseTab.tsx +1089 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/VercelTab.tsx +909 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/components/VercelConnection.tsx +368 -0
- tools/bolt.diy/app/components/@settings/tabs/vercel/components/index.ts +1 -0
- tools/bolt.diy/app/components/@settings/utils/tab-helpers.ts +54 -0
- tools/bolt.diy/app/components/chat/APIKeyManager.tsx +169 -0
- tools/bolt.diy/app/components/chat/Artifact.tsx +296 -0
- tools/bolt.diy/app/components/chat/AssistantMessage.tsx +192 -0
- tools/bolt.diy/app/components/chat/BaseChat.module.scss +47 -0
- tools/bolt.diy/app/components/chat/BaseChat.tsx +522 -0
- tools/bolt.diy/app/components/chat/Chat.client.tsx +670 -0
- tools/bolt.diy/app/components/chat/ChatAlert.tsx +108 -0
- tools/bolt.diy/app/components/chat/ChatBox.tsx +334 -0
- tools/bolt.diy/app/components/chat/CodeBlock.module.scss +10 -0
- tools/bolt.diy/app/components/chat/CodeBlock.tsx +85 -0
- tools/bolt.diy/app/components/chat/DicussMode.tsx +17 -0
- tools/bolt.diy/app/components/chat/ExamplePrompts.tsx +37 -0
- tools/bolt.diy/app/components/chat/FilePreview.tsx +38 -0
- tools/bolt.diy/app/components/chat/GitCloneButton.tsx +327 -0
- tools/bolt.diy/app/components/chat/ImportFolderButton.tsx +141 -0
- tools/bolt.diy/app/components/chat/LLMApiAlert.tsx +109 -0
- tools/bolt.diy/app/components/chat/MCPTools.tsx +129 -0
- tools/bolt.diy/app/components/chat/Markdown.module.scss +171 -0
- tools/bolt.diy/app/components/chat/Markdown.spec.ts +48 -0
- tools/bolt.diy/app/components/chat/Markdown.tsx +252 -0
- tools/bolt.diy/app/components/chat/Messages.client.tsx +102 -0
- tools/bolt.diy/app/components/chat/ModelSelector.tsx +797 -0
- tools/bolt.diy/app/components/chat/NetlifyDeploymentLink.client.tsx +51 -0
- tools/bolt.diy/app/components/chat/ProgressCompilation.tsx +110 -0
- tools/bolt.diy/app/components/chat/ScreenshotStateManager.tsx +33 -0
- tools/bolt.diy/app/components/chat/SendButton.client.tsx +39 -0
- tools/bolt.diy/app/components/chat/SpeechRecognition.tsx +28 -0
- tools/bolt.diy/app/components/chat/StarterTemplates.tsx +38 -0
- tools/bolt.diy/app/components/chat/SupabaseAlert.tsx +199 -0
- tools/bolt.diy/app/components/chat/SupabaseConnection.tsx +339 -0
- tools/bolt.diy/app/components/chat/ThoughtBox.tsx +43 -0
- tools/bolt.diy/app/components/chat/ToolInvocations.tsx +409 -0
- tools/bolt.diy/app/components/chat/UserMessage.tsx +101 -0
- tools/bolt.diy/app/components/chat/VercelDeploymentLink.client.tsx +158 -0
- tools/bolt.diy/app/components/chat/chatExportAndImport/ExportChatButton.tsx +49 -0
- tools/bolt.diy/app/components/chat/chatExportAndImport/ImportButtons.tsx +96 -0
- tools/bolt.diy/app/components/deploy/DeployAlert.tsx +197 -0
- tools/bolt.diy/app/components/deploy/DeployButton.tsx +277 -0
- tools/bolt.diy/app/components/deploy/GitHubDeploy.client.tsx +171 -0
- tools/bolt.diy/app/components/deploy/GitHubDeploymentDialog.tsx +1041 -0
- tools/bolt.diy/app/components/deploy/GitLabDeploy.client.tsx +171 -0
- tools/bolt.diy/app/components/deploy/GitLabDeploymentDialog.tsx +764 -0
- tools/bolt.diy/app/components/deploy/NetlifyDeploy.client.tsx +246 -0
- tools/bolt.diy/app/components/deploy/VercelDeploy.client.tsx +235 -0
- tools/bolt.diy/app/components/editor/codemirror/BinaryContent.tsx +7 -0
- tools/bolt.diy/app/components/editor/codemirror/CodeMirrorEditor.tsx +555 -0
- tools/bolt.diy/app/components/editor/codemirror/EnvMasking.ts +80 -0
- tools/bolt.diy/app/components/editor/codemirror/cm-theme.ts +192 -0
- tools/bolt.diy/app/components/editor/codemirror/indent.ts +68 -0
- tools/bolt.diy/app/components/editor/codemirror/languages.ts +112 -0
- tools/bolt.diy/app/components/git/GitUrlImport.client.tsx +147 -0
- tools/bolt.diy/app/components/header/Header.tsx +42 -0
- tools/bolt.diy/app/components/header/HeaderActionButtons.client.tsx +54 -0
- tools/bolt.diy/app/components/mandate/MandateSubmission.tsx +167 -0
- tools/bolt.diy/app/components/observability/DeploymentStatus.tsx +168 -0
- tools/bolt.diy/app/components/observability/EventTimeline.tsx +119 -0
- tools/bolt.diy/app/components/observability/FileDiffViewer.tsx +121 -0
- tools/bolt.diy/app/components/observability/GovernanceStatus.tsx +197 -0
- tools/bolt.diy/app/components/observability/GovernorMetrics.tsx +246 -0
- tools/bolt.diy/app/components/observability/LogStream.tsx +244 -0
- tools/bolt.diy/app/components/observability/MandateDetails.tsx +201 -0
- tools/bolt.diy/app/components/observability/ObservabilityDashboard.tsx +200 -0
- tools/bolt.diy/app/components/sidebar/HistoryItem.tsx +187 -0
- tools/bolt.diy/app/components/sidebar/Menu.client.tsx +536 -0
- tools/bolt.diy/app/components/sidebar/date-binning.ts +59 -0
- tools/bolt.diy/app/components/txt +1 -0
- tools/bolt.diy/app/components/ui/BackgroundRays/index.tsx +18 -0
- tools/bolt.diy/app/components/ui/BackgroundRays/styles.module.scss +246 -0
- tools/bolt.diy/app/components/ui/Badge.tsx +53 -0
- tools/bolt.diy/app/components/ui/BranchSelector.tsx +270 -0
- tools/bolt.diy/app/components/ui/Breadcrumbs.tsx +101 -0
- tools/bolt.diy/app/components/ui/Button.tsx +46 -0
- tools/bolt.diy/app/components/ui/Card.tsx +55 -0
- tools/bolt.diy/app/components/ui/Checkbox.tsx +32 -0
- tools/bolt.diy/app/components/ui/CloseButton.tsx +49 -0
- tools/bolt.diy/app/components/ui/CodeBlock.tsx +103 -0
- tools/bolt.diy/app/components/ui/Collapsible.tsx +9 -0
- tools/bolt.diy/app/components/ui/ColorSchemeDialog.tsx +378 -0
- tools/bolt.diy/app/components/ui/Dialog.tsx +449 -0
- tools/bolt.diy/app/components/ui/Dropdown.tsx +63 -0
- tools/bolt.diy/app/components/ui/EmptyState.tsx +154 -0
- tools/bolt.diy/app/components/ui/FileIcon.tsx +346 -0
- tools/bolt.diy/app/components/ui/FilterChip.tsx +92 -0
- tools/bolt.diy/app/components/ui/GlowingEffect.tsx +192 -0
- tools/bolt.diy/app/components/ui/GradientCard.tsx +100 -0
- tools/bolt.diy/app/components/ui/IconButton.tsx +84 -0
- tools/bolt.diy/app/components/ui/Input.tsx +22 -0
- tools/bolt.diy/app/components/ui/Label.tsx +20 -0
- tools/bolt.diy/app/components/ui/LoadingDots.tsx +27 -0
- tools/bolt.diy/app/components/ui/LoadingOverlay.tsx +32 -0
- tools/bolt.diy/app/components/ui/PanelHeader.tsx +20 -0
- tools/bolt.diy/app/components/ui/PanelHeaderButton.tsx +36 -0
- tools/bolt.diy/app/components/ui/Popover.tsx +29 -0
- tools/bolt.diy/app/components/ui/Progress.tsx +22 -0
- tools/bolt.diy/app/components/ui/RepositoryStats.tsx +87 -0
- tools/bolt.diy/app/components/ui/ScrollArea.tsx +41 -0
- tools/bolt.diy/app/components/ui/SearchInput.tsx +80 -0
- tools/bolt.diy/app/components/ui/SearchResultItem.tsx +134 -0
- tools/bolt.diy/app/components/ui/Separator.tsx +22 -0
- tools/bolt.diy/app/components/ui/SettingsButton.tsx +35 -0
- tools/bolt.diy/app/components/ui/Slider.tsx +73 -0
- tools/bolt.diy/app/components/ui/StatusIndicator.tsx +90 -0
- tools/bolt.diy/app/components/ui/Switch.tsx +37 -0
- tools/bolt.diy/app/components/ui/Tabs.tsx +52 -0
- tools/bolt.diy/app/components/ui/TabsWithSlider.tsx +112 -0
- tools/bolt.diy/app/components/ui/ThemeSwitch.tsx +29 -0
- tools/bolt.diy/app/components/ui/Tooltip.tsx +122 -0
- tools/bolt.diy/app/components/ui/index.ts +38 -0
- tools/bolt.diy/app/components/ui/use-toast.ts +66 -0
- tools/bolt.diy/app/components/workbench/DiffView.tsx +796 -0
- tools/bolt.diy/app/components/workbench/EditorPanel.tsx +174 -0
- tools/bolt.diy/app/components/workbench/ExpoQrModal.tsx +55 -0
- tools/bolt.diy/app/components/workbench/FileBreadcrumb.tsx +150 -0
- tools/bolt.diy/app/components/workbench/FileTree.tsx +565 -0
- tools/bolt.diy/app/components/workbench/Inspector.tsx +126 -0
- tools/bolt.diy/app/components/workbench/InspectorPanel.tsx +146 -0
- tools/bolt.diy/app/components/workbench/LockManager.tsx +262 -0
- tools/bolt.diy/app/components/workbench/PortDropdown.tsx +91 -0
- tools/bolt.diy/app/components/workbench/Preview.tsx +1049 -0
- tools/bolt.diy/app/components/workbench/ScreenshotSelector.tsx +293 -0
- tools/bolt.diy/app/components/workbench/Search.tsx +257 -0
- tools/bolt.diy/app/components/workbench/Workbench.client.tsx +506 -0
- tools/bolt.diy/app/components/workbench/terminal/Terminal.tsx +131 -0
- tools/bolt.diy/app/components/workbench/terminal/TerminalManager.tsx +68 -0
- tools/bolt.diy/app/components/workbench/terminal/TerminalTabs.tsx +277 -0
- tools/bolt.diy/app/components/workbench/terminal/theme.ts +36 -0
- tools/bolt.diy/app/components/workflow/WorkflowPhase.tsx +109 -0
- tools/bolt.diy/app/components/workflow/WorkflowStatus.tsx +60 -0
- tools/bolt.diy/app/components/workflow/WorkflowTimeline.tsx +150 -0
- tools/bolt.diy/app/entry.client.tsx +7 -0
- tools/bolt.diy/app/entry.server.tsx +80 -0
- tools/bolt.diy/app/root.tsx +156 -0
- tools/bolt.diy/app/routes/_index.tsx +175 -0
- tools/bolt.diy/app/routes/api.bug-report.ts +254 -0
- tools/bolt.diy/app/routes/api.chat.ts +463 -0
- tools/bolt.diy/app/routes/api.check-env-key.ts +41 -0
- tools/bolt.diy/app/routes/api.configured-providers.ts +110 -0
- tools/bolt.diy/app/routes/api.corporate-swarm-status.ts +55 -0
- tools/bolt.diy/app/routes/api.enhancer.ts +137 -0
- tools/bolt.diy/app/routes/api.export-api-keys.ts +44 -0
- tools/bolt.diy/app/routes/api.git-info.ts +69 -0
- tools/bolt.diy/app/routes/api.git-proxy.$.ts +178 -0
- tools/bolt.diy/app/routes/api.github-branches.ts +166 -0
- tools/bolt.diy/app/routes/api.github-deploy.ts +67 -0
- tools/bolt.diy/app/routes/api.github-stats.ts +198 -0
- tools/bolt.diy/app/routes/api.github-template.ts +242 -0
- tools/bolt.diy/app/routes/api.github-user.ts +287 -0
- tools/bolt.diy/app/routes/api.gitlab-branches.ts +143 -0
- tools/bolt.diy/app/routes/api.gitlab-deploy.ts +67 -0
- tools/bolt.diy/app/routes/api.gitlab-projects.ts +105 -0
- tools/bolt.diy/app/routes/api.health.ts +8 -0
- tools/bolt.diy/app/routes/api.llmcall.ts +298 -0
- tools/bolt.diy/app/routes/api.mandate.ts +351 -0
- tools/bolt.diy/app/routes/api.mcp-check.ts +16 -0
- tools/bolt.diy/app/routes/api.mcp-update-config.ts +23 -0
- tools/bolt.diy/app/routes/api.models.$provider.ts +2 -0
- tools/bolt.diy/app/routes/api.models.ts +90 -0
- tools/bolt.diy/app/routes/api.netlify-deploy.ts +240 -0
- tools/bolt.diy/app/routes/api.netlify-user.ts +142 -0
- tools/bolt.diy/app/routes/api.supabase-user.ts +199 -0
- tools/bolt.diy/app/routes/api.supabase.query.ts +92 -0
- tools/bolt.diy/app/routes/api.supabase.ts +56 -0
- tools/bolt.diy/app/routes/api.supabase.variables.ts +32 -0
- tools/bolt.diy/app/routes/api.system.diagnostics.ts +142 -0
- tools/bolt.diy/app/routes/api.system.disk-info.ts +311 -0
- tools/bolt.diy/app/routes/api.system.git-info.ts +332 -0
- tools/bolt.diy/app/routes/api.update.ts +21 -0
- tools/bolt.diy/app/routes/api.vercel-deploy.ts +497 -0
- tools/bolt.diy/app/routes/api.vercel-user.ts +161 -0
- tools/bolt.diy/app/routes/api.workflow-status.$proposalId.ts +309 -0
- tools/bolt.diy/app/routes/chat.$id.tsx +8 -0
- tools/bolt.diy/app/routes/execute.$mandateId.tsx +432 -0
- tools/bolt.diy/app/routes/git.tsx +25 -0
- tools/bolt.diy/app/routes/observability.$mandateId.tsx +50 -0
- tools/bolt.diy/app/routes/webcontainer.connect.$id.tsx +32 -0
- tools/bolt.diy/app/routes/webcontainer.preview.$id.tsx +97 -0
- tools/bolt.diy/app/routes/workflow.$proposalId.tsx +170 -0
- tools/bolt.diy/app/styles/animations.scss +49 -0
- tools/bolt.diy/app/styles/components/code.scss +9 -0
- tools/bolt.diy/app/styles/components/editor.scss +135 -0
- tools/bolt.diy/app/styles/components/resize-handle.scss +30 -0
- tools/bolt.diy/app/styles/components/terminal.scss +3 -0
- tools/bolt.diy/app/styles/components/toast.scss +23 -0
- tools/bolt.diy/app/styles/diff-view.css +72 -0
- tools/bolt.diy/app/styles/index.scss +73 -0
- tools/bolt.diy/app/styles/variables.scss +255 -0
- tools/bolt.diy/app/styles/z-index.scss +37 -0
- tools/bolt.diy/app/types/GitHub.ts +182 -0
- tools/bolt.diy/app/types/GitLab.ts +103 -0
- tools/bolt.diy/app/types/actions.ts +85 -0
- tools/bolt.diy/app/types/artifact.ts +5 -0
- tools/bolt.diy/app/types/context.ts +26 -0
- tools/bolt.diy/app/types/design-scheme.ts +93 -0
- tools/bolt.diy/app/types/global.d.ts +13 -0
- tools/bolt.diy/app/types/mandate.ts +333 -0
- tools/bolt.diy/app/types/model.ts +25 -0
- tools/bolt.diy/app/types/netlify.ts +94 -0
- tools/bolt.diy/app/types/supabase.ts +54 -0
- tools/bolt.diy/app/types/template.ts +8 -0
- tools/bolt.diy/app/types/terminal.ts +9 -0
- tools/bolt.diy/app/types/theme.ts +1 -0
- tools/bolt.diy/app/types/vercel.ts +67 -0
- tools/bolt.diy/app/utils/buffer.ts +29 -0
- tools/bolt.diy/app/utils/classNames.ts +65 -0
- tools/bolt.diy/app/utils/constants.ts +147 -0
- tools/bolt.diy/app/utils/debounce.ts +13 -0
- tools/bolt.diy/app/utils/debugLogger.ts +1284 -0
- tools/bolt.diy/app/utils/diff.spec.ts +11 -0
- tools/bolt.diy/app/utils/diff.ts +117 -0
- tools/bolt.diy/app/utils/easings.ts +3 -0
- tools/bolt.diy/app/utils/fileLocks.ts +96 -0
- tools/bolt.diy/app/utils/fileUtils.ts +121 -0
- tools/bolt.diy/app/utils/folderImport.ts +73 -0
- tools/bolt.diy/app/utils/formatSize.ts +12 -0
- tools/bolt.diy/app/utils/getLanguageFromExtension.ts +24 -0
- tools/bolt.diy/app/utils/githubStats.ts +9 -0
- tools/bolt.diy/app/utils/gitlabStats.ts +54 -0
- tools/bolt.diy/app/utils/logger.ts +162 -0
- tools/bolt.diy/app/utils/markdown.ts +155 -0
- tools/bolt.diy/app/utils/mobile.ts +4 -0
- tools/bolt.diy/app/utils/os.ts +4 -0
- tools/bolt.diy/app/utils/path.ts +19 -0
- tools/bolt.diy/app/utils/projectCommands.ts +197 -0
- tools/bolt.diy/app/utils/promises.ts +19 -0
- tools/bolt.diy/app/utils/react.ts +6 -0
- tools/bolt.diy/app/utils/sampler.ts +49 -0
- tools/bolt.diy/app/utils/selectStarterTemplate.ts +255 -0
- tools/bolt.diy/app/utils/shell.ts +384 -0
- tools/bolt.diy/app/utils/stacktrace.ts +27 -0
- tools/bolt.diy/app/utils/stripIndent.ts +23 -0
- tools/bolt.diy/app/utils/terminal.ts +11 -0
- tools/bolt.diy/app/utils/unreachable.ts +3 -0
- tools/bolt.diy/app/vite-env.d.ts +2 -0
- tools/bolt.diy/assets/entitlements.mac.plist +25 -0
- tools/bolt.diy/assets/icons/icon.icns +0 -0
- tools/bolt.diy/assets/icons/icon.ico +0 -0
- tools/bolt.diy/assets/icons/icon.png +0 -0
- tools/bolt.diy/bindings.js +78 -0
- tools/bolt.diy/bindings.sh +33 -0
- tools/bolt.diy/docker-compose.yaml +145 -0
- tools/bolt.diy/electron/main/index.ts +201 -0
- tools/bolt.diy/electron/main/tsconfig.json +30 -0
- tools/bolt.diy/electron/main/ui/menu.ts +29 -0
- tools/bolt.diy/electron/main/ui/window.ts +54 -0
- tools/bolt.diy/electron/main/utils/auto-update.ts +110 -0
- tools/bolt.diy/electron/main/utils/constants.ts +4 -0
- tools/bolt.diy/electron/main/utils/cookie.ts +40 -0
- tools/bolt.diy/electron/main/utils/reload.ts +35 -0
- tools/bolt.diy/electron/main/utils/serve.ts +71 -0
- tools/bolt.diy/electron/main/utils/store.ts +3 -0
- tools/bolt.diy/electron/main/utils/vite-server.ts +44 -0
- tools/bolt.diy/electron/main/vite.config.ts +44 -0
- tools/bolt.diy/electron/preload/index.ts +22 -0
- tools/bolt.diy/electron/preload/tsconfig.json +7 -0
- tools/bolt.diy/electron/preload/vite.config.ts +31 -0
- tools/bolt.diy/electron-builder.yml +64 -0
- tools/bolt.diy/electron-update.yml +4 -0
- tools/bolt.diy/eslint.config.mjs +57 -0
- tools/bolt.diy/functions/[[path]].ts +12 -0
- tools/bolt.diy/icons/angular.svg +1 -0
- tools/bolt.diy/icons/astro.svg +8 -0
- tools/bolt.diy/icons/chat.svg +1 -0
- tools/bolt.diy/icons/expo-brand.svg +1 -0
- tools/bolt.diy/icons/expo.svg +4 -0
- tools/bolt.diy/icons/logo-text.svg +1 -0
- tools/bolt.diy/icons/logo.svg +4 -0
- tools/bolt.diy/icons/mcp.svg +1 -0
- tools/bolt.diy/icons/nativescript.svg +1 -0
- tools/bolt.diy/icons/netlify.svg +10 -0
- tools/bolt.diy/icons/nextjs.svg +1 -0
- tools/bolt.diy/icons/nuxt.svg +1 -0
- tools/bolt.diy/icons/qwik.svg +1 -0
- tools/bolt.diy/icons/react.svg +1 -0
- tools/bolt.diy/icons/remix.svg +24 -0
- tools/bolt.diy/icons/remotion.svg +1 -0
- tools/bolt.diy/icons/shadcn.svg +21 -0
- tools/bolt.diy/icons/slidev.svg +60 -0
- tools/bolt.diy/icons/solidjs.svg +1 -0
- tools/bolt.diy/icons/stars.svg +1 -0
- tools/bolt.diy/icons/svelte.svg +1 -0
- tools/bolt.diy/icons/typescript.svg +1 -0
- tools/bolt.diy/icons/vite.svg +1 -0
- tools/bolt.diy/icons/vue.svg +1 -0
- tools/bolt.diy/load-context.ts +9 -0
- tools/bolt.diy/notarize.cjs +31 -0
- tools/bolt.diy/package.json +218 -0
- tools/bolt.diy/playwright.config.preview.ts +35 -0
- tools/bolt.diy/pre-start.cjs +26 -0
- tools/bolt.diy/public/apple-touch-icon-precomposed.png +0 -0
- tools/bolt.diy/public/apple-touch-icon.png +0 -0
- tools/bolt.diy/public/favicon.ico +0 -0
- tools/bolt.diy/public/favicon.svg +4 -0
- tools/bolt.diy/public/icons/AmazonBedrock.svg +1 -0
- tools/bolt.diy/public/icons/Anthropic.svg +4 -0
- tools/bolt.diy/public/icons/Cohere.svg +4 -0
- tools/bolt.diy/public/icons/Deepseek.svg +5 -0
- tools/bolt.diy/public/icons/Default.svg +4 -0
- tools/bolt.diy/public/icons/Google.svg +4 -0
- tools/bolt.diy/public/icons/Groq.svg +4 -0
- tools/bolt.diy/public/icons/HuggingFace.svg +4 -0
- tools/bolt.diy/public/icons/Hyperbolic.svg +3 -0
- tools/bolt.diy/public/icons/LMStudio.svg +5 -0
- tools/bolt.diy/public/icons/Mistral.svg +4 -0
- tools/bolt.diy/public/icons/Ollama.svg +4 -0
- tools/bolt.diy/public/icons/OpenAI.svg +4 -0
- tools/bolt.diy/public/icons/OpenAILike.svg +4 -0
- tools/bolt.diy/public/icons/OpenRouter.svg +4 -0
- tools/bolt.diy/public/icons/Perplexity.svg +4 -0
- tools/bolt.diy/public/icons/Together.svg +4 -0
- tools/bolt.diy/public/icons/xAI.svg +5 -0
- tools/bolt.diy/public/inspector-script.js +292 -0
- tools/bolt.diy/public/logo-dark-styled.png +0 -0
- tools/bolt.diy/public/logo-dark.png +0 -0
- tools/bolt.diy/public/logo-light-styled.png +0 -0
- tools/bolt.diy/public/logo-light.png +0 -0
- tools/bolt.diy/public/logo.svg +15 -0
- tools/bolt.diy/public/social_preview_index.jpg +0 -0
- tools/bolt.diy/scripts/clean.js +45 -0
- tools/bolt.diy/scripts/electron-dev.mjs +181 -0
- tools/bolt.diy/scripts/setup-env.sh +41 -0
- tools/bolt.diy/scripts/update-imports.sh +7 -0
- tools/bolt.diy/scripts/update.sh +52 -0
- tools/bolt.diy/services/execution-governor/Dockerfile +41 -0
- tools/bolt.diy/services/execution-governor/config.ts +42 -0
- tools/bolt.diy/services/execution-governor/index.ts +683 -0
- tools/bolt.diy/services/execution-governor/metrics.ts +141 -0
- tools/bolt.diy/services/execution-governor/package.json +31 -0
- tools/bolt.diy/services/execution-governor/priority-queue.ts +139 -0
- tools/bolt.diy/services/execution-governor/tsconfig.json +21 -0
- tools/bolt.diy/services/execution-governor/types.ts +145 -0
- tools/bolt.diy/services/headless-executor/Dockerfile +43 -0
- tools/bolt.diy/services/headless-executor/executor.ts +210 -0
- tools/bolt.diy/services/headless-executor/index.ts +323 -0
- tools/bolt.diy/services/headless-executor/package.json +27 -0
- tools/bolt.diy/services/headless-executor/tsconfig.json +21 -0
- tools/bolt.diy/services/headless-executor/types.ts +38 -0
- tools/bolt.diy/test-workflows.sh +240 -0
- tools/bolt.diy/tests/integration/corporate-swarm.test.ts +208 -0
- tools/bolt.diy/tests/mandates/budget-limited.json +34 -0
- tools/bolt.diy/tests/mandates/complex.json +53 -0
- tools/bolt.diy/tests/mandates/constraint-enforced.json +36 -0
- tools/bolt.diy/tests/mandates/simple.json +35 -0
- tools/bolt.diy/tsconfig.json +37 -0
- tools/bolt.diy/types/istextorbinary.d.ts +15 -0
- tools/bolt.diy/uno.config.ts +279 -0
- tools/bolt.diy/vite-electron.config.ts +76 -0
- tools/bolt.diy/vite.config.ts +112 -0
- tools/bolt.diy/worker-configuration.d.ts +22 -0
- tools/bolt.diy/wrangler.toml +6 -0
- tools/code_generator.py +461 -0
- tools/file_operations.py +465 -0
- tools/mandate_generator.py +337 -0
- tools/mcpClientUtils.py +1216 -0
- tools/sensors.py +285 -0
- utils/Agent_types.py +15 -0
- utils/AnyToStr.py +0 -0
- utils/HHCS.py +277 -0
- utils/__init__.py +30 -0
- utils/agent.py +3627 -0
- utils/aop.py +2948 -0
- utils/canonical.py +143 -0
- utils/conversation.py +1195 -0
- utils/doctrine_versioning +230 -0
- utils/formatter.py +474 -0
- utils/ledger.py +311 -0
- utils/out_types.py +16 -0
- utils/rollback.py +339 -0
- utils/router.py +929 -0
- utils/tui.py +1908 -0
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CRCA-SD MPC: Model-Predictive Control, Objectives, Scenarios, Stability, Estimation
|
|
3
|
+
|
|
4
|
+
This module implements:
|
|
5
|
+
- Multi-objective optimization with CVaR risk
|
|
6
|
+
- Rolling horizon MPC solver
|
|
7
|
+
- Scenario generation (Gaussian, Student-t, structured shocks)
|
|
8
|
+
- Stability enforcement (rate limits, smoothing)
|
|
9
|
+
- Pareto frontier extraction
|
|
10
|
+
- State estimation (EKF/UKF) for partial observability
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Dict, List, Optional, Tuple, Any, Union, Callable
|
|
14
|
+
import numpy as np
|
|
15
|
+
import time
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from loguru import logger
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
import cvxpy as cp
|
|
21
|
+
CVXPY_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
CVXPY_AVAILABLE = False
|
|
24
|
+
logger.warning("cvxpy not available, MPC will use scipy.optimize fallback")
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from scipy.optimize import minimize
|
|
28
|
+
from scipy.stats import t as student_t
|
|
29
|
+
SCIPY_AVAILABLE = True
|
|
30
|
+
except ImportError:
|
|
31
|
+
SCIPY_AVAILABLE = False
|
|
32
|
+
logger.warning("scipy not available, some features will be limited")
|
|
33
|
+
|
|
34
|
+
from crca_sd.crca_sd_core import StateVector, ControlVector, DynamicsModel, ConstraintChecker
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ObjectiveVector:
|
|
38
|
+
"""
|
|
39
|
+
Multi-objective cost vector.
|
|
40
|
+
|
|
41
|
+
Computes objective vector J = [J_U, J_ℓ, J_Y, J_ineq, J_C, J_risk]:
|
|
42
|
+
- J_U: Sum of unemployment over horizon
|
|
43
|
+
- J_ℓ: Negative sum of literacy (maximize literacy)
|
|
44
|
+
- J_Y: Negative sum of output (maximize output)
|
|
45
|
+
- J_ineq: Inequality measure (placeholder)
|
|
46
|
+
- J_C: Sum of ecological damage increase
|
|
47
|
+
- J_risk: CVaR on collapse proxy
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
horizon: Planning horizon for objective computation
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, horizon: int = 10) -> None:
|
|
54
|
+
"""Initialize objective vector computer."""
|
|
55
|
+
self.horizon = horizon
|
|
56
|
+
|
|
57
|
+
def compute(
|
|
58
|
+
self,
|
|
59
|
+
x_trajectory: List[StateVector],
|
|
60
|
+
u_trajectory: List[ControlVector]
|
|
61
|
+
) -> np.ndarray:
|
|
62
|
+
"""
|
|
63
|
+
Compute all objectives from trajectory.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
x_trajectory: List of state vectors
|
|
67
|
+
u_trajectory: List of control vectors
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
np.ndarray: Objective vector [J_U, J_ℓ, J_Y, J_ineq, J_C, J_risk]
|
|
71
|
+
"""
|
|
72
|
+
if len(x_trajectory) < 2:
|
|
73
|
+
return np.zeros(6)
|
|
74
|
+
|
|
75
|
+
# J_U: Sum of unemployment
|
|
76
|
+
J_U = sum(x.U for x in x_trajectory[1:])
|
|
77
|
+
|
|
78
|
+
# J_ℓ: Negative sum of literacy (maximize literacy = minimize negative)
|
|
79
|
+
J_ℓ = -sum(x.literacy for x in x_trajectory[1:])
|
|
80
|
+
|
|
81
|
+
# J_Y: Negative sum of output (maximize output = minimize negative)
|
|
82
|
+
J_Y = -sum(x.Y for x in x_trajectory[1:])
|
|
83
|
+
|
|
84
|
+
# J_ineq: Inequality measure (simplified: wage variance proxy)
|
|
85
|
+
wages = [x.W for x in x_trajectory[1:]]
|
|
86
|
+
if len(wages) > 1:
|
|
87
|
+
J_ineq = np.std(wages) # Higher variance = more inequality
|
|
88
|
+
else:
|
|
89
|
+
J_ineq = 0.0
|
|
90
|
+
|
|
91
|
+
# J_C: Sum of ecological damage increase
|
|
92
|
+
if len(x_trajectory) > 1:
|
|
93
|
+
C_initial = x_trajectory[0].C
|
|
94
|
+
C_final = x_trajectory[-1].C
|
|
95
|
+
J_C = C_final - C_initial
|
|
96
|
+
else:
|
|
97
|
+
J_C = 0.0
|
|
98
|
+
|
|
99
|
+
# J_risk: CVaR on collapse proxy (computed separately)
|
|
100
|
+
collapse_proxy = self._compute_collapse_proxy(x_trajectory)
|
|
101
|
+
J_risk = collapse_proxy # Will be replaced by actual CVaR in aggregate
|
|
102
|
+
|
|
103
|
+
return np.array([J_U, J_ℓ, J_Y, J_ineq, J_C, J_risk])
|
|
104
|
+
|
|
105
|
+
def aggregate(
|
|
106
|
+
self,
|
|
107
|
+
scenarios: List[Tuple[List[StateVector], List[ControlVector]]],
|
|
108
|
+
weights: Optional[np.ndarray] = None
|
|
109
|
+
) -> np.ndarray:
|
|
110
|
+
"""
|
|
111
|
+
Aggregate objectives across scenarios.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
scenarios: List of (trajectory, controls) tuples
|
|
115
|
+
weights: Scenario weights (default: uniform)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
np.ndarray: Expected objective vector
|
|
119
|
+
"""
|
|
120
|
+
if not scenarios:
|
|
121
|
+
return np.zeros(6)
|
|
122
|
+
|
|
123
|
+
if weights is None:
|
|
124
|
+
weights = np.ones(len(scenarios)) / len(scenarios)
|
|
125
|
+
|
|
126
|
+
objective_vectors = []
|
|
127
|
+
for traj, controls in scenarios:
|
|
128
|
+
obj = self.compute(traj, controls)
|
|
129
|
+
objective_vectors.append(obj)
|
|
130
|
+
|
|
131
|
+
objective_matrix = np.array(objective_vectors)
|
|
132
|
+
expected_objectives = np.average(objective_matrix, axis=0, weights=weights)
|
|
133
|
+
|
|
134
|
+
return expected_objectives
|
|
135
|
+
|
|
136
|
+
def _compute_collapse_proxy(self, trajectory: List[StateVector]) -> float:
|
|
137
|
+
"""
|
|
138
|
+
Compute collapse proxy (badness metric).
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
trajectory: State trajectory
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
float: Collapse proxy (higher = worse)
|
|
145
|
+
"""
|
|
146
|
+
if not trajectory:
|
|
147
|
+
return 0.0
|
|
148
|
+
|
|
149
|
+
# Collapse indicators
|
|
150
|
+
high_unemployment = sum(1 for x in trajectory if x.U > 0.15)
|
|
151
|
+
low_stability = sum(1 for x in trajectory if x.S < 0.4)
|
|
152
|
+
food_crisis = sum(1 for x in trajectory if x.F_stock < x.P * 0.01)
|
|
153
|
+
energy_crisis = sum(1 for x in trajectory if x.E_stock < 1000.0)
|
|
154
|
+
|
|
155
|
+
collapse_score = (
|
|
156
|
+
high_unemployment * 0.3 +
|
|
157
|
+
low_stability * 0.3 +
|
|
158
|
+
food_crisis * 0.2 +
|
|
159
|
+
energy_crisis * 0.2
|
|
160
|
+
) / len(trajectory)
|
|
161
|
+
|
|
162
|
+
return collapse_score
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class CVaRComputer:
|
|
166
|
+
"""
|
|
167
|
+
Conditional Value-at-Risk (CVaR) computer.
|
|
168
|
+
|
|
169
|
+
CVaR measures expected loss in worst-case scenarios (tail risk).
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
alpha: Confidence level (default: 0.05, i.e., worst 5%)
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def __init__(self, alpha: float = 0.05) -> None:
|
|
176
|
+
"""Initialize CVaR computer."""
|
|
177
|
+
if not (0 < alpha < 1):
|
|
178
|
+
raise ValueError(f"Alpha must be in (0, 1), got {alpha}")
|
|
179
|
+
self.alpha = alpha
|
|
180
|
+
|
|
181
|
+
def compute_cvar(self, z_scores: np.ndarray) -> float:
|
|
182
|
+
"""
|
|
183
|
+
Compute CVaR from z-scores (badness values).
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
z_scores: Array of badness scores (higher = worse)
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
float: CVaR value
|
|
190
|
+
"""
|
|
191
|
+
if len(z_scores) == 0:
|
|
192
|
+
return 0.0
|
|
193
|
+
|
|
194
|
+
# Sort in descending order (worst first)
|
|
195
|
+
sorted_scores = np.sort(z_scores)[::-1]
|
|
196
|
+
|
|
197
|
+
# Number of worst-case scenarios
|
|
198
|
+
n_worst = max(1, int(np.ceil(len(z_scores) * self.alpha)))
|
|
199
|
+
|
|
200
|
+
# CVaR = average of worst scenarios
|
|
201
|
+
cvar = np.mean(sorted_scores[:n_worst])
|
|
202
|
+
|
|
203
|
+
return float(cvar)
|
|
204
|
+
|
|
205
|
+
def collapse_proxy(self, x_trajectory: List[StateVector]) -> float:
|
|
206
|
+
"""
|
|
207
|
+
Compute collapse proxy from trajectory.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
x_trajectory: State trajectory
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
float: Collapse proxy (badness metric)
|
|
214
|
+
"""
|
|
215
|
+
if not x_trajectory:
|
|
216
|
+
return 0.0
|
|
217
|
+
|
|
218
|
+
# Use same logic as ObjectiveVector
|
|
219
|
+
high_unemployment = sum(1 for x in x_trajectory if x.U > 0.15)
|
|
220
|
+
low_stability = sum(1 for x in x_trajectory if x.S < 0.4)
|
|
221
|
+
food_crisis = sum(1 for x in x_trajectory if x.F_stock < x.P * 0.01)
|
|
222
|
+
energy_crisis = sum(1 for x in x_trajectory if x.E_stock < 1000.0)
|
|
223
|
+
|
|
224
|
+
collapse_score = (
|
|
225
|
+
high_unemployment * 0.3 +
|
|
226
|
+
low_stability * 0.3 +
|
|
227
|
+
food_crisis * 0.2 +
|
|
228
|
+
energy_crisis * 0.2
|
|
229
|
+
) / len(x_trajectory)
|
|
230
|
+
|
|
231
|
+
return collapse_score
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class ScenarioGenerator:
|
|
235
|
+
"""
|
|
236
|
+
Scenario generator for disturbance sequences.
|
|
237
|
+
|
|
238
|
+
Generates:
|
|
239
|
+
- Gaussian noise for benign uncertainty
|
|
240
|
+
- Student-t for heavy-tailed disasters
|
|
241
|
+
- Structured events (trade embargo, drought, etc.)
|
|
242
|
+
- Causal scenarios via CRCA (if available)
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
rng: Random number generator (default: new generator)
|
|
246
|
+
crca_agent: Optional CRCAAgent for causal scenario generation
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def __init__(
|
|
250
|
+
self,
|
|
251
|
+
rng: Optional[np.random.Generator] = None,
|
|
252
|
+
crca_agent: Optional[Any] = None
|
|
253
|
+
) -> None:
|
|
254
|
+
"""Initialize scenario generator."""
|
|
255
|
+
if rng is None:
|
|
256
|
+
rng = np.random.default_rng()
|
|
257
|
+
self.rng = rng
|
|
258
|
+
self.crca_agent = crca_agent
|
|
259
|
+
|
|
260
|
+
def generate_gaussian(
|
|
261
|
+
self,
|
|
262
|
+
n_scenarios: int,
|
|
263
|
+
horizon: int,
|
|
264
|
+
mean: Optional[Dict[str, float]] = None,
|
|
265
|
+
cov: Optional[np.ndarray] = None
|
|
266
|
+
) -> List[List[Dict[str, float]]]:
|
|
267
|
+
"""
|
|
268
|
+
Generate Gaussian disturbance scenarios.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
n_scenarios: Number of scenarios
|
|
272
|
+
horizon: Time horizon
|
|
273
|
+
mean: Mean disturbance vector (default: zeros)
|
|
274
|
+
cov: Covariance matrix (default: identity)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
List[List[Dict[str, float]]]: List of scenario sequences
|
|
278
|
+
"""
|
|
279
|
+
if mean is None:
|
|
280
|
+
mean = {
|
|
281
|
+
"demand_shock": 0.0,
|
|
282
|
+
"trade_shock": 0.0,
|
|
283
|
+
"productivity_shock": 1.0,
|
|
284
|
+
"disaster_shock": 0.0,
|
|
285
|
+
"labor_shock": 0.0,
|
|
286
|
+
"unemployment_shock": 0.0,
|
|
287
|
+
"energy_import": 0.0,
|
|
288
|
+
"food_import": 0.0,
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
# Default covariance (diagonal, small)
|
|
292
|
+
if cov is None:
|
|
293
|
+
n_vars = len(mean)
|
|
294
|
+
cov = np.eye(n_vars) * 0.01
|
|
295
|
+
|
|
296
|
+
scenarios = []
|
|
297
|
+
for _ in range(n_scenarios):
|
|
298
|
+
scenario = []
|
|
299
|
+
for _ in range(horizon):
|
|
300
|
+
# Sample from multivariate Gaussian
|
|
301
|
+
disturbance_vec = self.rng.multivariate_normal(
|
|
302
|
+
mean=list(mean.values()),
|
|
303
|
+
cov=cov
|
|
304
|
+
)
|
|
305
|
+
disturbance = {
|
|
306
|
+
key: float(val)
|
|
307
|
+
for key, val in zip(mean.keys(), disturbance_vec)
|
|
308
|
+
}
|
|
309
|
+
scenario.append(disturbance)
|
|
310
|
+
scenarios.append(scenario)
|
|
311
|
+
|
|
312
|
+
return scenarios
|
|
313
|
+
|
|
314
|
+
def generate_student_t(
|
|
315
|
+
self,
|
|
316
|
+
n_scenarios: int,
|
|
317
|
+
horizon: int,
|
|
318
|
+
df: float = 3.0, # Degrees of freedom (lower = heavier tails)
|
|
319
|
+
scale: float = 0.1
|
|
320
|
+
) -> List[List[Dict[str, float]]]:
|
|
321
|
+
"""
|
|
322
|
+
Generate Student-t disturbance scenarios (heavy-tailed).
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
n_scenarios: Number of scenarios
|
|
326
|
+
horizon: Time horizon
|
|
327
|
+
df: Degrees of freedom (default: 3.0 for heavy tails)
|
|
328
|
+
scale: Scale parameter
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
List[List[Dict[str, float]]]: List of scenario sequences
|
|
332
|
+
"""
|
|
333
|
+
if not SCIPY_AVAILABLE:
|
|
334
|
+
logger.warning("scipy not available, using Gaussian fallback")
|
|
335
|
+
return self.generate_gaussian(n_scenarios, horizon)
|
|
336
|
+
|
|
337
|
+
scenarios = []
|
|
338
|
+
for _ in range(n_scenarios):
|
|
339
|
+
scenario = []
|
|
340
|
+
for _ in range(horizon):
|
|
341
|
+
# Sample from Student-t
|
|
342
|
+
disturbance = {
|
|
343
|
+
"demand_shock": float(student_t.rvs(df, scale=scale, random_state=self.rng)),
|
|
344
|
+
"trade_shock": float(student_t.rvs(df, scale=scale, random_state=self.rng)),
|
|
345
|
+
"productivity_shock": 1.0 + float(student_t.rvs(df, scale=scale * 0.1, random_state=self.rng)),
|
|
346
|
+
"disaster_shock": float(student_t.rvs(df, scale=scale, random_state=self.rng)),
|
|
347
|
+
"labor_shock": float(student_t.rvs(df, scale=scale * 0.1, random_state=self.rng)),
|
|
348
|
+
"unemployment_shock": float(student_t.rvs(df, scale=scale * 0.1, random_state=self.rng)),
|
|
349
|
+
"energy_import": float(student_t.rvs(df, scale=scale * 100, random_state=self.rng)),
|
|
350
|
+
"food_import": float(student_t.rvs(df, scale=scale * 100, random_state=self.rng)),
|
|
351
|
+
}
|
|
352
|
+
scenario.append(disturbance)
|
|
353
|
+
scenarios.append(scenario)
|
|
354
|
+
|
|
355
|
+
return scenarios
|
|
356
|
+
|
|
357
|
+
def generate_structured_shock(
|
|
358
|
+
self,
|
|
359
|
+
event_type: str,
|
|
360
|
+
magnitude: float,
|
|
361
|
+
timing: int,
|
|
362
|
+
horizon: int
|
|
363
|
+
) -> List[Dict[str, float]]:
|
|
364
|
+
"""
|
|
365
|
+
Generate structured shock event (trade embargo, drought, etc.).
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
event_type: Type of shock ("trade_embargo", "drought", "productivity_crash")
|
|
369
|
+
magnitude: Shock magnitude
|
|
370
|
+
timing: Time step when shock occurs
|
|
371
|
+
horizon: Time horizon
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
List[Dict[str, float]]: Single scenario sequence
|
|
375
|
+
"""
|
|
376
|
+
scenario = [{}] * horizon
|
|
377
|
+
|
|
378
|
+
if event_type == "trade_embargo":
|
|
379
|
+
# Complete trade cutoff at timing
|
|
380
|
+
for t in range(timing, horizon):
|
|
381
|
+
scenario[t] = {
|
|
382
|
+
"trade_shock": 1.0, # Complete cutoff
|
|
383
|
+
"energy_import": -magnitude * 1000,
|
|
384
|
+
"food_import": -magnitude * 500,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
elif event_type == "drought":
|
|
388
|
+
# Food production crash
|
|
389
|
+
for t in range(timing, min(timing + 5, horizon)):
|
|
390
|
+
scenario[t] = {
|
|
391
|
+
"disaster_shock": magnitude,
|
|
392
|
+
"food_import": -magnitude * 300,
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
elif event_type == "productivity_crash":
|
|
396
|
+
# Productivity shock
|
|
397
|
+
for t in range(timing, min(timing + 3, horizon)):
|
|
398
|
+
scenario[t] = {
|
|
399
|
+
"productivity_shock": 1.0 - magnitude, # Reduce productivity
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
else:
|
|
403
|
+
logger.warning(f"Unknown event type: {event_type}, using generic shock")
|
|
404
|
+
if timing < horizon:
|
|
405
|
+
scenario[timing] = {"disaster_shock": magnitude}
|
|
406
|
+
|
|
407
|
+
return scenario
|
|
408
|
+
|
|
409
|
+
def generate_causal_scenarios(
|
|
410
|
+
self,
|
|
411
|
+
n_scenarios: int,
|
|
412
|
+
horizon: int,
|
|
413
|
+
current_state: Optional[StateVector] = None,
|
|
414
|
+
target_variables: Optional[List[str]] = None
|
|
415
|
+
) -> List[List[Dict[str, float]]]:
|
|
416
|
+
"""
|
|
417
|
+
Generate scenarios using CRCA causal reasoning (if available).
|
|
418
|
+
|
|
419
|
+
Uses CRCAAgent to generate counterfactual scenarios based on causal relationships.
|
|
420
|
+
Falls back to Gaussian if CRCA not available.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
n_scenarios: Number of scenarios
|
|
424
|
+
horizon: Time horizon
|
|
425
|
+
current_state: Current state vector (for counterfactual analysis)
|
|
426
|
+
target_variables: Variables to focus on (default: ["Y", "U", "S"])
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
List[List[Dict[str, float]]]: Causal scenarios
|
|
430
|
+
"""
|
|
431
|
+
if self.crca_agent is None or current_state is None:
|
|
432
|
+
# Fallback to Gaussian
|
|
433
|
+
logger.debug("CRCA not available or no state provided, using Gaussian scenarios")
|
|
434
|
+
return self.generate_gaussian(n_scenarios, horizon)
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
# Convert state to dict for CRCA
|
|
438
|
+
state_dict = {
|
|
439
|
+
"P": current_state.P,
|
|
440
|
+
"L": current_state.L,
|
|
441
|
+
"U": current_state.U,
|
|
442
|
+
"W": current_state.W,
|
|
443
|
+
"S": current_state.S,
|
|
444
|
+
"Y": current_state.Y,
|
|
445
|
+
"K": current_state.K,
|
|
446
|
+
"I": current_state.I,
|
|
447
|
+
"literacy": current_state.literacy,
|
|
448
|
+
"Ecap": current_state.Ecap,
|
|
449
|
+
"Hcap": current_state.Hcap,
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if target_variables is None:
|
|
453
|
+
target_variables = ["Y", "U", "S"] # GDP, unemployment, stability
|
|
454
|
+
|
|
455
|
+
# Use CRCA to generate counterfactual scenarios
|
|
456
|
+
crca_result = self.crca_agent.run(
|
|
457
|
+
initial_state=state_dict,
|
|
458
|
+
target_variables=target_variables,
|
|
459
|
+
max_steps=horizon
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Extract counterfactual scenarios
|
|
463
|
+
counterfactuals = crca_result.get("counterfactual_scenarios", [])
|
|
464
|
+
|
|
465
|
+
if not counterfactuals:
|
|
466
|
+
logger.debug("No counterfactuals from CRCA, using Gaussian scenarios")
|
|
467
|
+
return self.generate_gaussian(n_scenarios, horizon)
|
|
468
|
+
|
|
469
|
+
# Convert counterfactuals to scenario format
|
|
470
|
+
scenarios = []
|
|
471
|
+
for cf in counterfactuals[:n_scenarios]:
|
|
472
|
+
scenario = []
|
|
473
|
+
interventions = cf.interventions
|
|
474
|
+
|
|
475
|
+
# Create scenario as list of disturbance dicts
|
|
476
|
+
for step in range(horizon):
|
|
477
|
+
# Disturbances based on counterfactual interventions
|
|
478
|
+
disturbances = {}
|
|
479
|
+
for var, value in interventions.items():
|
|
480
|
+
if var in state_dict:
|
|
481
|
+
# Convert intervention to disturbance
|
|
482
|
+
current_val = state_dict.get(var, 0.0)
|
|
483
|
+
# Scale down to reasonable disturbance magnitude
|
|
484
|
+
disturbances[var] = (value - current_val) * 0.1
|
|
485
|
+
|
|
486
|
+
scenario.append(disturbances)
|
|
487
|
+
|
|
488
|
+
scenarios.append(scenario)
|
|
489
|
+
|
|
490
|
+
# Pad to n_scenarios if needed
|
|
491
|
+
while len(scenarios) < n_scenarios:
|
|
492
|
+
scenarios.extend(self.generate_gaussian(1, horizon))
|
|
493
|
+
|
|
494
|
+
logger.debug(f"Generated {len(scenarios)} causal scenarios via CRCA")
|
|
495
|
+
return scenarios[:n_scenarios]
|
|
496
|
+
|
|
497
|
+
except Exception as e:
|
|
498
|
+
logger.warning(f"CRCA scenario generation failed: {e}, using Gaussian scenarios")
|
|
499
|
+
return self.generate_gaussian(n_scenarios, horizon)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class StabilityEnforcer:
|
|
503
|
+
"""
|
|
504
|
+
Stability enforcer to prevent oscillations and planner thrash.
|
|
505
|
+
|
|
506
|
+
Implements:
|
|
507
|
+
- Budget change limits: ||b_t - b_{t-1}||_1 <= Δ_b
|
|
508
|
+
- Investment smoothing: I_inv_t = β * I_inv_{t-1} + (1-β) * I_hat_t
|
|
509
|
+
- Logistics ramp constraints
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
max_budget_change: Maximum L1 norm change in budget shares (default: 0.2)
|
|
513
|
+
investment_smoothing: Smoothing factor β (default: 0.7)
|
|
514
|
+
max_flow_change: Maximum change in logistics flows (default: 0.3)
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
def __init__(
|
|
518
|
+
self,
|
|
519
|
+
max_budget_change: float = 0.2,
|
|
520
|
+
investment_smoothing: float = 0.7,
|
|
521
|
+
max_flow_change: float = 0.3,
|
|
522
|
+
) -> None:
|
|
523
|
+
"""Initialize stability enforcer."""
|
|
524
|
+
self.max_budget_change = max_budget_change
|
|
525
|
+
self.investment_smoothing = investment_smoothing
|
|
526
|
+
self.max_flow_change = max_flow_change
|
|
527
|
+
|
|
528
|
+
def apply_rate_limits(
|
|
529
|
+
self,
|
|
530
|
+
u_t: ControlVector,
|
|
531
|
+
u_prev: Optional[ControlVector],
|
|
532
|
+
limits: Optional[Dict[str, float]] = None
|
|
533
|
+
) -> ControlVector:
|
|
534
|
+
"""
|
|
535
|
+
Apply rate limits to control vector.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
u_t: Proposed control vector
|
|
539
|
+
u_prev: Previous control vector (None if first step)
|
|
540
|
+
limits: Custom limits (default: use instance defaults)
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
ControlVector: Adjusted control vector
|
|
544
|
+
"""
|
|
545
|
+
if u_prev is None:
|
|
546
|
+
return u_t
|
|
547
|
+
|
|
548
|
+
if limits is None:
|
|
549
|
+
max_change = self.max_budget_change
|
|
550
|
+
else:
|
|
551
|
+
max_change = limits.get("max_budget_change", self.max_budget_change)
|
|
552
|
+
|
|
553
|
+
# Compute L1 norm of budget change
|
|
554
|
+
budget_change = {}
|
|
555
|
+
for cat in set(list(u_t.budget_shares.keys()) + list(u_prev.budget_shares.keys())):
|
|
556
|
+
prev_val = u_prev.budget_shares.get(cat, 0.0)
|
|
557
|
+
curr_val = u_t.budget_shares.get(cat, 0.0)
|
|
558
|
+
budget_change[cat] = abs(curr_val - prev_val)
|
|
559
|
+
|
|
560
|
+
total_change = sum(budget_change.values())
|
|
561
|
+
|
|
562
|
+
if total_change > max_change:
|
|
563
|
+
# Scale down changes proportionally
|
|
564
|
+
scale = max_change / total_change
|
|
565
|
+
adjusted_shares = {}
|
|
566
|
+
for cat in u_t.budget_shares:
|
|
567
|
+
prev_val = u_prev.budget_shares.get(cat, 0.0)
|
|
568
|
+
curr_val = u_t.budget_shares[cat]
|
|
569
|
+
change = (curr_val - prev_val) * scale
|
|
570
|
+
adjusted_shares[cat] = prev_val + change
|
|
571
|
+
|
|
572
|
+
# Renormalize to ensure simplex
|
|
573
|
+
total = sum(adjusted_shares.values())
|
|
574
|
+
if total > 0:
|
|
575
|
+
adjusted_shares = {k: v / total for k, v in adjusted_shares.items()}
|
|
576
|
+
else:
|
|
577
|
+
adjusted_shares = u_prev.budget_shares.copy()
|
|
578
|
+
|
|
579
|
+
u_adjusted = ControlVector(
|
|
580
|
+
budget_shares=adjusted_shares,
|
|
581
|
+
allocations=u_t.allocations.copy(),
|
|
582
|
+
flows=u_t.flows.copy(),
|
|
583
|
+
)
|
|
584
|
+
return u_adjusted
|
|
585
|
+
|
|
586
|
+
return u_t
|
|
587
|
+
|
|
588
|
+
def smooth_investment(
|
|
589
|
+
self,
|
|
590
|
+
I_hat: float,
|
|
591
|
+
I_prev: float,
|
|
592
|
+
beta: Optional[float] = None
|
|
593
|
+
) -> float:
|
|
594
|
+
"""
|
|
595
|
+
Smooth investment using exponential moving average.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
I_hat: Proposed investment
|
|
599
|
+
I_prev: Previous investment
|
|
600
|
+
beta: Smoothing factor (default: use instance default)
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
float: Smoothed investment
|
|
604
|
+
"""
|
|
605
|
+
if beta is None:
|
|
606
|
+
beta = self.investment_smoothing
|
|
607
|
+
|
|
608
|
+
I_smooth = beta * I_prev + (1 - beta) * I_hat
|
|
609
|
+
return I_smooth
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
class ParetoExtractor:
|
|
613
|
+
"""
|
|
614
|
+
Pareto frontier extractor using non-dominated sorting.
|
|
615
|
+
|
|
616
|
+
Finds all Pareto-efficient policies (non-dominated solutions).
|
|
617
|
+
"""
|
|
618
|
+
|
|
619
|
+
@staticmethod
|
|
620
|
+
def extract_pareto_frontier(
|
|
621
|
+
candidate_policies: List[ControlVector],
|
|
622
|
+
objectives: np.ndarray
|
|
623
|
+
) -> Tuple[List[ControlVector], np.ndarray]:
|
|
624
|
+
"""
|
|
625
|
+
Extract Pareto-efficient policies.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
candidate_policies: List of candidate policies
|
|
629
|
+
objectives: Objective matrix (n_policies x n_objectives)
|
|
630
|
+
|
|
631
|
+
Returns:
|
|
632
|
+
Tuple[List[ControlVector], np.ndarray]: (pareto_policies, pareto_objectives)
|
|
633
|
+
"""
|
|
634
|
+
if len(candidate_policies) == 0:
|
|
635
|
+
return [], np.array([])
|
|
636
|
+
|
|
637
|
+
if objectives.ndim == 1:
|
|
638
|
+
objectives = objectives.reshape(1, -1)
|
|
639
|
+
|
|
640
|
+
n_policies = len(candidate_policies)
|
|
641
|
+
n_objectives = objectives.shape[1]
|
|
642
|
+
|
|
643
|
+
# Non-dominated sorting
|
|
644
|
+
is_pareto = np.ones(n_policies, dtype=bool)
|
|
645
|
+
|
|
646
|
+
for i in range(n_policies):
|
|
647
|
+
for j in range(n_policies):
|
|
648
|
+
if i == j:
|
|
649
|
+
continue
|
|
650
|
+
|
|
651
|
+
# Check if j dominates i
|
|
652
|
+
# j dominates i if: all objectives of j <= i, and at least one <
|
|
653
|
+
obj_i = objectives[i]
|
|
654
|
+
obj_j = objectives[j]
|
|
655
|
+
|
|
656
|
+
# For minimization: j dominates i if obj_j <= obj_i (element-wise)
|
|
657
|
+
# and at least one obj_j < obj_i
|
|
658
|
+
if np.all(obj_j <= obj_i) and np.any(obj_j < obj_i):
|
|
659
|
+
is_pareto[i] = False
|
|
660
|
+
break
|
|
661
|
+
|
|
662
|
+
pareto_indices = np.where(is_pareto)[0]
|
|
663
|
+
pareto_policies = [candidate_policies[i] for i in pareto_indices]
|
|
664
|
+
pareto_objectives = objectives[pareto_indices]
|
|
665
|
+
|
|
666
|
+
return pareto_policies, pareto_objectives
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
class MPCSolver:
|
|
670
|
+
"""
|
|
671
|
+
Model-Predictive Control (MPC) solver.
|
|
672
|
+
|
|
673
|
+
Solves rolling-horizon optimization problem:
|
|
674
|
+
minimize: weighted_sum(objectives)
|
|
675
|
+
subject to:
|
|
676
|
+
- dynamics constraints
|
|
677
|
+
- hard constraints
|
|
678
|
+
- rate limits (if stability enabled)
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
dynamics: Dynamics model
|
|
682
|
+
constraint_checker: Constraint checker
|
|
683
|
+
objective_computer: Objective vector computer
|
|
684
|
+
horizon: Planning horizon (default: 10)
|
|
685
|
+
stability_enforcer: Stability enforcer (optional)
|
|
686
|
+
"""
|
|
687
|
+
|
|
688
|
+
def __init__(
|
|
689
|
+
self,
|
|
690
|
+
dynamics: DynamicsModel,
|
|
691
|
+
constraint_checker: ConstraintChecker,
|
|
692
|
+
objective_computer: ObjectiveVector,
|
|
693
|
+
horizon: int = 10,
|
|
694
|
+
stability_enforcer: Optional[StabilityEnforcer] = None,
|
|
695
|
+
) -> None:
|
|
696
|
+
"""Initialize MPC solver."""
|
|
697
|
+
self.dynamics = dynamics
|
|
698
|
+
self.constraint_checker = constraint_checker
|
|
699
|
+
self.objective_computer = objective_computer
|
|
700
|
+
self.horizon = horizon
|
|
701
|
+
self.stability_enforcer = stability_enforcer
|
|
702
|
+
|
|
703
|
+
def solve(
|
|
704
|
+
self,
|
|
705
|
+
x_t: StateVector,
|
|
706
|
+
scenarios: List[List[Dict[str, float]]],
|
|
707
|
+
objective_weights: Optional[np.ndarray] = None,
|
|
708
|
+
u_prev: Optional[ControlVector] = None
|
|
709
|
+
) -> Tuple[ControlVector, Dict[str, Any]]:
|
|
710
|
+
"""
|
|
711
|
+
Solve MPC optimization problem.
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
x_t: Current state
|
|
715
|
+
scenarios: List of disturbance scenarios
|
|
716
|
+
objective_weights: Weights for objectives (default: equal)
|
|
717
|
+
u_prev: Previous control (for rate limits)
|
|
718
|
+
|
|
719
|
+
Returns:
|
|
720
|
+
Tuple[ControlVector, Dict[str, Any]]: (optimal_policy, solver_info)
|
|
721
|
+
"""
|
|
722
|
+
if objective_weights is None:
|
|
723
|
+
objective_weights = np.ones(6) / 6.0 # Equal weights
|
|
724
|
+
|
|
725
|
+
# Sample candidate policies
|
|
726
|
+
budget_categories = [
|
|
727
|
+
"energy", "food", "infrastructure", "education",
|
|
728
|
+
"healthcare", "R&D", "welfare"
|
|
729
|
+
]
|
|
730
|
+
|
|
731
|
+
n_candidates = 20
|
|
732
|
+
candidate_policies = [
|
|
733
|
+
ControlVector.sample_budget_simplex(budget_categories)
|
|
734
|
+
for _ in range(n_candidates)
|
|
735
|
+
]
|
|
736
|
+
|
|
737
|
+
# Evaluate candidates under scenarios
|
|
738
|
+
best_policy = None
|
|
739
|
+
best_score = float('inf')
|
|
740
|
+
candidate_scores = []
|
|
741
|
+
|
|
742
|
+
for policy in candidate_policies:
|
|
743
|
+
# Apply rate limits if enabled
|
|
744
|
+
if self.stability_enforcer and u_prev is not None:
|
|
745
|
+
policy = self.stability_enforcer.apply_rate_limits(policy, u_prev)
|
|
746
|
+
|
|
747
|
+
# Evaluate under all scenarios
|
|
748
|
+
scenario_objectives = []
|
|
749
|
+
for scenario in scenarios:
|
|
750
|
+
traj, _, _ = self._simulate_policy(x_t, policy, scenario)
|
|
751
|
+
obj = self.objective_computer.compute(traj, [policy] * len(traj[1:]))
|
|
752
|
+
scenario_objectives.append(obj)
|
|
753
|
+
|
|
754
|
+
# Aggregate objectives (expected value)
|
|
755
|
+
expected_obj = np.mean(scenario_objectives, axis=0)
|
|
756
|
+
|
|
757
|
+
# Weighted sum
|
|
758
|
+
score = np.dot(objective_weights, expected_obj)
|
|
759
|
+
candidate_scores.append(score)
|
|
760
|
+
|
|
761
|
+
# Check feasibility
|
|
762
|
+
traj, _, first_violation = self._simulate_policy(x_t, policy, scenarios[0])
|
|
763
|
+
if first_violation is None: # Feasible
|
|
764
|
+
if score < best_score:
|
|
765
|
+
best_score = score
|
|
766
|
+
best_policy = policy
|
|
767
|
+
|
|
768
|
+
if best_policy is None:
|
|
769
|
+
# Fallback: use first feasible or first candidate
|
|
770
|
+
logger.warning("No feasible policy found, using first candidate")
|
|
771
|
+
best_policy = candidate_policies[0]
|
|
772
|
+
|
|
773
|
+
solver_info = {
|
|
774
|
+
"best_score": best_score,
|
|
775
|
+
"n_candidates": n_candidates,
|
|
776
|
+
"n_scenarios": len(scenarios),
|
|
777
|
+
"feasible_found": best_policy is not None,
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return best_policy, solver_info
|
|
781
|
+
|
|
782
|
+
def _simulate_policy(
|
|
783
|
+
self,
|
|
784
|
+
x_0: StateVector,
|
|
785
|
+
policy: ControlVector,
|
|
786
|
+
disturbances: List[Dict[str, float]]
|
|
787
|
+
) -> Tuple[List[StateVector], List[bool], Optional[int]]:
|
|
788
|
+
"""Simulate policy forward."""
|
|
789
|
+
from crca_sd.crca_sd_core import ForwardSimulator
|
|
790
|
+
|
|
791
|
+
simulator = ForwardSimulator(self.dynamics, self.constraint_checker)
|
|
792
|
+
return simulator.simulate_scenario(x_0, policy, disturbances, self.horizon)
|
|
793
|
+
|
|
794
|
+
def execute_policy_realtime(
|
|
795
|
+
self,
|
|
796
|
+
policy: ControlVector,
|
|
797
|
+
government_api_config: Optional[Dict[str, Any]] = None,
|
|
798
|
+
execution_id: Optional[str] = None
|
|
799
|
+
) -> Tuple[bool, str, Dict[str, Any]]:
|
|
800
|
+
"""
|
|
801
|
+
Execute policy in real-time via government API integration.
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
policy: Policy to execute
|
|
805
|
+
government_api_config: Government API configuration
|
|
806
|
+
execution_id: Optional execution ID
|
|
807
|
+
|
|
808
|
+
Returns:
|
|
809
|
+
Tuple[bool, str, Dict[str, Any]]: (success, message, execution_info)
|
|
810
|
+
"""
|
|
811
|
+
# Import PolicyExecutor from realtime module
|
|
812
|
+
try:
|
|
813
|
+
from crca_sd.crca_sd_realtime import PolicyExecutor
|
|
814
|
+
|
|
815
|
+
executor = PolicyExecutor(government_api_config)
|
|
816
|
+
success, message, info = executor.execute_policy(policy, execution_id)
|
|
817
|
+
|
|
818
|
+
logger.info(f"Policy execution {'succeeded' if success else 'failed'}: {message}")
|
|
819
|
+
|
|
820
|
+
return success, message, info
|
|
821
|
+
|
|
822
|
+
except ImportError:
|
|
823
|
+
logger.warning("PolicyExecutor not available, using mock execution")
|
|
824
|
+
return True, "Mock execution successful", {"execution_id": execution_id or "mock"}
|
|
825
|
+
|
|
826
|
+
def solve_and_execute(
|
|
827
|
+
self,
|
|
828
|
+
x_t: StateVector,
|
|
829
|
+
scenarios: List[List[Dict[str, float]]],
|
|
830
|
+
previous_policy: Optional[ControlVector] = None,
|
|
831
|
+
government_api_config: Optional[Dict[str, Any]] = None,
|
|
832
|
+
require_approval_check: Optional[Callable[[ControlVector, Optional[ControlVector]], Tuple[bool, str]]] = None
|
|
833
|
+
) -> Tuple[Optional[ControlVector], Dict[str, Any]]:
|
|
834
|
+
"""
|
|
835
|
+
Solve MPC and execute policy (with approval check if needed).
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
x_t: Current state
|
|
839
|
+
scenarios: Disturbance scenarios
|
|
840
|
+
previous_policy: Previous policy (for rate limits and approval check)
|
|
841
|
+
government_api_config: Government API configuration
|
|
842
|
+
require_approval_check: Function to check if approval is needed
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
Tuple[Optional[ControlVector], Dict[str, Any]]: (executed_policy, execution_info)
|
|
846
|
+
"""
|
|
847
|
+
# Solve MPC
|
|
848
|
+
policy, solver_info = self.solve(x_t, scenarios, u_prev=previous_policy)
|
|
849
|
+
|
|
850
|
+
# Check if approval is needed
|
|
851
|
+
requires_approval = False
|
|
852
|
+
if require_approval_check:
|
|
853
|
+
requires_approval, reason = require_approval_check(policy, previous_policy)
|
|
854
|
+
if requires_approval:
|
|
855
|
+
logger.info(f"Policy requires approval: {reason}")
|
|
856
|
+
return None, {"status": "pending_approval", "reason": reason, "policy": policy}
|
|
857
|
+
|
|
858
|
+
# Execute policy
|
|
859
|
+
success, message, exec_info = self.execute_policy_realtime(
|
|
860
|
+
policy,
|
|
861
|
+
government_api_config
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
execution_info = {
|
|
865
|
+
**solver_info,
|
|
866
|
+
**exec_info,
|
|
867
|
+
"requires_approval": requires_approval,
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return policy if success else None, execution_info
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
class StateEstimator:
|
|
874
|
+
"""
|
|
875
|
+
State estimator for partial observability (EKF/UKF).
|
|
876
|
+
|
|
877
|
+
Implements Extended Kalman Filter for state estimation with noisy observations.
|
|
878
|
+
Observation model: y_t = h(x_t) + ν_t
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
dynamics: Dynamics model
|
|
882
|
+
observation_noise_cov: Observation noise covariance matrix
|
|
883
|
+
process_noise_cov: Process noise covariance matrix
|
|
884
|
+
"""
|
|
885
|
+
|
|
886
|
+
def __init__(
|
|
887
|
+
self,
|
|
888
|
+
dynamics: DynamicsModel,
|
|
889
|
+
observation_noise_cov: Optional[np.ndarray] = None,
|
|
890
|
+
process_noise_cov: Optional[np.ndarray] = None,
|
|
891
|
+
update_frequency: float = 86400.0, # Daily updates
|
|
892
|
+
) -> None:
|
|
893
|
+
"""Initialize state estimator."""
|
|
894
|
+
self.dynamics = dynamics
|
|
895
|
+
|
|
896
|
+
# Default noise covariances (diagonal)
|
|
897
|
+
n_state = 16 # Number of state variables
|
|
898
|
+
if observation_noise_cov is None:
|
|
899
|
+
observation_noise_cov = np.eye(n_state) * 0.01
|
|
900
|
+
if process_noise_cov is None:
|
|
901
|
+
process_noise_cov = np.eye(n_state) * 0.001
|
|
902
|
+
|
|
903
|
+
self.R = observation_noise_cov # Observation noise
|
|
904
|
+
self.Q = process_noise_cov # Process noise
|
|
905
|
+
|
|
906
|
+
# State covariance
|
|
907
|
+
self.P = np.eye(n_state) * 0.1
|
|
908
|
+
|
|
909
|
+
# Real-time capabilities
|
|
910
|
+
self.update_frequency = update_frequency
|
|
911
|
+
self.last_update_time = 0.0
|
|
912
|
+
self.current_state_estimate: Optional[StateVector] = None
|
|
913
|
+
self.state_confidence: float = 0.0
|
|
914
|
+
|
|
915
|
+
def update(
|
|
916
|
+
self,
|
|
917
|
+
y_t: Dict[str, float],
|
|
918
|
+
u_t: ControlVector
|
|
919
|
+
) -> StateVector:
|
|
920
|
+
"""
|
|
921
|
+
Update state estimate with observation (EKF update step).
|
|
922
|
+
|
|
923
|
+
Args:
|
|
924
|
+
y_t: Noisy observation
|
|
925
|
+
u_t: Control applied
|
|
926
|
+
|
|
927
|
+
Returns:
|
|
928
|
+
StateVector: Updated state estimate
|
|
929
|
+
"""
|
|
930
|
+
# Simplified EKF: assume linear observation model h(x) = x
|
|
931
|
+
# In practice, h(x) would map state to observable variables
|
|
932
|
+
|
|
933
|
+
# Predict step (already done in predict)
|
|
934
|
+
x_pred = self.predict(u_t)
|
|
935
|
+
|
|
936
|
+
# Convert state to vector for EKF
|
|
937
|
+
x_vec = self._state_to_vector(x_pred)
|
|
938
|
+
y_vec = self._observation_to_vector(y_t, x_pred)
|
|
939
|
+
|
|
940
|
+
# Innovation
|
|
941
|
+
innovation = y_vec - x_vec # Simplified: h(x) = x
|
|
942
|
+
|
|
943
|
+
# Kalman gain
|
|
944
|
+
S = self.P + self.R # Innovation covariance
|
|
945
|
+
K = self.P @ np.linalg.pinv(S) # Kalman gain
|
|
946
|
+
|
|
947
|
+
# Update
|
|
948
|
+
x_updated_vec = x_vec + K @ innovation
|
|
949
|
+
self.P = (np.eye(len(x_vec)) - K) @ self.P
|
|
950
|
+
|
|
951
|
+
# Convert back to StateVector
|
|
952
|
+
x_updated = self._vector_to_state(x_updated_vec)
|
|
953
|
+
|
|
954
|
+
# Store updated estimate
|
|
955
|
+
self.current_state_estimate = x_updated
|
|
956
|
+
self.last_update_time = time.time()
|
|
957
|
+
|
|
958
|
+
# Compute confidence from covariance trace
|
|
959
|
+
trace_P = np.trace(self.P)
|
|
960
|
+
self.state_confidence = max(0.0, 1.0 - trace_P / 10.0) # Normalized confidence
|
|
961
|
+
|
|
962
|
+
return x_updated
|
|
963
|
+
|
|
964
|
+
def predict(
|
|
965
|
+
self,
|
|
966
|
+
u_t: ControlVector
|
|
967
|
+
) -> StateVector:
|
|
968
|
+
"""
|
|
969
|
+
One-step prediction: x_pred_{t+1} = f(x_hat_t, u_t).
|
|
970
|
+
|
|
971
|
+
Args:
|
|
972
|
+
u_t: Control vector
|
|
973
|
+
|
|
974
|
+
Returns:
|
|
975
|
+
StateVector: Predicted next state
|
|
976
|
+
"""
|
|
977
|
+
# Use stored state estimate
|
|
978
|
+
if self.current_state_estimate is None:
|
|
979
|
+
self.current_state_estimate = StateVector()
|
|
980
|
+
|
|
981
|
+
# Predict next state using dynamics
|
|
982
|
+
x_pred = self.dynamics.step(self.current_state_estimate, u_t)
|
|
983
|
+
|
|
984
|
+
return x_pred
|
|
985
|
+
|
|
986
|
+
def should_update(self) -> bool:
|
|
987
|
+
"""
|
|
988
|
+
Check if state should be updated based on frequency (daily).
|
|
989
|
+
|
|
990
|
+
Returns:
|
|
991
|
+
bool: True if should update
|
|
992
|
+
"""
|
|
993
|
+
if self.last_update_time == 0.0:
|
|
994
|
+
return True
|
|
995
|
+
|
|
996
|
+
elapsed = time.time() - self.last_update_time
|
|
997
|
+
return elapsed >= self.update_frequency
|
|
998
|
+
|
|
999
|
+
def get_current_estimate(self) -> Optional[StateVector]:
|
|
1000
|
+
"""Get current state estimate."""
|
|
1001
|
+
return self.current_state_estimate
|
|
1002
|
+
|
|
1003
|
+
def get_confidence(self) -> float:
|
|
1004
|
+
"""Get state estimate confidence."""
|
|
1005
|
+
return self.state_confidence
|
|
1006
|
+
|
|
1007
|
+
def update_with_multi_sensor(
|
|
1008
|
+
self,
|
|
1009
|
+
observations: List[Dict[str, float]],
|
|
1010
|
+
u_t: ControlVector,
|
|
1011
|
+
sensor_weights: Optional[Dict[str, float]] = None
|
|
1012
|
+
) -> StateVector:
|
|
1013
|
+
"""
|
|
1014
|
+
Update state estimate with multiple sensor observations (multi-sensor fusion).
|
|
1015
|
+
|
|
1016
|
+
Args:
|
|
1017
|
+
observations: List of observation dictionaries from different sensors
|
|
1018
|
+
u_t: Control vector
|
|
1019
|
+
sensor_weights: Optional weights for each sensor (by source name)
|
|
1020
|
+
|
|
1021
|
+
Returns:
|
|
1022
|
+
StateVector: Updated state estimate
|
|
1023
|
+
"""
|
|
1024
|
+
if not observations:
|
|
1025
|
+
return self.predict(u_t)
|
|
1026
|
+
|
|
1027
|
+
# Fuse observations (weighted average)
|
|
1028
|
+
fused_observation = {}
|
|
1029
|
+
total_weight = 0.0
|
|
1030
|
+
|
|
1031
|
+
for obs in observations:
|
|
1032
|
+
source = obs.get("source", "unknown")
|
|
1033
|
+
weight = sensor_weights.get(source, 1.0) if sensor_weights else 1.0
|
|
1034
|
+
|
|
1035
|
+
for key, value in obs.items():
|
|
1036
|
+
if key == "source":
|
|
1037
|
+
continue
|
|
1038
|
+
|
|
1039
|
+
if key not in fused_observation:
|
|
1040
|
+
fused_observation[key] = 0.0
|
|
1041
|
+
|
|
1042
|
+
fused_observation[key] += value * weight
|
|
1043
|
+
total_weight += weight
|
|
1044
|
+
|
|
1045
|
+
# Normalize
|
|
1046
|
+
if total_weight > 0:
|
|
1047
|
+
fused_observation = {k: v / total_weight for k, v in fused_observation.items()}
|
|
1048
|
+
|
|
1049
|
+
# Update with fused observation
|
|
1050
|
+
updated = self.update(fused_observation, u_t)
|
|
1051
|
+
|
|
1052
|
+
# Update stored estimate and confidence
|
|
1053
|
+
self.current_state_estimate = updated
|
|
1054
|
+
self.state_confidence = min(1.0, len(observations) * 0.1) # More sensors = higher confidence
|
|
1055
|
+
self.last_update_time = time.time()
|
|
1056
|
+
|
|
1057
|
+
return updated
|
|
1058
|
+
|
|
1059
|
+
def _state_to_vector(self, x: StateVector) -> np.ndarray:
|
|
1060
|
+
"""Convert StateVector to numpy array."""
|
|
1061
|
+
return np.array([
|
|
1062
|
+
x.P, x.L, x.U, x.W, x.S,
|
|
1063
|
+
x.literacy, x.Ecap, x.Hcap,
|
|
1064
|
+
x.K, x.I, x.Tcap,
|
|
1065
|
+
x.E_stock, x.F_stock, x.M_stock, x.C, x.Y
|
|
1066
|
+
])
|
|
1067
|
+
|
|
1068
|
+
def _vector_to_state(self, vec: np.ndarray) -> StateVector:
|
|
1069
|
+
"""Convert numpy array to StateVector."""
|
|
1070
|
+
return StateVector(
|
|
1071
|
+
P=vec[0], L=vec[1], U=vec[2], W=vec[3], S=vec[4],
|
|
1072
|
+
literacy=vec[5], Ecap=vec[6], Hcap=vec[7],
|
|
1073
|
+
K=vec[8], I=vec[9], Tcap=vec[10],
|
|
1074
|
+
E_stock=vec[11], F_stock=vec[12], M_stock=vec[13], C=vec[14], Y=vec[15]
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
def _observation_to_vector(
|
|
1078
|
+
self,
|
|
1079
|
+
y_t: Dict[str, float],
|
|
1080
|
+
x_t: StateVector
|
|
1081
|
+
) -> np.ndarray:
|
|
1082
|
+
"""Convert observation to vector (fill missing with state values)."""
|
|
1083
|
+
vec = self._state_to_vector(x_t)
|
|
1084
|
+
|
|
1085
|
+
# Override with observed values if available
|
|
1086
|
+
# Handle both direct values and percentage values (U is a rate)
|
|
1087
|
+
if "P" in y_t:
|
|
1088
|
+
vec[0] = float(y_t["P"])
|
|
1089
|
+
if "L" in y_t:
|
|
1090
|
+
vec[1] = float(y_t["L"])
|
|
1091
|
+
if "U" in y_t:
|
|
1092
|
+
# U can be percentage (like 1.434) or rate (like 0.01434)
|
|
1093
|
+
u_val = float(y_t["U"])
|
|
1094
|
+
if u_val > 1.0:
|
|
1095
|
+
vec[2] = u_val / 100.0 # Convert percentage to rate
|
|
1096
|
+
else:
|
|
1097
|
+
vec[2] = u_val
|
|
1098
|
+
if "Y" in y_t:
|
|
1099
|
+
vec[15] = float(y_t["Y"])
|
|
1100
|
+
if "W" in y_t:
|
|
1101
|
+
vec[3] = float(y_t["W"])
|
|
1102
|
+
if "S" in y_t:
|
|
1103
|
+
vec[4] = float(y_t["S"])
|
|
1104
|
+
if "literacy" in y_t:
|
|
1105
|
+
lit_val = float(y_t["literacy"])
|
|
1106
|
+
if lit_val > 1.0:
|
|
1107
|
+
vec[5] = lit_val / 100.0 # Convert percentage to rate
|
|
1108
|
+
else:
|
|
1109
|
+
vec[5] = lit_val
|
|
1110
|
+
if "Ecap" in y_t:
|
|
1111
|
+
vec[6] = float(y_t["Ecap"])
|
|
1112
|
+
if "Hcap" in y_t:
|
|
1113
|
+
vec[7] = float(y_t["Hcap"])
|
|
1114
|
+
if "K" in y_t:
|
|
1115
|
+
vec[8] = float(y_t["K"])
|
|
1116
|
+
if "I" in y_t:
|
|
1117
|
+
vec[9] = float(y_t["I"])
|
|
1118
|
+
if "Tcap" in y_t:
|
|
1119
|
+
vec[10] = float(y_t["Tcap"])
|
|
1120
|
+
if "E_stock" in y_t:
|
|
1121
|
+
vec[11] = float(y_t["E_stock"])
|
|
1122
|
+
if "F_stock" in y_t:
|
|
1123
|
+
vec[12] = float(y_t["F_stock"])
|
|
1124
|
+
if "M_stock" in y_t:
|
|
1125
|
+
vec[13] = float(y_t["M_stock"])
|
|
1126
|
+
if "C" in y_t:
|
|
1127
|
+
vec[14] = float(y_t["C"])
|
|
1128
|
+
|
|
1129
|
+
return vec
|
|
1130
|
+
|