diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..4329ecbd2 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,728 @@ +# yaml-language-server: $schema=https://storage.googleapis.com/coderabbit_public_assets/schema.v2.json + +# CodeRabbit Configuration Template +# Complete reference: https://docs.coderabbit.ai/reference/configuration +# Copy this file to .coderabbit.yaml in your repository root + +# ============================================================================= +# GLOBAL SETTINGS +# These settings apply to your entire CodeRabbit configuration +# ============================================================================= + +# Set the language for reviews by using the corresponding ISO language code. +# Options: de, de-DE, de-AT, de-CH, en, en-US, en-AU, en-GB, en-CA, en-NZ, en-ZA, es, es-AR, fr, fr-CA, fr-CH, fr-BE, nl, nl-BE, pt-AO, pt, pt-BR, pt-MZ, pt-PT, ar, ast-ES, ast, be-BY, be, br-FR, br, ca-ES, ca, ca-ES-valencia, ca-ES-balear, da-DK, da, de-DE-x-simple-language, el-GR, el, eo, fa, ga-IE, ga, gl-ES, gl, it, ja-JP, ja, km-KH, km, ko-KR, ko, pl-PL, pl, ro-RO, ro, ru-RU, ru, sk-SK, sk, sl-SI, sl, sv, ta-IN, ta, tl-PH, tl, tr, uk-UA, uk, zh-CN, zh, crh-UA, crh, cs-CZ, cs, nb, no, nl-NL, de-DE-x-simple-language-DE, es-ES, it-IT, fa-IR, sv-SE, de-LU, fr-FR, bg-BG, bg, he-IL, he, hi-IN, hi, vi-VN, vi, th-TH, th, bn-BD, bn +# Default: "en-US" +language: "en-US" + +# Set the tone of reviews and chat. Example: 'You must use talk like Mr. T. I pity the fool who doesn't!' +# Default: "" +tone_instructions: "" + +# Enable early-access features. +# Default: false +early_access: false + +# Enable free tier features for users not on a paid plan. +# Default: true +enable_free_tier: true + +# ============================================================================= +# REVIEWS +# Settings related to reviews. +# ============================================================================= + +# Settings related to reviews. +# Default: {} +reviews: + # Set the profile for reviews. Assertive profile yields more feedback, that may be considered nitpicky. + # Options: chill, assertive + # Default: "chill" + profile: "assertive" + + # Approve the review once CodeRabbit’s comments are resolved and no pre-merge checks are in an error state. Note: In GitLab, all discussions must be resolved. + # Default: false + request_changes_workflow: false + + # Generate a high level summary of the changes in the PR/MR description. + # Default: true + high_level_summary: true + + # Placeholder in the PR/MR description that gets replaced with the high level summary. + # Default: "@coderabbitai summary" + high_level_summary_placeholder: "@coderabbitai summary" + + # Include the high level summary in the walkthrough comment. + # Default: false + high_level_summary_in_walkthrough: false + + # Add this keyword in the PR/MR title to auto-generate the title. + # Default: "@coderabbitai" + auto_title_placeholder: "@coderabbitai" + + # Auto Title Instructions - Custom instructions for auto-generating the PR/MR title. + # Default: "" + auto_title_instructions: "" + + # Post review details on each review. Additionally, post a review status when a review is skipped in certain cases. + # Default: true + review_status: true + + # Set the commit status to 'pending' when the review is in progress and 'success' when it is complete. + # Default: true + commit_status: true + + # Set the commit status to 'failure' when the PR cannot be reviewed by CodeRabbit for any reason. + # Default: false + fail_commit_status: false + + # Generate walkthrough in a markdown collapsible section. + # Default: false + collapse_walkthrough: false + + # Generate a summary of the changed files in the walkthrough. + # Default: true + changed_files_summary: true + + # Generate sequence diagrams in the walkthrough. + # Default: true + sequence_diagrams: true + + # Estimate the code review effort in the walkthrough. + # Default: true + estimate_code_review_effort: true + + # Generate an assessment of how well the changes address the linked issues in the walkthrough. + # Default: true + assess_linked_issues: true + + # Include possibly related issues in the walkthrough. + # Default: true + related_issues: true + + # Related PRs - Include possibly related pull requests in the walkthrough. + # Default: true + related_prs: true + + # Suggest labels based on the changes in the pull request in the walkthrough. + # Default: true + suggested_labels: true + + # Automatically apply the suggested labels to the PR/MR. + # Default: false + auto_apply_labels: false + + # Suggest reviewers based on the changes in the pull request in the walkthrough. + # Default: true + suggested_reviewers: true + + # Automatically assign suggested reviewers to the pull request + # Default: false + auto_assign_reviewers: false + + # Generate a poem in the walkthrough comment. + # Default: true + poem: false + + # Labeling Instructions - Provide guidelines for suggesting labels for the PR/MR. When specific labels or instructions are provided, only those labels are considered, though previous examples are still used to inform the suggestions. If no such labels are provided, suggestions are based solely on previous PR/MRs. + # Default: [] + labeling_instructions: [] + + # Specify file patterns to include or exclude in a review using glob patterns (e.g., !dist/**, src/**). These patterns also apply to 'git sparse-checkout', including specified patterns and ignoring excluded ones (starting with '!') when cloning the repository. + # Default: [] + path_filters: ["!stubs/**"] + + # Path Instructions - Provide specific additional guidelines for code review based on file paths. + # Default: [] + path_instructions: [] + + # Abort the in-progress review if the pull request is closed or merged. + # Default: true + abort_on_close: true + + # Disable caching of code and dependencies. This will force CodeRabbit to download the code and dependencies fresh from the repository each time. + # Default: false + disable_cache: false + + # Configuration for auto review + # Default: {} + auto_review: + # Automatic Review - Automatic code review + # Default: true + enabled: true + + # Automatic Incremental Review - Automatic incremental code review on each push + # Default: true + auto_incremental_review: true + + # Ignore reviewing if the title of the pull request contains any of these keywords (case-insensitive). + # Default: [] + ignore_title_keywords: [] + + # List of labels to control which PRs/MRs to review. Labels starting with '!' are negative matches. Examples: ['bug', 'feature'] - reviews PRs with 'bug' OR 'feature' label. ['!wip'] - reviews all PRs except those with 'wip' label. ['bug', '!wip'] - reviews PRs with 'bug' label but not if they have 'wip' label. + # Default: [] + labels: [] + + # Review draft PRs/MRs. + # Default: false + drafts: false + + # Base branches (other than the default branch) to review. Accepts regex patterns. Use '.*' to match all branches. + # Default: [] + base_branches: [.*] + + # Ignore reviewing pull requests by these usernames. These should match the Git platform usernames exactly, not the email addresses. + # Default: [] + ignore_usernames: [] + + # Configuration for finishing touches + # Default: {} + finishing_touches: + # Docstrings - Options for generating Docstrings for your PRs/MRs. + # Default: {} + docstrings: + # Docstrings - Allow CodeRabbit to generate docstrings for PRs/MRs. + # Default: true + enabled: true + + # Unit Tests - Options for generating unit tests for your PRs/MRs. + # Default: {} + unit_tests: + # Unit Tests - Allow CodeRabbit to generate unit tests for PRs/MRs. + # Default: true + enabled: true + + # Configuration for pre merge checks + # Default: {} + pre_merge_checks: + # Docstring Coverage - Checks if the code has sufficient docstrings. + # Default: {} + docstrings: + # Mode - Determines how strictly the docstring coverage check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved. + # Options: off, warning, error + # Default: "warning" + mode: "warning" + + # Percentage threshold for docstring coverage check. + # Default: 80 + threshold: 80 + + # Title Check - Checks if the pull request title is appropriate and follows best practices. + # Default: {} + title: + # Mode - Determines how strictly the title check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved. + # Options: off, warning, error + # Default: "warning" + mode: "warning" + + # Requirements - Requirements for the pull request title. Example: 'Title should be concise and descriptive, ideally under 50 characters.' + # Default: "" + requirements: "" + + # Description Check - Checks if the pull request description is appropriate and follows best practices. + # Default: {} + description: + # Mode - Determines how strictly the description check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved. + # Options: off, warning, error + # Default: "warning" + mode: "warning" + + # Linked Issue Assessment - Checks if the pull request addresses the linked issues. Generate an assessment of how well the changes address the linked issues. + # Default: {} + issue_assessment: + # Mode - Determines how strictly the issue assessment check is enforced. Warning will only generate a warning and does not require the user to resolve the check. Error requires the user to resolve issues before merging the pull request. If set to error and the request changes workflow is enabled, the pull request will be blocked until the issues are resolved. + # Options: off, warning, error + # Default: "warning" + mode: "warning" + + # Custom Pre-merge Checks - Add unique checks to enforce your team's standards before merging a pull request. Each check must have a unique name (up to 50 characters) and clear instructions (up to 10000 characters). Use these to automatically verify coding, security, documentation, or business rules and maintain code quality. + # Default: [] + custom_checks: [] + + # Tools that provide additional context to code reviews. + # Default: {} + tools: + # Enable ast-grep - ast-grep is a code analysis tool that helps you to find patterns in your codebase using abstract syntax trees patterns. - v0.38.6 + # Default: {} + ast-grep: + # List of rules directories. + # Default: [] + rule_dirs: [] + + # List of utils directories. + # Default: [] + util_dirs: [] + + # Use ast-grep essentials package. + # Default: true + essential_rules: true + + # Predefined packages to be used. + # Default: [] + packages: [] + + # ShellCheck is a static analysis tool that finds bugs in your shell scripts. + # Default: {} + shellcheck: + # Enable ShellCheck - ShellCheck is a static analysis tool that finds bugs in your shell. - Enable ShellCheck integration. - v0.10.0 + # Default: true + enabled: true + + # Ruff is a Python linter and code formatter. + # Default: {} + ruff: + # Enable Ruff - Ruff is a Python linter and code formatter. - Enable Ruff integration. - v0.12.2 + # Default: true + enabled: true + + # markdownlint-cli2 is a static analysis tool to enforce standards and consistency for Markdown files. + # Default: {} + markdownlint: + # Enable markdownlint - markdownlint-cli2 is a static analysis tool to enforce standards and consistency for Markdown files. - Enable markdownlint integration. - v0.17.2 + # Default: true + enabled: true + + # GitHub Checks integration configuration. + # Default: {} + github-checks: + # Enable GitHub Checks - Enable integration, defaults to true - Enable GitHub Checks integration. + # Default: true + enabled: true + + # Time in milliseconds to wait for all GitHub Checks to conclude. Default 90 seconds, max 15 minutes (900000ms). + # Default: 90000 + timeout_ms: 90000 + + # LanguageTool is a style and grammar checker for 30+ languages. + # Default: {} + languagetool: + # Enable LanguageTool - Enable LanguageTool integration. + # Default: true + enabled: true + + # IDs of rules to be enabled. The rule won't run unless 'level' is set to a level that activates the rule. + # Default: [] + enabled_rules: [] + + # IDs of rules to be disabled. Note: EN_UNPAIRED_BRACKETS, and EN_UNPAIRED_QUOTES are always disabled. + # Default: [] + disabled_rules: [] + + # IDs of categories to be enabled. + # Default: [] + enabled_categories: [] + + # IDs of categories to be disabled. Note: TYPOS, TYPOGRAPHY, and CASING are always disabled. + # Default: [] + disabled_categories: [] + + # Only the rules and categories whose IDs are specified with 'enabledRules' or 'enabledCategories' are enabled. + # Default: false + enabled_only: false + + # If set to 'picky', additional rules will be activated, i.e. rules that you might only find useful when checking formal text. + # Options: default, picky + # Default: "default" + level: "default" + + # Biome is a fast formatter, linter, and analyzer for web projects. + # Default: {} + biome: + # Enable Biome - Biome is a fast formatter, linter, and analyzer for web projects. - Enable Biome integration. - v2.1.2 + # Default: true + enabled: true + + # Hadolint is a Dockerfile linter. + # Default: {} + hadolint: + # Enable Hadolint - Hadolint is a Dockerfile linter. - Enable Hadolint integration. - v2.12.0 + # Default: true + enabled: true + + # SwiftLint integration configuration object. + # Default: {} + swiftlint: + # Enable SwiftLint - SwiftLint is a Swift linter. - Enable SwiftLint integration. - v0.57.0 + # Default: true + enabled: true + + # Optional path to the SwiftLint configuration file relative to the repository. This is useful when the configuration file is named differently than the default '.swiftlint.yml' or '.swiftlint.yaml'. + config_file: "example-value" + + # PHPStan is a tool to analyze PHP code. + # Default: {} + phpstan: + # Enable PHPStan - PHPStan requires [config file](https://phpstan.org/config-reference#config-file) in your repository root. Please ensure that this file contains the `paths:` parameter. - v2.1.17 + # Default: true + enabled: true + + # Level - Specify the [rule level](https://phpstan.org/user-guide/rule-levels) to run. This setting is ignored if your configuration file already has a `level:` parameter. + # Options: default, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, max + # Default: "default" + level: "default" + + # PHPMD is a tool to find potential problems in PHP code. + # Default: {} + phpmd: + # Enable PHPMD - PHPMD is a tool to find potential problems in PHP code. - v2.15.0 + # Default: true + enabled: true + + # PHP CodeSniffer is a PHP linter and coding standard checker. + # Default: {} + phpcs: + # Enable PHP CodeSniffer - PHP CodeSniffer is a PHP linter and coding standard checker. - v3.7.2 + # Default: true + enabled: true + + # golangci-lint is a fast linters runner for Go. + # Default: {} + golangci-lint: + # Enable golangci-lint - golangci-lint is a fast linters runner for Go. - Enable golangci-lint integration. - v2.2.2 + # Default: true + enabled: true + + # Optional path to the golangci-lint configuration file relative to the repository. Useful when the configuration file is named differently than the default '.golangci.yml', '.golangci.yaml', '.golangci.toml', '.golangci.json'. + config_file: "example-value" + + # YAMLlint is a linter for YAML files. + # Default: {} + yamllint: + # Enable YAMLlint - YAMLlint is a linter for YAML files. - Enable YAMLlint integration. - v1.37.1 + # Default: true + enabled: true + + # Gitleaks is a secret scanner. + # Default: {} + gitleaks: + # Enable Gitleaks - Gitleaks is a secret scanner. - Enable Gitleaks integration. - v8.27.2 + # Default: true + enabled: true + + # Checkov is a static code analysis tool for infrastructure-as-code files. + # Default: {} + checkov: + # Enable Checkov - Checkov is a static code analysis tool for infrastructure-as-code files. - v3.2.334 + # Default: true + enabled: true + + # Detekt is a static code analysis tool for Kotlin files. + # Default: {} + detekt: + # Enable detekt - detekt is a static code analysis tool for Kotlin files. - v1.23.8 + # Default: true + enabled: true + + # Optional path to the detekt configuration file relative to the repository. + config_file: "example-value" + + # ESLint is a static code analysis tool for JavaScript files. + # Default: {} + eslint: + # Enable ESLint - ESLint is a static code analysis tool for JavaScript files. + # Default: true + enabled: true + + # Flake8 is a Python linter that wraps PyFlakes, pycodestyle and Ned Batchelder's McCabe script. + # Default: {} + flake8: + # Enable Flake8 - Flake8 is a Python linter that wraps PyFlakes, pycodestyle and Ned Batchelder's McCabe script. - v7.2.0 + # Default: true + enabled: true + + # RuboCop is a Ruby static code analyzer (a.k.a. linter ) and code formatter. + # Default: {} + rubocop: + # Enable RuboCop - RuboCop is a Ruby static code analyzer (a.k.a. linter ) and code formatter. - v1.76.1 + # Default: true + enabled: true + + # Buf offers linting for Protobuf files. + # Default: {} + buf: + # Enable Buf - Buf offers linting for Protobuf files. - v1.55.1 + # Default: true + enabled: true + + # Regal is a linter and language server for Rego. + # Default: {} + regal: + # Enable Regal - Regal is a linter and language server for Rego. - v0.35.1 + # Default: true + enabled: true + + # actionlint is a static checker for GitHub Actions workflow files. + # Default: {} + actionlint: + # Enable actionlint - is a static checker for GitHub Actions workflow files. - v1.7.7 + # Default: true + enabled: true + + # PMD is an extensible multilanguage static code analyzer. It’s mainly concerned with Java. + # Default: {} + pmd: + # Enable PMD - PMD is an extensible multilanguage static code analyzer. It’s mainly concerned with Java. - v7.15.0 + # Default: true + enabled: true + + # Optional path to the PMD configuration file relative to the repository. + config_file: "example-value" + + # Cppcheck is a static code analysis tool for the C and C++ programming languages. + # Default: {} + cppcheck: + # Enable Cppcheck - Cppcheck is a static code analysis tool for the C and C++ programming languages. - v2.17.1 + # Default: true + enabled: true + + # Semgrep is a static analysis tool designed to scan code for security vulnerabilities and code quality issues. + # Default: {} + semgrep: + # Enable Semgrep - Semgrep is a static analysis tool designed to scan code for security vulnerabilities and code quality issues. - Enable Semgrep integration. - v1.128.1 + # Default: true + enabled: true + + # Optional path to the Semgrep configuration file relative to the repository. + config_file: "example-value" + + # CircleCI tool is a static checker for CircleCI config files. + # Default: {} + circleci: + # Enable CircleCI - CircleCI tool is a static checker for CircleCI config files. - v0.1.32638 + # Default: true + enabled: true + + # Clippy is a collection of lints to catch common mistakes and improve your Rust code. + # Default: {} + clippy: + # Enable Clippy - Clippy is a collection of lints to catch common mistakes and improve your Rust code. - Enable Clippy integration. + # Default: true + enabled: true + + # SQLFluff is an open source, dialect-flexible and configurable SQL linter. + # Default: {} + sqlfluff: + # Enable SQLFluff - SQLFluff is an open source, dialect-flexible and configurable SQL linter. - v3.4.1 + # Default: true + enabled: true + + # Configuration for Prisma Schema linting to ensure schema file quality + # Default: {} + prismaLint: + # Enable Prisma Schema linting - Prisma Schema linting helps maintain consistent and error-free schema files - v0.10.2 + # Default: true + enabled: true + + # Pylint is a Python static code analysis tool. + # Default: {} + pylint: + # Enable Pylint - Pylint is a Python static code analysis tool. - v3.3.7 + # Default: true + enabled: true + + # Oxlint is a JavaScript/TypeScript linter for OXC written in Rust. + # Default: {} + oxc: + # Enable Oxlint - Oxlint is a JavaScript/TypeScript linter for OXC written in Rust. - v0.16.10 + # Default: true + enabled: true + + # Configuration for Shopify Theme Check to ensure theme quality and best practices + # Default: {} + shopifyThemeCheck: + # Enable Shopify Theme Check - A linter for Shopify themes that helps you follow Shopify theme & Liquid best practices - cli 3.77.1 - theme 3.58.2 + # Default: true + enabled: true + + # Configuration for Lua code linting to ensure code quality + # Default: {} + luacheck: + # Enable Lua code linting - Luacheck helps maintain consistent and error-free Lua code - v1.2.0 + # Default: true + enabled: true + + # Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications. - v7.0.2 + # Default: {} + brakeman: + # Enable Brakeman - Brakeman is a static analysis security vulnerability scanner for Ruby on Rails applications. - v7.0.2 + # Default: true + enabled: true + + # dotenv-linter is a tool for checking and fixing .env files for problems and best practices + # Default: {} + dotenvLint: + # Enable dotenv-linter - dotenv-linter is a tool for checking and fixing .env files for problems and best practices - v3.3.0 + # Default: true + enabled: true + + # HTMLHint is a static code analysis tool for HTML files. + # Default: {} + htmlhint: + # Enable HTMLHint - HTMLHint is a static code analysis tool for HTML files. - Enable HTMLHint integration. - v1.5.0 + # Default: true + enabled: true + + # checkmake is a linter for Makefiles. + # Default: {} + checkmake: + # Enable checkmake - checkmake is a linter for Makefiles. - v0.2.2 + # Default: true + enabled: true + + # OSV Scanner is a tool for vulnerability package scanning. + # Default: {} + osvScanner: + # Enable OSV Scanner - OSV Scanner is a tool for vulnerability package scanning - v2.1.0 + # Default: true + enabled: true + +# ============================================================================= +# CHAT +# Configuration for chat +# ============================================================================= + +# Configuration for chat +# Default: {} +chat: + # Generate art in response to chat messages. CodeRabbit expresses emotions as either ASCII or Emoji art. + # Default: true + art: true + + # Enable the bot to reply automatically without requiring the user to tag it. + # Default: true + auto_reply: true + + # Configuration for integrations + # Default: {} + integrations: + # Configuration for jira + # Default: {} + jira: + # Jira - Enable the Jira integration for opening issues, etc. 'auto' disables the integration for public repositories. + # Options: auto, enabled, disabled + # Default: "auto" + usage: "auto" + + # Configuration for linear + # Default: {} + linear: + # Linear - Enable the Linear integration for opening issues, etc. 'auto' disables the integration for public repositories. + # Options: auto, enabled, disabled + # Default: "auto" + usage: "auto" + +# ============================================================================= +# KNOWLEDGE BASE +# Configuration for knowledge base +# ============================================================================= + +# Configuration for knowledge base +# Default: {} +knowledge_base: + # Opt Out - Disable all knowledge base features that require data retention. If you opt out after opting in, all of your existing knowledge base data will be removed from the system. + # Default: false + opt_out: false + + # Configuration for web search + # Default: {} + web_search: + # Web Search - Enable the web search integration. + # Default: true + enabled: true + + # CodeRabbit will analyse and learn from your organization's code guidelines, which you can mention in the file patterns section. These guidelines will then be used to conduct thorough code reviews. + # Default: {} + code_guidelines: + # Enabled - Enable CodeRabbit to enforce your organization's coding standards during reviews. + # Default: true + enabled: true + + # File Patterns - Specify files for your coding guideline documents in this section. CodeRabbit will scan these files to understand your team's standards and apply them during code reviews. Multiple files supported. File names are case-sensitive. Common files like: (**/.cursorrules, .github/copilot-instructions.md, .github/instructions/*.instructions.md, **/CLAUDE.md, **/GEMINI.md, **/.cursor/rules/*, **/.windsurfrules, **/.clinerules/*, **/.rules/*, **/AGENT.md, **/AGENTS.md) are included by default. + # Default: [] + filePatterns: [] + + # Configuration for learnings + # Default: {} + learnings: + # Learnings - Specify the scope of learnings to use for the knowledge base. 'local' uses the repository's learnings, 'global' uses the organization's learnings, and 'auto' uses repository's learnings for public repositories and organization's learnings for private repositories. + # Options: local, global, auto + # Default: "auto" + scope: "auto" + + # Configuration for issues + # Default: {} + issues: + # Issues - Specify the scope of git platform (GitHub/GitLab) issues to use for the knowledge base. 'local' uses the repository's issues, 'global' uses the organization's issues, and 'auto' uses repository's issues for public repositories and organization's issues for private repositories. + # Options: local, global, auto + # Default: "auto" + scope: "auto" + + # Configuration for jira + # Default: {} + jira: + # Jira - Enable the Jira knowledge base integration. 'auto' disables the integration for public repositories. + # Options: auto, enabled, disabled + # Default: "auto" + usage: "auto" + + # Jira Project Keys - Specify the Jira project keys to use for the knowledge base. + # Default: [] + project_keys: [] + + # Configuration for linear + # Default: {} + linear: + # Linear - Enable the Linear knowledge base integration. 'auto' disables the integration for public repositories. + # Options: auto, enabled, disabled + # Default: "auto" + usage: "auto" + + # Linear Team Keys - Specify the Linear team keys (identifiers) to use for the knowledge base. E.g. 'ENG' + # Default: [] + team_keys: [] + + # Configuration for pull requests + # Default: {} + pull_requests: + # Pull Requests - Specify the scope of pull requests to use for the knowledge base. 'local' uses the repository's pull requests, 'global' uses the organization's pull requests, and 'auto' uses repository's pull requests for public repositories and organization's pull requests for private repositories. + # Options: local, global, auto + # Default: "auto" + scope: "auto" + + # Configuration for mcp + # Default: {} + mcp: + # MCP - Enable the MCP knowledge base integration. 'auto' disables the integration for public repositories. + # Options: auto, enabled, disabled + # Default: "auto" + usage: "auto" + + # MCP Disabled Servers - Specify MCP server labels to disable (case-insensitive). These servers will be excluded from reviews and knowledge base queries. + # Default: [] + disabled_servers: [] + +# ============================================================================= +# CODE GENERATION +# Configuration for code generation +# ============================================================================= + +# Configuration for code generation +# Default: {} +code_generation: + # Settings related to the generation of docstrings. + # Default: {"path_instructions":[]} + docstrings: + # Set the language for docstrings by using the corresponding ISO language code. + # Options: de, de-DE, de-AT, de-CH, en, en-US, en-AU, en-GB, en-CA, en-NZ, en-ZA, es, es-AR, fr, fr-CA, fr-CH, fr-BE, nl, nl-BE, pt-AO, pt, pt-BR, pt-MZ, pt-PT, ar, ast-ES, ast, be-BY, be, br-FR, br, ca-ES, ca, ca-ES-valencia, ca-ES-balear, da-DK, da, de-DE-x-simple-language, el-GR, el, eo, fa, ga-IE, ga, gl-ES, gl, it, ja-JP, ja, km-KH, km, ko-KR, ko, pl-PL, pl, ro-RO, ro, ru-RU, ru, sk-SK, sk, sl-SI, sl, sv, ta-IN, ta, tl-PH, tl, tr, uk-UA, uk, zh-CN, zh, crh-UA, crh, cs-CZ, cs, nb, no, nl-NL, de-DE-x-simple-language-DE, es-ES, it-IT, fa-IR, sv-SE, de-LU, fr-FR, bg-BG, bg, he-IL, he, hi-IN, hi, vi-VN, vi, th-TH, th, bn-BD, bn + # Default: "en-US" + language: "en-US" + + # Path Instructions - Provide additional guidelines for docstring generation based on file paths. + # Default: [] + path_instructions: [] + + # Settings related to the generation of unit tests. + # Default: {"path_instructions":[]} + unit_tests: + # Unit Test Generation - Provide additional guidelines for unit test generation based on file paths. + # Default: [] + path_instructions: [] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..b229c8eed --- /dev/null +++ b/.gitattributes @@ -0,0 +1,25 @@ +# Auto-detect text files and normalize them to LF on checkin. +# Force LF on checkout for all text files to avoid Windows CRLF issues. +* text=auto eol=lf + +# Explicitly force LF for text files, redundant but explicit +*.toml text eol=lf +*.txt text eol=lf +*.md text eol=lf +*.rst text eol=lf +*.py text eol=lf +*.json text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.sh text eol=lf + +# Explicitly declare binary files to avoid accidental corruption +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary +*.sqlite binary +*.db binary +*.pyc binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..79a94c47e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,86 @@ +--- +name: Bug report +about: Report a reproducible bug or unexpected behavior. +title: "[Bug]: Clear and concise description of the bug" +labels: bug, needs-triage +assignees: '' + +--- + +## Bug Description + +Clearly and concisely describe the bug you've encountered. What is the unexpected behavior? What did you expect to happen instead? + +## Steps to Reproduce + +Provide a clear, step-by-step procedure to reproduce the bug. This is crucial for us to understand and fix the issue. + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error '....' + +## Code or Configuration Example + +If the bug involves code or configuration, please provide a minimal, reproducible example that demonstrates the issue. This should be the smallest amount of code/config necessary to trigger the bug. + +```python +# Example of bug-inducing code (if relevant) +import my_library + +# Setup or initialization +config = { + "setting_a": "value", + "setting_b": 123 +} +processor = my_library.Processor(**config) + +# Action that triggers the bug +try: + processor.process_data(invalid_data) +except Exception as e: + print(f"Error: {e}") +``` + +## Error Message / Stack Trace + +If an error message or stack trace was produced, please include it here. Use code blocks for better readability. + +```text +# Example Error Message/Stack Trace +Traceback (most recent call last): + File "", line 7, in + processor.process_data(invalid_data) + File "/path/to/my_library/processor.py", line 42, in process_data + raise ValueError("Invalid data provided") +ValueError: Invalid data provided +``` + +## Screenshots / Videos + +If applicable, add screenshots to help explain the problem. You can drag and drop images directly into the issue description. + +## Expected Behavior + +Describe what you expected to happen when following the reproduction steps. + +## Actual Behavior + +Describe what actually happened, including any unexpected output or results. + +## Environment + +Please provide details about your environment. This helps us reproduce the issue. + +* **Operating System:** +* **Python Version:** +* **Project Version:** +* **Relevant Dependencies:** + +## Checklist + +* [ ] I have searched existing issues to ensure this bug hasn't already been reported. +* [ ] I have provided clear steps to reproduce the bug. +* [ ] I have provided a minimal, reproducible code example (if applicable). +* [ ] I have included any relevant error messages or stack traces. +* [ ] I have described the expected and actual behavior. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..c6162169c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,50 @@ +--- +name: Feature request +about: Suggest an idea for a new feature or enhancement. +title: "[Feature]: Concise title for your feature request" +labels: enhancement, needs-triage +assignees: '' + +--- + +## Feature Request + +Clearly and concisely describe the new feature or enhancement you are requesting. + +## Problem Statement + +What problem does this new feature or enhancement solve? Why is it needed? Describe the current limitations or pain points that this feature would address. + +## Proposed Solution (optional) + +Describe your proposed solution or how you envision this feature working. Be as specific as possible. Consider: + +* **User Interface/API Changes:** How would a user interact with this new feature? +* **Configuration:** Would new configuration options be needed? +* **Examples of Usage:** Provide a hypothetical example of how this feature would be used. +## Benefits + +What are the benefits of implementing this feature? How would it improve the project for users? + +* (e.g., "It would simplify X workflow for users.") +* (e.g., "It would enable Y functionality that is currently impossible.") +* (e.g., "It would improve performance by Z% in certain scenarios.") + +## Alternatives Considered + +Have you considered any alternative approaches or workarounds for the problem you're trying to solve? If so, why do you think this feature request is a better solution? + +## Additional Context + +Add any other context, links, or information about the feature request here. This could include: + +* Similar features in other projects. +* Relevant research papers or articles. +* Any potential challenges or considerations for implementation. + +## Checklist + +* [ ] I have searched existing issues to ensure this feature hasn't already been requested. +* [ ] I have clearly described the problem this feature solves. +* [ ] I have provided a detailed proposed solution (optional). +* [ ] I have explained the benefits of this feature. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..817c6d7fe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,41 @@ +--- +name: Question +about: Questions about the usage or working of a feature +title: "[Question]: Your concise question title here" +labels: needs-triage, question +assignees: '' + +--- + +## Question +Clearly and concisely state your question here. What are you trying to understand or achieve? + +## Context +Provide any relevant background information or context that might help us understand your question better. For example: + +* **What are you trying to do?** (e.g., "I'm trying to integrate X with Y," "I'm trying to achieve Z functionality.") +* **Why is this question important to you?** (e.g., "This is blocking my progress on feature A," "I'm trying to decide between two approaches.") +* **What steps have you already taken to find an answer?** (e.g., "I've checked the documentation for X," "I've searched existing issues for similar topics," "I've tried searching on Stack Overflow.") + +## Details & Examples + +If applicable, provide specific details, code snippets, or examples that illustrate your question. This is especially helpful for questions related to: + +* **Usage:** How are you currently trying to use the project? +* **Configuration:** What relevant configuration are you using? +* **Code:** If your question involves code, please provide a minimal, reproducible example. + +## Environment + +Please provide details about your environment. This helps us reproduce issues or understand potential incompatibilities. + +* **Operating System:** +* **Python Version:** +* **Project Version:** +* **Relevant Dependencies:** + +## Checklist + +* [ ] I have checked the [documentation](https://docs.temoaproject.org/en/latest/) for an answer. +* [ ] I have searched existing issues to ensure my question hasn't already been asked. +* [ ] I have provided as much detail as possible to help answer my question. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..27c87ee99 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,85 @@ +name: CI + +on: + push: + branches: + [ + "main", + "temoa_alpha", + "ci_testing", + "1.0.0-dev-operator", + "temoa_davey_code", + "unstable", + ] + pull_request: + branches: + [ + "main", + "temoa_alpha", + "ci_testing", + "1.0.0-dev-operator", + "temoa_davey_code", + "unstable", + ] + +jobs: + test: + name: setup and test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.12", "3.13", "3.14"] + + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + + - name: Install uv + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + with: + version: "0.9.26" + python-version: ${{ matrix.python-version }} + enable-cache: true + + - name: Install the project + run: uv sync --locked --all-extras --dev + + - name: Install Graphviz (Ubuntu) + if: runner.os == 'Linux' + run: sudo apt-get install -y graphviz + + - name: Install Graphviz (macOS) + if: runner.os == 'macOS' + run: brew install graphviz + + - name: Install Graphviz (Windows) + if: runner.os == 'Windows' + run: choco install graphviz -y + + - name: Run tests + env: + CI: 1 + run: uv run pytest tests + + type-check: + name: type check + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12"] + + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + + - name: Install uv + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + with: + version: "0.9.26" + python-version: ${{ matrix.python-version }} + enable-cache: true + + - name: Install the project + run: uv sync --locked --all-extras --dev + + - name: Run mypy + run: uv run mypy --config-file=pyproject.toml . diff --git a/.github/workflows/dependency-canary.yml b/.github/workflows/dependency-canary.yml new file mode 100644 index 000000000..0a268ca7f --- /dev/null +++ b/.github/workflows/dependency-canary.yml @@ -0,0 +1,110 @@ +name: Dependency Canary + +on: + schedule: + - cron: "0 0 * * *" # Every day at 00:00 UTC + workflow_dispatch: # Allow manual triggering + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + canary: + name: Upgrade and Validate Dependencies + runs-on: ubuntu-latest + + steps: + - name: Checkout unstable branch + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + ref: unstable + + - name: Install uv + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + with: + version: "0.9.26" + enable-cache: true + + - name: Upgrade dependencies + run: | + uv lock --upgrade + uv pip compile pyproject.toml -o requirements.txt + uv pip compile pyproject.toml --all-extras --all-groups -o requirements-dev.txt + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Install Graphviz + run: sudo apt-get update && sudo apt-get install -y graphviz + + - name: Run type check (mypy) + run: uv run mypy --config-file=pyproject.toml . + + - name: Run tests (pytest) + env: + CI: 1 + run: uv run pytest tests + + - name: Alert on Failure + if: failure() + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'dependency-canary' + }); + + const issueTitle = '🚨 Dependency Canary Failure'; + const issueBody = `The scheduled dependency canary workflow failed. This likely means an upstream dependency update broke the build. + + **Run Details:** + - **Run ID:** [${context.runId}](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) + - **Workflow:** ${context.workflow} + - **Event:** ${context.eventName} + + Please investigate the logs to identify the breaking package.`; + + if (issues.length === 0) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: issueTitle, + body: issueBody, + labels: ['dependency-canary', 'bug'] + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issues[0].number, + body: `The canary failed again. [See run ${context.runId}](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})` + }); + } + + - name: Create Pull Request + if: success() + uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore: upgrade dependencies (canary)" + branch: dependency-canary-updates + base: unstable + title: "📦 Dependency Canary: Upgrade all packages" + body: | + This PR was automatically generated by the Dependency Canary workflow. + It upgrades all dependencies to their latest compatible versions and validates them with `mypy` and `pytest`. + + **Changes:** + - Updated `uv.lock` + - Updated `requirements.txt` + - Updated `requirements-dev.txt` + + All checks passed successfully. + labels: | + dependencies + automated-pr diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..594378548 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,43 @@ +name: "Publish" + +on: + push: + tags: + # Publish on any tag starting with a `v`, e.g., v0.1.0 + - v* + +jobs: + run: + runs-on: ubuntu-latest + environment: + name: pypi + permissions: + id-token: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install uv + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - name: Install Python 3.12 + run: uv python install 3.12 + - name: Build + run: uv build + - name: Verify tag matches version + run: | + VERSION=$(python3 -c "import ast; d = {}; content = open('temoa/__about__.py').read(); exec(compile(ast.parse(content), 'temoa/__about__.py', 'exec'), d); print(d['__version__'])") + TAG=${GITHUB_REF#refs/tags/v} + if [ "$VERSION" != "$TAG" ]; then + echo "Error: Tag v$TAG does not match version $VERSION in temoa/__about__.py" + exit 1 + fi + echo "Tag v$TAG matches version $VERSION" + # Check that basic features work and we didn't miss to include crucial files + - name: Smoke test (wheel) + timeout-minutes: 10 + run: uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py + - name: Smoke test (source distribution) + timeout-minutes: 10 + run: uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py + - name: Publish + run: uv publish diff --git a/.gitignore b/.gitignore index 1d51d8a49..f3a30a2ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,33 @@ -*.pyc -data_files/debug_logs/*.log -data_files/debug_logs/lp_files/* -data_files/untracked_data/* -.DS_Store -*.ipynb_checkpoints +# ignore all root items +/* -# ignore the .idea folder from pycharm -- a pycharm specific config folder -.idea/* +# unignore folders +!temoa/ +!tests/ +!docs/ +!notebooks/ +!output_files/ +!.github/ +!stubs/ +!scripts/ -# ignore goings-on in the venv -/venv/* +# unignore files +!.gitignore +!.gitattributes +!README.md +!pyproject.toml +!uv.lock +!requirements*.txt +!LICENSE +!CHANGELOG.md +!CONTRIBUTING.md +!.pre-commit-config.yaml +!.readthedocs.yaml +!.coderabbit.yaml +!CITATION.cff -# ignore (for now) associated jupyter notebooks -other_notebooks/ -/temoa/temoa_model/config_sample_9R +# recursively re-ignore +__pycache__ + +# ignore built docs +docs/_build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..8e69cea87 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,60 @@ +repos: + - repo: https://github.com/astral-sh/uv-pre-commit + # uv version. + rev: 0.9.17 + hooks: + # Dependency management + - id: uv-lock + name: uv-lock (Update uv.lock if pyproject.toml changed) + files: ^pyproject\.toml$ + + # Compile requirements + - id: pip-compile + name: pip-compile requirements.txt (core dependencies) + args: + - pyproject.toml # Input file + - -o # Output flag + - requirements.txt # Output file name + files: ^pyproject\.toml$ + + - id: pip-compile + name: pip-compile requirements-dev.txt (core + all optional dependencies) + args: + - pyproject.toml + - --all-extras # Include all optional dependency groups + - -o + - requirements-dev.txt # Output file name + files: ^pyproject\.toml$ + + # Code quality and formatting + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-toml + - id: check-json + - id: check-merge-conflict + - id: check-added-large-files + args: ["--maxkb=1024"] + - id: debug-statements + - repo: https://github.com/asottile/pyupgrade + rev: v3.21.2 + hooks: + - id: pyupgrade + args: ["--py312-plus"] + # Python Linting & Formatting with Ruff + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: "v0.14.8" + hooks: + - id: ruff + name: ruff (linter) + args: [--fix, --exit-non-zero-on-fix] + # --fix: attempts to automatically fix linting issues. + # --exit-non-zero-on-fix: ensures that if fixes were made, + # the commit fails, prompting you to + # review and re-stage the changes. + - id: ruff-format + name: ruff-format (formatter) diff --git a/.pre-commit-config.yaml.yml b/.pre-commit-config.yaml.yml deleted file mode 100644 index 63d4fee13..000000000 --- a/.pre-commit-config.yaml.yml +++ /dev/null @@ -1,17 +0,0 @@ -# repos: -# - repo: https://github.com/pre-commit/pre-commit-hooks -# rev: v4.4.0 -# hooks: -# - id: check-yaml -# # - id: end-of-file-fixer -# - id: trailing-whitespace -# - id: check-added-large-files -# args: ['--maxkb=80000'] -# - repo: https://github.com/psf/black -# rev: 23.3.0 -# hooks: -# - id: black -# - repo: https://github.com/PyCQA/isort -# rev: 5.12.0 -# hooks: -# - id: isort diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..8fd13ec63 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version, and other tools you might need +build: + os: ubuntu-24.04 + tools: + python: "3.13" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/source/conf.py + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..277c7e49c --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,17 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - family-name: "Hunter" + given-names: "Kevin" + - family-name: "Sreepathi" + given-names: "Sarat" + - family-name: "DeCarolis" + given-names: "Joseph F." +title: "Modeling for insight using Tools for Energy Model Optimization and Analysis (Temoa)" +journal: "Energy Economics" +volume: "40" +pages: "339-349" +year: 2013 +month: 11 # November +doi: "10.1016/j.eneco.2013.07.014" +url: "https://doi.org/10.1016/j.eneco.2013.07.014" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c65b10181..bd73d727d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,42 +1,129 @@ -# How to contribute # +# Contributing to Temoa -I'm really happy that you're interested in contributing to Temoa! Building a sustainable energy future with an open source energy system model takes a lot of effort, and we can use all the help we can get. +Thank you for your interest in contributing to Temoa! Building a sustainable energy future with an open source energy system model takes a lot of effort, and we welcome all contributions. -Here are some resources to help get you started: -* A [preprint](https://temoacloud.com/wp-content/uploads/2019/12/Hunter_etal_2013.pdf) of our Energy Economics paper laying out the motivation for our effort as well as the core model formulation. -* The [model documentation](https://temoacloud.com/docs/), which evolves with changes to the model formulation and the addition of new features. -* Use [our forum](https://groups.google.com/forum/#!forum/temoa-project) to ask questions about the model. +## Resources -## Bugs and feature requests ## -If you would like to report a bug in the code or request a feature, please use our [Issue Tracker](https://github.com/TemoaProject/temoa/issues). If you're unsure or have questions, use [the forum](https://groups.google.com/forum/#!forum/). +- **Documentation**: [https://docs.temoaproject.org/en/latest/](https://docs.temoaproject.org/en/latest/) +- **Project Website**: [https://temoaproject.org](https://temoaproject.org) +- **Issue Tracker**: [https://github.com/TemoaProject/temoa/issues](https://github.com/TemoaProject/temoa/issues) +- **Academic Paper**: Hunter et al. (2013) - [Modeling for insight using Tools for Energy Model Optimization and Analysis (Temoa)](https://doi.org/10.1016/j.eneco.2013.07.014) -## Submitting Changes ## +## Getting Help -To make changes to the code, first clone the repository. If you would like to share those changes back to our main repository, then you need to issue a pull request on GitHub. Details on how to contribute code are nicely outlined in [this blog post by Rob Allen](https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/). If you'd like to make changes but don't feel comfortable with GitHub, get in touch with us through the [forum](https://groups.google.com/forum/#!forum/). +If you have questions or need help: +- **Bug Reports & Feature Requests**: Use our [GitHub Issues](https://github.com/TemoaProject/temoa/issues). Please use the provided templates for Bug Reports or Feature Requests. +- **General Questions**: Open an issue using the **Question** template. We have phased out the legacy Google Group forum in favor of GitHub-based discussions. -When making commits to the repository, please use verbose commit messages for all but the simplest changes. Every commit to the repository should include an appropriate summary message about the accompanying code changes. Include enough context so that users get a high-level understanding of the changes without having to check the code. For example, "Fixed broken algorithm" does not convey much information. A more appropriate and complete summary message might be: +## Development Setup +### Prerequisites + +- Python 3.12 or higher +- [uv](https://github.com/astral-sh/uv) (recommended) +- Git + +### Setting Up Your Development Environment + + +1. **Fork and clone the repository:** + + ```bash + git clone https://github.com/YOUR-USERNAME/temoa.git + cd temoa + ``` + +2. **Install dependencies using uv (recommended):** + + ```bash + # Install all dependencies including development tools + uv sync --all-extras --dev + ``` + +3. **Install pre-commit hooks:** + + ```bash + uv run pre-commit install + ``` + + This will automatically run code quality checks (Ruff, Mypy) before each commit. + +## Infrastructure & Testing + +We maintain a robust testing infrastructure to ensure model correctness and code reliability. + +### Solvers +All core tests are designed to run using the **HiGHS** solver (provided by the `highspy` core dependency). + +### Running Tests + +```bash +# Run all tests +uv run pytest + +# Run with coverage report +uv run pytest --cov=temoa --cov-report=html + +# Run specific test file +uv run pytest tests/test_specific.py +``` + +### Testing Infrastructure +If you are writing a new feature, you **must** include tests that verify its functionality. +- Refer to `tests/conftest.py` for the available testing infrastructure. +- Use the `system_test_run` fixture for full model execution tests. +- Test databases are automatically refreshed from SQL scripts in `tests/testing_data/` during the test setup phase. + +## Code Quality Tools + +We use several tools to maintain high code standards: + +### Type Checking with mypy +All new code must include type hints. We enforce strict typing for core modules. +```bash +uv run mypy temoa ``` -Add NEOS solve functionality to config file -With this commit, users can now use the NEOS server: https://neos-server.org/neos/ -to solve their Temoa models. If users do not have a local install of cplex, this -is a potentially faster option. Note that when using NEOS, both --neos and --solver -must be used in the config file. +### Linting and Formatting with Ruff +We use Ruff for both linting and formatting. +```bash +# Check and auto-fix linting issues +uv run ruff check --fix . + +# Format code +uv run ruff format . ``` -In general, we try to follow [these 7 rules](https://chris.beams.io/posts/git-commit/) when writing commit messages: +## Making Changes + +### Pull Request Process + +1. **Open an Issue first**: For significant changes or new features, please open an issue to discuss the approach before starting work. +2. **Create a new branch**: + ```bash + git checkout -b feature/your-feature-name + ``` +3. **Implement and Test**: Ensure your changes follow the [Code Style Guidelines](#code-style-guidelines) and include relevant tests. +4. **Verify Locally**: Run `pytest`, `mypy`, and `ruff` locally before pushing. +5. **Open a Pull Request**: Submit your PR against the `unstable` branch. Ensure the PR description clearly explains the changes and links to the relevant issue. + +### Commit Message Guidelines + +We advise following the [Conventional Commits](https://www.conventionalcommits.org/) style. + +**Format:** `: ` (e.g., `feat: Add new carbon constraint`) + +**Rules:** +1. Use the imperative mood ("Add feature" not "Added feature"). +2. Limit the subject line to 50 characters. +3. Use the body to explain *what* and *why* vs. *how*. + +## Code Style Guidelines -1. Separate subject from body with a blank line -2. Limit the subject line to 50 characters -3. Capitalize the subject line -4. Do not end the subject line with a period -5. Use the imperative mood in the subject line -6. Wrap the body at 72 characters -7. Use the body to explain what and why vs. how +- **Type Hints**: Required for all public function signatures. +- **Docstrings**: Use Google-style docstrings. +- **Line Length**: Maximum 100 characters (enforced by Ruff). -Regarding how we format code, please see Chapter 7 of our user manual, which serves as the Temoa Code Style Guide. Be sure that all modified files included in the pull request have unix line endings. +--- -Thanks, -Joe DeCarolis -NC State University \ No newline at end of file +Thank you for contributing to Temoa! diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..9db61c127 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-2025 TemoaProject Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 6c5b18cf1..000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,280 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md index 0713d6def..92851db41 100644 --- a/README.md +++ b/README.md @@ -1,192 +1,366 @@ -# Getting Started with TEMOA and Version 3 +# TEMOA + +[![PyPI](https://img.shields.io/pypi/v/temoa?label=pypi%20package)](https://pypi.org/project/temoa/) +[![CI](https://github.com/TemoaProject/temoa/actions/workflows/ci.yml/badge.svg?branch=unstable)](https://github.com/TemoaProject/temoa/actions/workflows/ci.yml) +[![Documentation Status](https://readthedocs.org/projects/temoa/badge/?version=latest)](https://temoa.readthedocs.io/en/latest/?badge=latest) +[![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](https://pyreadiness.org/3.12/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![Type Checked with mypy](https://img.shields.io/badge/type--checked-mypy-blue?style=flat-square&logo=python)](https://img.shields.io/badge/type--checked-mypy-blue?style=flat-square&logo=python) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv) ## Overview -The main subdirectories in the project are: +TEMOA (Tools for Energy Model Optimization and Analysis) is a sophisticated energy systems optimization framework that supports various modeling approaches including perfect foresight, myopic planning, uncertainty analysis, and alternative generation. + +## Quick Start + +### Standard Installation + +```bash +# Install from PyPI in a virtual environment +python -m venv .venv + +# Activate virtual environment +# On Linux/Mac: +source .venv/bin/activate +# On Windows: +.venv\Scripts\activate + +# Install temoa +pip install temoa +``` + +### Get Started in 30 Seconds + +In a virtual env with temoa installed, run: + +```bash +# Create tutorial files in the current directory +# Creates tutorial_config.toml and tutorial_database.sqlite +temoa tutorial + +# Run the model +temoa run tutorial_config.toml +``` + +## Package Structure + +The Temoa package is organized into clear modules: + +- **`temoa.core`** - Public API for end users (TemoaModel, TemoaConfig, TemoaMode) +- **`temoa.cli`** - Command-line interface and utilities +- **`temoa.components`** - Model components and constraints +- **`temoa.data_io`** - Data loading and validation +- **`temoa.extensions`** - Optional extensions for different modeling approaches + - `modeling_to_generate_alternatives` - MGA analysis + - `method_of_morris` - Sensitivity analysis + - `monte_carlo` - Uncertainty quantification + - `myopic` - Sequential decision making +- **`temoa.model_checking`** - Model validation and integrity checking +- **`temoa.data_processing`** - Output analysis and visualization +- **`temoa.utilities`** - Helper scripts and migration tools + +## Installation & Setup + +### Development Installation + +For users who want to contribute to or modify Temoa should install in development mode using `uv`: + +```bash +# Install uv if you haven't already +curl -LsSf https://astral.sh/uv/install.sh | sh + +``` + +```bash +# Clone repository +git clone https://github.com/TemoaProject/temoa.git +cd temoa + +# Setup development environment with uv +uv sync --all-extras --dev + +# Install pre-commit hooks +uv run pre-commit install -1. `temoa/` -Contains the core Temoa model -2. `temoa/temoa_model` -The core model code necessary to build and solve a Temoa instance -3. `temoa/data_processing` -Code for post-processing solved models and working with output -4. `temoa/extensions` -Model extensions to solve the model using differing techniques. Note: There is some legacy and non-working -code in these modules that is planned future work. +# Run tests +uv run pytest -5. `data_files/` -Intended to hold input data files and config files. Examples are included. -Note that the example file utopia.sql represents a simple system called 'Utopia', which -is packaged with the MARKAL model generator and has been used -extensively for benchmarking exercises. -6. `output_files/` -The target for run-generated output including log files and other requested products. Temoa will create -time-stamped folders to gather output for runs -4. `docs/` -Contains the source code for the Temoa project manual, in reStructuredText -(ReST) format. -5. `notebooks/` -jupyter notebooks associated with the project. Note: Not all of these are functional at this time, but are -retained to guide future development +# Run type checking +uv run mypy +``` + +## Command Line Interface + +Temoa provides a modern, user-friendly CLI built with Typer: -## Guide to Setup +### Basic Commands + +**Run a model:** + +```bash +temoa run tutorial_config.toml +temoa run tutorial_config.toml --output results/ +temoa run tutorial_config.toml --build-only # Build without solving +``` -1. Obtain a current copy of Python from the python.org website. The model has been tested with 3.11 and 3.12. It will -fail (raise error) on earlier versions. -2. A `requirements.txt` file has been included to allow for use of `pip` to populate a virtual environment. In order to use that the steps are: -- Ensure you have a copy of python 3.11/3.12 installed on your machine ([python.org](https://www.python.org)) -- Make and activate a virtual environment using the `venv` package: +**Validate configuration:** +```bash +temoa validate tutorial_config.toml +temoa validate tutorial_config.toml --debug ``` -$ python3.11 -m venv venv -$ source venv/bin/activate # for linux/osx, windows activation command may differ + +**Database migration:** + +```bash +temoa migrate old_database.sql --output new_database.sql +temoa migrate old_database.db --type db +temoa migrate old_database.sqlite --output migrated_v4.sqlite ``` -- Verify that you have a prepended indicator on your cursor that you are in the virtual environment (see below) -- After activating the venv, use `pip` *within* the venv to install everything. Most IDEs have automated tools to -help set up and associate this venv with the project. It is also possible from the command line: + +**Generate tutorial files:** + +```bash +temoa tutorial # Creates tutorial_config.toml and tutorial_database.sqlite +temoa tutorial my_model my_db # Custom names ``` -(venv) $ pip install -r requirements.txt + +### Global Options + +```bash +temoa --version # Show version information +temoa --how-to-cite # Show citation information +temoa --help # Full help ``` -- For Conda users, an environment.yml file is provided that is not currently fully tested. Additional installs may -be required. -3. The entry point for regular execution is now at the top level of the project so a "sample" run should be initiated as: +### Using with uv + +When working with the source code, use `uv run` to ensure you're using the correct dependencies: + +```bash +uv run temoa run tutorial_config.toml # Run with project dependencies +uv run temoa validate tutorial_config.toml # Validate configuration +uv run temoa tutorial # Create tutorial files ``` -(venv) temoa $ python main.py --config data_files/my_configs/config_sample.toml + +## Programmatic Usage + +You can use Temoa as a Python library: + +```python +import temoa +from pathlib import Path +from temoa import TemoaModel, TemoaConfig, TemoaMode + +# Create configuration +config = TemoaConfig( + scenario="my_scenario", + scenario_mode=TemoaMode.PERFECT_FORESIGHT, + input_database=Path("path/to/input.db"), + output_database=Path("path/to/output.db"), + output_path=Path("path/to/output"), + solver_name="appsi_highs" +) + +# Build and solve model +model = TemoaModel(config) +result = model.run() # Equivalent to: temoa run tutorial_config.toml + +# Check if run was successful +if result: + print("Model solved successfully!") +else: + print("Model failed to solve") ``` ## Database Setup -- Several sample database files in Version 3 format are provided in SQL format for learning/testing. These are provided in the -`data_files/example_dbs` folder. In order to use them, they must be converted into sqlite database files. This can -be done from the command line using the sqlite3 engine to convert them. sqlite3 is packaged with Python and should be -available. If not, most configuration managers should be able to install it. The command to make the `.sqlite` file -is (for Utopia as an example): -``` -(venv) $ sqlite3 utopia.sqlite < utopia.sql -``` -- Converting legacy db's to Version 3 can be done with the included database migration tool. Users who use this -tool are advised to carefully review the console outputs during conversion to ensure accuracy and check the -converted database carefully. The migration tool will build an empty new Version 3 database and move data from -the old database, preserving the legacy database in place. The command can be run from the top level of the -project and needs pointers to the target database and the Version 3 schema file. A typical execution from top level -should look like: - -``` -(venv) $ python temoa/utilities/db_migration_to_v3.py --source data_files/.sqlite --schema data_files/temoa_schema_v3.sql -``` -- Users may also create a blank full or minimal version of the database from the two schema files in the `data_files` -directory as described above using the `sqlite3` command. The "minimal" version excludes some of the group -parameters and is recommended as a starting point for entry-level models. It can be upgraded to the full set of -tables by executing the full schema SQL command on the resulting database later, which will add the missing tables. - -## Config Files - -- A configuration (config) file is required to run the model. The `sample_config.toml` is provided as a reference -and has all parameters in it. It can be copied/renamed, etc. -- Notes on Config Options: - -| Field | Notes | -|------------------------|------------------------------------------------------------------------------------------------------------| -| Scenario Name | A name used in output tables for results (cannot contain dash '-' symbol) | -| Temoa Mode | The execution mode. See note below on currently supported modes | -| Input/Output DB | The source (and optionally diffent) output database. Note for myopic, MGA input must be same as output | -| Price Checking | Run the "price checker" on the built model to look for costing deficiencies and log them | -| Source Tracing | Check the integrity of the commodity flow network in every region-period combination. Required for Myopic | -| Plot Commodity Network | Produce HTML (viewable in any browser) displays of the networks built (see note at bottom) | -| Solver | The exact name of the solver executable to call | -| Save Excel | Save core output data to excel files. Needed if user intends to use the graphviz post-processing modules | -| Save LP | Save the created LP model files | -| Save Duals | Save the values of the Dual Variables in the Output Tables. (Only supported by some solvers) | -| Mode Specific Settings | See the README files within mode folders for up-to-date values | - -## Currently Supported Modes -### Check -Build the model and run the numerous checks on it. Results will be in the log file. No solve is attempted. -Note: The LP file for the model can be saved with this option and solved later/independently by selecting -the ``save_lp_file`` option in the config. + +### Quick Setup with Tutorial + +The fastest way to get started: + +```bash +temoa tutorial +``` + +This creates: + +- `tutorial_config.toml` - Configuration file with example settings +- `tutorial_database.sqlite` - Sample database for learning + +**Migration from older versions:** + +```bash +# Migrate from v3.1 to v4 +temoa migrate old_database_v3.1.sql --output new_database_v4.sql + +# or for SQLite databases +temoa migrate old_database_v4.sqlite --output new_database_v4.sqlite +``` + +## Configuration Files + +A configuration file is required to run the model. The tutorial command creates a complete example: + +```toml +scenario = "tutorial" +scenario_mode = "perfect_foresight" +input_database = "tutorial_database.sqlite" +output_database = "tutorial_database.sqlite" +solver_name = "appsi_highs" +``` + +### Configuration Options + +| Field | Notes | +|-------|-------| +| Scenario Name | Name used in output tables (cannot contain '-' symbol) | +| Temoa Mode | Execution mode (PERFECT_FORESIGHT, MYOPIC, MGA, etc.) | +| Input/Output DB | Source and output database paths | +| Price Checking | Run pricing analysis on built model | +| Source Tracing | Verify commodity flow network integrity | +| Plot Network | Generate HTML network visualizations | +| Solver | Solver executable name (appsi_highs, cbc, gurobi, cplex, etc.) | +| Save Excel | Export core output to Excel files | +| Save LP | Save LP model files for external solving | + +## Supported Modes + ### Perfect Foresight -All-in-one run that solves the entire model at once. It is possible to run this without source tracing, which will -use raw data in the model without checking the integrity of the underlying network. It is highly advised to use -source tracing for most accurate results. + +Solves the entire model at once. Most common mode for optimization. + ### Myopic -Solve the model sequentially through iterative solves based on Myopic settings. Source tracing is required to -accommodate build/no-build decisions made per iteration to ensure follow-on models are well built. + +Sequential solving through iterative builds. Required for stepwise decision analysis. + ### MGA (Modeling to Generate Alternatives) -An iterative solving process to explore near cost-optimal solutions. See the documentation on this mode. + +Explores near cost-optimal solutions for robustness analysis. + ### SVMGA (Single Vector MGA) -A sequence of 2 model solves that establishes a base optimal cost, then relaxes the cost then minimizes an -alternate unweighted objective function comprised of variables associated with labels selected in lists in the -config file. + +Two-solve process focusing on specific variables in the objective. + ### Method of Morris -A limited sensitivity analysis of user-selected variables using a Method of Morris approach. See the documentation -on this mode. + +Limited sensitivity analysis of user-selected variables. + ### Build Only -Mostly for test/troubleshooting. This builds/returns an un-solved model. -Several other options are possible to pass to the main execution command including changing the logging level to -`debug` or running silent (no console feedback) which may be best for server runs. Also, redirecting the output -products is possible. To see available options invoke the `main.py` file with the `-h` flag: +Builds model without solving. Useful for validation and troubleshooting. -``` -(venv) $ python main.py -h -``` +## Typical Workflow -## Typical Run -1. Prepare a database (or copy of one) as described above. Runs will fill the output tables and overwrite any data with the -same scenario name. -2. Perepare a config file with paths to the database(s) relative to the top of the project, as in the example -3. Run the model, using the `main.py` entry point from the top-level of the project: -``` -(venv) temoa $ python main.py --config data_files/my_configs/config_sample.toml -``` -4. Review the config display and accept -5. Review the log file and output products which are automatically placed in a time-stamped folder in `output_files`, -unless user has redirected output -6. Review the data in the Output tables +1. **Setup**: Create configuration and database files: -## Testing -Users who wish to exercise the `pytest` based test in the test folder can do so from the command line or any IDE. -Note that many of the tests perform solves on small models using the freely available `cbc` solver, which is -required to run the testing suite. + ```bash + temoa tutorial + ``` -The tests should all run and pass (several are currently skipped and reflect in-process work). Tests should normally -be run from the top level of the `tests folder`. If `pytest` is installed it will locate tests within the folder and -run/report them. Note the dot '.' below indicating current folder: +2. **Configure**: Edit the configuration file to match your scenario -``` -(venv) temoa/tests pytest . -``` -Several of the packages used may currently generate warnings during this testing process, but the tests should all PASS -with the exception of skipped tests. +3. **Validate**: Check configuration before running: + + ```bash + temoa validate tutorial_config.toml + ``` + +4. **Run**: Execute the model: + + ```bash + temoa run tutorial_config.toml + ``` + +5. **Review**: Check results in `output_files/YYYY-MM-DD_HHMMSS/` + +6. **Iterate**: Modify configuration and run again + +## Advanced Features + +### Extensions + +Temoa includes optional extensions for advanced analysis: + +- **Monte Carlo**: Uncertainty quantification +- **Stochastic Programming**: Scenario-based optimization +- **Method of Morris**: Sensitivity analysis + +### Data Processing + +- Excel output generation +- Graphviz network visualization +- Interactive network diagrams -## Documentation and Additional Information +### Model Validation -The full Temoa documentation can be built by following the build README file in the Documentation folder. +- Built-in validation checks +- Commodity flow verification +- Price consistency analysis -## Hot Fix for Network Plots on Windows Machines +### Solver Dependencies -Users wishing to utilize the feature to make the html network plots of the energy network using the -`plot_commodity_network` option in the config file who are working on Windows Operating System may need to make a -"hot fix" to the library code. See note here: https://github.com/robert-haas/gravis/issues/10 +TEMOA requires at least one optimization solver: -The `gravis` library which nicely makes these plots appears to currently be non-maintained and a 1-line fix is -likely needed to avoid error on Windows machines: -1. Within the `venv` that contains project dependencies, navigate to the `gravis` folder -2. Open the file `gravis/_internal/plotting/data_structures.py` and edit line 120 to include the encoding flag: +- **Free**: [HiGHS](https://ergo-code.github.io/HiGHS/) + - Included via the `highspy` Python package (automatically installed with Temoa) + - Default solver for tutorial and testing +- **Free**: [CBC](https://github.com/coin-or/Cbc) + - Requires separate installation (see [CBC documentation](https://github.com/coin-or/Cbc)) + - Alternative free solver option - `with open(filepath, 'w', encoding='utf-8') as file_handle:` +- **Commercial**: Gurobi, CPLEX, or Xpress + - Requires separate license and installation + - See individual solver documentation +## Troubleshooting -## Hot Fix for Graphviz +### Solver Issues -Users wishing to utilize the `graphviz` package to visualize results as described in the `README.md` file -in the `data_processing` package/folder may need to re-install `graphviz` using another delivery means -other than `pip`. The current `requirements.txt` will attempt to install `graphviz`, but according to -their project page, this needs to be done with another configuration manager like `apt` or `homebrew`. +If you encounter solver errors: -Mac users wishing to use `graphviz` should re-install using `homebrew` with the command: +```bash +# For commercial solvers (Gurobi, CPLEX) +pip install ".[solver]" # Include specific solver packages -`brew install graphviz` +# For free solver +temoa run tutorial_config.toml --debug # Get detailed error information +``` + +## Documentation & Support + +- **Full Documentation**: Built by following docs/README.md +- **API Reference**: See `temoa.core` module for public API +- **GitHub Issues**: Report bugs and request features +- **Tutorials**: Run `temoa tutorial` for guided examples + +## Code Style & Quality + +For contributors: + +- **Ruff**: Code formatting and linting +- **mypy**: Type checking +- **pytest**: Testing framework +- **Pre-commit**: Automated quality checks + +See CONTRIBUTING.md for detailed development guidelines. + +## Citation + +If you use Temoa in your research, please cite: + +```bibtex +@article{hunter2013modeling, + title={Modeling for insight using Tools for Energy Model Optimization and Analysis (Temoa)}, + journal={Energy Economics}, + volume={40}, + pages={339--349}, + year={2013}, + doi={10.1016/j.eneco.2013.07.014} +} +``` -(Any Windows users who have tips/info on this are asked to submit a PR to this file to update this section.) +Or use: `temoa --how-to-cite` diff --git a/data_files/.gitignore b/data_files/.gitignore deleted file mode 100644 index 7878eb8af..000000000 --- a/data_files/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# locally ignore all .dat files and newly added .sqlite db's -*.sqlite -*.dat -*.xlsx -*.log - diff --git a/data_files/README.txt b/data_files/README.txt deleted file mode 100644 index 1949067c1..000000000 --- a/data_files/README.txt +++ /dev/null @@ -1,11 +0,0 @@ -This directory contains sample files that can be used to test Temoa. - -Temoa works by reading a relational database (sqlite). - -*.sql files represent text files of SQL commands used to construct a relational database file. - -*.sqlite files represent the compiled sqlite databases. A sqlite file -is created from a sql file using sqlite, which is freely available: -https://sqlite.org/index.html. From the command prompt: - -$ sqlite3 temoa_utopia.sqlite < temoa_utopia.sql diff --git a/data_files/example_dbs/.gitignore b/data_files/example_dbs/.gitignore deleted file mode 100644 index a91930053..000000000 --- a/data_files/example_dbs/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# ignore any built databases -*.sqlite -*.db \ No newline at end of file diff --git a/data_files/example_dbs/README.txt b/data_files/example_dbs/README.txt deleted file mode 100644 index eb3a5fc2e..000000000 --- a/data_files/example_dbs/README.txt +++ /dev/null @@ -1,8 +0,0 @@ -The exemplar databases here are version controlled in textual .sql format, which is more workable -with Git than binary .sqlite files. - -If you wish to use them, use sqlite to convert them to database format. The command is: - -> sqlite3 utopia.sqlite < utopia.sql - -This command will make the sqlite database from the sql commands \ No newline at end of file diff --git a/data_files/example_dbs/morris_utopia.sql b/data_files/example_dbs/morris_utopia.sql deleted file mode 100644 index 9eb1b62b8..000000000 --- a/data_files/example_dbs/morris_utopia.sql +++ /dev/null @@ -1,1348 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('myopic_base_year',2000,'Base Year for Myopic Analysis'); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','day','E31',2000,0.27530000000000001136,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','night','E31',2000,0.27530000000000001136,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','day','E31',2000,0.27530000000000001136,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','night','E31',2000,0.27530000000000001136,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','day','E31',2000,0.27530000000000001136,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','night','E31',2000,0.27530000000000001136,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','day','E31',2010,0.27560000000000002273,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','night','E31',2010,0.27560000000000002273,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','day','E31',2010,0.27560000000000002273,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','night','E31',2010,0.27560000000000002273,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','day','E31',2010,0.27560000000000002273,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','night','E31',2010,0.27560000000000002273,''); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E01',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E01',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E01',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E01',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E01',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E01',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E21',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E21',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E21',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E21',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E21',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E21',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E31',0.275,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E31',0.275,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E31',0.275,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E31',0.275,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E31',0.275,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E31',0.275,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E51',0.17000000000000001776,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E51',0.17000000000000001776,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E51',0.17000000000000001776,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E51',0.17000000000000001776,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E51',0.17000000000000001776,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E51',0.17000000000000001776,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E70',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E70',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E70',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E70',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E70',0.8,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E70',0.8,''); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO CapacityToActivity VALUES('utopia','E01',31.539999999999999147,''); -INSERT INTO CapacityToActivity VALUES('utopia','E21',31.539999999999999147,''); -INSERT INTO CapacityToActivity VALUES('utopia','E31',31.539999999999999147,''); -INSERT INTO CapacityToActivity VALUES('utopia','E51',31.539999999999999147,''); -INSERT INTO CapacityToActivity VALUES('utopia','E70',31.539999999999999147,''); -INSERT INTO CapacityToActivity VALUES('utopia','RHE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','RHO',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','RL1',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','SRE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXD',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXG',1.0,''); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ethos','s','# dummy commodity to supply inputs (makes graph easier to read)'); -INSERT INTO Commodity VALUES('DSL','p','# diesel'); -INSERT INTO Commodity VALUES('ELC','p','# electricity'); -INSERT INTO Commodity VALUES('FEQ','p','# fossil equivalent'); -INSERT INTO Commodity VALUES('GSL','p','# gasoline'); -INSERT INTO Commodity VALUES('HCO','p','# coal'); -INSERT INTO Commodity VALUES('HYD','p','# water'); -INSERT INTO Commodity VALUES('OIL','p','# crude oil'); -INSERT INTO Commodity VALUES('URN','p','# uranium'); -INSERT INTO Commodity VALUES('co2','e','#CO2 emissions'); -INSERT INTO Commodity VALUES('nox','e','#NOX emissions'); -INSERT INTO Commodity VALUES('RH','d','# residential heating'); -INSERT INTO Commodity VALUES('RL','d','# residential lighting'); -INSERT INTO Commodity VALUES('TX','d','# transportation'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('s','source commodity'); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -INSERT INTO CostEmission VALUES('utopia',2000,'nox',5.0,NULL,NULL); -INSERT INTO CostEmission VALUES('utopia',2010,'co2',6.0,NULL,NULL); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1960,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1970,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1980,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1990,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1970,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1980,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1990,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',2000,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',1980,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',1990,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',2000,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',2010,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E21',2000,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',2000,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',2010,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',2000,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',2000,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',2010,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',2010,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1960,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1970,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1970,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',2010,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1970,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1980,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1980,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',2000,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2000,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2010,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1980,9.4600000000000008526,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1990,9.4600000000000008526,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RL1',2000,9.4600000000000008526,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RL1',2010,9.4600000000000008526,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1970,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1980,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1990,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1980,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1990,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',2000,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2000,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2010,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXE',1990,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXE',1990,90.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXE',2000,90.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2000,80.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2010,80.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1970,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1980,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1990,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1980,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1990,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',2000,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2000,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2010,48.0,'',''); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('utopia',1990,'RH',25.200000000000000177,'',''); -INSERT INTO Demand VALUES('utopia',2000,'RH',37.799999999999998046,'',''); -INSERT INTO Demand VALUES('utopia',2010,'RH',56.699999999999999289,'',''); -INSERT INTO Demand VALUES('utopia',1990,'RL',5.5999999999999996447,'',''); -INSERT INTO Demand VALUES('utopia',2000,'RL',8.4000000000000003552,'',''); -INSERT INTO Demand VALUES('utopia',2010,'RL',12.600000000000000088,'',''); -INSERT INTO Demand VALUES('utopia',1990,'TX',5.2000000000000001776,'',''); -INSERT INTO Demand VALUES('utopia',2000,'TX',7.7999999999999998223,'',''); -INSERT INTO Demand VALUES('utopia',2010,'TX',11.69000000000000039,'',''); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','night','RH',0.06,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','day','RH',0.54669999999999996376,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','night','RH',0.27329999999999996518,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','day','RL',0.15,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','night','RL',0.05,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','summer','day','RL',0.15,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','summer','night','RL',0.05,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','day','RL',0.5,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','night','RL',0.1,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','day','RH',0.11999999999999999644,NULL); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.075,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.075,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',0.088999999999999985789,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.075,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'',''); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1960,0.175,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1970,0.175,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1980,0.15,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E31',1980,0.1,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E51',1980,0.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1960,0.05,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1970,0.05,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1980,0.2,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RHO',1970,12.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RHO',1980,12.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RL1',1980,5.5999999999999996447,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXD',1970,0.4,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXD',1980,0.2,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXG',1970,3.1000000000000000888,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXG',1980,1.5,'',''); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LoanLifetimeTech VALUES('utopia','E01',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E21',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E31',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E51',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E70',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RHE',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RHO',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RL1',10.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','SRE',50.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXD',15.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXE',15.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXG',15.0,''); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO LifetimeProcess VALUES('utopia','RL1',1980,20.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXD',1970,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXD',1980,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXG',1970,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXG',1980,30.0,'#forexistingcap'); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('utopia','E01',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E21',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E31',100.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E51',100.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E70',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RHE',30.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RHO',30.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RL1',10.0,''); -INSERT INTO LifetimeTech VALUES('utopia','SRE',50.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXD',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXE',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXG',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPDSL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPGSL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPHCO1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPOIL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPURN1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPHYD',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPFEQ',1000.0,''); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxCapacity VALUES('utopia',1990,'E31',0.13000000000000000444,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2000,'E31',0.17000000000000001776,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2010,'E31',0.21000000000000000888,'',''); -INSERT INTO MaxCapacity VALUES('utopia',1990,'RHE',0.0,'',''); -INSERT INTO MaxCapacity VALUES('utopia',1990,'TXD',0.6,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2000,'TXD',1.7599999999999999644,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2010,'TXD',4.7599999999999997868,'',''); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinCapacity VALUES('utopia',1990,'E31',0.13000000000000000444,'',''); -INSERT INTO MinCapacity VALUES('utopia',2000,'E31',0.13000000000000000444,'',''); -INSERT INTO MinCapacity VALUES('utopia',2010,'E31',0.13000000000000000444,'',''); -INSERT INTO MinCapacity VALUES('utopia',1990,'SRE',0.1,'',''); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('utopia',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('inter','day',0.16669999999999998152,'# I-D'); -INSERT INTO TimeSegmentFraction VALUES('inter','night',0.08330000000000000071,'# I-N'); -INSERT INTO TimeSegmentFraction VALUES('summer','day',0.16669999999999998152,'# S-D'); -INSERT INTO TimeSegmentFraction VALUES('summer','night',0.08330000000000000071,'# S-N'); -INSERT INTO TimeSegmentFraction VALUES('winter','day',0.33329999999999997406,'# W-D'); -INSERT INTO TimeSegmentFraction VALUES('winter','night',0.16669999999999998152,'# W-N'); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -INSERT INTO TechOutputSplit VALUES('utopia',1990,'SRE','DSL',0.7,''); -INSERT INTO TechOutputSplit VALUES('utopia',2000,'SRE','DSL',0.7,''); -INSERT INTO TechOutputSplit VALUES('utopia',2010,'SRE','DSL',0.7,''); -INSERT INTO TechOutputSplit VALUES('utopia',1990,'SRE','GSL',0.3,''); -INSERT INTO TechOutputSplit VALUES('utopia',2000,'SRE','GSL',0.3,''); -INSERT INTO TechOutputSplit VALUES('utopia',2010,'SRE','GSL',0.3,''); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'day'); -INSERT INTO TimeOfDay VALUES(2,'night'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,1960,'e'); -INSERT INTO TimePeriod VALUES(2,1970,'e'); -INSERT INTO TimePeriod VALUES(3,1980,'e'); -INSERT INTO TimePeriod VALUES(4,1990,'f'); -INSERT INTO TimePeriod VALUES(5,2000,'f'); -INSERT INTO TimePeriod VALUES(6,2010,'f'); -INSERT INTO TimePeriod VALUES(7,2020,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(2,'summer'); -INSERT INTO TimeSeason VALUES(3,'winter'); -INSERT INTO TimeSeason VALUES(1,'inter'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('IMPDSL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); -INSERT INTO Technology VALUES('IMPGSL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); -INSERT INTO Technology VALUES('IMPHCO1','r','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); -INSERT INTO Technology VALUES('IMPOIL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); -INSERT INTO Technology VALUES('IMPURN1','r','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); -INSERT INTO Technology VALUES('IMPFEQ','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); -INSERT INTO Technology VALUES('IMPHYD','r','supply','','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); -INSERT INTO Technology VALUES('E01','pb','electric','coal','',0,0,0,1,1,0,0,0,' coal power plant'); -INSERT INTO Technology VALUES('E21','pb','electric','nuclear','',0,0,0,1,1,0,0,0,' nuclear power plant'); -INSERT INTO Technology VALUES('E31','pb','electric','hydro','',0,0,0,1,1,0,0,0,' hydro power'); -INSERT INTO Technology VALUES('E51','ps','electric','electric','',0,0,0,1,0,0,0,0,' electric storage'); -INSERT INTO Technology VALUES('E70','p','electric','petroleum','',0,0,0,1,1,0,0,0,' diesel power plant'); -INSERT INTO Technology VALUES('RHE','p','residential','electric','',0,0,0,1,1,0,0,0,' electric residential heating'); -INSERT INTO Technology VALUES('RHO','p','residential','petroleum','',0,0,0,1,1,0,0,0,' diesel residential heating'); -INSERT INTO Technology VALUES('RL1','p','residential','electric','',0,0,0,1,1,0,0,0,' residential lighting'); -INSERT INTO Technology VALUES('SRE','p','supply','petroleum','',0,0,0,1,1,0,0,0,' crude oil processor'); -INSERT INTO Technology VALUES('TXD','p','transport','petroleum','',0,0,0,1,1,0,0,0,' diesel powered vehicles'); -INSERT INTO Technology VALUES('TXE','p','transport','electric','',0,0,0,1,1,0,0,0,' electric powered vehicles'); -INSERT INTO Technology VALUES('TXG','p','transport','petroleum','',0,0,0,1,1,0,0,0,' gasoline powered vehicles'); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -CREATE TABLE IF NOT EXISTS "CostInvest" -( - region TEXT, - tech TEXT - references Technology, - vintage INTEGER - references TimePeriod, - cost REAL, - units TEXT, - notes TEXT, - MMAnalysis TEXT, - primary key (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('utopia','E01',1990,2000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E01',2000,1300.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E01',2010,1200.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E21',1990,5000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E21',2000,5000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E21',2010,5000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E31',1990,3000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E31',2000,3000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E31',2010,3000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E51',1990,900.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E51',2000,900.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E51',2010,900.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E70',1990,1000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E70',2000,1000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','E70',2010,1000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','RHE',1990,90.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','RHE',2000,90.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','RHE',2010,90.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','RHO',1990,100.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','RHO',2000,100.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','RHO',2010,100.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','SRE',1990,100.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','SRE',2000,100.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','SRE',2010,100.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXD',1990,1044.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXD',2000,1044.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXD',2010,1044.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXE',1990,2000.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXE',2000,1750.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXE',2010,1500.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXG',1990,1044.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXG',2000,1044.0,'','',NULL); -INSERT INTO CostInvest VALUES('utopia','TXG',2010,1044.0,'','',NULL); -CREATE TABLE IF NOT EXISTS "CostVariable" -( - region TEXT not null, - period INTEGER not null - references TimePeriod, - tech TEXT not null - references Technology, - vintage INTEGER not null - references TimePeriod, - cost REAL, - units TEXT, - notes TEXT, - MMAnalysis TEXT, - primary key (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPDSL1',1990,10.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPDSL1',1990,10.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPDSL1',1990,10.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPGSL1',1990,15.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPGSL1',1990,15.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPGSL1',1990,15.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPHCO1',1990,2.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPHCO1',1990,2.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPHCO1',1990,2.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPOIL1',1990,8.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPOIL1',1990,8.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPOIL1',1990,8.0,'','','OIL_COST'); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPURN1',1990,2.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPURN1',1990,2.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPURN1',1990,2.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1960,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1970,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1980,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1990,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1970,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1980,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1990,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',2000,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',1980,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',1990,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',2000,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',2010,0.3,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E21',1990,1.5,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E21',1990,1.5,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',1990,1.5,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E21',2000,1.5,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',2000,1.5,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',2010,1.5,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1960,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1970,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1980,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1990,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1970,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1980,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1990,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',2000,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',1980,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',1990,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',2000,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',2010,0.4,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',1990,'SRE',1990,10.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'SRE',1990,10.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2000,'SRE',2000,10.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',1990,10.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2000,10.0,'','',NULL); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2010,10.0,'','',NULL); -CREATE TABLE IF NOT EXISTS "Efficiency" -( - region TEXT, - input_comm TEXT - references Commodity, - tech TEXT - references Technology, - vintage INTEGER - references TimePeriod, - output_comm TEXT - references Commodity, - efficiency REAL, - notes TEXT, - MMAnalysis TEXT, - primary key (region, input_comm, tech, vintage, output_comm), - check (efficiency > 0) -); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,'',NULL); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,'',NULL); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,'',NULL); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,'',NULL); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,'',NULL); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,'',NULL); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,'',NULL); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1960,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1970,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1980,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1990,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',2000,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',2010,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',1990,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2000,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2010,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','URN','E21',1990,'ELC',0.4,'# 1/2.5',NULL); -INSERT INTO Efficiency VALUES('utopia','URN','E21',2000,'ELC',0.4,'# 1/2.5',NULL); -INSERT INTO Efficiency VALUES('utopia','URN','E21',2010,'ELC',0.4,'# 1/2.5',NULL); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',1980,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',1990,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',2000,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',2010,'ELC',0.32000000000000001776,'# 1/3.125',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1960,'ELC',0.29399999999999998578,'# 1/3.4',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1970,'ELC',0.29399999999999998578,'# 1/3.4',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1980,'ELC',0.29399999999999998578,'# 1/3.4',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1990,'ELC',0.29399999999999998578,'# 1/3.4',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',2000,'ELC',0.29399999999999998578,'# 1/3.4',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',2010,'ELC',0.29399999999999998578,'# 1/3.4',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',1980,'ELC',0.71999999999999992894,'# 1/1.3889',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',1990,'ELC',0.71999999999999992894,'# 1/1.3889',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',2000,'ELC',0.71999999999999992894,'# 1/1.3889',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',2010,'ELC',0.71999999999999992894,'# 1/1.3889',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',1990,'RH',1.0,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2000,'RH',1.0,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2010,'RH',1.0,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1970,'RH',0.7,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1980,'RH',0.7,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1990,'RH',0.7,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2000,'RH',0.7,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2010,'RH',0.7,'# direct translation from DMD_EFF','Res_heating_2010_eff'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1980,'RL',1.0,'# direct translation from DMD_EFF','Res_lighting_eff'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1990,'RL',1.0,'# direct translation from DMD_EFF','Res_lighting_eff'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2000,'RL',1.0,'# direct translation from DMD_EFF','Res_lighting_eff'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2010,'RL',1.0,'# direct translation from DMD_EFF','Res_lighting_eff'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT',NULL); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT',NULL); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT',NULL); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT',NULL); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT',NULL); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1970,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1980,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1990,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2000,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2010,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',1990,'TX',0.82699999999999995736,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2000,'TX',0.82699999999999995736,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2010,'TX',0.82699999999999995736,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1970,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1980,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1990,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2000,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2010,'TX',0.23100000000000000532,'# direct translation from DMD_EFF',NULL); -COMMIT; diff --git a/data_files/example_dbs/stepped_demand.sql b/data_files/example_dbs/stepped_demand.sql deleted file mode 100644 index 6b6fce42d..000000000 --- a/data_files/example_dbs/stepped_demand.sql +++ /dev/null @@ -1,1417 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -INSERT INTO MetaData VALUES('myopic_base_year',2000,''); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05000000000000000277,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorTech VALUES('electricville','inter','day','EF',1.0,''); -INSERT INTO CapacityFactorTech VALUES('electricville','winter','day','EF',1.0,''); -INSERT INTO CapacityFactorTech VALUES('electricville','summer','day','EF',1.0,''); -INSERT INTO CapacityFactorTech VALUES('electricville','inter','day','EH',1.0,''); -INSERT INTO CapacityFactorTech VALUES('electricville','winter','day','EH',1.0,''); -INSERT INTO CapacityFactorTech VALUES('electricville','summer','day','EH',1.0,''); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO CapacityToActivity VALUES('electricville','bulbs',1.0,''); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ELC','p','# electricity'); -INSERT INTO Commodity VALUES('HYD','p','# water'); -INSERT INTO Commodity VALUES('co2','e','#CO2 emissions'); -INSERT INTO Commodity VALUES('RL','d','# residential lighting'); -INSERT INTO Commodity VALUES('earth','s','# the source of stuff'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('s','source commodity'); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostFixed VALUES('electricville',2000,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2005,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2010,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2015,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2020,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2025,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2035,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2040,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2045,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2050,'EH',1995,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2010,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2015,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2020,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2025,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2030,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2035,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2040,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2045,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2050,'EF',2010,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2000,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2005,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2010,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2015,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2020,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2025,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2030,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2035,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2040,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2045,'EH',2000,2.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2050,'EH',2000,2.0,'',''); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('electricville','EF',2010,200.0,'',''); -INSERT INTO CostInvest VALUES('electricville','EH',2000,100.0,'',''); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('electricville',2010,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2015,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2020,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2025,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2030,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2035,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2040,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2045,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2050,'EF',2010,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2000,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2005,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2010,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2015,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2020,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2025,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2030,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2035,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2040,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2045,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2050,'EH',1995,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2000,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2005,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2010,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2015,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2020,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2025,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2030,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2035,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2040,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2045,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2050,'EH',2000,2.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2000,'well',2000,1.0,NULL,NULL); -INSERT INTO CostVariable VALUES('electricville',2010,'well',2000,1.0,NULL,NULL); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('electricville',2000,'RL',2.0,'',''); -INSERT INTO Demand VALUES('electricville',2005,'RL',2.0,'',''); -INSERT INTO Demand VALUES('electricville',2010,'RL',2.0,'',''); -INSERT INTO Demand VALUES('electricville',2015,'RL',2.0,'',''); -INSERT INTO Demand VALUES('electricville',2020,'RL',10.0,'',''); -INSERT INTO Demand VALUES('electricville',2025,'RL',10.0,'',''); -INSERT INTO Demand VALUES('electricville',2030,'RL',10.0,'',''); -INSERT INTO Demand VALUES('electricville',2035,'RL',50.0,'',''); -INSERT INTO Demand VALUES('electricville',2040,'RL',10.0,NULL,NULL); -INSERT INTO Demand VALUES('electricville',2045,'RL',10.0,NULL,NULL); -INSERT INTO Demand VALUES('electricville',2050,'RL',2.0,NULL,NULL); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('electricville','inter','day','RL',0.3332999999999999852,''); -INSERT INTO DemandSpecificDistribution VALUES('electricville','summer','day','RL',0.3332999999999999852,''); -INSERT INTO DemandSpecificDistribution VALUES('electricville','winter','day','RL',0.3332999999999999852,''); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('electricville','HYD','EH',1995,'ELC',1.0,'est'); -INSERT INTO Efficiency VALUES('electricville','HYD','EH',2000,'ELC',1.0,'est'); -INSERT INTO Efficiency VALUES('electricville','HYD','EF',2010,'ELC',10.0,'est'); -INSERT INTO Efficiency VALUES('electricville','ELC','bulbs',2000,'RL',1.0,NULL); -INSERT INTO Efficiency VALUES('electricville','earth','well',2000,'HYD',1.0,'water source'); -INSERT INTO Efficiency VALUES('electricville','HYD','EH',2020,'ELC',1.0,NULL); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('electricville','co2','HYD','EH',1995,'ELC',0.05000000000000000277,'',''); -INSERT INTO EmissionActivity VALUES('electricville','co2','HYD','EF',2010,'ELC',0.0100000000000000002,'',''); -INSERT INTO EmissionActivity VALUES('electricville','co2','HYD','EH',2000,'ELC',0.02000000000000000041,NULL,NULL); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO ExistingCapacity VALUES('electricville','EH',1995,0.5,'',''); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO TechGroup VALUES('RPS_global',''); -INSERT INTO TechGroup VALUES('RPS_common',''); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LoanLifetimeTech VALUES('electricville','EF',50.0,''); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO LifetimeProcess VALUES('electricville','EH',1995,80.0,'#forexistingcap'); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('electricville','EH',100.0,''); -INSERT INTO LifetimeTech VALUES('electricville','EF',100.0,''); -INSERT INTO LifetimeTech VALUES('electricville','bulbs',100.0,'super LED!'); -INSERT INTO LifetimeTech VALUES('electricville','well',100.0,NULL); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxCapacity VALUES('electricville',2000,'EH',5.0,'',''); -INSERT INTO MaxCapacity VALUES('electricville',2005,'EH',5.0,'',''); -INSERT INTO MaxCapacity VALUES('electricville',2010,'EH',5.0,'',''); -INSERT INTO MaxCapacity VALUES('electricville',2015,'EH',5.0,'',''); -INSERT INTO MaxCapacity VALUES('electricville',2020,'EH',5.0,'',''); -INSERT INTO MaxCapacity VALUES('electricville',2025,'EH',5.0,'',''); -INSERT INTO MaxCapacity VALUES('electricville',2030,'EH',5.0,'',''); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinCapacity VALUES('electricville',2000,'EH',0.2000000000000000111,'',''); -INSERT INTO MinCapacity VALUES('electricville',2005,'EH',0.2000000000000000111,'',''); -INSERT INTO MinCapacity VALUES('electricville',2010,'EH',0.2000000000000000111,'',''); -INSERT INTO MinCapacity VALUES('electricville',2015,'EH',0.2000000000000000111,'',''); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2000,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2000,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2005,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2005,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2025,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2015,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2010,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2020,'EH',2020,3.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2030,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2020,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2020,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2010,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2030,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2025,'EH',2020,3.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2025,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2015,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2020,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2025,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2015,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2010,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2030,'EH',2020,3.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2030,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2035,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2035,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2035,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2035,'EH',2020,3.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2040,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2040,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2040,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2040,'EH',2020,3.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2045,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2045,'EH',1995,0.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2045,'EH',2020,3.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2045,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2050,'EH',2020,3.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2050,'EF',2010,45.0); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2050,'EH',2000,1.5); -INSERT INTO OutputNetCapacity VALUES('myo_1','electricville','electric',2050,'EH',1995,0.5); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -INSERT INTO OutputBuiltCapacity VALUES('myo_1','electricville','electric','EH',2000,1.5); -INSERT INTO OutputBuiltCapacity VALUES('myo_1','electricville','electric','EF',2010,45.0); -INSERT INTO OutputBuiltCapacity VALUES('myo_1','electricville','electric','EH',2020,3.0); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2000,'summer','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2000,'inter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2000,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2000,'summer','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2000,'winter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2000,'winter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2000,'summer','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2000,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2000,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2000,'inter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2000,'inter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2000,'winter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2005,'inter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2005,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2005,'inter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2005,'winter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2005,'summer','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2005,'summer','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2005,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2005,'inter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2005,'winter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2005,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2005,'winter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2005,'summer','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2020,'winter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2030,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2020,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2025,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2020,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2020,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2015,'summer','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2010,'winter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2025,'inter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2020,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2020,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2025,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2010,'inter','day','HYD','EF',2010,'ELC',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2030,'winter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2025,'winter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2025,'summer','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2010,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2015,'winter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2020,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2025,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2010,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2020,'summer','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2020,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2015,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2025,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2020,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2015,'winter','day','HYD','EF',2010,'ELC',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2030,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2030,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2010,'summer','day','HYD','EF',2010,'ELC',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2025,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2010,'inter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2025,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2015,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2015,'inter','day','HYD','EF',2010,'ELC',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2015,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2030,'inter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2015,'inter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2025,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2015,'summer','day','HYD','EF',2010,'ELC',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2030,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2030,'summer','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2030,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2020,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2020,'inter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2030,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2010,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2030,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2010,'summer','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2030,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2010,'winter','day','HYD','EF',2010,'ELC',0.06665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2025,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2030,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2025,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EF',2010,'ELC',1.499849999999999906); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2035,'winter','day','earth','well',2000,'HYD',3.166349999999999998); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2035,'inter','day','ELC','bulbs',2000,'RL',16.66499999999999915); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2035,'summer','day','ELC','bulbs',2000,'RL',16.66499999999999915); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2035,'inter','day','earth','well',2000,'HYD',3.166349999999999998); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EF',2010,'ELC',1.499849999999999906); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2035,'winter','day','ELC','bulbs',2000,'RL',16.66499999999999915); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EF',2010,'ELC',1.499849999999999906); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2035,'summer','day','earth','well',2000,'HYD',3.166349999999999998); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2040,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2040,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2040,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2040,'inter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2040,'summer','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2040,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2040,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2040,'winter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2040,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2040,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2040,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2040,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2045,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2045,'summer','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2045,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2045,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2045,'inter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2045,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2045,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2045,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2045,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2045,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2045,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2045,'winter','day','HYD','EF',2010,'ELC',0.2333099999999999897); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2050,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2050,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2050,'summer','day','HYD','EH',2020,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','residential',2050,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2050,'winter','day','HYD','EH',2020,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2050,'inter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2050,'summer','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','supply',2050,'winter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowIn VALUES('myo_1','electricville','electric',2050,'inter','day','HYD','EH',2020,'ELC',0.6665999999999999704); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2000,'summer','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2000,'inter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2000,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2000,'summer','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2000,'winter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2000,'winter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2000,'summer','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2000,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2000,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2000,'inter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2000,'inter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2000,'winter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2005,'inter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2005,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2005,'inter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2005,'winter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2005,'summer','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2005,'summer','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2005,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2005,'inter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2005,'winter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2005,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2005,'winter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2005,'summer','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2020,'winter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2030,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2020,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2025,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2020,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2020,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2015,'summer','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2010,'winter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2025,'inter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2020,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2020,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2025,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2010,'inter','day','HYD','EF',2010,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2030,'winter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2025,'winter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2025,'summer','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2010,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2015,'winter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2020,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2025,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2010,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2020,'summer','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2020,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2015,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2025,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2020,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2015,'winter','day','HYD','EF',2010,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2030,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2030,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2010,'summer','day','HYD','EF',2010,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2025,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2010,'inter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2025,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2015,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2015,'inter','day','HYD','EF',2010,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2015,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2030,'inter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2015,'inter','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2025,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2015,'summer','day','HYD','EF',2010,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2030,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2030,'summer','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2030,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2020,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2020,'inter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2030,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2010,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2030,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2010,'summer','day','earth','well',2000,'HYD',0.06665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2030,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2010,'winter','day','HYD','EF',2010,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2025,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2030,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2025,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EF',2010,'ELC',14.99849999999999995); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2035,'winter','day','earth','well',2000,'HYD',3.166349999999999998); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2035,'inter','day','ELC','bulbs',2000,'RL',16.66499999999999915); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2035,'summer','day','ELC','bulbs',2000,'RL',16.66499999999999915); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2035,'inter','day','earth','well',2000,'HYD',3.166349999999999998); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EF',2010,'ELC',14.99849999999999995); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'summer','day','HYD','EH',1995,'ELC',0.1666499999999999926); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2035,'winter','day','ELC','bulbs',2000,'RL',16.66499999999999915); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'inter','day','HYD','EF',2010,'ELC',14.99849999999999995); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2035,'winter','day','HYD','EH',2000,'ELC',0.4999500000000000055); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2035,'summer','day','earth','well',2000,'HYD',3.166349999999999998); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2040,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2040,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2040,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2040,'inter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2040,'summer','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2040,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2040,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2040,'winter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2040,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2040,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2040,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2040,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2045,'summer','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2045,'summer','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2045,'inter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2045,'inter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2045,'inter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2045,'winter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2045,'summer','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2045,'winter','day','earth','well',2000,'HYD',1.233209999999999918); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2045,'summer','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2045,'winter','day','HYD','EH',2020,'ELC',0.999900000000000011); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2045,'inter','day','ELC','bulbs',2000,'RL',3.333000000000000184); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2045,'winter','day','HYD','EF',2010,'ELC',2.333099999999999952); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2050,'inter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2050,'winter','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2050,'summer','day','HYD','EH',2020,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','residential',2050,'summer','day','ELC','bulbs',2000,'RL',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2050,'winter','day','HYD','EH',2020,'ELC',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2050,'inter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2050,'summer','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','supply',2050,'winter','day','earth','well',2000,'HYD',0.6665999999999999704); -INSERT INTO OutputFlowOut VALUES('myo_1','electricville','electric',2050,'inter','day','HYD','EH',2020,'ELC',0.6665999999999999704); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('electricville',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('inter','day',0.3332999999999999852,'# I-D'); -INSERT INTO TimeSegmentFraction VALUES('summer','day',0.3332999999999999852,'# S-D'); -INSERT INTO TimeSegmentFraction VALUES('winter','day',0.3332999999999999852,'# W-D'); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'day'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,1995,'e'); -INSERT INTO TimePeriod VALUES(2,2000,'f'); -INSERT INTO TimePeriod VALUES(3,2005,'f'); -INSERT INTO TimePeriod VALUES(4,2010,'f'); -INSERT INTO TimePeriod VALUES(5,2015,'f'); -INSERT INTO TimePeriod VALUES(6,2020,'f'); -INSERT INTO TimePeriod VALUES(7,2025,'f'); -INSERT INTO TimePeriod VALUES(8,2030,'f'); -INSERT INTO TimePeriod VALUES(9,2035,'f'); -INSERT INTO TimePeriod VALUES(10,2040,'f'); -INSERT INTO TimePeriod VALUES(11,2045,'f'); -INSERT INTO TimePeriod VALUES(12,2050,'f'); -INSERT INTO TimePeriod VALUES(13,2055,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'inter'); -INSERT INTO TimeSeason VALUES(2,'summer'); -INSERT INTO TimeSeason VALUES(3,'winter'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2000,'co2','EH',2000,0.02999700000000000283); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2000,'co2','EH',1995,0.02499749999999999889); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2005,'co2','EH',1995,0.02499749999999999889); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2005,'co2','EH',2000,0.02999700000000000283); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2010,'co2','EF',2010,0.01999800000000000189); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2015,'co2','EF',2010,0.01999800000000000189); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2020,'co2','EF',2010,0.06999299999999999967); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2025,'co2','EF',2010,0.06999299999999999967); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2030,'co2','EF',2010,0.06999299999999999967); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2035,'co2','EF',2010,0.4499549999999999939); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2035,'co2','EH',1995,0.02499749999999999889); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2035,'co2','EH',2000,0.02999700000000000283); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2040,'co2','EF',2010,0.06999299999999999967); -INSERT INTO OutputEmission VALUES('myo_1','electricville','electric',2045,'co2','EF',2010,0.06999299999999999967); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('well','r','supply','water','',1,0,0,0,0,0,0,0,'plain old water'); -INSERT INTO Technology VALUES('bulbs','p','residential','electric','',1,0,0,0,0,0,0,0,' residential lighting'); -INSERT INTO Technology VALUES('EH','p','electric','hydro','',0,0,0,0,0,0,0,0,'hydro power electric plant'); -INSERT INTO Technology VALUES('EF','p','electric','electric','',0,0,0,0,0,0,0,0,'fusion plant'); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -INSERT INTO OutputCost VALUES('myo_1','electricville',2000,'EH',1995,0.0,4.545950504162363793,4.545495909111947341,0.0,0.0,5.0,4.999500000000000277,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2000,'EH',2000,61.27462483904239577,13.63785151248709226,13.63648772733584202,0.0,194.2568624481849327,15.0,14.99849999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2000,'well',2000,0.0,0.0,9.090991818223894682,0.0,0.0,0.0,9.99900000000000055,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2005,'EH',1995,0.0,3.561871171481695076,3.561514984364547054,0.0,0.0,5.0,4.999500000000000277,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2005,'EH',2000,0.0,10.68561351444508566,10.68454495309364027,0.0,0.0,15.0,14.99849999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2010,'EH',1995,0.0,2.790819264445571157,0.0,0.0,0.0,5.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2015,'EH',1995,0.0,2.186679919577362518,0.0,0.0,0.0,5.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2020,'EH',1995,0.0,1.713320934680008679,0.0,0.0,0.0,5.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2025,'EH',1995,0.0,1.342431783879983964,0.0,0.0,0.0,5.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2010,'EH',2000,0.0,8.37245779333671436,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2015,'EH',2000,0.0,6.560039758732087555,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2020,'EH',2000,0.0,5.139962804040026256,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2025,'EH',2000,0.0,4.027295351639951448,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2030,'EH',2000,0.0,3.155491288106695436,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2010,'well',2000,0.0,0.0,0.5581080365038253443,0.0,0.0,0.0,0.999900000000000011,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2010,'EF',2010,4493.317939551132441,251.1737338001014166,11.16216073007650599,0.0,14789.71858114884889,450.0,19.9980000000000011,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2015,'EF',2010,0.0,196.8011927619626248,8.745845006341619765,0.0,0.0,450.0,19.9980000000000011,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2020,'EF',2010,0.0,154.1988841212007913,23.98409443621157066,0.0,0.0,450.0,69.992999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2025,'EF',2010,0.0,120.8188605491985613,18.79216556982234465,0.0,0.0,450.0,69.992999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2030,'EF',2010,0.0,94.664738643200863,14.72415344856346131,0.0,0.0,450.0,69.992999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2035,'EH',1995,0.0,0.8241366640982862312,0.8240542504318764116,0.0,0.0,5.0,4.999500000000000277,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2035,'EH',2000,0.0,2.472409992294858583,2.472162751295629235,0.0,0.0,15.0,14.99849999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2035,'EF',2010,0.0,74.17229976884576104,74.1648825388688806,0.0,0.0,450.0,449.9549999999999841,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2040,'EH',1995,0.0,0.6457326410670342077,0.0,0.0,0.0,5.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2040,'EH',2000,0.0,1.937197923201102512,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2040,'EF',2010,0.0,58.1159376960330789,9.039352949240985425,0.0,0.0,450.0,69.992999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2045,'EH',1995,0.0,0.5059484208188066435,0.0,0.0,0.0,5.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2045,'EH',2000,0.0,1.517845262456420042,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2045,'EF',2010,0.0,45.53535787369259679,7.082569563674146807,0.0,0.0,450.0,69.992999999999995,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2050,'EH',1995,0.0,0.3964238265949301953,0.0,0.0,0.0,5.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2050,'EH',2000,0.0,1.18927147978479053,0.0,0.0,0.0,15.0,0.0,0.0); -INSERT INTO OutputCost VALUES('myo_1','electricville',2050,'EF',2010,0.0,35.67814439354371813,0.0,0.0,0.0,450.0,0.0,0.0); -CREATE TABLE MyopicEfficiency -( - base_year integer, - region text, - input_comm text, - tech text, - vintage integer, - output_comm text, - efficiency real, - lifetime integer, - - FOREIGN KEY (tech) REFERENCES Technology (tech), - PRIMARY KEY (region, input_comm, tech, vintage, output_comm) -); -INSERT INTO MyopicEfficiency VALUES(-1,'electricville','HYD','EH',1995,'ELC',1.0,80); -INSERT INTO MyopicEfficiency VALUES(2000,'electricville','HYD','EH',2000,'ELC',1.0,100); -INSERT INTO MyopicEfficiency VALUES(2000,'electricville','ELC','bulbs',2000,'RL',1.0,100); -INSERT INTO MyopicEfficiency VALUES(2000,'electricville','earth','well',2000,'HYD',1.0,100); -INSERT INTO MyopicEfficiency VALUES(2010,'electricville','HYD','EF',2010,'ELC',10.0,100); -INSERT INTO MyopicEfficiency VALUES(2010,'electricville','HYD','EH',2020,'ELC',1.0,100); -CREATE INDEX region_tech_vintage ON MyopicEfficiency (region, tech, vintage); -COMMIT; diff --git a/data_files/example_dbs/test_system.sql b/data_files/example_dbs/test_system.sql deleted file mode 100644 index 35767f2af..000000000 --- a/data_files/example_dbs/test_system.sql +++ /dev/null @@ -1,1079 +0,0 @@ -BEGIN TRANSACTION; -CREATE TABLE "time_season" ( - "t_season" text, - PRIMARY KEY("t_season") -); -INSERT INTO `time_season` VALUES ('spring'); -INSERT INTO `time_season` VALUES ('summer'); -INSERT INTO `time_season` VALUES ('fall'); -INSERT INTO `time_season` VALUES ('winter'); -CREATE TABLE "time_periods" ( - "t_periods" integer, - "flag" text, - PRIMARY KEY("t_periods"), - FOREIGN KEY("flag") REFERENCES "time_period_labels"("t_period_labels") -); -INSERT INTO `time_periods` VALUES (2015,'e'); -INSERT INTO `time_periods` VALUES (2020,'f'); -INSERT INTO `time_periods` VALUES (2025,'f'); -INSERT INTO `time_periods` VALUES (2030,'f'); -INSERT INTO `time_periods` VALUES (2035,'f'); -CREATE TABLE "time_period_labels" ( - "t_period_labels" text, - "t_period_labels_desc" text, - PRIMARY KEY("t_period_labels") -); -INSERT INTO `time_period_labels` VALUES ('e','existing vintages'); -INSERT INTO `time_period_labels` VALUES ('f','future'); -CREATE TABLE "time_of_day" ( - "t_day" text, - PRIMARY KEY("t_day") -); -INSERT INTO `time_of_day` VALUES ('day'); -INSERT INTO `time_of_day` VALUES ('night'); -CREATE TABLE "technology_labels" ( - "tech_labels" text, - "tech_labels_desc" text, - PRIMARY KEY("tech_labels") -); -INSERT INTO `technology_labels` VALUES ('r','resource technology'); -INSERT INTO `technology_labels` VALUES ('p','production technology'); -INSERT INTO `technology_labels` VALUES ('pb','baseload production technology'); -INSERT INTO `technology_labels` VALUES ('ps','storage production technology'); -CREATE TABLE "technologies" ( - "tech" text, - "flag" text, - "sector" text, - "tech_desc" text, - "tech_category" text, - PRIMARY KEY("tech"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("flag") REFERENCES "technology_labels"("tech_labels") -); -INSERT INTO `technologies` VALUES ('S_IMPETH','r','supply',' imported ethanol',''); -INSERT INTO `technologies` VALUES ('S_IMPOIL','r','supply',' imported crude oil',''); -INSERT INTO `technologies` VALUES ('S_IMPNG','r','supply',' imported natural gas',''); -INSERT INTO `technologies` VALUES ('S_IMPURN','r','supply',' imported uranium',''); -INSERT INTO `technologies` VALUES ('S_OILREF','p','supply',' crude oil refinery',''); -INSERT INTO `technologies` VALUES ('E_NGCC','p','electric',' natural gas combined-cycle',''); -INSERT INTO `technologies` VALUES ('E_SOLPV','p','electric',' solar photovoltaic',''); -INSERT INTO `technologies` VALUES ('E_BATT','ps','electric',' lithium-ion battery',''); -INSERT INTO `technologies` VALUES ('E_NUCLEAR','pb','electric',' nuclear power plant',''); -INSERT INTO `technologies` VALUES ('T_BLND','p','transport','ethanol - gasoline blending process',''); -INSERT INTO `technologies` VALUES ('T_DSL','p','transport','diesel vehicle',''); -INSERT INTO `technologies` VALUES ('T_GSL','p','transport','gasoline vehicle',''); -INSERT INTO `technologies` VALUES ('T_EV','p','transport','electric vehicle',''); -INSERT INTO `technologies` VALUES ('R_EH','p','residential',' electric residential heating',''); -INSERT INTO `technologies` VALUES ('R_NGH','p','residential',' natural gas residential heating',''); -INSERT INTO `technologies` VALUES ('E_TRANS','p','electric','electric transmission',''); -CREATE TABLE "tech_reserve" ( - "tech" text, - "notes" text, - PRIMARY KEY("tech") -); -CREATE TABLE "tech_exchange" ( - "tech" text, - "notes" TEXT, - PRIMARY KEY("tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `tech_exchange` VALUES ('E_TRANS',''); -CREATE TABLE "tech_curtailment" ( - "tech" text, - "notes" TEXT, - PRIMARY KEY("tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "tech_flex" ( - "tech" text, - "notes" TEXT, - PRIMARY KEY("tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `tech_curtailment` VALUES ('S_OILREF',NULL); -CREATE TABLE "tech_annual" ( - "tech" text, - "notes" TEXT, - PRIMARY KEY("tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "tech_retirement" ( - "tech" text, - "notes" TEXT, - PRIMARY KEY("tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "sector_labels" ( - "sector" text, - PRIMARY KEY("sector") -); -INSERT INTO `sector_labels` VALUES ('supply'); -INSERT INTO `sector_labels` VALUES ('electric'); -INSERT INTO `sector_labels` VALUES ('transport'); -INSERT INTO `sector_labels` VALUES ('commercial'); -INSERT INTO `sector_labels` VALUES ('residential'); -INSERT INTO `sector_labels` VALUES ('industrial'); -CREATE TABLE "regions" ( - "regions" TEXT, - "region_note" TEXT, - PRIMARY KEY("regions") -); -INSERT INTO `regions` VALUES ('R1',NULL); -INSERT INTO `regions` VALUES ('R2',NULL); -CREATE TABLE "groups" ( - "group_name" text, - "notes" text, - PRIMARY KEY("group_name") -); -CREATE TABLE "commodity_labels" ( - "comm_labels" text, - "comm_labels_desc" text, - PRIMARY KEY("comm_labels") -); -INSERT INTO `commodity_labels` VALUES ('p','physical commodity'); -INSERT INTO `commodity_labels` VALUES ('e','emissions commodity'); -INSERT INTO `commodity_labels` VALUES ('d','demand commodity'); -CREATE TABLE "commodities" ( - "comm_name" text, - "flag" text, - "comm_desc" text, - PRIMARY KEY("comm_name"), - FOREIGN KEY("flag") REFERENCES "commodity_labels"("comm_labels") -); -INSERT INTO `commodities` VALUES ('ethos','p','dummy commodity to supply inputs (makes graph easier to read)'); -INSERT INTO `commodities` VALUES ('OIL','p','crude oil'); -INSERT INTO `commodities` VALUES ('NG','p','natural gas'); -INSERT INTO `commodities` VALUES ('URN','p','uranium'); -INSERT INTO `commodities` VALUES ('ETH','p','ethanol'); -INSERT INTO `commodities` VALUES ('SOL','p','solar insolation'); -INSERT INTO `commodities` VALUES ('GSL','p','gasoline'); -INSERT INTO `commodities` VALUES ('DSL','p','diesel'); -INSERT INTO `commodities` VALUES ('ELC','p','electricity'); -INSERT INTO `commodities` VALUES ('E10','p','gasoline blend with 10% ethanol'); -INSERT INTO `commodities` VALUES ('VMT','d','travel demand for vehicle-miles traveled'); -INSERT INTO `commodities` VALUES ('RH','d','demand for residential heating'); -INSERT INTO `commodities` VALUES ('CO2','e','CO2 emissions commodity'); -CREATE TABLE "TechOutputSplit" ( - "regions" TEXT, - "periods" integer, - "tech" text, - "output_comm" text, - "to_split" real, - "to_split_notes" text, - PRIMARY KEY("regions","periods","tech","output_comm"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods") -); -INSERT INTO `TechOutputSplit` VALUES ('R1',2020,'S_OILREF','GSL',0.9,''); -INSERT INTO `TechOutputSplit` VALUES ('R1',2020,'S_OILREF','DSL',0.1,''); -INSERT INTO `TechOutputSplit` VALUES ('R1',2025,'S_OILREF','GSL',0.9,''); -INSERT INTO `TechOutputSplit` VALUES ('R1',2025,'S_OILREF','DSL',0.1,''); -INSERT INTO `TechOutputSplit` VALUES ('R1',2030,'S_OILREF','GSL',0.9,''); -INSERT INTO `TechOutputSplit` VALUES ('R1',2030,'S_OILREF','DSL',0.1,''); -INSERT INTO `TechOutputSplit` VALUES ('R2',2020,'S_OILREF','GSL',0.72,''); -INSERT INTO `TechOutputSplit` VALUES ('R2',2020,'S_OILREF','DSL',0.08,''); -INSERT INTO `TechOutputSplit` VALUES ('R2',2025,'S_OILREF','GSL',0.72,''); -INSERT INTO `TechOutputSplit` VALUES ('R2',2025,'S_OILREF','DSL',0.08,''); -INSERT INTO `TechOutputSplit` VALUES ('R2',2030,'S_OILREF','GSL',0.72,''); -INSERT INTO `TechOutputSplit` VALUES ('R2',2030,'S_OILREF','DSL',0.08,''); -CREATE TABLE "TechInputSplit" ( - "regions" TEXT, - "periods" integer, - "input_comm" text, - "tech" text, - "ti_split" real, - "ti_split_notes" text, - PRIMARY KEY("regions","periods","input_comm","tech"), - FOREIGN KEY("input_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods") -); -INSERT INTO `TechInputSplit` VALUES ('R1',2020,'GSL','T_BLND',0.9,''); -INSERT INTO `TechInputSplit` VALUES ('R1',2020,'ETH','T_BLND',0.1,''); -INSERT INTO `TechInputSplit` VALUES ('R1',2025,'GSL','T_BLND',0.9,''); -INSERT INTO `TechInputSplit` VALUES ('R1',2025,'ETH','T_BLND',0.1,''); -INSERT INTO `TechInputSplit` VALUES ('R1',2030,'GSL','T_BLND',0.9,''); -INSERT INTO `TechInputSplit` VALUES ('R1',2030,'ETH','T_BLND',0.1,''); -INSERT INTO `TechInputSplit` VALUES ('R2',2020,'GSL','T_BLND',0.72,''); -INSERT INTO `TechInputSplit` VALUES ('R2',2020,'ETH','T_BLND',0.08,''); -INSERT INTO `TechInputSplit` VALUES ('R2',2025,'GSL','T_BLND',0.72,''); -INSERT INTO `TechInputSplit` VALUES ('R2',2025,'ETH','T_BLND',0.08,''); -INSERT INTO `TechInputSplit` VALUES ('R2',2030,'GSL','T_BLND',0.72,''); -INSERT INTO `TechInputSplit` VALUES ('R2',2030,'ETH','T_BLND',0.08,''); -CREATE TABLE "StorageDuration" ( - "regions" text, - "tech" text, - "duration" real, - "duration_notes" text, - PRIMARY KEY("regions","tech") -); -INSERT INTO `StorageDuration` VALUES ('R1','E_BATT',8.0,'8-hour duration specified as fraction of a day'); -INSERT INTO `StorageDuration` VALUES ('R2','E_BATT',8.0,'8-hour duration specified as fraction of a day'); -CREATE TABLE "SegFrac" ( - "season_name" text, - "time_of_day_name" text, - "segfrac" real CHECK("segfrac" >= 0 AND "segfrac" <= 1), - "segfrac_notes" text, - PRIMARY KEY("season_name","time_of_day_name"), - FOREIGN KEY("season_name") REFERENCES "time_season"("t_season"), - FOREIGN KEY("time_of_day_name") REFERENCES "time_of_day"("t_day") -); -INSERT INTO `SegFrac` VALUES ('spring','day',0.125,'Spring - Day'); -INSERT INTO `SegFrac` VALUES ('spring','night',0.125,'Spring - Night'); -INSERT INTO `SegFrac` VALUES ('summer','day',0.125,'Summer - Day'); -INSERT INTO `SegFrac` VALUES ('summer','night',0.125,'Summer - Night'); -INSERT INTO `SegFrac` VALUES ('fall','day',0.125,'Fall - Day'); -INSERT INTO `SegFrac` VALUES ('fall','night',0.125,'Fall - Night'); -INSERT INTO `SegFrac` VALUES ('winter','day',0.125,'Winter - Day'); -INSERT INTO `SegFrac` VALUES ('winter','night',0.125,'Winter - Night'); -CREATE TABLE "PlanningReserveMargin" ( - `regions` text, - `reserve_margin` REAL, - PRIMARY KEY(regions), - FOREIGN KEY(`regions`) REFERENCES regions -); -CREATE TABLE "Output_V_Capacity" ( - "regions" text, - "scenario" text, - "sector" text, - "t_periods" integer, - "tech" text, - "vintage" integer, - "capacity" real, - FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - PRIMARY KEY("regions","scenario","t_periods","tech","vintage") -); -CREATE TABLE "Output_V_NewCapacity" ( - "regions" text, - "scenario" text, - "sector" text, - "tech" text, - "vintage" integer, - "capacity" real, - PRIMARY KEY("regions","scenario","tech","vintage"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") -); -CREATE TABLE "Output_V_RetiredCapacity" ( - "regions" text, - "scenario" text, - "sector" text, - "t_periods" integer, - "tech" text, - "vintage" integer, - "capacity" real, - FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - PRIMARY KEY("regions","scenario","t_periods","tech","vintage") -); -CREATE TABLE "Output_VFlow_Out" ( - "regions" text, - "scenario" text, - "sector" text, - "t_periods" integer, - "t_season" text, - "t_day" text, - "input_comm" text, - "tech" text, - "vintage" integer, - "output_comm" text, - "vflow_out" real, - PRIMARY KEY("regions","scenario","t_periods","t_season","t_day","input_comm","tech","vintage","output_comm"), - FOREIGN KEY("input_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("t_day") REFERENCES "time_of_day"("t_day"), - FOREIGN KEY("t_season") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name") -); -CREATE TABLE "Output_VFlow_In" ( - "regions" text, - "scenario" text, - "sector" text, - "t_periods" integer, - "t_season" text, - "t_day" text, - "input_comm" text, - "tech" text, - "vintage" integer, - "output_comm" text, - "vflow_in" real, - PRIMARY KEY("regions","scenario","t_periods","t_season","t_day","input_comm","tech","vintage","output_comm"), - FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("t_day") REFERENCES "time_of_day"("t_day"), - FOREIGN KEY("t_season") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("input_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector") -); -CREATE TABLE "Output_Objective" ( - "scenario" text, - "objective_name" text, - "total_system_cost" real -); -CREATE TABLE "Output_Emissions" ( - "regions" text, - "scenario" text, - "sector" text, - "t_periods" integer, - "emissions_comm" text, - "tech" text, - "vintage" integer, - "emissions" real, - PRIMARY KEY("regions","scenario","t_periods","emissions_comm","tech","vintage"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("emissions_comm") REFERENCES "EmissionActivity"("emis_comm"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") -); -CREATE TABLE "Output_Curtailment" ( - "regions" text, - "scenario" text, - "sector" text, - "t_periods" integer, - "t_season" text, - "t_day" text, - "input_comm" text, - "tech" text, - "vintage" integer, - "output_comm" text, - "curtailment" real, - PRIMARY KEY("regions","scenario","t_periods","t_season","t_day","input_comm","tech","vintage","output_comm"), - FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("t_day") REFERENCES "time_of_day"("t_day"), - FOREIGN KEY("input_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("t_season") REFERENCES "time_periods"("t_periods") -); -CREATE TABLE "Output_Costs" ( - "regions" text, - "scenario" text, - "sector" text, - "output_name" text, - "tech" text, - "vintage" integer, - "output_cost" real, - PRIMARY KEY("regions","scenario","output_name","tech","vintage"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "Output_Duals" ( - "constraint_name" text, - "scenario" text, - "dual" real, - PRIMARY KEY("constraint_name","scenario") -); -CREATE TABLE "Output_CapacityByPeriodAndTech" ( - "regions" text, - "scenario" text, - "sector" text, - "t_periods" integer, - "tech" text, - "capacity" real, - PRIMARY KEY("regions","scenario","t_periods","tech"), - FOREIGN KEY("sector") REFERENCES "sector_labels"("sector"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("t_periods") REFERENCES "time_periods"("t_periods") -); -CREATE TABLE "MyopicBaseyear" ( - "year" real - "notes" text -); -CREATE TABLE "MinGenGroupWeight" ( - "regions" text, - "tech" text, - "group_name" text, - "act_fraction" REAL, - "tech_desc" text, - PRIMARY KEY("tech","group_name","regions") -); -CREATE TABLE "MinGenGroupTarget" ( - "regions" text, - "periods" integer, - "group_name" text, - "min_act_g" real, - "notes" text, - PRIMARY KEY("periods","group_name","regions") -); -CREATE TABLE "MinCapacity" ( - "regions" text, - "periods" integer, - "tech" text, - "mincap" real, - "mincap_units" text, - "mincap_notes" text, - PRIMARY KEY("regions","periods","tech"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "MinActivity" ( - "regions" text, - "periods" integer, - "tech" text, - "minact" real, - "minact_units" text, - "minact_notes" text, - PRIMARY KEY("regions","periods","tech"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `MinActivity` VALUES ('R1',2020,'T_GSL',35.0,'',''); -INSERT INTO `MinActivity` VALUES ('R1',2025,'T_GSL',35.0,'',''); -INSERT INTO `MinActivity` VALUES ('R1',2030,'T_GSL',35.0,'',''); -INSERT INTO `MinActivity` VALUES ('R2',2020,'T_GSL',15.0,'',''); -INSERT INTO `MinActivity` VALUES ('R2',2025,'T_GSL',15.0,'',''); -INSERT INTO `MinActivity` VALUES ('R2',2030,'T_GSL',15.0,'',''); -CREATE TABLE "MaxCapacity" ( - "regions" text, - "periods" integer, - "tech" text, - "maxcap" real, - "maxcap_units" text, - "maxcap_notes" text, - PRIMARY KEY("regions","periods","tech"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "MaxActivity" ( - "regions" text, - "periods" integer, - "tech" text, - "maxact" real, - "maxact_units" text, - "maxact_notes" text, - PRIMARY KEY("regions","periods","tech"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "LifetimeTech" ( - "regions" text, - "tech" text, - "life" real, - "life_notes" text, - PRIMARY KEY("regions","tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `LifetimeTech` VALUES ('R1','S_IMPETH',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','S_IMPOIL',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','S_IMPNG',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','S_IMPURN',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','S_OILREF',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','E_NGCC',30.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','E_SOLPV',30.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','E_BATT',20.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','E_NUCLEAR',50.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','T_BLND',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','T_DSL',12.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','T_GSL',12.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','T_EV',12.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','R_EH',20.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1','R_NGH',20.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','S_IMPETH',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','S_IMPOIL',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','S_IMPNG',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','S_IMPURN',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','S_OILREF',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','E_NGCC',30.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','E_SOLPV',30.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','E_BATT',20.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','E_NUCLEAR',50.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','T_BLND',100.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','T_DSL',12.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','T_GSL',12.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','T_EV',12.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','R_EH',20.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2','R_NGH',20.0,''); -INSERT INTO `LifetimeTech` VALUES ('R1-R2','E_TRANS',30.0,''); -INSERT INTO `LifetimeTech` VALUES ('R2-R1','E_TRANS',30.0,''); -CREATE TABLE "LifetimeProcess" ( - "regions" text, - "tech" text, - "vintage" integer, - "life_process" real, - "life_process_notes" text, - PRIMARY KEY("regions","tech","vintage"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "LifetimeLoanTech" ( - "regions" text, - "tech" text, - "loan" real, - "loan_notes" text, - PRIMARY KEY("regions","tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','S_IMPETH',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','S_IMPOIL',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','S_IMPNG',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','S_IMPURN',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','S_OILREF',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','E_NGCC',30.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','E_SOLPV',30.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','E_BATT',20.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','E_NUCLEAR',50.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','T_BLND',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','T_DSL',12.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','T_GSL',12.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','T_EV',12.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','R_EH',20.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R1','R_NGH',20.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','S_IMPETH',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','S_IMPOIL',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','S_IMPNG',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','S_IMPURN',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','S_OILREF',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','E_NGCC',30.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','E_SOLPV',30.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','E_BATT',20.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','E_NUCLEAR',50.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','T_BLND',100.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','T_DSL',12.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','T_GSL',12.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','T_EV',12.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','R_EH',20.0,''); -INSERT INTO `LifetimeLoanTech` VALUES ('R2','R_NGH',20.0,''); -CREATE TABLE "GrowthRateSeed" ( - "regions" text, - "tech" text, - "growthrate_seed" real, - "growthrate_seed_units" text, - "growthrate_seed_notes" text, - PRIMARY KEY("regions","tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "GrowthRateMax" ( - "regions" text, - "tech" text, - "growthrate_max" real, - "growthrate_max_notes" text, - PRIMARY KEY("regions","tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "GlobalDiscountRate" ( - "rate" real -); -INSERT INTO `GlobalDiscountRate` VALUES (0.05); -CREATE TABLE "ExistingCapacity" ( - "regions" text, - "tech" text, - "vintage" integer, - "exist_cap" real, - "exist_cap_units" text, - "exist_cap_notes" text, - PRIMARY KEY("regions","tech","vintage"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") -); -INSERT INTO `ExistingCapacity` VALUES ('R1','E_NUCLEAR',2015,0.07,'GW',''); -INSERT INTO `ExistingCapacity` VALUES ('R2','E_NUCLEAR',2015,0.03,'GW',''); -INSERT INTO `ExistingCapacity` VALUES ('R1-R2','E_TRANS',2015,10.0,'GW',''); -INSERT INTO `ExistingCapacity` VALUES ('R2-R1','E_TRANS',2015,10.0,'GW',''); -CREATE TABLE "EmissionLimit" ( - "regions" text, - "periods" integer, - "emis_comm" text, - "emis_limit" real, - "emis_limit_units" text, - "emis_limit_notes" text, - PRIMARY KEY("regions","periods","emis_comm"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("emis_comm") REFERENCES "commodities"("comm_name") -); -INSERT INTO `EmissionLimit` VALUES ('R1',2020,'CO2',25000.0,'kT CO2',''); -INSERT INTO `EmissionLimit` VALUES ('R1',2025,'CO2',24000.0,'kT CO2',''); -INSERT INTO `EmissionLimit` VALUES ('R1',2030,'CO2',23000.0,'kT CO2',''); -INSERT INTO `EmissionLimit` VALUES ('global',2020,'CO2',37500.0,'kT CO2',''); -INSERT INTO `EmissionLimit` VALUES ('global',2025,'CO2',36000.0,'kT CO2',''); -INSERT INTO `EmissionLimit` VALUES ('global',2030,'CO2',34500.0,'kT CO2',''); -CREATE TABLE "EmissionActivity" ( - "regions" text, - "emis_comm" text, - "input_comm" text, - "tech" text, - "vintage" integer, - "output_comm" text, - "emis_act" real, - "emis_act_units" text, - "emis_act_notes" text, - PRIMARY KEY("regions","emis_comm","input_comm","tech","vintage","output_comm"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("input_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("emis_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `EmissionActivity` VALUES ('R1','CO2','ethos','S_IMPNG',2020,'NG',50.3,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO `EmissionActivity` VALUES ('R1','CO2','OIL','S_OILREF',2020,'GSL',67.2,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO `EmissionActivity` VALUES ('R1','CO2','OIL','S_OILREF',2020,'DSL',69.4,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO `EmissionActivity` VALUES ('R2','CO2','ethos','S_IMPNG',2020,'NG',50.3,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO `EmissionActivity` VALUES ('R2','CO2','OIL','S_OILREF',2020,'GSL',67.2,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO `EmissionActivity` VALUES ('R2','CO2','OIL','S_OILREF',2020,'DSL',69.4,'kT/PJ','taken from MIT Energy Fact Sheet'); -CREATE TABLE "Efficiency" ( - "regions" text, - "input_comm" text, - "tech" text, - "vintage" integer, - "output_comm" text, - "efficiency" real CHECK("efficiency" > 0), - "eff_notes" text, - PRIMARY KEY("regions","input_comm","tech","vintage","output_comm"), - FOREIGN KEY("output_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("input_comm") REFERENCES "commodities"("comm_name") -); -INSERT INTO `Efficiency` VALUES ('R1','ethos','S_IMPETH',2020,'ETH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','ethos','S_IMPOIL',2020,'OIL',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','ethos','S_IMPNG',2020,'NG',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','ethos','S_IMPURN',2020,'URN',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','OIL','S_OILREF',2020,'GSL',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','OIL','S_OILREF',2020,'DSL',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','ETH','T_BLND',2020,'E10',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','GSL','T_BLND',2020,'E10',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','NG','E_NGCC',2020,'ELC',0.55,''); -INSERT INTO `Efficiency` VALUES ('R1','NG','E_NGCC',2025,'ELC',0.55,''); -INSERT INTO `Efficiency` VALUES ('R1','NG','E_NGCC',2030,'ELC',0.55,''); -INSERT INTO `Efficiency` VALUES ('R1','SOL','E_SOLPV',2020,'ELC',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','SOL','E_SOLPV',2025,'ELC',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','SOL','E_SOLPV',2030,'ELC',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','URN','E_NUCLEAR',2015,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R1','URN','E_NUCLEAR',2020,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R1','URN','E_NUCLEAR',2025,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R1','URN','E_NUCLEAR',2030,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','E_BATT',2020,'ELC',0.85,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','E_BATT',2025,'ELC',0.85,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','E_BATT',2030,'ELC',0.85,''); -INSERT INTO `Efficiency` VALUES ('R1','E10','T_GSL',2020,'VMT',0.25,''); -INSERT INTO `Efficiency` VALUES ('R1','E10','T_GSL',2025,'VMT',0.25,''); -INSERT INTO `Efficiency` VALUES ('R1','E10','T_GSL',2030,'VMT',0.25,''); -INSERT INTO `Efficiency` VALUES ('R1','DSL','T_DSL',2020,'VMT',0.3,''); -INSERT INTO `Efficiency` VALUES ('R1','DSL','T_DSL',2025,'VMT',0.3,''); -INSERT INTO `Efficiency` VALUES ('R1','DSL','T_DSL',2030,'VMT',0.3,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','T_EV',2020,'VMT',0.89,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','T_EV',2025,'VMT',0.89,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','T_EV',2030,'VMT',0.89,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','R_EH',2020,'RH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','R_EH',2025,'RH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','ELC','R_EH',2030,'RH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R1','NG','R_NGH',2020,'RH',0.85,''); -INSERT INTO `Efficiency` VALUES ('R1','NG','R_NGH',2025,'RH',0.85,''); -INSERT INTO `Efficiency` VALUES ('R1','NG','R_NGH',2030,'RH',0.85,''); -INSERT INTO `Efficiency` VALUES ('R2','ethos','S_IMPETH',2020,'ETH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','ethos','S_IMPOIL',2020,'OIL',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','ethos','S_IMPNG',2020,'NG',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','ethos','S_IMPURN',2020,'URN',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','OIL','S_OILREF',2020,'GSL',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','OIL','S_OILREF',2020,'DSL',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','ETH','T_BLND',2020,'E10',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','GSL','T_BLND',2020,'E10',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','NG','E_NGCC',2020,'ELC',0.55,''); -INSERT INTO `Efficiency` VALUES ('R2','NG','E_NGCC',2025,'ELC',0.55,''); -INSERT INTO `Efficiency` VALUES ('R2','NG','E_NGCC',2030,'ELC',0.55,''); -INSERT INTO `Efficiency` VALUES ('R2','SOL','E_SOLPV',2020,'ELC',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','SOL','E_SOLPV',2025,'ELC',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','SOL','E_SOLPV',2030,'ELC',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','URN','E_NUCLEAR',2015,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R2','URN','E_NUCLEAR',2020,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R2','URN','E_NUCLEAR',2025,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R2','URN','E_NUCLEAR',2030,'ELC',0.4,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','E_BATT',2020,'ELC',0.85,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','E_BATT',2025,'ELC',0.85,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','E_BATT',2030,'ELC',0.85,''); -INSERT INTO `Efficiency` VALUES ('R2','E10','T_GSL',2020,'VMT',0.25,''); -INSERT INTO `Efficiency` VALUES ('R2','E10','T_GSL',2025,'VMT',0.25,''); -INSERT INTO `Efficiency` VALUES ('R2','E10','T_GSL',2030,'VMT',0.25,''); -INSERT INTO `Efficiency` VALUES ('R2','DSL','T_DSL',2020,'VMT',0.3,''); -INSERT INTO `Efficiency` VALUES ('R2','DSL','T_DSL',2025,'VMT',0.3,''); -INSERT INTO `Efficiency` VALUES ('R2','DSL','T_DSL',2030,'VMT',0.3,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','T_EV',2020,'VMT',0.89,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','T_EV',2025,'VMT',0.89,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','T_EV',2030,'VMT',0.89,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','R_EH',2020,'RH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','R_EH',2025,'RH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','ELC','R_EH',2030,'RH',1.0,''); -INSERT INTO `Efficiency` VALUES ('R2','NG','R_NGH',2020,'RH',0.85,''); -INSERT INTO `Efficiency` VALUES ('R2','NG','R_NGH',2025,'RH',0.85,''); -INSERT INTO `Efficiency` VALUES ('R2','NG','R_NGH',2030,'RH',0.85,''); -INSERT INTO `Efficiency` VALUES ('R1-R2','ELC','E_TRANS',2015,'ELC',0.9,''); -INSERT INTO `Efficiency` VALUES ('R2-R1','ELC','E_TRANS',2015,'ELC',0.9,''); -CREATE TABLE "DiscountRate" ( - "regions" text, - "tech" text, - "vintage" integer, - "tech_rate" real, - "tech_rate_notes" text, - PRIMARY KEY("regions","tech","vintage"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -CREATE TABLE "DemandSpecificDistribution" ( - "regions" text, - "season_name" text, - "time_of_day_name" text, - "demand_name" text, - "dds" real CHECK("dds" >= 0 AND "dds" <= 1), - "dds_notes" text, - PRIMARY KEY("regions","season_name","time_of_day_name","demand_name"), - FOREIGN KEY("time_of_day_name") REFERENCES "time_of_day"("t_day"), - FOREIGN KEY("season_name") REFERENCES "time_season"("t_season"), - FOREIGN KEY("demand_name") REFERENCES "commodities"("comm_name") -); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','spring','day','RH',0.05,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','spring','night','RH',0.1,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','summer','day','RH',0.0,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','summer','night','RH',0.0,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','fall','day','RH',0.05,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','fall','night','RH',0.1,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','winter','day','RH',0.3,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R1','winter','night','RH',0.4,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','spring','day','RH',0.05,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','spring','night','RH',0.1,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','summer','day','RH',0.0,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','summer','night','RH',0.0,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','fall','day','RH',0.05,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','fall','night','RH',0.1,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','winter','day','RH',0.3,''); -INSERT INTO `DemandSpecificDistribution` VALUES ('R2','winter','night','RH',0.4,''); -CREATE TABLE "Demand" ( - "regions" text, - "periods" integer, - "demand_comm" text, - "demand" real, - "demand_units" text, - "demand_notes" text, - PRIMARY KEY("regions","periods","demand_comm"), - FOREIGN KEY("demand_comm") REFERENCES "commodities"("comm_name"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods") -); -INSERT INTO `Demand` VALUES ('R1',2020,'RH',30.0,'',''); -INSERT INTO `Demand` VALUES ('R1',2025,'RH',33.0,'',''); -INSERT INTO `Demand` VALUES ('R1',2030,'RH',36.0,'',''); -INSERT INTO `Demand` VALUES ('R1',2020,'VMT',84.0,'',''); -INSERT INTO `Demand` VALUES ('R1',2025,'VMT',91.0,'',''); -INSERT INTO `Demand` VALUES ('R1',2030,'VMT',98.0,'',''); -INSERT INTO `Demand` VALUES ('R2',2020,'RH',70.0,'',''); -INSERT INTO `Demand` VALUES ('R2',2025,'RH',77.0,'',''); -INSERT INTO `Demand` VALUES ('R2',2030,'RH',84.0,'',''); -INSERT INTO `Demand` VALUES ('R2',2020,'VMT',36.0,'',''); -INSERT INTO `Demand` VALUES ('R2',2025,'VMT',39.0,'',''); -INSERT INTO `Demand` VALUES ('R2',2030,'VMT',42.0,'',''); -CREATE TABLE "CostVariable" ( - "regions" text NOT NULL, - "periods" integer NOT NULL, - "tech" text NOT NULL, - "vintage" integer NOT NULL, - "cost_variable" real, - "cost_variable_units" text, - "cost_variable_notes" text, - PRIMARY KEY("regions","periods","tech","vintage"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") -); -INSERT INTO `CostVariable` VALUES ('R1',2020,'S_IMPETH',2020,32.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'S_IMPETH',2020,32.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'S_IMPETH',2020,32.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2020,'S_IMPOIL',2020,20.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'S_IMPOIL',2020,20.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'S_IMPOIL',2020,20.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2020,'S_IMPNG',2020,4.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'S_IMPNG',2020,4.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'S_IMPNG',2020,4.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2020,'S_OILREF',2020,1.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'S_OILREF',2020,1.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'S_OILREF',2020,1.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2020,'E_NGCC',2020,1.6,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'E_NGCC',2020,1.6,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'E_NGCC',2025,1.7,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'E_NGCC',2020,1.6,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'E_NGCC',2025,1.7,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'E_NGCC',2030,1.8,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2020,'E_NUCLEAR',2020,0.24,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'E_NUCLEAR',2020,0.24,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2025,'E_NUCLEAR',2025,0.25,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'E_NUCLEAR',2020,0.24,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'E_NUCLEAR',2025,0.25,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1',2030,'E_NUCLEAR',2030,0.26,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2020,'S_IMPETH',2020,25.6,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'S_IMPETH',2020,25.6,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'S_IMPETH',2020,25.6,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2020,'S_IMPOIL',2020,16.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'S_IMPOIL',2020,16.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'S_IMPOIL',2020,16.0,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2020,'S_IMPNG',2020,3.2,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'S_IMPNG',2020,3.2,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'S_IMPNG',2020,3.2,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2020,'S_OILREF',2020,0.8,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'S_OILREF',2020,0.8,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'S_OILREF',2020,0.8,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2020,'E_NGCC',2020,1.28,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'E_NGCC',2020,1.28,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'E_NGCC',2025,1.36,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'E_NGCC',2020,1.28,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'E_NGCC',2025,1.36,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'E_NGCC',2030,1.44,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2020,'E_NUCLEAR',2020,0.192,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'E_NUCLEAR',2020,0.192,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2025,'E_NUCLEAR',2025,0.2,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'E_NUCLEAR',2020,0.192,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'E_NUCLEAR',2025,0.2,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2',2030,'E_NUCLEAR',2030,0.208,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1-R2',2020,'E_TRANS',2015,0.1,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1-R2',2025,'E_TRANS',2015,0.1,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R1-R2',2030,'E_TRANS',2015,0.1,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2-R1',2020,'E_TRANS',2015,0.1,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2-R1',2025,'E_TRANS',2015,0.1,'$M/PJ',''); -INSERT INTO `CostVariable` VALUES ('R2-R1',2030,'E_TRANS',2015,0.1,'$M/PJ',''); -CREATE TABLE "CostInvest" ( - "regions" text, - "tech" text, - "vintage" integer, - "cost_invest" real, - "cost_invest_units" text, - "cost_invest_notes" text, - PRIMARY KEY("regions","tech","vintage"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `CostInvest` VALUES ('R1','E_NGCC',2020,1050.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_NGCC',2025,1025.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_NGCC',2030,1000.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_SOLPV',2020,900.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_SOLPV',2025,560.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_SOLPV',2030,800.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_NUCLEAR',2020,6145.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_NUCLEAR',2025,6045.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_NUCLEAR',2030,5890.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_BATT',2020,1150.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_BATT',2025,720.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','E_BATT',2030,480.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R1','T_GSL',2020,2570.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_GSL',2025,2700.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_GSL',2030,2700.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_DSL',2020,2715.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_DSL',2025,2810.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_DSL',2030,2810.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_EV',2020,3100.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_EV',2025,3030.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','T_EV',2030,2925.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','R_EH',2020,4.1,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','R_EH',2025,4.1,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','R_EH',2030,4.1,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','R_NGH',2020,7.6,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','R_NGH',2025,7.6,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R1','R_NGH',2030,7.6,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','E_NGCC',2020,840.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_NGCC',2025,820.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_NGCC',2030,800.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_SOLPV',2020,720.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_SOLPV',2025,448.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_SOLPV',2030,640.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_NUCLEAR',2020,4916.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_NUCLEAR',2025,4836.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_NUCLEAR',2030,4712.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_BATT',2020,920.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_BATT',2025,576.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','E_BATT',2030,384.0,'$M/GW',''); -INSERT INTO `CostInvest` VALUES ('R2','T_GSL',2020,2056.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_GSL',2025,2160.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_GSL',2030,2160.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_DSL',2020,2172.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_DSL',2025,2248.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_DSL',2030,2248.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_EV',2020,2480.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_EV',2025,2424.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','T_EV',2030,2340.0,'$/bvmt/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','R_EH',2020,3.28,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','R_EH',2025,3.28,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','R_EH',2030,3.28,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','R_NGH',2020,6.08,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','R_NGH',2025,6.08,'$/PJ/yr',''); -INSERT INTO `CostInvest` VALUES ('R2','R_NGH',2030,6.08,'$/PJ/yr',''); -CREATE TABLE "CostFixed" ( - "regions" text NOT NULL, - "periods" integer NOT NULL, - "tech" text NOT NULL, - "vintage" integer NOT NULL, - "cost_fixed" real, - "cost_fixed_units" text, - "cost_fixed_notes" text, - PRIMARY KEY("regions","periods","tech","vintage"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("periods") REFERENCES "time_periods"("t_periods"), - FOREIGN KEY("vintage") REFERENCES "time_periods"("t_periods") -); -INSERT INTO `CostFixed` VALUES ('R1',2020,'E_NGCC',2020,30.6,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_NGCC',2020,9.78,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_NGCC',2025,9.78,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_NGCC',2020,9.78,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_NGCC',2025,9.78,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_NGCC',2030,9.78,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2020,'E_SOLPV',2020,10.4,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_SOLPV',2020,10.4,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_SOLPV',2025,9.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_SOLPV',2020,10.4,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_SOLPV',2025,9.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_SOLPV',2030,9.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2020,'E_NUCLEAR',2020,98.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_NUCLEAR',2020,98.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_NUCLEAR',2025,98.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_NUCLEAR',2020,98.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_NUCLEAR',2025,98.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_NUCLEAR',2030,98.1,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2020,'E_BATT',2020,7.05,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_BATT',2020,7.05,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2025,'E_BATT',2025,7.05,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_BATT',2020,7.05,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_BATT',2025,7.05,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R1',2030,'E_BATT',2030,7.05,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2020,'E_NGCC',2020,24.48,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_NGCC',2020,7.824,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_NGCC',2025,7.824,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_NGCC',2020,7.824,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_NGCC',2025,7.824,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_NGCC',2030,7.824,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2020,'E_SOLPV',2020,8.32,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_SOLPV',2020,8.32,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_SOLPV',2025,7.28,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_SOLPV',2020,8.32,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_SOLPV',2025,7.28,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_SOLPV',2030,7.28,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2020,'E_NUCLEAR',2020,78.48,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_NUCLEAR',2020,78.48,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_NUCLEAR',2025,78.48,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_NUCLEAR',2020,78.48,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_NUCLEAR',2025,78.48,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_NUCLEAR',2030,78.48,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2020,'E_BATT',2020,5.64,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_BATT',2020,5.64,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2025,'E_BATT',2025,5.64,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_BATT',2020,5.64,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_BATT',2025,5.64,'$M/GWyr',''); -INSERT INTO `CostFixed` VALUES ('R2',2030,'E_BATT',2030,5.64,'$M/GWyr',''); -CREATE TABLE "CapacityToActivity" ( - "regions" text, - "tech" text, - "c2a" real, - "c2a_notes" TEXT, - PRIMARY KEY("regions","tech"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech") -); -INSERT INTO `CapacityToActivity` VALUES ('R1','S_IMPETH',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','S_IMPOIL',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','S_IMPNG',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','S_IMPURN',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','S_OILREF',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','E_NGCC',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','E_SOLPV',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','E_BATT',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','E_NUCLEAR',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','T_BLND',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','T_DSL',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','T_GSL',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','T_EV',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','R_EH',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1','R_NGH',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','S_IMPETH',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','S_IMPOIL',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','S_IMPNG',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','S_IMPURN',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','S_OILREF',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','E_NGCC',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','E_SOLPV',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','E_BATT',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','E_NUCLEAR',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','T_BLND',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','T_DSL',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','T_GSL',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','T_EV',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','R_EH',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R2','R_NGH',1.0,''); -INSERT INTO `CapacityToActivity` VALUES ('R1-R2','E_TRANS',31.54,''); -INSERT INTO `CapacityToActivity` VALUES ('R2-R1','E_TRANS',31.54,''); -CREATE TABLE "CapacityFactorTech" ( - "regions" text, - "season_name" text, - "time_of_day_name" text, - "tech" text, - "cf_tech" real CHECK("cf_tech" >= 0 AND "cf_tech" <= 1), - "cf_tech_notes" text, - PRIMARY KEY("regions","season_name","time_of_day_name","tech"), - FOREIGN KEY("season_name") REFERENCES "time_season"("t_season"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("time_of_day_name") REFERENCES "time_of_day"("t_day") -); -INSERT INTO `CapacityFactorTech` VALUES ('R1','spring','day','E_SOLPV',0.6,''); -INSERT INTO `CapacityFactorTech` VALUES ('R1','spring','night','E_SOLPV',0.0,''); -INSERT INTO `CapacityFactorTech` VALUES ('R1','summer','day','E_SOLPV',0.6,''); -INSERT INTO `CapacityFactorTech` VALUES ('R1','summer','night','E_SOLPV',0.0,''); -INSERT INTO `CapacityFactorTech` VALUES ('R1','fall','day','E_SOLPV',0.6,''); -INSERT INTO `CapacityFactorTech` VALUES ('R1','fall','night','E_SOLPV',0.0,''); -INSERT INTO `CapacityFactorTech` VALUES ('R1','winter','day','E_SOLPV',0.6,''); -INSERT INTO `CapacityFactorTech` VALUES ('R1','winter','night','E_SOLPV',0.0,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','spring','day','E_SOLPV',0.48,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','spring','night','E_SOLPV',0.0,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','summer','day','E_SOLPV',0.48,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','summer','night','E_SOLPV',0.0,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','fall','day','E_SOLPV',0.48,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','fall','night','E_SOLPV',0.0,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','winter','day','E_SOLPV',0.48,''); -INSERT INTO `CapacityFactorTech` VALUES ('R2','winter','night','E_SOLPV',0.0,''); -CREATE TABLE "CapacityFactorProcess" ( - "regions" text, - "season_name" text, - "time_of_day_name" text, - "tech" text, - "vintage" integer, - "cf_process" real CHECK("cf_process" >= 0 AND "cf_process" <= 1), - "cf_process_notes" text, - PRIMARY KEY("regions","season_name","time_of_day_name","tech","vintage"), - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("season_name") REFERENCES "time_season"("t_season"), - FOREIGN KEY("time_of_day_name") REFERENCES "time_of_day"("t_day") -); -CREATE TABLE "CapacityCredit" ( - "regions" text, - "periods" integer, - "tech" text, - "vintage" integer, - "cf_tech" real CHECK("cf_tech" >= 0 AND "cf_tech" <= 1), - "cf_tech_notes" text, - PRIMARY KEY("regions","periods","tech","vintage") -); -CREATE TABLE "MaxResource" ( - "regions" text, - "tech" text, - "maxres" real, - "maxres_units" text, - "maxres_notes" text, - FOREIGN KEY("tech") REFERENCES "technologies"("tech"), - PRIMARY KEY("regions","tech") -); -CREATE TABLE "LinkedTechs" ( - "primary_region" text, - "primary_tech" text, - "emis_comm" text, - "linked_tech" text, - "tech_linked_notes" text, - FOREIGN KEY("primary_tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("linked_tech") REFERENCES "technologies"("tech"), - FOREIGN KEY("emis_comm") REFERENCES "commodities"("comm_name"), - PRIMARY KEY("primary_region","primary_tech", "emis_comm") -); -COMMIT; diff --git a/data_files/example_dbs/utopia.sql b/data_files/example_dbs/utopia.sql deleted file mode 100644 index 704867bb6..000000000 --- a/data_files/example_dbs/utopia.sql +++ /dev/null @@ -1,1343 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('myopic_base_year',2000,'Base Year for Myopic Analysis'); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','day','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','night','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','day','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','night','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','day','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','night','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','day','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','night','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','day','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','night','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','day','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','night','E31',2010,0.2756000000000000116,''); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E70',0.8000000000000000444,''); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO CapacityToActivity VALUES('utopia','E01',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E21',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E31',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E51',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E70',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','RHE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','RHO',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','RL1',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','SRE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXD',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXG',1.0,''); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ethos','s','# dummy commodity to supply inputs (makes graph easier to read)'); -INSERT INTO Commodity VALUES('DSL','p','# diesel'); -INSERT INTO Commodity VALUES('ELC','p','# electricity'); -INSERT INTO Commodity VALUES('FEQ','p','# fossil equivalent'); -INSERT INTO Commodity VALUES('GSL','p','# gasoline'); -INSERT INTO Commodity VALUES('HCO','p','# coal'); -INSERT INTO Commodity VALUES('HYD','p','# water'); -INSERT INTO Commodity VALUES('OIL','p','# crude oil'); -INSERT INTO Commodity VALUES('URN','p','# uranium'); -INSERT INTO Commodity VALUES('co2','e','#CO2 emissions'); -INSERT INTO Commodity VALUES('nox','e','#NOX emissions'); -INSERT INTO Commodity VALUES('RH','d','# residential heating'); -INSERT INTO Commodity VALUES('RL','d','# residential lighting'); -INSERT INTO Commodity VALUES('TX','d','# transportation'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('s','source commodity'); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1960,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1970,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1980,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1990,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1970,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1980,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1990,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',2000,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',1980,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',1990,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',2000,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',2010,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E21',2000,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',2000,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',2010,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',2000,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',2000,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',2010,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',2010,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1960,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1970,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1970,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',2010,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1970,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1980,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1980,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',2000,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2000,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2010,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1980,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1990,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RL1',2000,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RL1',2010,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1970,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1980,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1990,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1980,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1990,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',2000,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2000,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2010,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXE',1990,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXE',1990,90.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXE',2000,90.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2000,80.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2010,80.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1970,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1980,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1990,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1980,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1990,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',2000,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2000,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2010,48.0,'',''); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('utopia','E01',1990,2000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E01',2000,1300.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E01',2010,1200.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E21',1990,5000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E21',2000,5000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E21',2010,5000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E31',1990,3000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E31',2000,3000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E31',2010,3000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E51',1990,900.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E51',2000,900.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E51',2010,900.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E70',1990,1000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E70',2000,1000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E70',2010,1000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHE',1990,90.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHE',2000,90.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHE',2010,90.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHO',1990,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHO',2000,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHO',2010,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','SRE',1990,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','SRE',2000,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','SRE',2010,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXD',1990,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXD',2000,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXD',2010,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXE',1990,2000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXE',2000,1750.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXE',2010,1500.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXG',1990,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXG',2000,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXG',2010,1044.0,'',''); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPDSL1',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPDSL1',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPDSL1',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPGSL1',1990,15.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPGSL1',1990,15.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPGSL1',1990,15.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPHCO1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPHCO1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPHCO1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPOIL1',1990,8.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPOIL1',1990,8.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPOIL1',1990,8.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPURN1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPURN1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPURN1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1960,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1970,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1980,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1990,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1970,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1980,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1990,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',2000,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',1980,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',1990,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',2000,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',2010,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E21',1990,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E21',1990,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',1990,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E21',2000,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',2000,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',2010,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1960,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1970,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1980,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1990,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1970,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1980,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1990,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',2000,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',1980,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',1990,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',2000,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',2010,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'SRE',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'SRE',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'SRE',2000,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2000,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2010,10.0,'',''); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('utopia',1990,'RH',25.19999999999999929,'',''); -INSERT INTO Demand VALUES('utopia',2000,'RH',37.79999999999999715,'',''); -INSERT INTO Demand VALUES('utopia',2010,'RH',56.70000000000000284,'',''); -INSERT INTO Demand VALUES('utopia',1990,'RL',5.599999999999999645,'',''); -INSERT INTO Demand VALUES('utopia',2000,'RL',8.400000000000000355,'',''); -INSERT INTO Demand VALUES('utopia',2010,'RL',12.59999999999999965,'',''); -INSERT INTO Demand VALUES('utopia',1990,'TX',5.200000000000000177,'',''); -INSERT INTO Demand VALUES('utopia',2000,'TX',7.799999999999999823,'',''); -INSERT INTO Demand VALUES('utopia',2010,'TX',11.68999999999999951,'',''); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','day','RH',0.1199999999999999956,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','night','RH',0.05999999999999999778,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','day','RH',0.5466999999999999638,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','night','RH',0.2732999999999999874,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','day','RL',0.1499999999999999945,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','night','RL',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','summer','day','RL',0.1499999999999999945,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','summer','night','RL',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','day','RL',0.5,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','night','RL',0.1000000000000000055,''); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,''); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1960,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1970,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1980,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1990,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',2000,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',2010,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',1990,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2000,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2010,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','URN','E21',1990,'ELC',0.4000000000000000222,'# 1/2.5'); -INSERT INTO Efficiency VALUES('utopia','URN','E21',2000,'ELC',0.4000000000000000222,'# 1/2.5'); -INSERT INTO Efficiency VALUES('utopia','URN','E21',2010,'ELC',0.4000000000000000222,'# 1/2.5'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',1980,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',1990,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',2000,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',2010,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1960,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1970,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1980,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1990,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',2000,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',2010,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',1980,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',1990,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',2000,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',2010,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',1990,'RH',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2000,'RH',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2010,'RH',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1970,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1980,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1990,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2000,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2010,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1980,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1990,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2000,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2010,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1970,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1980,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1990,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2000,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2010,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',1990,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2000,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2010,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1970,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1980,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1990,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2000,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2010,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.07499999999999999723,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.07499999999999999723,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',0.08899999999999999579,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.07499999999999999723,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'',''); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1960,0.1749999999999999889,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1970,0.1749999999999999889,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1980,0.1499999999999999945,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E31',1980,0.1000000000000000055,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E51',1980,0.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1960,0.05000000000000000277,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1970,0.05000000000000000277,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1980,0.2000000000000000111,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RHO',1970,12.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RHO',1980,12.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RL1',1980,5.599999999999999645,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXD',1970,0.4000000000000000222,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXD',1980,0.2000000000000000111,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXG',1970,3.100000000000000088,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXG',1980,1.5,'',''); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LoanLifetimeTech VALUES('utopia','E01',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E21',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E31',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E51',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E70',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RHE',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RHO',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RL1',10.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','SRE',50.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXD',15.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXE',15.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXG',15.0,''); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO LifetimeProcess VALUES('utopia','RL1',1980,20.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXD',1970,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXD',1980,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXG',1970,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXG',1980,30.0,'#forexistingcap'); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('utopia','E01',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E21',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E31',100.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E51',100.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E70',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RHE',30.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RHO',30.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RL1',10.0,''); -INSERT INTO LifetimeTech VALUES('utopia','SRE',50.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXD',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXE',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXG',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPDSL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPGSL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPHCO1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPOIL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPURN1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPHYD',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPFEQ',1000.0,''); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxCapacity VALUES('utopia',1990,'E31',0.1300000000000000044,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2000,'E31',0.1700000000000000122,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2010,'E31',0.2099999999999999923,'',''); -INSERT INTO MaxCapacity VALUES('utopia',1990,'RHE',0.0,'',''); -INSERT INTO MaxCapacity VALUES('utopia',1990,'TXD',0.5999999999999999778,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2000,'TXD',1.760000000000000008,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2010,'TXD',4.759999999999999787,'',''); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinCapacity VALUES('utopia',1990,'E31',0.1300000000000000044,'',''); -INSERT INTO MinCapacity VALUES('utopia',2000,'E31',0.1300000000000000044,'',''); -INSERT INTO MinCapacity VALUES('utopia',2010,'E31',0.1300000000000000044,'',''); -INSERT INTO MinCapacity VALUES('utopia',1990,'SRE',0.1000000000000000055,'',''); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('utopia',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('inter','day',0.166699999999999987,'# I-D'); -INSERT INTO TimeSegmentFraction VALUES('inter','night',0.08329999999999999905,'# I-N'); -INSERT INTO TimeSegmentFraction VALUES('summer','day',0.166699999999999987,'# S-D'); -INSERT INTO TimeSegmentFraction VALUES('summer','night',0.08329999999999999905,'# S-N'); -INSERT INTO TimeSegmentFraction VALUES('winter','day',0.3332999999999999852,'# W-D'); -INSERT INTO TimeSegmentFraction VALUES('winter','night',0.166699999999999987,'# W-N'); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -INSERT INTO TechOutputSplit VALUES('utopia',1990,'SRE','DSL',0.6999999999999999556,''); -INSERT INTO TechOutputSplit VALUES('utopia',2000,'SRE','DSL',0.6999999999999999556,''); -INSERT INTO TechOutputSplit VALUES('utopia',2010,'SRE','DSL',0.6999999999999999556,''); -INSERT INTO TechOutputSplit VALUES('utopia',1990,'SRE','GSL',0.2999999999999999889,''); -INSERT INTO TechOutputSplit VALUES('utopia',2000,'SRE','GSL',0.2999999999999999889,''); -INSERT INTO TechOutputSplit VALUES('utopia',2010,'SRE','GSL',0.2999999999999999889,''); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'day'); -INSERT INTO TimeOfDay VALUES(2,'night'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,1960,'e'); -INSERT INTO TimePeriod VALUES(2,1970,'e'); -INSERT INTO TimePeriod VALUES(3,1980,'e'); -INSERT INTO TimePeriod VALUES(4,1990,'f'); -INSERT INTO TimePeriod VALUES(5,2000,'f'); -INSERT INTO TimePeriod VALUES(6,2010,'f'); -INSERT INTO TimePeriod VALUES(7,2020,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'inter'); -INSERT INTO TimeSeason VALUES(2,'summer'); -INSERT INTO TimeSeason VALUES(3,'winter'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('IMPDSL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); -INSERT INTO Technology VALUES('IMPGSL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); -INSERT INTO Technology VALUES('IMPHCO1','r','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); -INSERT INTO Technology VALUES('IMPOIL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); -INSERT INTO Technology VALUES('IMPURN1','r','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); -INSERT INTO Technology VALUES('IMPFEQ','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); -INSERT INTO Technology VALUES('IMPHYD','r','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); -INSERT INTO Technology VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); -INSERT INTO Technology VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); -INSERT INTO Technology VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); -INSERT INTO Technology VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); -INSERT INTO Technology VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); -INSERT INTO Technology VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); -INSERT INTO Technology VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); -INSERT INTO Technology VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); -INSERT INTO Technology VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); -INSERT INTO Technology VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); -INSERT INTO Technology VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); -INSERT INTO Technology VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -COMMIT; diff --git a/data_files/monte_carlo/run_settings_1.csv b/data_files/monte_carlo/run_settings_1.csv deleted file mode 100644 index 869ee12dd..000000000 --- a/data_files/monte_carlo/run_settings_1.csv +++ /dev/null @@ -1,5 +0,0 @@ -run,param,index,mod,value,notes -1,MaxCapacity,utopia|2010|TXD,a,-1.0,reduce the max capacity of TXD in region Utopia in period 2010 by 1.0 units (absolute) -2,Demand,utopia|*|RH,r,0.5,make Res Heat costlier by 50% in all 3 periods -2,CostVariable,*|1990/2000|IMPOIL1|*,s,20.0,substitute cost of 20.0 for var cost of IMPOIL in periods 1990/2000 in all regions (just utopia exists) for all vintages -3,CostVariable,china|1990|IMPOIL1|*,s,1000,bad input: unknown region (china) should fail and be logged \ No newline at end of file diff --git a/data_files/my_configs/.gitignore b/data_files/my_configs/.gitignore deleted file mode 100644 index d806ef659..000000000 --- a/data_files/my_configs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# this will auto-ignore any new .toml config files. It is always possible to override this with a Git ADD -*.toml diff --git a/data_files/my_configs/config_sample.toml b/data_files/my_configs/config_sample.toml deleted file mode 100644 index 1763880ab..000000000 --- a/data_files/my_configs/config_sample.toml +++ /dev/null @@ -1,114 +0,0 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# ---------------------------------------------------------- -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file -# (cannot contain "-" dash) -scenario = "zulu" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect_foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check, monte_carlo] -scenario_mode = "perfect_foresight" - -# Input database (Mandatory) -input_database = "data_files/example_dbs/utopia.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# For Pefrect Foresight, the user may target the same input file or a separate / -# copied sqlite file in a different location. Myopic, MGA require that input_database = output_database -output_database = "data_files/example_dbs/utopia.sqlite" - -# ------------------------------------ -# DATA / MODEL CHECKS -# To check data / cost integrity -# ------------------------------------ - -# See the documentation section on Data Quality for notes on the features below - -# Check the pricing structure for common errors, which are reported in the log file -# Strongly recommended -price_check = true - -# Check the network connectivity for processes in the model. Strongly -# recommended to ensure proper performance. Results are reported in log file -# This requires that source commodities be marked with 's' in Commodity table -# This is required for Myopic runs -source_trace = true - -# Produce HTML files for Commodity Networks. Requires source_trace above -plot_commodity_network = true - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve. (Currently NOT supported) -neos = false - -# solver (Mandatory) -# Depending on what client machine has installed. -# [cbc, appsi_highs, gurobi, cplex, ...] -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder -save_excel = true - -# save the duals in the output Database (may slow execution slightly?) -save_duals = true - -# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!) -save_lp_file = false - -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- -[MGA] -# see notes on these in the extensions/modeling_to_generate_alternatives folder readme.txt -cost_epsilon = 0.03 # propotional relaxation on optimal cost (ex: 0.05 = bound at 105% of original optimal cost) -iteration_limit = 55 # max iterations to perform -time_limit_hrs = 1 # max time -axis = "tech_category_activity" # use the tech activity Manager to control exploration based on categories in Tech -weighting = "hull_expansion" # use a convex hull expansion algorithm to weight exploration - -[myopic] -view_depth = 2 # number of periods seen/analyzed per iteration -step_size = 1 # number of periods to step by (must be <= view depth) - -[morris] -perturbation = 0.10 # amount to perturb marked parameters (ex: 0.10 -> +/- 10%) -levels = 8 # number of levels in param grid (must be even number) -trajectories = 10 # number of Morris trajectories to generate/explore -seed = false # random seed for use in generation/analysis for repeatable results. false=system derived -cores = 0 # number of CPU cores to use. 0 (default) = cpu count -# Note: Problem size (in general) is (Groups + 1) * trajectories see the SALib Dox -# Groups = number of unique labels used in MM analysis columns in DB - -[SVMGA] -cost_epsilon = 0.05 -# labels from appropriate tables in database. It is recommended to only use one of the lists below and leave -# the others blank -emission_labels = ['co2', 'nox'] -capacity_labels = ['TXD', 'TXG'] -activity_labels = [] - -[monte_carlo] -# a path from the PROJECT ROOT to the settings file that contains the run data. -run_settings = 'data_files/monte_carlo/run_settings_1.csv' - diff --git a/data_files/my_configs/mga_utopia.toml b/data_files/my_configs/mga_utopia.toml deleted file mode 100644 index 250f00159..000000000 --- a/data_files/my_configs/mga_utopia.toml +++ /dev/null @@ -1,94 +0,0 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# ---------------------------------------------------------- -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [
] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file -scenario = "sierra" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect_foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check] -scenario_mode = "mga" - -# Input database (Mandatory) -input_database = "data_files/example_dbs/utopia.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# For Pefrect Foresight, the user may target the same input file or a separate / -# copied sqlite file in a different location. Myopic requires that input_database = output_database -output_database = "data_files/example_dbs/utopia.sqlite" - -# ------------------------------------ -# DATA / MODEL CHECKS -# To check data / cost integrity -# ------------------------------------ - -# Check the pricing structure for common errors, which are reported in the log file -# Strongly recommended -price_check = true - -# Check the network connectivity for processes in the model. Strongly -# recommended to ensure proper performance. Results are reported in log file -# This requires that source commodities be marked with 's' in Commodity table -# This is required for Myopic runs -source_trace = true - -# Produce HTML files for Commodity Networks. Requires source_trace above -plot_commodity_network = true - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve. (Currently NOT supported) -neos = false - -# solver (Mandatory) -# Depending on what client machine has installed. -# [cbc, appsi_highs, gurobi, cplex, ...] -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder -save_excel = false - -# save the duals in the output Database (may slow execution slightly?) -save_duals = false - -# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!) -save_lp_file = false - -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- -[MGA] -# see notes on these in the extensions/modeling_to_generate_alternatives folder readme.txt -cost_epsilon = 0.03 # propotional relaxation on optimal cost (ex: 0.05 = bound at 105% of original optimal cost) -iteration_limit = 20 # max iterations to perform -time_limit_hrs = 1 # max time -axis = "tech_category_activity" # use the tech activity Manager to control exploration based on categories in Tech -weighting = "hull_expansion" # use a convex hull expansion algorithm to weight exploration - - -[myopic] -view_depth = 2 # number of periods seen/analyzed per iteration -step_size = 1 # number of periods to step by (must be <= view depth) - - - - diff --git a/data_files/my_configs/monte_carlo_utopia.toml b/data_files/my_configs/monte_carlo_utopia.toml deleted file mode 100644 index 875946bfe..000000000 --- a/data_files/my_configs/monte_carlo_utopia.toml +++ /dev/null @@ -1,114 +0,0 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# ---------------------------------------------------------- -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [
] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file -# (cannot contain "-" dash) -scenario = "Orange Squirrel" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect_foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check, monte_carlo] -scenario_mode = "monte_carlo" - -# Input database (Mandatory) -input_database = "data_files/example_dbs/utopia.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# For Pefrect Foresight, the user may target the same input file or a separate / -# copied sqlite file in a different location. Myopic, MGA require that input_database = output_database -output_database = "data_files/example_dbs/utopia.sqlite" - -# ------------------------------------ -# DATA / MODEL CHECKS -# To check data / cost integrity -# ------------------------------------ - -# See the documentation section on Data Quality for notes on the features below - -# Check the pricing structure for common errors, which are reported in the log file -# Strongly recommended -price_check = true - -# Check the network connectivity for processes in the model. Strongly -# recommended to ensure proper performance. Results are reported in log file -# This requires that source commodities be marked with 's' in Commodity table -# This is required for Myopic runs -source_trace = true - -# Produce HTML files for Commodity Networks. Requires source_trace above -plot_commodity_network = true - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve. (Currently NOT supported) -neos = false - -# solver (Mandatory) -# Depending on what client machine has installed. -# [cbc, appsi_highs, gurobi, cplex, ...] -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder -save_excel = true - -# save the duals in the output Database (may slow execution slightly?) -save_duals = true - -# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!) -save_lp_file = false - -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- -[MGA] -# see notes on these in the extensions/modeling_to_generate_alternatives folder readme.txt -cost_epsilon = 0.03 # propotional relaxation on optimal cost (ex: 0.05 = bound at 105% of original optimal cost) -iteration_limit = 55 # max iterations to perform -time_limit_hrs = 1 # max time -axis = "tech_category_activity" # use the tech activity Manager to control exploration based on categories in Tech -weighting = "hull_expansion" # use a convex hull expansion algorithm to weight exploration - -[myopic] -view_depth = 2 # number of periods seen/analyzed per iteration -step_size = 1 # number of periods to step by (must be <= view depth) - -[morris] -perturbation = 0.10 # amount to perturb marked parameters (ex: 0.10 -> +/- 10%) -levels = 8 # number of levels in param grid (must be even number) -trajectories = 10 # number of Morris trajectories to generate/explore -seed = false # random seed for use in generation/analysis for repeatable results. false=system derived -cores = 0 # number of CPU cores to use. 0 (default) = cpu count -# Note: Problem size (in general) is (Groups + 1) * trajectories see the SALib Dox -# Groups = number of unique labels used in MM analysis columns in DB - -[SVMGA] -cost_epsilon = 0.05 -# labels from appropriate tables in database. It is recommended to only use one of the lists below and leave -# the others blank -emission_labels = ['co2', 'nox'] -capacity_labels = ['TXD', 'TXG'] -activity_labels = [] - -[monte_carlo] -# a path from the PROJECT ROOT to the settings file that contains the run data. -run_settings = 'data_files/monte_carlo/run_settings_4.csv' - diff --git a/data_files/my_configs/morris_utopia.toml b/data_files/my_configs/morris_utopia.toml deleted file mode 100644 index d9d64fa68..000000000 --- a/data_files/my_configs/morris_utopia.toml +++ /dev/null @@ -1,98 +0,0 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# ---------------------------------------------------------- -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [
] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file -scenario = "chili" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect_foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check] -scenario_mode = "method_of_morris" - -# Input database (Mandatory) -input_database = "data_files/example_dbs/morris_utopia.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# For Pefrect Foresight, the user may target the same input file or a separate / -# copied sqlite file in a different location. Myopic requires that input_database = output_database -output_database = "data_files/example_dbs/morris_utopia.sqlite" - -# ------------------------------------ -# DATA / MODEL CHECKS -# To check data / cost integrity -# ------------------------------------ - -# Check the pricing structure for common errors, which are reported in the log file -# Strongly recommended -price_check = true - -# Check the network connectivity for processes in the model. Strongly -# recommended to ensure proper performance. Results are reported in log file -# This requires that source commodities be marked with 's' in Commodity table -# This is required for Myopic runs -source_trace = true - -# Produce HTML files for Commodity Networks. Requires source_trace above -plot_commodity_network = false - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve. (Currently NOT supported) -neos = false - -# solver (Mandatory) -# Depending on what client machine has installed. -# [cbc, appsi_highs, gurobi, cplex, ...] -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder -save_excel = false - -# save the duals in the output Database (may slow execution slightly?) -save_duals = false - -# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!) -save_lp_file = false - -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- -[MGA] - - - -[myopic] -view_depth = 2 # number of periods seen/analyzed per iteration -step_size = 1 # number of periods to step by (must be <= view depth) - -[morris] -perturbation = 0.10 # amount to perturb marked parameters (ex: 0.10 -> +/- 10%) -levels = 8 # number of levels in param grid (must be even number) -trajectories = 10 # number of Morris trajectories to generate/explore -seed = false # random seed for use in generation/analysis for repeatable results. false=system derived -cores = 0 # number of CPU cores to use. 0 (default) = cpu count -# Note: Problem size (in general) is (Groups + 1) * trajectories see the SALib Dox -# Groups = number of unique labels used in MM analysis columns in DB - - - - diff --git a/data_files/my_configs/stepped_demand.toml b/data_files/my_configs/stepped_demand.toml deleted file mode 100644 index fe401af28..000000000 --- a/data_files/my_configs/stepped_demand.toml +++ /dev/null @@ -1,87 +0,0 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# ---------------------------------------------------------- -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [
] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file -scenario = "myo_1" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect_foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check] -scenario_mode = "myopic" - -# Input database (Mandatory) -input_database = "data_files/example_dbs/stepped_demand.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# For Pefrect Foresight, the user may target the same input file or a separate / -# copied sqlite file in a different location. Myopic requires that input_database = output_database -output_database = "data_files/example_dbs/stepped_demand.sqlite" - -# ------------------------------------ -# DATA / MODEL CHECKS -# To check data / cost integrity -# ------------------------------------ - -# Check the pricing structure for common errors, which are reported in the log file -# Strongly recommended -price_check = true - -# Check the network connectivity for processes in the model. Strongly -# recommended to ensure proper performance. Results are reported in log file -# This requires that source commodities be marked with 's' in Commodity table -# This is required for Myopic runs -source_trace = true - -# Produce HTML files for Commodity Networks. Requires source_trace above -plot_commodity_network = true - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve. (Currently NOT supported) -neos = false - -# solver (Mandatory) -# Depending on what client machine has installed. -# [cbc, appsi_highs, gurobi, cplex, ...] -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder -save_excel = true - -# save the duals in the output Database (may slow execution slightly?) -save_duals = false - -# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!) -save_lp_file = false - -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- -[MGA] - - -[myopic] -view_depth = 2 # number of periods seen/analyzed per iteration -step_size = 1 # number of periods to step by (must be <= view depth) - - - diff --git a/definitions.py b/definitions.py deleted file mode 100644 index 3a1682b13..000000000 --- a/definitions.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -top-level folder to provide project-wide definitions & references -""" - -# Written by: J. F. Hyink -# jeff@westernspark.us -# https://westernspark.us - -# Created on: 6/28/23 - -import os -from pathlib import Path - - -PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) - -OUTPUT_PATH = None - - -def get_OUTPUT_PATH(): - if OUTPUT_PATH is None: - raise RuntimeError('Output path not yet defined') - return OUTPUT_PATH - - -def set_OUTPUT_PATH(path: Path): - global OUTPUT_PATH - OUTPUT_PATH = path diff --git a/docs/Database Upgrade and Troubleshooting.md b/docs/Database Upgrade and Troubleshooting.md index e71f82874..142f416dd 100644 --- a/docs/Database Upgrade and Troubleshooting.md +++ b/docs/Database Upgrade and Troubleshooting.md @@ -5,12 +5,12 @@ The upgrades in Version 3 of the Temoa codebase include much tighter scrutiny of the commodity network via source tracing. Source tracing is optional in some modes, but mandatory in Myopic mode. The goal of source tracing is to ensure the network of commodities is performing properly and not allowing -artificial sources of commodities to enter the network. Additional details on this process are in +artificial sources of commodities to enter the network. Additional details on this process are in the `commodity network notes.md` file. The complexity of some of the larger databases and networks and the actions of source tracing can pose challenges in troubleshooting models that are infeasible or unbounded. This set of notes -is intended to help modelers working with new large datasets or transitioned datasets in +is intended to help modelers working with new large datasets or transitioned datasets in working through infeasibilities and "getting the model breathing" so that refinements can be made. ## First steps @@ -21,13 +21,13 @@ following **must** be done to get the model working. 1. Mark Source Commodities. In the `Commodity` table, source commodities must be marked with an "s" for the flag value. This action identifies them as bedrock sources that serve as sources for network analysis. Things like "Ethos" are typically sources. A source commodity -is interpreted as a freely available starting commodity that has no predecessors. Multiple +is interpreted as a freely available starting commodity that has no predecessors. Multiple sources are possible for bookkeeping or model clarity. 2. Identify Unlimited Capacity Technologies. The `Technology` table has a new field labeled -`unlim_cap` to identify technologies that have no capacity limit. This may represent things +`unlim_cap` to identify technologies that have no capacity limit. This may represent things like imports or use taxes or other notions that don't logically support a capacity determination/assignment. Assigning technologies to this category does several things. -First, it makes the model smaller and more flexible because capacity variables are +First, it makes the model smaller and more flexible because capacity variables are excluded for the technology, although activity is still an active variable. It also enables modes like Myopic to function more cleanly because the model does not make capacity decisions on things that may need to be revised with higher demands in later @@ -42,21 +42,21 @@ the log file should be addressed. If the model does not solve after the steps above the following are possible actions to take. Note that several of these involve altering the model code and should be done carefully. A suggested approach for items that involve injecting or commenting out code is to try them, -make corrections as needed to the data, and then use Git's `rollback` feature on the +make corrections as needed to the data, and then use Git's `rollback` feature on the core model code to undo any "hacks" or patches that were used. In no particular order: -- If the model is unbounded. Look at negative cost items carefully. A technique to +- If the model is unbounded. Look at negative cost items carefully. A technique to limit runaway activity is to limit all flows in the model. The following code can be slipped in to the `temoa_model.py` code anywhere in the constraint section. It will limit **all** flows to an upper bound. If using this approach, the -modeler should also limit any arbitrarily large demands in the +modeler should also limit any arbitrarily large demands in the demand table that may need to be revised downward in conjuction with this limit. -The `OutputFlowOut` table can then be inspected for unusually high activity. +The `output_flow_out` table can then be inspected for unusually high activity. ``` -@M.Constraint(M.FlowVar_rpsditvo) +@M.Constraint(M.flow_var_rpsditvo) def flow_max(M, *rpsditvo): - return M.V_FlowOut[rpsditvo] <= 10_000_000 + return M.v_flow_out[rpsditvo] <= 10_000_000 ``` - Double check that items in the first period that should be marked as `unlim_capacity` @@ -69,7 +69,7 @@ Of note in this example, 2020 is the first optimization period: -- technologies unique to first period SELECT t.tech, unlim_cap FROM Technology t - LEFT OUTER JOIN (SELECT DISTINCT e.tech FROM Efficiency e WHERE e.vintage != 2020) AS base + LEFT OUTER JOIN (SELECT DISTINCT e.tech FROM efficiency e WHERE e.vintage != 2020) AS base ON t.tech = base.tech WHERE base.tech IS NULL; ``` @@ -78,5 +78,5 @@ WHERE base.tech IS NULL; easily by commenting out the read-in process for parameter data in `hybrid_loader.py`. It should be possible to search for the table name and then just comment out the entire block that reads in that table. Much easier than manipulating the database. -Consideration for excluding MinActivity, MaxActivity, EmissionLimit, etc. is -suggested. \ No newline at end of file +Consideration for excluding min_activity, max_activity, EmissionLimit, etc. is +suggested. diff --git a/docs/Makefile b/docs/Makefile index 465f6c0ef..0f0566256 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -5,9 +5,7 @@ SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = -BUILDDIR = build -# The below doesn't seem to work properly.... changed to 'build' directory (above) within the docs folder -# BUILDDIR = /tmp/TemoaDocumentationBuild +BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 @@ -31,7 +29,7 @@ help: @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdf to make LaTeX files and run them through xelatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @@ -102,16 +100,29 @@ latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ + @echo "Run \`make' in that directory to run these through xelatex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex \ - LATEXOPTS='-interaction=nonstopmode -file-line-error -halt-on-error' \ - all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + @echo "Running LaTeX files through xelatex..." + @if command -v latexmk > /dev/null 2>&1; then \ + $(MAKE) -C $(BUILDDIR)/latex \ + LATEXOPTS='-interaction=nonstopmode -file-line-error -halt-on-error' \ + all-pdf; \ + else \ + echo "latexmk not found, falling back to manual xelatex..."; \ + $(MAKE) -C $(BUILDDIR)/latex \ + PDFLATEX="xelatex -interaction=nonstopmode -file-line-error -halt-on-error" \ + LATEXMKOPTS="" \ + all-pdf || true; \ + echo "Running xelatex again for references..."; \ + $(MAKE) -C $(BUILDDIR)/latex \ + PDFLATEX="xelatex -interaction=nonstopmode -file-line-error -halt-on-error" \ + LATEXMKOPTS="" \ + all-pdf; \ + fi + @echo "xelatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @@ -156,4 +167,3 @@ doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." - diff --git a/docs/README.md b/docs/README.md index 3e05d2c3b..4696ba7d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,31 +1,105 @@ -This directory contains the source files necessary to generate the Temoa documentation in [ReST](https://en.wikipedia.org/wiki/ReStructuredText) format. +# Temoa Documentation -## Required Software +This directory contains the source files for generating the Temoa documentation in [ReStructuredText](https://en.wikipedia.org/wiki/ReStructuredText) format. -The required software elements to produce the documentation are included in the Temoa [environment file](https://github.com/TemoaProject/temoa/blob/energysystem/environment.yml). +## Building the Documentation -The only additional software element required is LaTex. On a Mac, [MacTeX](https://www.tug.org/mactex/mactex-download.html) is a good option, and must be installed manually. [MiKTeX](https://miktex.org/download) is also available across platforms and is installed manually. On a Windows machine, MikTeX can also be installed through `conda` with the following command: +### Prerequisites -```$ conda install -c conda-forge miktex``` +The documentation build requires Sphinx and related packages. These are included in the `docs` extra dependency group. -## Producing documentation -The Temoa documentation draws from a couple of sources: (1) the static descriptions of model elements included in [Documentation.rst](source//Documentation.rst), and (2) the doc strings -embedded in [temoa_rules.py](../temoa_model/temoa_rules.py) that document the objective function and constraints. Sphinx retrieves these doc strings and generates LaTeX-formatted equations in the "Equations" section of the documentation. +### Setup +Install the documentation dependencies using uv (recommended): -From this folder, execute the following to generate the html documentation: +```bash +cd /path/to/temoa +uv sync --extra docs +``` -```$ make html``` +Or using pip: -To generate the PDF documentation, from the same folder, execute the following: +```bash +pip install -e ".[docs]" +``` -```$ make latexpdf``` +### Generating HTML Documentation -Sometimes this automatic PDF generation fails. If that is the case, navigate to `/tmp/TemoaDocumentationBuild/` and manually generate the pdf: +From the `docs` directory, execute: -```$ pdflatex toolsforenergymodeloptimizationandanalysistemoa.pdf``` +```bash +uv run sphinx-build source _build/html +``` +Or from the repository root: +```bash +uv run sphinx-build docs/source docs/_build/html +``` +The generated HTML files will be in `docs/_build/html/`. Open `index.html` in your browser to view the documentation. +### Generating PDF Documentation +To generate PDF documentation, you'll need LaTeX installed. latexmk is recommended for automatic PDF generation: +- **macOS**: [MacTeX](https://www.tug.org/mactex/mactex-download.html) +- **Windows/Linux**: [MiKTeX](https://miktex.org/download) or TeX Live + +Then run: + +```bash +uv run make latexpdf +``` + +The PDF will be generated in `docs/_build/latex/`. + +If automatic PDF generation fails, navigate to the build directory and manually generate the PDF: + +```bash +cd docs/_build/latex +pdflatex toolsforenergymodeloptimizationandanalysistemoa.tex +``` + +## Documentation Structure + +The Temoa documentation draws from two main sources: + +1. **Static descriptions** - Model elements and concepts described in `.rst` files in `source/` (e.g., `mathematical_formulation.rst`, `database.rst`) + +2. **Code docstrings** - Objective function and constraint documentation from module docstrings in: + - `temoa/components/costs.py` - Objective function + - `temoa/components/*.py` - Constraint implementations + +Sphinx retrieves these docstrings and generates LaTeX-formatted equations in the "Equations" section of the documentation. + +## Checking for Issues + +### Link Checking + +To check for broken links in the documentation: + +```bash +uv run sphinx-build -b linkcheck source _build/linkcheck +``` + +Review the output in `docs/_build/linkcheck/output.txt`. + +### Build Warnings + +To treat warnings as errors (useful for CI): + +```bash +uv run sphinx-build -W -b html source _build/html +``` + +## Contributing to Documentation + +When contributing to the documentation: + +1. Follow ReStructuredText formatting guidelines +2. Ensure all code examples are tested and working +3. Build the documentation locally to check for warnings +4. Run the link checker to verify external links +5. Update docstrings in code when changing model equations + +See [CONTRIBUTING.md](../CONTRIBUTING.md) for general contribution guidelines. diff --git a/docs/automake.sh b/docs/automake.sh index 3384cb2ad..87d3af9a0 100755 --- a/docs/automake.sh +++ b/docs/automake.sh @@ -1,27 +1,6 @@ #!/bin/bash -# Tools for Energy Model Optimization and Analysis (Temoa): -# An open source framework for energy systems optimization modeling -# -# Copyright (C) 2015, NC State University -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# A complete copy of the GNU General Public License v2 (GPLv2) is available -# in LICENSE.txt. Users uncompressing this from an archive may not have -# received this license file. If not, see . - - - set -e # Only for ls and inotifywait (to make sure files exist) ... # files to watch diff --git a/docs/commodity network notes.md b/docs/commodity network notes.md index 1cc57ca7c..123e60a2f 100644 --- a/docs/commodity network notes.md +++ b/docs/commodity network notes.md @@ -14,7 +14,7 @@ In a standard, static, and lossless ``s-t`` network, flow balance constraints fa place naturally. The input flow at the source, ``s`` is set to equal the output flow at termination point ``t`` and for all intermediary nodes, flow in is constrained to equal flow out. Temoa's energy network, however, is not static and has losses in the form -of efficiency losses within the ``tech`` processes that serve as the links between +of efficiency losses within the ``tech`` processes that serve as the links between ``commodity`` nodes. When a model is solved over several time periods with "perfect foresight" the network may look quite different in each region and time period based on endogenous selections of new technologies, lifetime expirations, etc. This diversification of networks is compounded when running the @@ -28,12 +28,12 @@ to help avoid pitfalls.... The energy network in each region and period is built dynamically from the `techs` that are identified as existing capacity and whatever the model has ability to build from elements in -the optimization window identified in the ``Efficiency`` table. In order enforce conservation of +the optimization window identified in the ``efficiency`` table. In order enforce conservation of flow constraints, the model identifies output flows (commodities), including the demand commodities and inventories all possible `techs` (existing or available) that *could* produce that commodity from any input commodity and enforces flow balance by requiring capacity in one of the available technologies, so that *some tech* must provide the flow to support that output. -This is the basis for satisfying demands at the termination of the network. +This is the basis for satisfying demands at the termination of the network. If at any point along the chain there are no `techs` available to produce the commodity in question, the model *assumes this is a base or source commodity* for @@ -56,9 +56,10 @@ During pre-processing, before the model is built, Temoa can identify breaks in t data before the model is loaded/built. Temoa can also check network integrity on a built model before solve is initiated. Currently, discrepancies are noted in the log file for the model. The user can also request network plots, which are browser-capable html files that can be used in diagnosis of problem areas. The general intent is to ensure that all flows to Demand -commodities can be cleanly traced back to original sources. +commodities can be cleanly traced back to original sources. + +#### An example -#### An example: Consider the simple network below with one source, one demand, and intermediate physical commodities ``{P1, P2, P3, P4}`` and the connective technologies ``T1`` through ``T6``. This well-connected network works as intended and the singular demand is traceable via either path @@ -66,35 +67,32 @@ back to source. ![Good Network](source/images/commodity_network.png) - A defective network (shown below) may occur for a several reasons, as cited in the previous section. Suppose that for some reason `T3` is no longer available in this or a subsequent period (never made available, lifetime expiration earlier than other links, not selected by myopic process--which would normally remove the other links as well, unless they had replacement vintages and ``T3`` did not, etc.) Several problems now exist: -1. Supply side orphans. Technology ``T1`` is now a "supply side" orphan, which shouldn't cause model problems, but represents bloat +1. Supply side orphans. Technology ``T1`` is now a "supply side" orphan, which shouldn't cause model problems, but represents bloat in the model. Legacy (pre version 3.0) Temoa does screen for unused outputs (like ``P1`` in this case) that are not used by other processes and are not end demands, but it is currently only done 'globally' in all periods/regions. Resultantly, -this orphan may not trigger a model error if it were used in another region/period. +this orphan may not trigger a model error if it were used in another region/period. These will now generate **WARNING** level log entries with source tracing. It *could* be a source of unbounded behavior in the case where the -modeler attempts to use negative values for costs. +modeler attempts to use negative values for costs. -2. Technology ``T5`` and perhaps a now-available new vintage ``T5'`` are now "demand-side" orphans. These are -problematic and will generate **WARNING** level log entries by source tracing because they would allow a +2. Technology ``T5`` and perhaps a now-available new vintage ``T5'`` are now "demand-side" orphans. These are +problematic and will generate **WARNING** level log entries by source tracing because they would allow a false/unlimited supply of ``P3`` as their inputs. -3. New technology ``T7`` (and any other linkages that are not reachable from either source or demand) +3. New technology ``T7`` (and any other linkages that are not reachable from either source or demand) are complete orphans. They will generate a **WARNING** level log entry during source tracing. - - ![Bad Network](source/images/broken_commodity_network.png) Tech Suppression --------------- -When source tracing is used, Temoa will attempt to remedy network escapes as described above by suppressing +When source tracing is used, Temoa will attempt to remedy network escapes as described above by suppressing problematic `techs` or chains of technologies. During runs where source tracing is enabled, Temoa's internal `Network Data Magager` will preload essential data in order to analyze all networks (each region/period) within the current optimization window. Network tracing from demands down and sources up reveals `techs` that are "orphaned" @@ -102,28 +100,29 @@ These `techs` and any unused commodities, etc. are removed with log entries and used to filter the ingestion of the full model data to support the build. ### Basic rules for `tech` Suppression + - All demand side orphans (and chains of orphans) are suppressed -- All supply-side orphans (and chains of orpahns) are suppressed -- For technologies that have multiple inputs or outputs, EACH commodity is treated separately (as it is -represented in the `Efficiency` table.) The modeler is advised to screen the log file for these entries if a +* All supply-side orphans (and chains of orpahns) are suppressed +* For technologies that have multiple inputs or outputs, EACH commodity is treated separately (as it is +represented in the `efficiency` table.) The modeler is advised to screen the log file for these entries if a particular commodity is essential (a catalyst for example?) to a particular operation. -- For linked technologies, an "all or nothing" logic is implemented such that both the driver and driven +* For linked technologies, an "all or nothing" logic is implemented such that both the driver and driven technologies must prove independently viable (without regard for the emission linkage) or the pair will be suppressed. -- Currently, exchange technologies are not checked during source tracing. Any `tech` with a region link -(denoted with a dash `R1-R2`) provided for the region name is assumed good. It behooves the modeler to +* Currently, exchange technologies are not checked during source tracing. Any `tech` with a region link +(denoted with a dash `R1-R2`) provided for the region name is assumed good. It behooves the modeler to ensure that both ends of each link are well-connected in the model and to review their performance in output. ### Views across modeling periods -The approach descibed above is applied individually to each region-period pair within the optimization window. +The approach descibed above is applied individually to each region-period pair within the optimization window. Recall, that for perfect foresight runs, this will be all future periods. For myopic or other limited-visibility runs, -this will be applied to each period within view. The orphans are identified on a by period-region basis, but _the -suppression is applied for all periods within the view._ For example, if technology `power_plant` has a vintage of +this will be applied to each period within view. The orphans are identified on a by period-region basis, but *the +suppression is applied for all periods within the view.* For example, if technology `power_plant` has a vintage of 2020 and a lifetime of 40 years covering model periods {2020, 2030, 2040, 2050} and it is not viable in 2030, it will be suppressed and not available in any of the periods. Currently, for myopic applications, this decision is made on a per-iteration basis, so the technology may be -suppressed within the current iteration only. For example, if the myopic view was 1 period, and if the `power_plant` +suppressed within the current iteration only. For example, if the myopic view was 1 period, and if the `power_plant` was selected for build in its vintage year, 2020, the modeler may see that it is used in 2020, "suppressed" in 2030 because it may run freely or cause problems in that period, but available again in 2040-2050. This may/may not be desired behavior and the modeler should be aware of these behaviors logged in the log file. @@ -134,12 +133,3 @@ before there is a user of same) or via myopic actions were a technology in a cha rendering other processes in the chain irrelevant in a later period. The main goal of using tracing and suppression is to prevent "free" midstream commodities from erroneously feeding processes and prevent free-running techs that might have a negative cost from making the model unbounded or infeasible. - - - - - - - - - diff --git a/docs/source/Documentation.rst b/docs/source/Documentation.rst deleted file mode 100644 index 1c144a34c..000000000 --- a/docs/source/Documentation.rst +++ /dev/null @@ -1,3000 +0,0 @@ - - -======= -Preface -======= - - -This manual, in both `PDF`_ and `HTML`_ form, is the official documentation of -Tools for Energy Model Optimization and Analysis (Temoa). It describes all -functionality of the Temoa model, and provides a mathematical description of -the implemented equations. - -Besides this documentation, there are a couple other sources for Temoa-oriented -information. The most interactive is the `mailing list`_, and we encourage any -and all questions related to energy system modeling. Publications are good -introductory resources, but are not guaranteed to be the most up-to-date as -information and implementations evolve quickly. As with many software-oriented -projects, even before this manual, `the code is the most definitive resource`. -That said, please let us know (via the `mailing list`_, or other avenue) of any -discrepancies you find, and we will fix it as soon as possible. - -What is Temoa? --------------- - -Temoa is an energy system optimization model (ESOM). Briefly, ESOMs optimize the -installation and utilization of energy technology capacity over a user-defined -time horizon. Optimal decisions are driven by an objective function that minimizes -the cost of energy supply. Conceptually, one may think of an ESOM as a "left-to-right" -network graph, with a set of energy sources on the lefthand side of the graph that -are transformed into consumable energy commodities by a set of energy technologies, -which are ultimately used to meet demands on the righthand side of the network graph. -[#esom_definition]_ - -Key features of the core Temoa model include: - - * Flexible time slicing by season and time-of-day - * Variable length model time periods - * Technology vintaging - * Separate technology loan periods and lifetimes - * Global and technology-specific discount rates - * Capability to perform stochastic optimization - * Capability to perform modeling-to-generate alternatives (MGA) - - -Temoa design features include: - - * Source code licensed under GPLv2, available through Github [#open_source_realities]_ - * Open source software stack - * Part of a rich Python ecosystem - * Data stored in a relational database system (sqlite) - * Ability to utilize multi-core and compute cluster environments - -The word 'Temoa' is actually an acronym for "Tools for Energy Model Optimization -and Analysis," currently composed of four (major) pieces of infrastructure: - - * The mathematical model - * The implemented model (code) - * Surrounding tools - * An online presence - -Each of these pieces is fundamental to creating a transparent and usable model -with a community oriented around collaboration. - - -Why Temoa? ----------- - -In short, because we believe that ESOM-based analyses should be repeatable by -independent third parties. The only way to make this happen is to -have a freely available model, and to create an ecosystem of freely shared data -and model inputs. - -For a longer explanation, please see :cite:`Hunter_etal_2013` (available from -the `project website `_. In summary, -ESOM-based analyses are (1) impossible to validate, (2) complex enough as to be -non-repeatable without electronic access to **exact** versions of code *and* data -input, and (3) often do a poor job addressing uncertainty. We believe that -ESOM-based analyses should be completely open, independently reproducible, -electronically available, and address uncertainty about the future. - - -Temoa Origin and Pronunciation ------------------------------- - -While we use 'Temoa' as an acronym, it is an actual word in the Nahuatl (Aztec) -language, meaning "to seek something." - -.. Figure:: images/temoa_definition.* - :align: center - :figclass: center - :figwidth: 50% - -One pronounces the word 'Temoa' as "teh", "moe", "uh". Though TEMOA is an acronym -for 'Tools for Energy Model Optimization and Analysis', we generally use 'Temoa' -as a proper noun, and so forgo the need for all-caps. - - -Bug Reporting -------------- - -Temoa strives for correctness. Unfortunately, as an energy system model and software -project there are plenty of levels and avenues for error. If you spot a bug, -inconsistency, or general "that could be improved", we want to hear about it. - -If you are a software developer-type, feel free to open an issue on our `GitHub -Issue tracker`_\ . If you would rather not create a GitHub account, feel free -to let us know the issue on our `mailing list`_\ . - - -=========== -Quick Start -=========== - - -Installing Software Elements ----------------------------- - -Temoa is implemented in `Pyomo `_, which is in turn -written in `Python `_. Consequently, Temoa will run on -Linux, Mac, Windows, or any operating system that Pyomo supports. There are -several open source software elements required to run Temoa. The easiest way to -install these elements is to create a conda environment in which to run the -model. Creating a customized environment (or virtual environment) ensures that the latest version of -Temoa is compatible with the required software elements. Currently, users have a choice -between using Pythons :code:`pip` installation tool to set up a virtual environment or use -:code:`anaconda` as described below: - -**Anaconda**: To begin, you need to -have conda installed either via `miniconda -`_ or -`anaconda `_. -Next, download the `environment.yml -`_ file -from our `Github repo `__, and place it in -a new directory named ‘temoa-py3.’ Create this new directory in a location where -you wish to store the environment. On a MAc, the anaconda environment files are -stored in the following location by default: :code:`/opt/anaconda/envs` -Navigate to this directory and execute the following from the command line: - -.. parsed-literal:: - $ conda env create - -Then activate the environment as follows: - -.. parsed-literal:: - $ conda activate temoa-py3 - -For additional guidance, `This YouTube tutorial ` -walks through the creation of the Temoa environment. It is slightly dated with the -release of Version 3 of Temoa, but is still informative on how to set up. -More information on virtual environments can be found -`here `_. -This new conda environment contains several elements, including Python 3, a -compatible version of Pyomo, matplotlib, numpy, scipy. - -**Pip**: For those who prefer to use Python's :code:`pip` installation tool, instructions -are included in the top level README.md file - -Solvers -------- - -A few notes for on the choice of solvers. Different solvers -have widely varying solution times. If you plan to run Temoa with large datasets -and/or conduct uncertainty analysis, you may want to consider installing -commercial linear solvers such as `CPLEX -`_ or `Gurobi -`_. Both offer free academic licenses. - -For smaller models, Temoa has been tested with both the `CBC `_ -solver and the more recently released `HiGHS `_ solver. -Each of the respective websites contains installation instructions for the individual -solvers. For those wishing to run the internal tests on Temoa, the :code:`CBC` solver -is required. - -There are three ways to run the model, each of which is detailed below. Note that -the example commands utilize 'temoa_utopia', a commonly used test case for ESOMs. - -Obtaining Temoa ---------------- - -Now that you have functioning environment, you need to obtain the source code -for Temoa. There are a couple of options for obtaining and running Temoa from -GitHub. If you want to simply run the model, you can download Temoa from GitHub -as a zip file. Navigate to our `Github repo `__, -and click the green ‘clone or download’ button near the top-right corner. Select -‘Download ZIP,’ and you can download the entire Temoa :code:`main` (our main branch) -to your local machine. The second option creates a local copy of the model source -code in our GitHub repository. This is a two step process: first install git and -then ‘clone’ the repository. Under Linux, git can be installed through the default -package manager. Git for Windows and Mac can be downloaded from the `Git website -`_. To clone the Temoa repository, navigate to -the directory where you want the model to reside and type the following from the -prompt: - -.. parsed-literal:: - $ git clone https://github.com/TemoaProject/temoa/ - -Note that cloning the repository will supply the latest version of the code, and -allow you to archive changes to the code and data in your own local git -repository. - -A few basic input data files are included in the :code:`temoa/data_files`` folder. -Additional Temoa-compatible datasets are available in `this separate GitHub -repo `_. - -The installation procedures above are meant to be generic and should work across -different platforms. Nonetheless, system-specific ambiguities and unexpected -conditions inevitably arise. Please use the `Temoa forum -`_ to ask for help. - -Running Temoa -------------- -Temoa should always be run from the top-level from the top-level -:code:`temoa` directory. - -To run the model, a configuration (‘config’) file is needed. An -example config file called :code:`config_sample.toml` resides within the -:code:`data_files/my_configs` folder. Running the model with a config file allows -the user to (1) use a sqlite -database for storing input and output data, (2) create a formatted Excel output -file, (2) specify the solver to use, (3) return the log file produced during -model execution, (4) return the lp file utilized by the solver, and (5) to -execute several of the modeling extensions. - -.. parsed-literal:: - $ python temoa_model/ --config=data_files/my_configs/config_sample.toml - -**For general help, use --help:** - -.. parsed-literal:: - $ **python main.py --help** - usage: main.py [-h] [--config CONFIG_FILE] [-b] [-s] [-d] [-o OUTPUT_PATH] [--how_to_cite] [-v] - - options: - -h, --help show this help message and exit - --config CONFIG_FILE Path to file containing configuration information. - -b, --build_only Build and return an unsolved TemoaModel instance. - -s, --silent Silent run. No prompts. - -d, --debug Set logging level to DEBUG to see debugging output in log file. - -o OUTPUT_PATH, --output_path OUTPUT_PATH - Set the path for log and program outputs to an existing directory. Default is time-stamped folder in output_files. - --how_to_cite Show citation information for publishing purposes. - -v, --version Show current Temoa version - -.. - dated references, preserved as comment here: - - To supplement this documentation, we have also created a - `YouTube video tutorial ` that explains - how to run Temoa from the command line. There is also an option to run - `Temoa on the cloud `, which - is explained in `this video tutorial `. - -==================================== -Database Construction -==================================== - -Input datasets in Temoa can be constructed either as text files or relational -databases. Input text files are referred to as 'DAT' files and follow a specific -format. Take a look at the example DAT files in the :code:`temoa/data_files` -directory. - -While DAT files work fine for small datasets, relational databases are preferred -for larger datasets. To first order, you can think of a database as a collection -of tables, where a 'primary key' within each table defines a unique entry (i.e., -row) within the table. In addition, a 'foreign key' defines a table element drawn -from another table. Foreign keys enforce the defined relationships between -different sets and parameters. - -Temoa uses `sqlite`_, a widely used, self-contained database -system. Building a database first requires constructing a sql file, which is -simply a text file that defines the structure of different database tables and -includes the input data. The snippet below is from the technology table used to -define the 'temoa_utopia' dataset: - -**NOTE**: *As of Version 3, the below table format is dated, but still shows the general structure of -the SQL files used to create the database.* - -.. parsed-literal:: - CREATE TABLE technologies ( - tech text primary key, - flag text, - sector text, - tech_desc text, - tech_category text, - FOREIGN KEY(flag) REFERENCES technology_labels(tech_labels), - FOREIGN KEY(sector) REFERENCES sector_labels(sector)); - INSERT INTO "technologies" VALUES('IMPDSL1','r','supply',' imported diesel','petroleum'); - INSERT INTO "technologies" VALUES('IMPGSL1','r','supply',' imported gasoline','petroleum'); - INSERT INTO "technologies" VALUES('IMPHCO1','r','supply',' imported coal','coal'); - -The first line creates the table. **Lines 2-6** define the columns within this table. -Note that the the technology ('tech') name defines the primary key. Therefore, the -same technology name cannot be entered twice; each technology name must be unique. -**Lines 7-8** define foreign keys within the table. For example, each technology -should be specified with a label (e.g., 'r' for 'resource'). Those labels must -come from the 'technology_labels' table. Likewise, the sector name must be defined -in the 'sector_labels' table. This enforcement of names across tables using -foreign keys helps immediately catch typos. (As you can imagine, typos happen in -plain text files and Excel when defining thousands of rows of data.) Another big -advantage of using databases is that the model run outputs are stored in -separate database output tables. The outputs by model run are indexed by a scenario name, -which makes it possible to perform thousands of runs, programatically store all -the results, and execute arbitrary queries that instantaneously return the requested -data. - -Because some database table elements serve as foreign keys in other tables, we -recommend that you populate input tables in the following order: - -**Group 1: labels used for internal database processing** - * CommodityType: Need to identify which type of commodity. Do NOT change these abbreviations. - * TechnologyType: Need to identify which type of technology. Do NOT change these abbreviations. Categorizing - and sub-categorizing can be done in the Technology table itself. - * TimePeriodType: Used to distinguish which time periods are simply used to specify pre-existing - vintages and which represent future optimization periods. - - -**Group 2: sets used within Temoa** - * Commodity: list of commodities used within the database - * Technology: list of technologies used within the database - * TimePeriod: list of both past and future time periods considered in the database - * TimeSeason: seasons modeled in the database - * TimeOfDay: time of day segments modeled in the database - - -**Group 3: parameters used to define processes within Temoa** - * GlobalDiscountRate - * Demand - * DemandSpecificDistribution - * Efficiency - * ExistingCapacity - * CapacityFactor - * CapacityFactorProcess (only if CF varies by vintage; overwrites CapacityFactor) - * Capacity2Activity - * CostFixed - * CostInvest - * CostVariable - * EmissionsActivity - * LoanLifetimeTech - * LifetimeProcess - * LifetimeTech - -**Group 4: parameters used to define constraints within Temoa** - * GrowthRateSeed - * GrowthRateMax - * MinCapacity - * MaxCapacity - * MinActivity - * MaxActivity - * RampUp - * RampDown - * TechOutputSplit - * TechInputSplit - -For help getting started, take a look at how :code:`data_files/temoa_utopia.sql` is -constructed. Use :code:`data_files/temoa_schema.sql` (a database file with the requisite -structure but no data added) to begin building your own database file. We recommend -leaving the database structure intact, and simply adding data to the schema file, or -constructing an empty database from the schema file and then using a database editor -to import data. -Once the sql file is complete, you can convert it into a binary sqlite file by -installing sqlite3 and executing the following command: - -.. parsed-literal:: - $ sqlite3 my_database.sqlite < my_database.sql - -Now you can specify this database as the source for both input and output data -in the config file. - -============ -Data Quality -============ - -In addition to numerous internal checks, Temoa (optionally) employs two quality checks on -data read in from the database. The outputs (actions and warnings) generated by these processes -are reported in the log file for the run. - -Both of the checks below can be run to QA data by running the model in `CHECK` mode and inspecting -the log file. During `CHECK` mode runs, no solve is attempted on the model. - -Price Checking --------------- -The "price checker" reviews cost data in the 3 cost tables and considers technology lifetime. It -screens for possible inconsistencies that would corrupt output quality. Larger models may have -well over 100K cost entries and an overlooked investment cost for a particular vintage tech in -a particular region could easily be overlooked. Price checks performed/reported: - -1. **Missing Costs (Check 0)**: This check looks for technologies that have no fixed/invest/variable - costs at all. Other checks are more discriminating, so this check is only reported when Temoa is run - in `debug` mode by using the `-d` flag on the run command. -2. **Missing Fixed/Investment Costs (Check 1a)**: This check identifies technologies that are *not* - flagged as `uncapacitated` with neither a fixed or investment cost associated. These *might* be - problematic for solve because the model minimizes cost, so capacity in these technologies would be - free. `uncapacitated` technologies have no capacity measure, so fixed/investment costs are prohibited - for them and that is checked elsewhere. -3. **Inconsistent Fixed/Investment Cost (Check 1b)**: This check looks for inconsistent application - of fixed or base costs in the "base" or vintage year across all vintages and regions. So, if a tech has - a fixed cost in some particular region and vintage year, but not in all, it will be flagged as a likely - omission. -4. **Inconsistent Fixed & Variable Costs (Check 2)**: This check identifies techs that have - inconsistencies in the application of fixed - variable costs. Techs that have *any* fixed cost for - a particular [region, tech, vintage] process, but do not have entries that match the variable cost - entries for the same process are flagged, and vice-versa. This would hopefully identify an - accidental omission of some of the fixed/var costs for processes that have at least 1 entry for either. -5. **Lifetime Costing (Check 3)**: This check identifies costs that fall short or are missing - during the process's lifetime. If a process has a variable cost in *any* year during the lifetime, but - not all years, it is flagged. Same for fixed cost. -6. **Uncapacitated Tech Costs**: Any technology flagged as `uncapacitated` will trigger warnings here - if it has any fixed/invest costs. - -Source Tracing --------------- - -Temoa works backwards from demands to identify chains of technologies required to meet the demand. -Source Tracing is designed to ensure that this backward tracing from demands describes a proper -commodity network without gaps that might allow intermediate commodities to be treated as a free -"source" commodity. Further description of possible network problems is included in the -`commodity network notes.md` file in the `docs` folder. - -Source Tracing pre-builds the entire commodity network in each region-period contained in the -data and analyzes it for "orphans" which likely represent gaps in the network that would lead -to erroneous output data. The operation is enabled by tagging foundational commodities for which -there are no predecessors as "source" commodities in the `Commodity` database table with an `s` tag. -Orphans (or chains of orphans) on either the demand or supply side are reported and *suppressed* in -the data to prevent network corruption. - -Note that the myopic mode *requires* the use of Source Tracing to ensure accuracy as some orphans -may be produced by endogenous decisions in myopic runs. - -Commodity Network Visualization -------------------------------- -The output of the Source Tracing operation can be visualized by enabling the commodity network plots -in the config file. This will add a set of region-period specific html files to the Outputs folder. -These files *should* be open-able in any web browser. (See the note in the main `README.md` for trouble -with Windows OS systems). - -.. Figure:: images/utopia_commodity_network.png - :align: center - :figclass: center - :figwidth: 60% - - An example of the Commodity Network for Utopia (interactive view in web browser by opening - the generated html file) - -The color legend for Commodity Networks is as follows: - -* Green dot: Source Commodity -* Orange dot: Demand Commodity -* Violet dot: Intermediate Commodity -* Black arc: Technology -* Blue arc: Linked Technology (the driven tech, *not* the driver) -* Yellow arc: Supply-Side Orphan (shown, but suppressed when model built) -* Red arc: Demand-Side Orphan (shown, but suppressed when model built) -* Green arc: Any Tech with a Negative Variable Cost - -============= -Visualization -============= - -Network Diagrams ----------------- - -Since Temoa model consists of an energy network in which technologies are connected -by the flow of energy commodities, a directed network graph represents an excellent way -to visualize a given energy system representation in a Temoa-compatible input database. -Temoa utilizes an open source graphics package called `Graphviz`_ to create a series of -data-specific and interactive energy-system maps. Currently, the output graphs consist of -a full energy system map as well as capacity and activity results per model time period. -In addition, users can create subgraphs focused on a particular commodity or technology. - -There are a couple ways to utilize Graphviz. The first way is to use the version embedded -in the :code:`Network_diagrams.ipynb` jupyter notebook available in the :code:`data_processing` -folder. This `YouTube video tutorial ` walks through the process -of using the jupyter notebook. - -The second way is to use graphviz from the command line. To do so, navigate to the -:code:`data_processing` folder, where the graphviz script resides. To review all of the -graphviz options, use the :code:`--help` flag: - -.. parsed-literal:: - $ python MakeGraphviz.py --help - -The most basic way to use graphviz is to view the full energy system map: - -.. parsed-literal:: - $ python MakeGraphviz.py -i ../data_files/temoa_utopia.sqlite - -In the command above, note that we have to point the Graphviz module to the -:code:`temoa_utopia` database file, which resides in the :code:`data_files` -directory. The resultant system map will look like this: - -.. Figure:: images/simple_model.* - :align: center - :figclass: center - :figwidth: 60% - - This is a map of the simple 'Utopia' system, which we often use for testing - purposes. The map shows the possible commodity flows through the system, - providing a comprehensive overview of the system. Creating the simple system - map is useful for debugging purposes in order to make sure that technologies - are linked together properly via commodity flows. - -It is also possible to create a system map showing the optimal installed capacity -and technology flows in a particular model time period. These results are associated -with a specific model run stored in the model database. To view the results, include -the scenario flag (:code:`-s`) and a specific model year (:code:`-y`). - -Note that when Graphiz runs, it creates a folder within the :code:`data_processing` -folder. The folder itself is assigned the name of the database file, with -:code:`input_graphviz` appended to the end. This descriptor changes if using Graphviz -to visualize output graphics. Within this Graphviz-generated folder are two files. The -graphics file (default: svg) is a viewable image of the network. The dot file is the -input file to Graphviz that is created programmatically. Note that the dot files provide -another means to debug the model and create an archive of visualizations for auditing -purposes. In addition, we have taken care to make these intermediate files well-formatted. - -.. parsed-literal:: - $ python MakeGraphviz.py -i ../data_files/temoa_utopia.sqlite -s test_run -y 1990 - -.. figure:: images/global_results.* - :align: center - :figclass: center - :figwidth: 60% - - This graph shows the optimal installed capacity and commodity flows from the - 'utopia' test system in 2010. - -The output can also be fine-tuned to show results associated with a specific -commodity or technology. For example: - -.. parsed-literal:: - $ python MakeGraphviz.py -i ../data_files/temoa_utopia.sqlite -s test_run -y 2010 -b E31 - -.. figure:: images/techvintage_results.* - :align: center - :figclass: center - :figwidth: 60% - - In this case, the graph shows the commodity flow in and out of - technology 'E31' in 2010, which is from the 'test_run' scenario drawn from the - 'temoa_utopia' database. - -Output Graphs -------------- - -Temoa can also be used to generate output graphs using `matplotlib `. -From the command line, navigate to the :code:`data_processing` folder and execute the following command: - -.. parsed-literal:: - $ python MakeOutputPlots.py --help - -The command above will specify all of the flags required to created a stacked bar -or line plot. For example, consider the following command: - -.. parsed-literal:: - $ python MakeOutputPlots.py -i ../data_files/temoa_utopia.sqlite -s test_run -p capacity -c electric --super - -Here is the result: - -.. figure:: images/output_flow_example.* - :align: center - :figclass: center - :figwidth: 60% - - This stacked bar plot represents the activity (i.e., output commodity flow) - associated with each technology in the electric sector from the 'test_run' - scenario drawn from the 'temoa_utopia' database. Because the :code:`super` - flag was specified, technologies are grouped together based on user-specified - categories in the :code:`tech_category`` column of the :code:`technologies` - table of the database. - - -===================== -The Math Behind Temoa -===================== - - To understand this section, the reader will need at least a cursory - understanding of mathematical optimization. We omit here that introduction, - and instead refer the reader to `various`_ `available`_ `online`_ `sources`_. - Temoa is formulated as an algebraic model that requires information organized - into sets, parameters, variables, and equation - definitions. - -The heart of Temoa is a technology explicit energy system optimization model. -It is an algebraic network of linked processes -- where each process is defined -by a set of engineering characteristics (e.g. capital cost, efficiency, capacity -factor, emission rates) -- that transform raw energy sources into end-use -demands. The model objective function minimizes the present-value cost of -energy supply by optimizing installed capacity and its utilization over time. - -.. _simple_system: - -.. figure:: images/simple_system2.* - :align: center - :width: 100% - :alt: A simple energy system, with energy sources on the left and energy - sinks (end-use demands) on the right. - :figclass: align-center - :figwidth: 70% - - A common visualization of energy system models is a directed network graph, - with energy sources on the left and end-use demands on the right. The - modeler must specify the end-use demands to be met, the technologies defined - within the system (rectangles), and the inputs and outputs of each (red and green - arrows). The circles represent distinct energy carriers that connect - technologies within the energy system network. - -The most fundamental tenet of the model is the understanding of energy flow, -treating all processes as black boxes that take inputs and produce outputs. -Specifically, Temoa does not care about the inner workings of a process, only -its global input and output characteristics. In this vein, the above graphic -can be broken down into process-specific elements. For example, the coal power -plant takes as input coal and produces electricity, and is subject to various -costs (e.g. variable costs) and constraints (e.g. efficiency) along the way. - -.. figure:: images/coal_process.png - :align: center - :figclass: center - :figwidth: 60% - - -The modeler defines the processes and engineering characteristics through a -combination of sets and parameters, described in the next few sections. Temoa then -utilizes these parameters, along with the associated technology-specific decision -variables for capacity and activity, to create the objective function and -constraints that are used during the optimization process. - -.. _Sets: - - -Conventions ------------ - - * In the mathematical notation, we use CAPITALIZATION to denote a container, - like a set, indexed variable, or indexed parameter. Sets use only a single - letter, so we use the lower case to represent an item from the set. For - example, :math:`T` represents the set of all technologies and :math:`t` - represents a single item from :math:`T`. - - * Variables are named V\_VarName within the code to aid readability. However, - in the documentation where there is benefit of italics and other font - manipulations, we elide the 'V\_' prefix. - - * In all equations, we **bold** variables to distinguish them from parameters. - Take, for example, this excerpt from the Temoa default objective function: - - .. math:: - C_{variable} = \sum_{p, s, d, i, t, v, o \in \Theta_{VC}} \left ( - {VC}_{p, t, v} - \cdot R_p - \cdot \textbf{FO}_{p, s, d, i, t, v, o} - \right ) - - Note that :math:`C_{variable}` is not bold, as it is a temporary variable - used for clarity while constructing the objective function. It is not a - structural variable and the solver never sees it. - - * Where appropriate, we put the variable on the right side of the coefficient. - In other words, this is not a preferred form of the previous equation: - - .. math:: - - C_{variable} = \sum_{r, p, s, d, i, t, v, o \in \Theta_{VC}} \left ( - \textbf{FO}_{r, p, s, d, i, t, v, o} - \cdot {VC}_{r, p, t, v} - \cdot R_p - \right ) - - * We generally put the limiting or defining aspect of an equation on the right - hand side of the relational operator, and the aspect being limited or defined - on the left hand side. For example, equation :eq:`Capacity` defines Temoa's - mathematical understanding of a process capacity (:math:`\textbf{CAP}`) in - terms of that process' activity (:math:`\textbf{ACT}`): - - .. math:: - - \left ( - \text{CFP}_{r, t, v} - \cdot \text{C2A}_{r, t} - \cdot \text{SEG}_{s, d} - \cdot \text{TLF}_{r, p, t, v} - \right ) - \cdot \textbf{CAP}_{r, t, v} - = - \sum_{I, O} \textbf{FO}_{r, p, s, d,i, t, v, o} - + - \sum_{I, O} \textbf{CUR}_{r, p, s, d, i, t, v, o} - - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{FO}} - - * We use the word 'slice' to refer to the tuple of season and time of day - :math:`\{s,d\}`. Note that these time slices are user-defined, and can - represent time ranging large blocks of time (e.g., winter-night) to every - hour in a given season. - - * We use the word 'process' to refer to the tuple of technology and vintage - (:math:`\{t,v\}`). For example, solar PV (technology) installed in 2030 - (vintage). - - - * Mathematical notation: - - * We use the symbol :math:`\mathbb{I}` to represent the unit interval ([0, - 1]). - - * We use the symbol :math:`\mathbb{Z}` to represent "the set of all - integers." - - * We use the symbol :math:`\mathbb{N}` to represent natural numbers (i.e., - integers greater than zero: 1, 2, 3, :math:`\ldots`). - - * We use the symbol :math:`\mathbb{R}` to denote the set of real numbers, and - :math:`\mathbb{R}^+_0` to denote non-negative real numbers. - - -Sets ----- - -.. _table_set: - -.. csv-table:: List of all Temoa sets with which a modeler might interact. The - asterisked (\*) elements are automatically derived by the model and - are not user-specifiable. - :header: "Set","Temoa Name","Data Type","Short Description" - :widths: 8, 28, 14, 50 - - ":math:`{}^*\text{C}`",":code:`commodity_all`","string","union of all commodity sets" - ":math:`\text{C}^d`",":code:`commodity_demand`","string","end-use demand commodities" - ":math:`\text{C}^e`",":code:`commodity_emissions`","string","emission commodities (e.g. :math:`\text{CO}_\text{2}` :math:`\text{NO}_\text{x}`)" - ":math:`\text{C}^p`",":code:`commodity_physical`","string","general energy forms (e.g. electricity, coal, uranium, oil)" - ":math:`{}^*\text{C}^c`",":code:`commodity_carrier`","string","physical energy carriers and end-use demands (:math:`\text{C}_p \cup \text{C}_d`)" - ":math:`\text{I}`",,"string","alias of :math:`\text{C}^p`; used in documentation only to mean ""input""" - ":math:`\text{O}`",,"string","alias of :math:`\text{C}^c`; used in documentation only to mean ""output""" - ":math:`\text{P}^e`",":code:`time_existing`",":math:`\mathbb{Z}`","model periods before optimization begins" - ":math:`\text{P}^f`",":code:`time_future`",":math:`\mathbb{Z}`","model time scale of interest; the last year is not optimized" - ":math:`{}^*\text{P}^o`",":code:`time_optimize`",":math:`\mathbb{Z}`","model time periods to optimize; (:math:`\text{P}^f - \text{max}(\text{P}^f)`)" - ":math:`\text{R}`",":code:`regions`","string","distinct geographical regions" - ":math:`{}^*\text{V}`",":code:`vintage_all`",":math:`\mathbb{Z}`","possible tech vintages; (:math:`\text{P}^e \cup \text{P}^o`)" - ":math:`\text{S}`",":code:`time_season`","string","seasonal divisions (e.g. winter, summer)" - ":math:`\text{D}`",":code:`time_of_day`","string","time-of-day divisions (e.g. morning)" - ":math:`{}^*\text{T}`",":code:`tech_all`","string","all technologies to be modeled; (:math:`{T}^r \cup {T}^p`)" - ":math:`\text{T}^u`",":code:`tech_unlim_cap`","string","technologies that have no bound on capacity, and can have variable costs only (imports, taxes, etc.); (:math:`{T}^a \subset (T - T^{res}`)" - ":math:`\text{T}^a`",":code:`tech_annual`","string","technologies that produce constant annual output; (:math:`{T}^a \subset T`)" - ":math:`\text{T}^b`",":code:`tech_baseload`","string","baseload electric generators; (:math:`{T}^b \subset T`)" - ":math:`\text{T}^c`",":code:`tech_curtailment`","string","technologies with curtailable output and no upstream cost; (:math:`{T}^c \subset (T - T^{res})`)" - ":math:`\text{T}^e`",":code:`tech_exchange`","string","technologies used for interregional commodity flow; (:math:`{T}^e \subset T`). See Note 1 below on capacity and cost application for `tech_exchange`" - ":math:`\text{T}^f`",":code:`tech_flex`","string","technologies producing excess commodity flows; (:math:`{T}^f \subset T`)" - "",":code:`TechGroupName`","string","named groups for use in group parameters or RegionalPortfolioStandard" - "",":code:`TechGroupMember`","(TechGroupName, tech)","technologies belonging to each group defined above" - ":math:`\text{T}^p`",":code:`tech_production`","string","techs producing intermediate commodities" - ":math:`\text{T}^r`",":code:`tech_resource`","string","resource extraction technologies" - ":math:`\text{T}^m`",":code:`tech_ramping`","string","electric generators with a ramp rate limit; (:math:`{T}^m \subset T`)" - ":math:`\text{T}^{res}`",":code:`tech_reserve`","string","electric generators contributing to the reserve margin requirement; (:math:`{T}^e \subset T`)" - ":math:`\text{T}^s`",":code:`tech_storage`","string","storage technologies; (:math:`{T}^s \subset T`)" - ":math:`\text{T}^v`",":code:`tech_variable`","string","technologies used in TechInputSplitAverage constraint; (:math:`{T}^v \subset T`)" - -Note 1: Temoa sets Capacity for Exchange Technologies to be equal in both directions on the link automatically. -Costs are apportioned as follows: If both directions of the link have a cost parameter, costs are accrued to -each region region directly based on flow *to* that region. If only 1 element of the link holds a populated cost value, -then that cost divided between the 2 regions automatically based on use, where each region is "billed" according -to use as a receiver. - -Temoa uses two different set notation styles, one for code representation and -one that utilizes standard algebraic notation. For brevity, the mathematical -representation uses capital letters to denote sets, and lower case letters to -represent items within sets. For example, :math:`T` represents the set of all -technologies and :math:`t` represents an item within :math:`T`. - -The code representation is more verbose than the algebraic version, using full -words. This documentation presents them in an italicized font. The same -example of all technologies is represented in the code as :code:`tech_all`. -:ref:`Table 1 ` lists all of the Temoa sets, with both sets of notation. - -There are four basic set "groups" within Temoa: periods, sub-annual "time slices", -technologies, and energy commodities. The technology-related sets contain all the -possible energy technologies that the model may build and the commodities sets -contain all the input and output forms of energy that technologies consume and -produce. The period and time slice sets merit a slightly longer discussion. - -Temoa's conceptual model of *time* is broken up into three levels, and energy supply -and demand is balanced at each of these levels: - - * **Periods** - consecutive blocks of years, marked by the first year in the - period. For example, a two-period model might consist of :math:`\text{P}^f = - \{2010, 2015, 2025\}`, representing the two periods of years from 2010 - through 2014, and from 2015 through 2024. Note the that last period element - \(2025\) does not represent a new time period, but rather defines the upper bound - for the second time period. - - * **Seasonal** - Each year may have multiple seasons. In a conventional time-sliced - model, the seasons may typically represent the four seasons: winter, spring summer, - and fall. However, the seasonal slices can represent any amount of time at the - sub-period scale. For example, in a database with representative days, the seasonal - slices can be used as generic containers to represent blocks of days. - - * **Daily** - Within a season, a given day can be further subdivided into different - time segments. Less detailed databases may include larger blocks of time, such as morning, - afternoon, and night. In a database with representative days, each daily segment can - be used to represent every hour of the day. - - -There are two specifiable period sets: :code:`time_exist` (:math:`\text{P}^e`) -and :code:`time_future` (:math:`\text{P}^f`). The :code:`time_exist` set -contains periods before :code:`time_future`. Its primary purpose is to specify -the vintages for capacity that exist prior to the model optimization. -The :code:`time_future` set contains the future periods that the model will -optimize. As this set must contain only integers, Temoa interprets the elements -to be the boundaries of each period of interest. Thus, this is an ordered set -and Temoa uses its elements to automatically calculate the length of each -optimization period; modelers may exploit this to create variable period lengths -within a given input database. Temoa "names" each optimization period by the first -year, and makes them easily accessible via the :code:`time_optimize` set. This final -"period" set is not user-specifiable, but is an exact duplicate of -:code:`time_future`, less the largest element. In the above example, since -:math:`\text{P}^f = \{2010, 2015, 2025\}`, :code:`time_optimize` does not -contain 2025: :math:`\text{P}^o =\{2010, 2015\}`. - -One final note on periods: rather than optimizing each year within a period -individually, Temoa makes the simplifying assumption that each time period contains -:math:`n` copies of a single, representative year. Temoa optimizes capacity -and activity for just this characteristic year within each time period, assuming -the results for different years in the same time period are identical. The Temoa -objective function, however, accounts for the total cost across all years in all -model time periods. Figure 3.3 gives a graphical explanation of the annual -delineation. - -.. _FigureObjectiveComparison: - -.. figure:: images/ObjectiveUsageVsCostComparison.png - :align: center - :width: 100% - :alt: Energy use same each year; time-value of annual costs reduced each year - :figclass: align-center - :figwidth: 60% - - The left graph is of energy, while the right graph is of the annual costs. - The energy used in a period by a process is the same for all - years (with exception for those processes that cease their useful life - mid-period). However, even though the costs incurred will be the same, the - time-value of money changes due to the discount-rate. As the fixed costs of - a process are tied to the length of its useful life, those processes that do - not fall on a period boundary require unique time-value multipliers in the - objective function. - -As noted above, Temoa allows the modeler to subdivide each year into a set of time -slices, comprised of a season and a time of day. Unlike :code:`time_future`, there -is no restriction on what labels the modeler may assign to the :code:`time_season` -and :code:`time_of_day` set elements. - - -A Word on Index Ordering -^^^^^^^^^^^^^^^^^^^^^^^^ - -The ordering of the indices is consistent throughout the model to promote an -intuitive "left-to-right" description of each parameter, variable, and -constraint set. For example, Temoa's output commodity flow variable -:math:`FO_{r,p,s,d,i,t,v,o}` may be described as "in region (:math:`r`), -in period (:math:`p`) during season (:math:`s`) at time of day (:math:`d`), -the flow of input commodity (:math:`i`) to technology (:math:`t`) of vintage -(:math:`v`) generates an output commodity flow (:math:`o`) of -:math:`FO_{r,p,s,d,i,t,v,o}`." For any indexed parameter or variable within -Temoa, our intent is to enable a mental model of a simple left-to-right, arrow-box-arrow -mnemonic to describe the "input :math:`\rightarrow` process -:math:`\rightarrow` output" flow of energy. And while not all variables, parameters, -or constraints have 8 indices, the 8-index order mentioned here (r, p, s, d, i, t, v, o) -is the canonical ordering. If you note any case where, for example, d comes before s, -that is an oversight. In general, if there is an index ordering that does not follow -this rubric, we view that as a bug. - - -Deviations from Standard Mathematical Notation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Temoa deviates from standard mathematical notation and set understanding in two -ways. The first is that Temoa places a restriction on the *time* set elements. -Specifically, while most optimization programs treat set elements as arbitrary -labels, Temoa assumes that all elements of the :code:`time_existing` and -:code:`time_future` sets are integers. Further, these sets are assumed to be -ordered, such that the minimum element is "naught". For example, if -:math:`\text{P}^f = \{2015, 2020, 2030\}`, then :math:`P_0 = 2015`. In -other words, the capital :math:`\text{P}` with the naught subscript indicates -the first element in the :code:`time_future` set. We will explain the reason -for this notation shortly. - -The second set of deviations revolves around the use of the Theta superset -(:math:`\Theta`). The Temoa code makes heavy use of sparse sets, for both -correctness and efficient use of computational resources. For brevity, and -to avoid discussion of implementation details, we do not enumerate their -logical creation here. Instead, we rely on the readers general understanding of -the context. For example, in the sparse creation of the constraints of the -Demand constraint class (explained in :ref:`NetworkConstraints` and -:ref:`constraint-anatomy`), we state simply that the constraint is instantiated -"for all the :math:`\{p, s, d, dem\}` tuples in -:math:`\Theta_{\text{demand}}`". This means that the constraint is only defined -for the exact indices for which the modeler specified end-use demands via the -Demand parameter in the input data file. - -Summations also occur in a sparse manner. For example, let's take another look at -the :code:`Capacity` :eq:`Capacity` Constraint: - -.. math:: - - \left ( - \text{CFP}_{r, s, d, t, v} - \cdot \text{C2A}_{r, t} - \cdot \text{SEG}_{s, d} - \cdot \text{TLF}_{r, p, t, v} - \right ) - \cdot \textbf{CAP}_{r, t, v} - = - \sum_{I, O} \textbf{FO}_{r, p, s, d,i, t, v, o} - + - \sum_{I, O} \textbf{CUR}_{r, p, s, d, i, t, v, o} - - \\ - \forall \{p, s, d, t, v\} \in \Theta_{\text{Capacity}} - -It defines the Capacity variable for every valid combination of :math:`\{p, v\}`, -and includes the sum over all inputs and outputs of the FlowOut variable. A -naive implementation of this equation might include nonsensical items in each -summation, such as an input of vehicle miles traveled to an oil refinery or an -output of sunlight from nuclear generating capacity. However, in this context, -summing over the inputs and outputs (:math:`i` and :math:`o`) implicitly -includes only the valid combinations of :math:`\{p, s, d, i, t, v, o\}`. - - -Parameters ----------- - -.. _table_parameter: - -.. csv-table:: List of Temoa parameters with which a modeler might interact. - The asterisked (\*) parameters specified at the end of the table - are automatically derived by the model and are not user-specifiable. - :header: "Parameter","Temoa Name","Domain","Short Description" - :widths: 14, 27, 10, 49 - - ":math:`\text{CC}_{r,p,t,v}`","CapacityCredit",":math:`\mathbb{I}`","Process-specific capacity credit" - ":math:`\text{CFT}_{r,s,d,t}`","CapacityFactorTech",":math:`\mathbb{I}`","Technology-specific capacity factor" - ":math:`\text{CFP}_{r,s,d,t,v}`","CapacityFactorProcess",":math:`\mathbb{I}`","Process-specific capacity factor" - ":math:`\text{C2A}_{r,t,v}`","CapacityToActivity",":math:`\mathbb{R}^+_0`","Converts from capacity to activity units" - ":math:`\text{CF}_{r,p,t,v}`","CostFixed",":math:`\mathbb{R}`","Fixed operations \& maintenance cost" - ":math:`\text{CI}_{r,t,v}`","CostInvest",":math:`\mathbb{R}`","Tech-specific investment cost" - ":math:`\text{CV}_{r,p,t,v}`","CostVariable",":math:`\mathbb{R}`","Variable operations \& maintenance cost" - ":math:`\text{DEM}_{r,p,c}`","Demand",":math:`\mathbb{R}^+_0`","End-use demands, by period" - ":math:`\text{DDD}_{p,s,d}`","DemandDefaultDistribution",":math:`\mathbb{I}`","Default demand distribution (currently not supported)" - ":math:`\text{DSD}_{r,p,s,d,c}`","DemandSpecificDistribution",":math:`\mathbb{I}`","Demand-specific distribution" - ":math:`\text{EFF}_{r,i,t,v,o}`","Efficiency",":math:`\mathbb{R}^+_0`","Tech- and commodity-specific efficiency" - ":math:`\text{EAC}_{r,i,t,v,o,e}`","EmissionActivity",":math:`\mathbb{R}`","Tech-specific emissions rate" - ":math:`\text{ELM}_{r,p,e}`","EmissionLimit",":math:`\mathbb{R}^+_0`","Emissions limit by region and period" - ":math:`\text{ECAP}_{r,t,v}`","ExistingCapacity",":math:`\mathbb{R}^+_0`","Pre-existing capacity" - ":math:`\text{GDR}`","GlobalDiscountRate",":math:`\mathbb{R}`","Global rate used to calculate present cost" - ":math:`\text{GRM}_{r,t}`","GrowthRateMax",":math:`\mathbb{R}`","Global rate used to calculate present cost" - ":math:`\text{GRS}_{r,t}`","GrowthRateSeed",":math:`\mathbb{R}`","Global rate used to calculate present cost" - ":math:`\text{LTP}_{r,t,v}`","LifetimeProcess",":math:`\mathbb{N}`","Tech- and vintage-specific lifetime (default=LifetimeTech)" - ":math:`\text{LTT}_{r,t}`","LifetimeTech",":math:`\mathbb{N}`","Tech-specific lifetime (default=40 periods)" - ":math:`\text{LIT}_{r,t,e,t}`","LinkedTechs","text","Dummy techs used to convert CO2 emissions to physical commodity" - ":math:`\text{LLT}_{r,t}`","LoanLifetimeTech",":math:`\mathbb{N}`","Tech-specific loan term (default=10 periods)" - ":math:`\text{LR}_{r,t,v}`","LoanRate",":math:`\mathbb{R}`","Tech-specific interest rate on investment cost" - ":math:`\text{MAA}_{r,p,t}`","MaxActivity",":math:`\mathbb{R}^+_0`","Maximum tech-specific activity by region and period" - ":math:`\text{MAC}_{r,p,t}`","MaxCapacity",":math:`\mathbb{R}^+_0`","Maximum tech-specific capacity by period" - ":math:`\text{MCS}_{t}`","MaxCapacitySum",":math:`\mathbb{R}^+_0`","Maximum capacity for a technology group" - ":math:`\text{MAR}_{r,t}`","MaxResource",":math:`\mathbb{R}^+_0`","Maximum resource production by tech across time periods (currently not supported)" - ":math:`\text{MIA}_{r,p,t}`","MinActivity",":math:`\mathbb{R}^+_0`","Minimum tech-specific activity by region and period" - ":math:`\text{MIC}_{r,p,t}`","MinCapacity",":math:`\mathbb{R}^+_0`","Minimum tech-specific capacity by period" - ":math:`\text{MCS}_{t}`","MinCapacitySum",":math:`\mathbb{R}^+_0`","Minimum capacity for a technology group" - ":math:`\text{MGT}_{r}`","MinGenGroupTarget",":math:`\mathbb{R}^+_0`","Target applied to techs in MinActivityGroup constraint" - ":math:`\text{MGW}_{r,t}`","MinGenGroupWeight",":math:`\mathbb{R}^+_0`","Weight applied to techs in MinActivityGroup constraint" - ":math:`\text{MBY}`","MyopicBaseYear",":math:`\mathbb{N}`","Objective function base year when running myopically" - ":math:`\text{PRM}_{r}`","PlanningReserveMargin",":math:`\mathbb{I}`","Margin used to ensure sufficient generating capacity" - ":math:`\text{RMD}_{r,t}`","RampDown",":math:`\mathbb{R}`","Rate at which generation techs can ramp output down" - ":math:`\text{RMU}_{r,t}`","RampUp",":math:`\mathbb{R}`","Rate at which generation techs can ramp output up" - ":math:`\text{RSC}_{r.p,c}`","ResourceBound",":math:`\mathbb{R}^+_0`","Maximum resource production by tech and period (currently not supported)" - ":math:`\text{SD}_{r,t}`","StorageDuration",":math:`\mathbb{N}`","Storage duration per technology, specified in hours" - ":math:`\text{SEG}_{s,d}`","SegFrac",":math:`\mathbb{I}`","Fraction of year represented by each (s, d) tuple" - ":math:`\text{SIF}_{t}`","StorageInitFrac",":math:`\mathbb{I}`","Initial storage charge level expressed as fraction of full charge" - ":math:`\text{TIS}_{r,i,t}`","TechInputSplit",":math:`\mathbb{I}`","Technology input fuel ratio at time slice level" - ":math:`\text{TISA}_{r,i,t}`","TechInputSplitAverage",":math:`\mathbb{I}`","Average annual technology input fuel ratio" - ":math:`\text{TOS}_{r,t,o}`","TechOutputSplit",":math:`\mathbb{I}`","Technology output fuel ratio at time slice level" - ":math:`{}^*\text{LA}_{t,v}`","LoanAnnualize",":math:`\mathbb{R}^+_0`","Loan amortization by tech and vintage; based on :math:`DR_t`" - ":math:`{}^*\text{MPL}_{p,t,v}`","ModelProcessLife",":math:`\mathbb{N}`","Smaller of remaining model horizon or process tech life" - ":math:`{}^*\text{PLF}_{r,p,t,v}`","ProcessLifeFrac",":math:`\mathbb{I}`","Fraction of available process capacity by region and period " - ":math:`{}^*\text{LEN}_p`","PeriodLength",":math:`\mathbb{N}`","Number of years in period :math:`p`" - - -.. _influential_efficiency: - -Efficiency -^^^^^^^^^^ - -:math:`{EFF}_{r \in R, i \in C_p, t \in T, v \in V, o \in C_c}` - -We present the efficiency (:math:`EFF`) parameter first as it is one of the most -critical model parameters. Beyond defining the conversion efficiency of each -process, Temoa also utilizes the indices to understand the valid input -:math:`\rightarrow` process :math:`\rightarrow` output paths for energy. For -instance, if a modeler does not specify an efficiency for a 2020 vintage coal -power plant, then Temoa will recognize any mention of a 2020 vintage coal power -plant elsewhere as an error. Generally, if a process is not specified in the -efficiency table,\ [#efficiency_table]_ Temoa assumes it is not a valid process -and will provide the user a warning with pointed debugging information. - - -.. _CapacityFactorTech: - -CapacityCredit -^^^^^^^^^^^^^^ - -:math:`{CC}_{r \in R, p \in P, t \in T, v \in V}` - -The capacity credit represents the fraction of total installed capacity of -a process that can be relied upon during the time slice in which peak -electricity demand occurs. This parameter is used in the :math:`ReserveMargin` -constraint. - -CapacityFactorTech -^^^^^^^^^^^^^^^^^^ - -:math:`{CFT}_{r \in R, s \in S, d \in D, t \in T}` - -Temoa indexes the :code:`CapacityFactorTech` parameter by season, time-of-day, -and technology. - -CapacityFactorProcess -^^^^^^^^^^^^^^^^^^^^^ - -:math:`{CFP}_{r \in R, s \in S, d \in D, t \in T, v \in V}` - -In addition to :ref:`CapacityFactorTech`, there may be cases where different -vintages of the same technology have different capacity factors. For example, -newer vintages of wind turbines may have higher capacity factors. So, -:code:`CapacityFactorProcess` allows users to specify the capacity factor by -season, time-of-day, technology, and vintage. - - -CapacityToActivity -^^^^^^^^^^^^^^^^^^ - -:math:`{C2A}_{r \in R, t \in T}` - -Capacity and Activity are inherently two different units of measure. Capacity -represents the maximum flow of energy per time (:math:`\frac{energy}{time}`), -while Activity is a measure of total energy actually produced. However, there are -times when one needs to compare the two, and this parameter makes those -comparisons more natural. For example, a capacity of 1 GW for one year works -out to an activity of - -.. math:: - - {1 GW} \cdot {8,760 \tfrac{hr}{yr}} \cdot {3,600 \tfrac{sec}{hr}} \cdot - {10^{-6} \tfrac{P}{G}} = {31.536 \tfrac{PJ}{yr}} - -.. centered:: - or - -.. math:: - - {1 GW} \cdot {8,760 \tfrac{hr}{yr}} \cdot {10^{-3} \tfrac{T}{G}} = {8.75 TWh} - -When comparing one capacity to another, the comparison is easy, unit wise. -However, when one *needs* to compare capacity and activity, how does one -reconcile the units? One way to think about the utility of this parameter is in -the context of the question: "How much activity would this capacity create, if -used 100% of the time?" - - -CostFixed -^^^^^^^^^ - -:math:`{CF}_{r \in R, p \in P, t \in T, v \in V}` - -The :code:`CostFixed` parameter specifies the fixed cost associated with any -process. Fixed costs are those that must be paid, regardless of how much the -process is utilized. For instance, if the model decides to build a nuclear -power plant, even if it decides not utilize the plant, the model must pay the -fixed costs. These costs are in addition to the capital cost, so once the -capital is paid off, these costs are still incurred every year the process -exists. - -Temoa's default objective function assumes the modeler has specified this -parameter in units of currency per unit capacity (:math:`\tfrac{Dollars}{Unit -Cap}`). - - -CostInvest -^^^^^^^^^^ - -:math:`{CI}_{r \in R, t \in T, v \in P}` - -The :code:`CostInvest` parameter specifies the process-specific investment cost. -Unlike the :code:`CostFixed` and :code:`CostVariable` parameters, -:code:`CostInvest` only applies to vintages of technologies within the model -optimization horizon (:math:`\text{P}^o`). Like :code:`CostFixed`, -:code:`CostInvest` is specified in units of cost per unit of capacity and is -only used in the default objective function (:math:`\tfrac{Dollars}{Unit Cap}`). - - -CostVariable -^^^^^^^^^^^^ - -:math:`{CV}_{r \in R, p \in P,t \in T,v \in V}` - -The :code:`CostVariable` parameter represents the cost of a process-specific unit -of activity. Thus the incurred variable costs are proportional to the activity -of the process. - - -.. _Demand: - -Demand -^^^^^^ - -:math:`{DEM}_{r \in r, p \in P, c \in C^d}` - -The :code:`Demand` parameter allows the modeler to define the total end-use -demand levels for all periods. In combination with the :code:`Efficiency` -parameter, this parameter is the most important because without it, the rest of -model has no incentive to build anything. This parameter specifies the end-use -demands that appear at the far right edge of the system diagram. - -To specify the distribution of demand, look to the -:code:`DemandDefaultDistribution` (DDD) and :code:`DemandSpecificDistribution` -(DSD) parameters. - -As a historical note, this parameter was at one time also indexed by season and -time of day, allowing modelers to specify exact demands for every time slice. -However, while extremely flexible, this proved too tedious to maintain for any -data set of appreciable size. Thus, we implemented the DDD and DSD parameters. - - -.. _DDD: - -DemandDefaultDistribution -^^^^^^^^^^^^^^^^^^^^^^^^^ - -:math:`{DDD}_{s \in S, d \in D}` - -**Note**: The Demand Specific Distribution is currently not supported in -the project. Modelers should use the DSD or rely on a "flat" distribution default -from the TimeSegmentFraction. - -By default, Temoa assumes that end-use demands (:ref:`Demand`) are evenly -distributed throughout a year. In other words, the Demand will be apportioned -by the :code:`SegFrac` parameter via: - -.. math:: - - \text{EndUseDemand}_{s, d, c} = {SegFrac}_{s, d} \cdot {Demand}_{p, c} - -Temoa enables this default action by automatically setting DDD equivalent to -:code:`SegFrac` for all seasons and times of day. If a modeler would like a -different default demand distribution, the indices and values of the DDD -parameter must be specified. Like the :ref:`SegFrac` parameter, the sum of -DDD must be 1. - - -DemandSpecificDistribution -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:math:`{DSD}_{r \in R, s \in S, d \in D, c \in C^d}` - -If there is an end-use demand that varies over the course of a day or across -seasons -- for example, heating or cooling in the summer or winter -- the -modeler may specify the fraction of annual demand occurring in each time slice. -Like :ref:`SegFrac` and :ref:`DDD`, the sum of DSD for each :math:`c` must be 1. -If the modeler does not define DSD for a season, time of day, and demand -commodity, Temoa automatically populates this parameter according to DDD. -It is this parameter that is actually multiplied by the :code:`Demand` parameter -in the Demand constraint. - - -EmissionActivity -^^^^^^^^^^^^^^^^ - -:math:`{EAC}_{e \in C_e,\{r,i,t,v,o\} \in \Theta_{\text{efficiency}}}` - -Temoa currently has two methods for enabling a process to produce an output: the -:code:`Efficiency` parameter, and the :code:`EmissionActivity` parameter. Where -the :code:`Efficiency` parameter defines the amount of output energy a process -produces per unit of input, the :code:`EmissionActivity` parameter allows for -secondary outputs. As the name suggests, this parameter was originally intended -to account for emissions per unit activity, but it more accurately describes -*parallel* activity. It is restricted to emissions accounting (by the -:math:`e \in C^e` set restriction). - - -EmissionLimit -^^^^^^^^^^^^^ - -:math:`{ELM}_{r \in R, p \in P, e \in C^e}` - -The :code:`EmissionLimit` parameter ensures that Temoa finds a solution that -fits within the modeler-specified limit of emission :math:`e` in time period -:math:`p`. - - -ExistingCapacity -^^^^^^^^^^^^^^^^ - -:math:`{ECAP}_{r \in R, t \in T, v \in \text{P}^e}` - -The :code:`ExistingCapacity` parameter defines the capacity installed prior to the -beginning of :code:`time_optimize`. Note that processes with existing capacity -require all of the engineering-economic characteristics of a standard process, -with the exception of an investment cost. - -.. _GDR: - -GlobalDiscountRate -^^^^^^^^^^^^^^^^^^ - -:math:`{GDR}` - -The :code:`GDR` parameter represents the global discount rate used to convert -cash flows in future model time periods into a present value. The future value -(FV) of a sum of currency is related to the net present value (NPV) via the -formula: - -.. math:: - - \text{FV} = \text{NPV} \cdot {(1 + GDR)^n} - -where :math:`n` is in years. This parameter is used to calculate all discounted -costs, which are the basis of the objective function. Costs are discounted to the -first future time period in the model by default. If running a Myopic run, the -discount base year can be set in the MetaData table. This is the :code:`MyopicBaseYear`. - -The output in the :code:`OutputCost` table shows both discounted and non-discounted (raw) -values for all model costs. Of note, all loan costs are displayed as an annuity cost in -the vintage year, not as a string of payments. - -The Global Discount Rate is entered in the MetaDataReal table in the database. - -GrowthRateMax -^^^^^^^^^^^^^ - -:math:`{GRM}_{r \in R, t \in T}` - -The :code:`GRM` parameter defines the maximum annual rate at which the capacity of -a given technology can grow. Note that the growth rate is not defined by vintage, -but rather across all vintages of a given technology. - - -GrowthRateSeed -^^^^^^^^^^^^^^ - -:math:`{GRS}_{r \in R, t \in T}` - -The :code:`GRS` parameter defines the maximum capacity of a given technology when -first installed in a given time period. The growth rate is applied to this initial -capacity seed in subsequent time periods. - - -LoanLifetimeProcess -^^^^^^^^^^^^^^^^^^^ - -:math:`{LLP}_{r \in R, t \in T, v \in P}` - -**Note**: :code:`LifetimeLoanProcess` is currently not supported in the database. -Modelers should use the :code:`LoanLifetimeTech` below. - -Temoa gives the modeler the ability to separate the loan lifetime from the -useful life of a process. This parameter specifies the loan term associated -with capital investment in a process, in years. If not specified, the model -assigns the technology lifetime to the loan period in :code:`temoa_initialize.py`. - - -LoanLifetimeTech -^^^^^^^^^^^^^^^^ - -:math:`{LLT}_{r \in R, t \in T}` - -Same as the :code:`LoanLifetimeProcess` but without the vintage index. If all -vintages of a given technology are assumed to have the same loan term, then -:code:`LoanLifetimeTech` can be defined instead of :code:`LoanLifetimeProcess`. - - -LifetimeProcess -^^^^^^^^^^^^^^^ - -:math:`{LTP}_{r \in R, t \in T, v \in P}` - -This parameter specifies the total useful life of a given process in years. - - -LifetimeTech -^^^^^^^^^^^^ - -:math:`{LTT}_{r \in R, t \in T}` - -Similar to LifetimeProcess, this parameter specifies the total useful life of a -given technology in years. If all vintages of a given technology have the same -lifetime, then :code:`LifeTimeTech` can be used instead of :code:`LifeTimeProcess`. - - -LinkedTechs -^^^^^^^^^^^ - -:math:`{LIT}_{r \in R, t \in T, e \in C^e, t \in T}` - -In power-to-gas pathways, :math:`CO2` is an input to some processes, including -synthetic natural gas production and liquid fuel production via Fischer-Tropsch. -Within the model, :math:`CO2` must be converted from an emissions commodity to a -physical commodity that can be included in the :code:`Efficiency` table. The -:code:`LinkedTechs` parameter specifies the dummy technology used to convert an -emissions commodity to a physical commodity. Note that the first :code:`t` -represents the primary upstream technology linked to the dummy linked technology, -which is represented by the second :code:`t` index. - - -LoanRate -^^^^^^^^ - -:math:`{LR}_{r \in r, t \in T, v \in V}` - -The interest rate used for loans supporting investment costs. The default -loan rate is accessible in the MetaDataReals table in the database. - - -.. _ParamMaxCapacity: - - -MaxActivity -^^^^^^^^^^^ - -:math:`{MAA}_{r \in R, p \in P, t \in T}` - -The :code:`MaxActivity` parameter is used to constrain the total activity (i.e., -energy production) from a given technology in each model time period. Note that the -total activity is constrained across all vintages of a technology. This parameter -is used in the :code:`MaxActivity_Constraint`. - - -MaxCapacity -^^^^^^^^^^^ - -:math:`{MAC}_{r \in R, p \in P, t \in T}` - -The :code:`MaxCapacity` parameter represents an upper bound on the total installed -capacity of a given technology in each model time period. Note that the total -capacity is constrained across all vintages of a technology. This parameter is -used in the :code:`MaxCapacity_Constraint`. - - -MaxCapacitySum -^^^^^^^^^^^^^^ - -:math:`{MCS}_{t \in T}` - -Similar to the :code:`MaxCapacity` parameter, but represents an upper bound on -the total installed capacity across all model time periods. In addition, -this parameter specifies the upper bound on a group of technologies specified -in the :code:`tech_capacity_max` subset. This parameter is used in the -:code:`MaxCapacitySet_Constraint`. - - -MaxResource -^^^^^^^^^^^ - -:math:`{MAR}_{r \in R, t \in T}` - -**Note**: The MaxResource parameter/constraint is currently not supported in the model. - -The :code:`MaxResource` parameter represents an upper bound on the cumulative -amount of commodity that can be produced by region and technology over the model time -horizon. This parameter is used in :code:`MaxResource_Constraint`. Note that -this parameter differs from :code:`ResourceBound`, which is also indexed by -model time period. - - -MinActivity -^^^^^^^^^^^ - -:math:`{MIA}_{r \in R, p \in P, t \in T}` - -The :code:`MinActivity` parameter represents a lower bound on the total activity (i.e., -energy production) of a given technology in each model time period. Note that the -total activity is constrained across all vintages of a technology. This parameter -is used in the :code:`MinActivity_Constraint`. - - -MinCapacity -^^^^^^^^^^^ - -:math:`{MIC}_{r \in R, p \in P,t \in T}` - -The :code:`MinCapacity` parameter represents a lower bound on the total installed -capacity of a given technology in each model time period. Note that the total -capacity is constrained across all vintages of a technology. This parameter is -used in the :code:`MinCapacity_Constraint`. - - -MinCapacitySum -^^^^^^^^^^^^^^ - -:math:`{MCS}_{t \in T}` - -The :code:`MinCapacitySum` parameter represents the minimum cumulative -capacity associated with technologies belonging to :code:`tech_group`. -This parameter is used in the :code:`MinActivityGroup_Constraint`. - - -MinGenGroupTarget -^^^^^^^^^^^^^^^^^ - -:math:`{MGT}_{r \in R}` - -The :code:`MinGenGroupTarget` parameter is similar to :code:`MinActivity`, but -represents a minimum activity limit for a user-defined technology group -(:code:`tech_groups`) rather than a single technology. This parameter is used -in the :code:`MinActivityGroup_Constraint`. - - -MinGenGroupWeight -^^^^^^^^^^^^^^^^^ - -:math:`{MGW}_{r \in R, t \in T}` - -The :code:`MinGenGroupWeight` parameter represents a weight that is applied -to each technology within each :code:`tech_group`, which determines the -technology-specific activity shares that can count towards meeting the -:code:`MinActivityGroup_Constraint`. - - -MyopicBaseYear -^^^^^^^^^^^^^^ - -:math:`MBY` - -Temoa is typically run in "perfect foresight" mode, where all decision variables -in all time periods are solved simultaneously. However, it is also possible to -solve the model myopically, whereby the model solves a subset of time periods -in sequence. The :code:`MyopicBaseYear` parameter specifies the base year to which -all future costs are discounted. This parameter is located in the :code:`MetaData` -table in the database. - - -PlanningReserveMargin -^^^^^^^^^^^^^^^^^^^^^ - -:math:`{PRM}_{r \in R}` - -The :code:`PlanningReserveMargin` parameter specifies that capacity reserve margin -in the electric sector by region. The capacity reserve margin represents the -installed generating capacity - expressed as a share of peak load - that must be -available to meet contingencies. Note that since electricity demand is often -endogeous in Temoa databases, we calculate electricity production by time slice -to estimate the peak electricity demand. This parameter is used in -:code:`ReserveMargin_Constraint`. - - -RampDown -^^^^^^^^ - -:math:`{RMD}_{r \in R, t \in T}` - -To account for the limited ramping capability of some thermal generators, a -ramp down rate can be specified via the :code:`RampDown` parameter. The specified -value represents the fraction of installed capacity that can be ramped down when moving -from one time slice to the next. There is an equivalent :code:`RampUp` parameter, to -specify ramping limits in the upward direction. This parameter is used in the -:code:`RampDownDay_Constraint` and `RampDownSeason_Constraint`. The former constrains -the downward ramp rate between time-of-day slices, and the latter constrains the downward -ramp rate between the last time-of-day slice in a given season and the first time-of-day -slice in the next season. - - -RampUp -^^^^^^ - -:math:`{RMU}_{r \in R, t \in T}` - -To account for the limited ramping capability of some thermal generators, a -ramp up rate can be specified via the :code:`RampUp` parameter. The specified -value represents the fraction of installed capacity that can be ramped up when moving -from one time slice to the next. There is an equivalent :code:`RampDown` parameter, to -specify ramping limits in the downward direction. This parameter is used in the -:code:`RampUpDay_Constraint` and `RampUpSeason_Constraint`. The former constrains -the upward ramp rate between time-of-day slices, and the latter constrains the upward -ramp rate between the last time-of-day slice in a given season and the first time-of-day -slice in the next season. - - -ResourceBound -^^^^^^^^^^^^^ - -:math:`{RSC}_{r \in R, p \in P, c \in C_p}` - -This parameter allows the modeler to specify commodity production limits per period. -Note that a constraint in one period does not relate to any other periods. For -instance, if the modeler specifies a limit in period 1 and does not specify a -limit in period 2, then the model may use as much of that resource as it would -like in period 2. This parameter is used in :code:`ResourceExtraction_Constraint`. -Note that the :code:`MaxResource` parameter is similar, but constrains total -cumulative resource consumption across all model time periods. - - -.. _SegFrac: - -SegFrac -^^^^^^^ - -:math:`{SEG}_{s \in S,d \in D}` - -The :code:`SegFrac` parameter specifies the fraction of the year represented by -each combination of season and time of day. The sum of all combinations within -:code:`SegFrac` must be 1, representing 100% of a year. - - -StorageDuration -^^^^^^^^^^^^^^^ - -:math:`{SD}_{r \in R, t \in T^{S}}` - -The :code:`StorageDuration` parameter represents the number of hours over which -storage can discharge if it starts at full charge and produces maximum output -until empty. The parameter value defaults to 4 hours if not specified by the user. - - -StorageInit -^^^^^^^^^^^ - -:math:`{SI}_{r \in R, t \in T^{S}, v \in P}` - -The :code:`StorageInit` parameter determines the initial charge level associated -with each storage technology. The value should be expressed as a fraction between -0 and 1. - -Note 1: that this is an optional parameter and should only be used if the -user wishes to set the initial charge rather than allowing the model to optimize it. - -Note 2: This initialization is currently *not supported*. Values in the StorageInit -table will be ignored and a log warning will be generated. - - -TechInputSplit -^^^^^^^^^^^^^^ - -:math:`{TIS}_{r \in R, p \in P, i \in C_p, t \in T}` - -Some technologies have a single output but have multiple input fuels. The -:code:`TechInputSplit` parameter fixes the shares of commodity input to a -specific technology in a given period. Note that this fixed share is maintained -across all model time slices. This parameter is used in -:code:`TechInputSplit_Constraint`. - - -TechInputSplitAverage -^^^^^^^^^^^^^^^^^^^^^ - -:math:`{TISA}_{r \in R, p \in P, i \in C_p, t \in T}` - -The :code:`TechInputSplitAverage` is similar to :code:`TechInputSplit`, as -they both fix input commodity shares to technologies with multiple inputs. -However, :code:`TechInputSplitAverage` only fixes the average shares at the -annual level, allowing the shares at the time slice level to vary. This -parameter is used in :code:`TechInputSplitAverage_Constraint`. - - -TechOutputSplit -^^^^^^^^^^^^^^^ - -:math:`{TOS}_{t \in T, o \in C_c}` - -Some technologies have a single input fuel but have multiple outputs. The -:code:`TechOutputSplit` parameter fixes the shares of commodity input to a -specific technology in a given period. Note that this fixed share is maintained -across all model time slices. This parameter is used in -:code:`TechOutputSplit_Constraint`. - - -\*LoanAnnualize -^^^^^^^^^^^^^^^ - -:math:`{LA}_{r \in R, t \in T, v \in P}` - -This is a model-calculated parameter based on the process-specific loan length -(its indices are the same as the :code:`LifetimeLoan` parameter), and -process-specific discount rate (the :code:`DiscountRate` parameter). It is -calculated via the formula: - -.. math:: - - LA_{t,v} = \frac{DR_{r,t,v}}{1 - (1 + DR_{r,t,v})^{{}^- LLN_{r,t,v}}} - - \forall \{t, v\} \in \Theta_\text{CostInvest} - - -ModelProcessLife -^^^^^^^^^^^^^^^^ - -:math:`{MPL}_{r \in R, p \in P, t \in T, v \in P}` - -The :code:`ModelProcessLife` parameter is internally-derived by the model calcuated in -:code:`ParamModelProcessLife_rule` and which makes use of the :code:`LifetimeProcess` -parameter. For a given technology vintage in a given model time period, it returns the -lesser of the period length and the remaining process lifetime. This parameter is used -to sum the annual :code:`fixed_costs` and :code:`variable_costs` across all years within -a given time period. - - -\*PeriodLength -^^^^^^^^^^^^^^ - -:math:`{LEN}_{p \in P}` - -Given that the modeler may specify arbitrary time period boundaries, this -parameter specifies the number of years contained in each period. The final year -is the largest element in :code:`time_future` which is specifically not included -in the list of periods in :code:`time_optimize` (:math:`\text{P}^o`). The length -calculation for each period then exploits the fact that the ``time`` sets are -ordered: - -.. math:: - - \text{LET boundaries} & = \text{sorted}(\text{P}^f) \\ - \text{LET I(p)} & = \text{index of p in boundaries} \\ - & \therefore \\ - {LEN}_p & = \text{boundaries}[ I(p) + 1 ] - p - - \forall p \in P - -The first line creates a sorted array of the period boundaries, called -*boundaries*. The second line defines a function `I` that finds the index of -period :math:`p` in boundaries. The third line then defines the length of period -:math:`p` to be the number of years between period :math:`p` and the next -period. For example, if :math:`\text{P}^f = \{2015, 2020, 2030, 2045\}`, -then *boundaries* would be :code:`[2015, 2020, 2030, 2045]`. For 2020, I(2020) -would return 2. Similarly, boundaries[ 3 ] = 2030. Then, - -.. math:: - {LEN}_{2020} & = \text{boundaries}[I(2020) + 1] - (2020) \\ - & = \text{boundaries} [2 + 1] - 2020 \\ - & = \text{boundaries} [3] - 2020 \\ - & = 2030 - 2020 \\ - & = 10 - -Note that LEN is only defined for elements in :math:`\text{P}^o`, and is -specifically not defined for the final element in :math:`\text{P}^f`. - - -\*ProcessLifeFrac -^^^^^^^^^^^^^^^^^ - -:math:`{PLF}_{r \in R, p \in P,t \in T,v \in P}` - -The modeler may specify a useful lifetime of a process such that the process -will be decommissioned part way through a period. Rather than attempt to -delineate each year within that final period, Temoa averages the total output -of the process over the entire period but limits the available capacity and -output of the decommissioning process by the ratio of how long through the -period the process is active. This parameter is that ratio, formally defined -as: - -.. math:: - - PLF_{p,t,v} = \frac{v + LTP_{t,v} - p}{LEN_p} - - \\ - \forall \{p,t,v\} & \in \Theta_\text{Activity by PTV} | \\ - v + LTP_{t,v} & \notin P, \\ - v + LTP_{t,v} & \le max(F), \\ - p & = max(P | p < v + LTP_{t,v}) - -Note that this parameter is defined over the same indices as -:code:`CostVariable` -- the active periods for each process :math:`\{p, t, -v\}`. As an example, if a model has :math:`P = \{2010, 2012, -2020, 2030\}`, and a process :math:`\{t, v\} = \{car, 2010\}` has a useful -lifetime of 5 years, then this parameter would include only the first two -activity indices for the process. Namely, :math:`p \in \{2010, 2012\}` as -:math:`\{p, t, v\} \in \{\{2010, car, 2010\}, \{2012, car, -2010\}\}`. The values would be :math:`{TLF}_{2010, car, 2010} = 1`, and -:math:`{TLF}_{2012, car, 2010} = \frac{3}{8}`. - - -Variables ---------- - -.. _table_variable: -.. csv-table:: Temoa's Main Variables - :header: "Variable","Temoa Name","Domain","Short Description" - :widths: 18, 22, 10, 50 - - ":math:`FO_{r,p,s,d,i,t,v,o}`","V_FlowOut",":math:`\mathbb{R}^+_0`","Commodity flow by time slice out of a tech based on a given input" - ":math:`FOA_{r,p,s,d,i,t,v,o}`","V_FlowOutAnnual",":math:`\mathbb{R}^+_0`","Annual commodity flow out of a tech based on a given input" - ":math:`FIS_{r,p,s,d,i,t,v,o}`","V_FlowIn",":math:`\mathbb{R}^+_0`","Commodity flow into a storage tech to produce a given output" - ":math:`FLX_{r,p,s,d,i,t,v,o}`","V_Flex",":math:`\mathbb{R}^+_0`","The portion of commodity production exceeding demand" - ":math:`FLXA_{r,p,i,t,v,o}`","V_FlexAnnual",":math:`\mathbb{R}^+_0`","The portion of commodity production from constant production techs exceeding demand" - ":math:`CUR_{r,p,s,d,i,t,v,o}`","V_Curtailment",":math:`\mathbb{R}^+_0`","Commodity flow out of a tech that is curtailed" - ":math:`CAP_{r,t,v}`","V_Capacity",":math:`\mathbb{R}^+_0`","Required tech capacity to support associated activity" - ":math:`CAPAVL_{r,p,t}`","V_CapacityAvailable ByPeriodAndTech",":math:`\mathbb{R}^+_0`","Derived variable representing the capacity of technology :math:`t` available in period :math:`p`" - ":math:`SI_{r,t,v}`","V_StorageInit",":math:`\mathbb{R}^+_0`","Initial charge level associated with storage techs" - ":math:`SL_{r,p,s,d,t,v}`","V_StorageLevel",":math:`\mathbb{R}^+_0`","Charge level each time slice associated with storage techs" - -V_FlowOut -^^^^^^^^^ - -:math:`FO_{r,p,s,d,i,t,v,o}` - -The most fundamental variable in the Temoa formulation is the -:code:`V_FlowOut` variable. It describes the commodity flow out of a -process in a given time slice. To balance input and output flows in the -:code:`CommodityBalance_Constraint`, the commodity flow into a given -process can be calculated as -:math:`\sum_{T, V, O} \textbf{FO}_{p, s, d, c, t, v, o} -/EFF_{c,t,v,o}`. - -V_FlowOutAnnual -^^^^^^^^^^^^^^^ - -:math:`FOA_{r,p,i,t,v,o}` - -Similar to :code:`V_FlowOut`, but used for technologies that are members -of the :code:`tech_annual` set, whose output does not vary across seasons -and times-of-day. Eliminating the :code:`s,d` indices for these technologies -improves computational performance. - - -V_Flex -^^^^^^ - -:math:`FLX_{r,p,s,d,i,t,v,o}` - -In some cases, the overproduction of a commodity may be required, such -that supply exceeds the endogenous demand. Refineries represent a -common example, where the share of different refined products are governed -by TechOutputSplit, but total production is driven by a particular commodity. -For example, gasoline production may be artificially constrained in order to -ensure the appropriate balance for lower demand fuels such as propane or -kerosene. Instead, we allow overproduction, i.e., production exceeding -endogenous demand, for commodities produced by technologies belonging to -the :code:`tech_flex` set. In the example above, adding the refinery to -the :code:`tech_flex` set allows for the overproduction of propane and -kerosene, allowing the model to fulfill the endogenous demand -for gasoline. This flexible technology designation activates a slack -variable (:math:`\textbf{FLX}_{r, p, s, d, i, t, v, c}`)representing -the excess production in the :code:`CommodityBalanceAnnual_Constraint`. - - -V_FlexAnnual -^^^^^^^^^^^^ - -:math:`FLXA_{r,p,i,t,v,o}` - -Similar to :code:`V_Flex`, but used for technologies that are members -of the :code:`tech_flex` set, whose output does not vary across seasons -and times-of-day. Eliminating the :code:`s,d` indices for these technologies -improves computational performance. - - -V_Curtailment -^^^^^^^^^^^^^ - -:math:`CUR_{r,p,s,d,i,t,v,o}` - -The :code:`V_Curtailment` variable is an accounting tool to help calculate -the unused production capacity of technologies annotated in the Technology -database table as curtailable technologies belonging to the -:code:`tech_curtailment` set. -Renewables such as wind and solar are often placed in this set. While we -used to simply formulate the :code:`Capacity` and :code:`CommodityBalance` -constraints as inequalities that implicitly allowed for curtailment, this -simpler approach does not work with renewable targets because the curtailed -portion of the electricity production counts towards the target, and there is -no way to distinguish it from the useful production. Including an explicit -curtailment term addresses the issue. Curtailment in the model is simply -the production activity that is not used in the model and is reported as -such in the OutputCurtailment table. Note: Outputs presented in the -`OutputCurtailment` table for curtailment (the table separately includes -flex outputs) are limited by Capacity Factor. Meaning: if a tech has a -capacity of 10 units, and a CF of 0.8 and a usage of 5 units, then the reported -curtailment is 3 units (0.8 x 10 - 5). - - -V_FlowInStorage -^^^^^^^^^^^^^^^ - -:math:`FIS_{r,p,s,d,i,t,v,o}` - -Because the production and consumption associated with storage techs occur -across different time slices, the comodity flow into a storage technologiy -cannot be discerned from :code:`V_FlowOut`. Thus an explicit :math:`FlowIn` -variable is required for storage. - -V_Capacity -^^^^^^^^^^ - -:math:`CAP_{r,t,v}` - -The :code:`V_Capacity` variable determines the required capacity of all processes -across the user-defined system. It is indexed for each process (t,v), and Temoa -constrains the capacity variable to be able to meet the total commodity flow out -of that process in all time slices in which it is active :eq:`Capacity`. - -V_CapacityAvailableByPeriodAndTech -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -:math:`CAPAVL_{r,p,t}` - -:code:`CapacityAvailableByPeriodAndTech` is a convenience variable that is -not strictly necessary, but used where the individual vintages of a technology -are not warranted (e.g. in calculating the maximum or minimum total capacity -allowed in a given time period). - -V_StorageInit -^^^^^^^^^^^^^ - -:math:`SI_{r,t,v}` - -The :code:`V_StorageInit` variable determines the initial storage charge level -at the beginning of the first time slice within a given time period. Each vintage -of each technology can have a different optimal initial value. Note that -this value also determines the ending storage charge level at the end of the -last time slice within each model time period. - -V_StorageLevel -^^^^^^^^^^^^^^ - -:math:`SL_{r,p,s,d,t,v}` - -The :code:`V_StorageLevel` variable tracks the storage charge level across ordered -time slices and is critical to ensure that storage charge and dispatch is constrained -by the energy available in the storage units. - - - -We explain the equations governing these variables the :ref:`Constraints` -section. - - -.. _Constraints: - -Equations ---------- - -There are four main equations that govern the flow of energy through the model -network. The :code:`Demand_Constrant` :eq:`Demand` ensures that the supply meets -demand in every time slice. For each process, the :code:`Capacity_Constraint` :eq:`Capacity` -ensures that there is sufficient capacity to meet the optimal commodity flows across all -time slices. Between processes, the :code:`CommodityBalance_Constraint` :eq:`CommodityBalance` -ensures that global commodity production across the energy system is sufficient to meet the -endogenous demands for that commodity. Finally, the objective function :eq:`obj_loan` drives -the model to minimize the system-wide cost of energy supply by optimizing the deployment and -utilization of energy technologies across the system. - -One additional point regarding the model formulation. Technologies that -produce constant annual output can be placed in the :code:`tech_annual` set. -While not required, doing so improves computational performance by eliminating the -season and time of day :code:`(s,d)` indices associated with these technologies. -In order to ensure the model functions correctly with these simplified technologies, -slightly different formulations of the capacity and commodity balance constraints -are required. See the :code:`CommodityBalanceAnnual_Constraint` :eq:`CommodityBalanceAnnual` -and :code:`CapacityAnnual_Constraint` :eq:`CapacityAnnual` below for details. - -The rest of this section defines each model constraint, with a rationale for -existence. We use the implementation-specific names for the constraints to -highlight the organization of the functions within the actual code. Note that -the definitions below are pulled directly from the docstrings embedded in -:code:`temoa_rules.py`. - - -.. _DecisionVariables: - -Constraints Defining Derived Decision Variables -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -These first four constraints define derived variables that are used within -the model. The :code:`Capacity_Constraint` and :code:`CapacityAnnual_Constraint` -are particularly important because they define the relationship between installed -capacity and allowable commodity flow. - -.. autofunction:: temoa_rules.Capacity_Constraint - -.. autofunction:: temoa_rules.AdjustedCapacity_Constraint - -.. autofunction:: temoa_rules.CapacityAnnual_Constraint - -.. autofunction:: temoa_rules.CapacityAvailableByPeriodAndTech_Constraint - -.. autofunction:: temoa_rules.ActivityByTech_Constraint - - -.. _NetworkConstraints: - -Network Constraints -^^^^^^^^^^^^^^^^^^^ - -These three constraints define the core of the Temoa model; together, they -define the algebraic energy system network. - -.. autofunction:: temoa_rules.Demand_Constraint - -.. autofunction:: temoa_rules.CommodityBalance_Constraint - -.. autofunction:: temoa_rules.CommodityBalanceAnnual_Constraint - - -Physical and Operational Constraints -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -These constraints fine-tune the model formulation to account for -various physical and operational real-world phenomena. - -.. autofunction:: temoa_rules.BaseloadDiurnal_Constraint - -.. autofunction:: temoa_rules.DemandActivity_Constraint - -.. autofunction:: temoa_rules.StorageEnergy_Constraint - -.. autofunction:: temoa_rules.StorageEnergyUpperBound_Constraint - -.. autofunction:: temoa_rules.StorageChargeRate_Constraint - -.. autofunction:: temoa_rules.StorageDischargeRate_Constraint - -.. autofunction:: temoa_rules.StorageThroughput_Constraint - -.. autofunction:: temoa_rules.StorageInit_Constraint - -.. autofunction:: temoa_rules.RampUpDay_Constraint - -.. autofunction:: temoa_rules.RampDownDay_Constraint - -.. autofunction:: temoa_rules.RampUpSeason_Constraint - -.. autofunction:: temoa_rules.RampDownSeason_Constraint - -.. autofunction:: temoa_rules.ReserveMargin_Constraint - - -Objective Function -^^^^^^^^^^^^^^^^^^ - -.. autofunction:: temoa_rules.TotalCost_rule - - -User-Specific Constraints -^^^^^^^^^^^^^^^^^^^^^^^^^ - -The constraints provided in this section are not required for proper system -operation, but allow the modeler some further degree of system specification. - -.. commented out... not used? - .. autofunction:: temoa_rules.ExistingCapacity_Constraint - -.. autofunction:: temoa_rules.EmissionLimit_Constraint - -.. autofunction:: temoa_rules.GrowthRateConstraint_rule - -.. autofunction:: temoa_rules.MaxActivity_Constraint - -.. autofunction:: temoa_rules.MinActivity_Constraint - -.. autofunction:: temoa_rules.MinActivityGroup_Constraint - -.. _MaxCapacity_Constraint: - -.. autofunction:: temoa_rules.MaxCapacity_Constraint - -.. commented out... not used? - .. autofunction:: temoa_rules.MaxCapacitySet_Constraint - -.. autofunction:: temoa_rules.MinCapacity_Constraint - -.. commented out... not used? - .. autofunction:: temoa_rules.MinCapacitySet_Constraint - -.. autofunction:: temoa_rules.ResourceExtraction_Constraint - -.. _TechOutputSplit_Constraint: - -.. autofunction:: temoa_rules.TechInputSplit_Constraint - -.. autofunction:: temoa_rules.TechOutputSplit_Constraint - - - -General Caveats ---------------- - -Temoa does not currently provide an easy avenue to track multiple concurrent -energy flows through a process. Consider a cogeneration plant. Where a -conventional power plant might simply emit excess heat as exhaust, a -cogeneration plant harnesses some or all of that heat for heating purposes, -either very close to the plant, or generally as hot water for district heating. -Temoa's flow variables can track both flows through a process, but each flow -will have its own efficiency from the Efficiency parameter. This implies that -to produce 1 unit of electricity will require :math:`\frac{1}{elc eff}` units of -input. At the same time, to produce 1 unit of heat will require units of input -energy, and to produce both output units of heat and energy, both flows must be -active, and the desired activity will be double-counted by Temoa. - -To model a parallel output device (c.f., a cogeneration plant), the modeler must -currently set up the process with the :code:`TechInputSplit` and -:code:`TechOutputSplit` parameters, appropriately adding each flow to the -Efficiency parameter and accounting for the overall process efficiency through -all flows. - - - -====================================== -The Temoa Computational Implementation -====================================== - -We have implemented Temoa within an algebraic modeling environment (AME). AMEs -provide both a convenient way to describe mathematical optimization models -for a computational context, and allow for abstract model\ [#abstract_model]_ -formulations :cite:`Kallrath_2004`. In contrast to describing a model in a -formal computer programming language like C or Java, AMEs generally have syntax -that directly translates to standard mathematical notation. Consequently, -models written in AMEs are more easily understood by a wider variety of researchers. -Further, by allowing abstract formulations, a model written with an AME may be -used with many different input data sets. - -Three well-known and popular algebraic modeling environments are the General -Algebraic Modeling System (GAMS) :cite:`Brooke_Rosenthal_2003`, AMPL -:cite:`Fourer_etal_1987`, and GNU MathProg :cite:`Makhorin_2000`. All three -environments provide concise syntax that closely resembles standard (paper) -notation. We decided to implement Temoa within an AME called -Python Optimization Modeling Objects (Pyomo). - -Pyomo provides similar functionality to GAMS, AMPL, and MathProg, but is open -source and written in the Python scripting language. This has two general -consequences of which to be aware: - - * Python is a scripting language; in general, scripts are an order of - magnitude slower than an equivalent compiled program. - * Pyomo provides similar functionality, but because of its Python heritage, is - **much** more verbose than GAMS, AMPL, or MathProg. - -It is our view that the speed penalty of Python as compared to compiled -languages is inconsequential in the face of other large resource bottle necks, -so we omit any discussion of it as an issue. However, the "boiler-plate" code -(verbosity) overhead requires some discussion. We discuss this in the -:ref:`Anatomy of a Constraint `. - - -.. _constraint-anatomy: - -Anatomy of a Constraint ------------------------ - -To help explain the Pyomo implementation, we discuss a single constraint in -detail. Consider the :code:`Demand` :eq:`Demand` constraint: - -.. math:: - \sum_{I, T, V} \textbf{FO}_{r, p, s, d, i, t, v, dem} + - SEG_{s,d} \cdot \sum_{I, T^{a}, V} \textbf{FOA}_{r, p, i, t, v, dem} - \ge - {DEM}_{r, p, dem} \cdot {DSD}_{r, s, d, dem} - - \\ - \forall \{r, p, s, d, dem\} \in \Theta_{\text{Demand}} - -Implementing this with Pyomo requires two pieces, and optionally a third: - - #. a constraint definition (in ``temoa_model.py``), - #. the constraint implementation (in ``temoa_rules.py``), and - #. (optional) sparse constraint index creation (in ``temoa_initialize.py``). - -We discuss first a straightforward implementation of this constraint, that -specifies the sets over which the constraint is defined. We will follow it with -the actual implementation which utilizes a more computationally efficient but -less transparent constraint index definition (the optional step 3). - -A simple definition of this constraint is: - -.. topic:: in ``temoa_model.py`` - - .. code-block:: python - :linenos: - - M.DemandConstraint = Constraint( - M.regions, M.time_optimize, M.time_season, M.time_of_day, M.commodity_demand, - rule=Demand_Constraint - ) - -In line 1, '``M.DemandConstraint =``' creates a place holder in the model object -``M``, called 'DemandConstraint'. Like a variable, this is the name through -which Pyomo will reference this class of constraints. ``Constraint(...)`` is a -Pyomo-specific function that creates each individual constraint in the class. -The first arguments (line 2) are the index sets of the constraint class. Line 2 -is the Pyomo method of saying "for all" (:math:`\forall`). Line 3 contains the -final, mandatory argument (``rule=...``) that specifies the name of the -implementation rule for the constraint, in this case ``Demand_Constraint``. -Pyomo will call this rule with each tuple in the Cartesian product of the index -sets. - -An associated implementation of this constraint based on the definition above -is: - -.. topic:: temoa_rules.py - - ... - - .. code-block:: python - :linenos: - - def Demand_Constraint ( M, r, p, s, d, dem ): - if (r,p,s,d,dem) not in M.DemandSpecificDistribution.sparse_keys(): # If user did not specify this Demand, tell - return Constraint.Skip # Pyomo to ignore this constraint index. - - supply = sum( - M.V_FlowOut[r, p, s, d, S_i, S_t, S_v, dem] - for S_t, S_v in M.commodityUStreamProcess[r, p, dem] if S_t not in M.tech_annual - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, dem] - ) - - supply_annual = sum( - M.V_FlowOutAnnual[r, p, S_i, S_t, S_v, dem] - for S_t, S_v in M.commodityUStreamProcess[r, p, dem] if S_t in M.tech_annual - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, dem] - ) * value( M.SegFrac[ s, d]) - - DemandConstraintErrorCheck(supply + supply_annual, r, p, s, d, dem) - - expr = supply + supply_annual == M.Demand[r, p, dem] * M.DemandSpecificDistribution[r, s, d, dem] - return expr - - ... - -The Python boiler-plate code to create the rule is on line 1. It begins with -:code:`def`, followed by the rule name (matching the :code:`rule=...` argument -in the constraint definition in ``temoa_model``), followed by the argument list. -The argument list will always start with the model (Temoa convention shortens -this to just :code:`M`) followed by local variable names in which to store the -index set elements passed by Pyomo. Note that the ordering is the same as -specified in the constraint definition. Thus the first item after :code:`M` -will be an item from :code:`region`, the second from :code:`time_optimize`, -the third from :code:`time_season`, fourth from :code:`time_of_day`, and the -fifth from :code:`commodity_demand`. Though one could choose :code:`a`, :code:`b`, -:code:`c`, :code:`d`, and :code:`e` (or any naming scheme), we chose :code:`p`, :code:`s`, -:code:`d`, and :code:`dem` as part of a :ref:`naming scheme -` to aid in mnemonic understanding. Consequently, the rule -signature (Line 1) is another place to look to discover what indices define a -constraint. - -Lines 2 and 3 are an indication that this constraint is implemented in a -non-sparse manner. That is, Pyomo does not inherently know the valid indices -for a given model parameter or equation. In ``temoa_model``, the constraint definition -listed five index sets, so Pyomo will naively call this function for every -possible combination of tuple :math:`\{r, p, s, d, dem\}`. However, as there -may be slices for which a demand does not exist (e.g., the winter season might -have no cooling demand), there is no need to create a constraint for any tuple -involving 'winter' and 'cooling'. Indeed, an attempt to access a demand for -which the modeler has not specified a value results in a Pyomo error, so it is -necessary to ignore any tuple for which no Demand exists. - -Lines 5 through 11 represent two *source-lines* that we split over several lines for -clarity. These lines implement the summations of the demand commodity ``dem`` -produced by demand technologies with both variable and constant output across the -year, summed over all relevant technologies, vintages, and the inputs. The -:code:`supply` and :code:`supply_annual` are local variables used in the expression -(:code:`expr`) shown below. Note that the sum is performed with sparse indices, which -are returned from dictionaries created in :code:`temoa_initialize.py`. - -Lines 5 through 11 also showcase a very common idiom in Python: -list-comprehension. List comprehension is a concise and efficient syntax to -create lists. As opposed to building a list element-by-element with for-loops, -list comprehension can convert many statements into a single operation. -Consider a naive approach to calculating the supply:: - - to_sum = list() - for S_t in M.tech_all: - for S_v in M.vintage_all: - for S_i in ProcessInputsByOutput( p, S_t, S_v, dem ): - to_sum.append( M.V_FlowOut[p, s, d, S_i, S_t, S_v, dem] ) - supply = sum( to_sum ) - -This implementation creates an extra list (:code:`to_sum`), then builds the list -element by element with :code:`.append()`, before finally calculating the summation. -This means that the Python interpreter must iterate through the elements of the -summation, not once, but twice. - -A less naive approach would replace the :code:`.append()` call with the -:code:`+=` operator, reducing the number of iterations through the elements to -one:: - - supply = 0 - for S_t in M.tech_all: - for S_v in M.vintage_all: - for S_i in ProcessInputsByOutput( p, S_t, S_v, dem ): - supply += M.V_FlowOut[p, s, d, S_i, S_t, S_v, dem] - -Why is list comprehension necessary? Strictly speaking, it is not, especially -in light of this last example, which may read more familiar to those comfortable -with C, Fortran, or Java. However, due to quirks of both Python and Pyomo, -list-comprehension is preferred both syntactically as "the Pythonic" way, and as -the more efficient route for many list manipulations. (It also *may* seem -slightly more familiar to those used to a more mainstream algebraic modeling -language.) - -With the correct model variables summed and stored in the ``supply`` and -``supply_annual`` variables, Line 17 calls a function defined in -:code:`temoa_initialize.py` that checks to make sure there is technology -that can supply each demand commodity ``dem`` in each :math:`\{r, p, s, d\}`. - -If no process supplies the demand, then it quits computation immediately rather -than completing a potentially lengthy model generation and waiting for the -solver to recognize the infeasibility of the model. Further, the function -lists potential ways for the modeler to correct the problem. This is one of the -benefits of Temoa: we've incorporated error handling in several places to -try and capture the most common user errors. This capability is subtle, but in -practice extremely useful while building and debugging a model. - -Line 19 creates the actual inequality comparison. This line is superfluous, but -we leave it in the code as a reminder that inequality operators (i.e. :code:`<=` -and :code:`>=`) with a Pyomo object (like supply) generate a Pyomo *expression -object*, not a boolean True or False as one might expect.\ [#return_expression]_ -It is this expression object that must be returned to Pyomo, as on Line 20. - -In the above implementation, the constraint is called for every tuple in the -Cartesian product of the indices, and the constraint must then decide whether -each tuple is valid. The below implementation differs from the one above -because it only calls the constraint rule for the valid tuples within the -Cartesian product, which is computationally more efficient than the simpler -implementation above. - -.. topic:: in ``temoa_model.py`` (actual implementation) - - .. code-block:: python - :linenos: - - M.DemandConstraint_rpsdc = Set( dimen=5, rule=DemandConstraintIndices ) - # ... - M.DemandConstraint = Constraint( M.DemandConstraint_rpsdc, rule=Demand_Constraint ) - - -As discussed above, the DemandConstraint is only valid for certain -:math:`\{r, p, s, d, dem\}` tuples. Since the modeler can specify the demand -distribution per commodity (necessary to model demands like heating, that do not -apply in all time slices), Temoa must ascertain the valid tuples. We have -implemented this logic in the function :code:`DemandConstraintIndices` in -``temoa_initialize.py``. Thus, Line 1 tells Pyomo to instantiate -:code:`DemandConstraint_rpsdc` as a Set of 5-length tuples indices -(:code:`dimen=5`), and populate it with what Temoa's rule -:code:`DemandConstraintIndices` returns. We omit here an explanation of the -implementation of the :code:`DemandConstraintIndices` function, stating merely -that it returns the exact indices over which the DemandConstraint must to be -created. With the sparse set :code:`DemandConstraint_rpsdc` created, we can now -can use it in place of the five sets specified in the non-sparse -implementation. Pyomo will now call the constraint implementation rule the -minimum number of times. - -On the choice of the :code:`_rpsdc` suffix for the index set name, there is no -Pyomo-enforced restriction. However, use of an index set in place of the -non-sparse specification obfuscates over what indexes a constraint is defined. -While it is not impossible to deduce, either from this documentation -or from looking at the :code:`DemandConstraintIndices` or -:code:`Demand_Constraint` implementations, the Temoa convention includes -index set names that feature the one-character representation of each set dimension. -In this case, the name :code:`DemandConstraint_rpsdc` implies that this set has a -dimensionality of 5, and (following the :ref:`naming scheme -`) the first index of each tuple will be an element of -:code:`region`, the second an element of :code:`time_optimize`, the third -an element of :code:`time_season`, fourth an element of :code:`time_of_day`, -and fifth a commodity. From the contextual information that this is the -Demand constraint, one can assume that the ``c`` represents an element from -:code:`commodity_demand`. - - - - - -A Word on Verbosity -------------------- - -Implementing this same constraint in AMPL, GAMS, or MathProg would require only -a single source-line (in a single file). Using MathProg as an example, it might -look like: - -.. code-block:: ampl - - s.t. DemandConstraint{(p, s, d, dem) in sDemand_psd_dem} : - sum{(p, s, d, Si, St, Sv, dem) in sFlowVar_psditvo} - V_FlowOut[p, s, d, Si, St, Sv, dem] - = - pDemand[p, s, d, dem]; - -While the syntax is not a direct translation, the indices of the constraint -(``p``, ``s``, ``d``, and ``dem``) are clear, and by inference, so are the -indices of summation (``i``, ``t``, ``v``) and operand (``V_FlowOut``). This -one-line definition creates an inequality for each period, season, time of day, -and demand, ensuring that total output meets each demand in each time slice -- -almost exactly as we have formulated the demand constraint :eq:`Demand`. In -contrast, Temoa's implementation in Pyomo takes 47 source-lines (the code -discussed above does not include the function documentation). While some of the -verbosity is inherent to working with a general purpose scripting language, and -most of it is our formatting for clarity, the absolute minimum number of lines a -Pyomo constraint can be is 2 lines, and that likely will be even less readable. - -So why use Python and Pyomo if they are so verbose? In short, for four -reasons: - - * Temoa has the full power of Python, and has access to a rich ecosystem of - tools (e.g. numpy, matplotlib) that are not as cleanly available to other - AMLs. For instance, there is minimal capability in MathProg to error check a - model before a solve, and providing interactive feedback like what Temoa's - DemandConstraintErrorCheck function does is difficult, if not impossible. - While a subtle addition, specific and directed error messages are an - effective measure to reduce the learning curve for new modelers. - - * Python has a vibrant community. Whereas mathematical optimization has a - small community, its open-source segment even smaller, and the energy modeling segment - significantly smaller than that, the Python community is huge, and - encompasses many disciplines. This means that where a developer may struggle - to find an answer, implementation, or workaround to a problem with a more - standard AML, Python will likely enable a community-suggested solution. - - * Powerful documentation tools. One of the available toolsets in the Python - world is documentation generators that *dynamically* introspect Python code. - While it is possible to inline and block comment with more traditional AMLs, - the integration with Python that many documentation generators have is much - more powerful. Temoa uses this capability to embed user-oriented - documentation literally in the code, and almost every constraint has a block - comment. Having both the documentation and implementation in one place helps - reduce the mental friction and discrepancies often involved in maintaining - multiple sources of model authority. - - * AMLs are not as concise as thought. - -This last point is somewhat esoteric, but consider the MathProg implementation -of the Demand constraint in contrast with the last line of the Pyomo version:: - - expr = (supply = M.Demand[p, s, d, dem]) - -While the MathProg version indeed translates more directly to standard notation, -consider that standard notation itself needs extensive surrounding text to -explain the significance of an equation. *Why* does the equation compare the -sum of a subset of FlowOut to Demand? In Temoa's implementation, a high-level -understanding of what a constraint does requires only the last line of code: -"Supply must meet demand." - - -Project Structure ------------------ - -The Temoa model code is split into several packages: - -1. ``temoa_model`` contains the mathematical model and supporting code to perform basic runs - - * ``temoa_model.py`` - contains the overall model definition, defining the - various sets, parameters, variables, and equations of the Temoa model. - Peruse this file for a high-level overview of the model. It references rule - implementations in the ``temoa_rules.py`` file for implementations. Of note, - the ``TemoaModel`` class needs to remain serializable by Python's ``pickle`` - module, so there are no functions/lambdas within the class. This is enforced by - one of the project tests. - - * ``temoa_rules.py`` - mainly contains the rule implementations. That is, this - file implements the objective function, internal parameters, and constraint - logic. Where ``temoa_model`` provides the high-level overview, this file - provides the actual equation implementations. - - * ``temoa_initialize.py`` - contains the code used to initialize the model, - including sparse matrix indexing and checks on parameter and constraint - specifications. - - * ``temoa_sequencer.py`` - contains the code required to run the execution sequence - during the model run. This module may pass control to other extensions as reqd. - - * ``run_actions.py`` - contains actions needed to build/solve the model in the course - of a run - - * ``hybrid_loader.py`` - contains the interface to load model data from a sqlite - database and, when requested, interface with the source-tracing code to QA data. - - - * ``table_writer.py`` - formats the results returned by the model; includes - outputting results to the shell, storing them in a database, and if requested, - calling 'DB_to_Excel.py' to create the Excel file outputs. - -2. ``data_processing`` - contains modules to process output results - -3. ``extensions`` - contains sub packages to execute alternative solve modes - - * ``myopic`` - contains a separate sequencer and support files to run the model - in myopic mode, which is configurable via the config file. - - * ``stochastic`` - contains the PySP required alterations to the - deterministic model for use in a stochastic model. Specifically, Temoa - only needs one additional constraint class in order to partition the - calculation of the objective function per period. - - * ``modeling_to_generate_alternatives`` - contains modules to execute an "MGA" - series of runs on the model via multiprocessing. MGA is configured via the - config file and the additional files in this package. - - * ``method_of_morris`` - contains modules to execute basic sensitivity analysis - using method of morris techniques and is described more fully in the ``readme`` - in that package. - -If you are working with a Temoa Git repository, these packages/files are in the -``temoa/`` subdirectory. - - -The Bleeding Edge ------------------ - -The Temoa Project uses the Git source code management system, and the services -of Github.com. If you are inclined to work with the bleeding edge of the Temoa -Project code base, then take a look at the Temoa repository. To acquire a -copy, make sure you have Git installed on your local machine, then execute this -command to clone the repository: - -.. code:: - - $ git clone git://github.com/TemoaProject/temoa.git - Cloning into 'temoa'... - remote: Counting objects: 2386, done. - remote: Compressing objects: 100% (910/910), done. - remote: Total 2386 (delta 1552), reused 2280 (delta 1446) - Receiving objects: 100% (2386/2386), 2.79 MiB | 1.82 MiB/s, done. - Resolving deltas: 100% (1552/1552), done. - -You will now have a new subdirectory called ``temoa``, that contains the entire -Temoa Project code and archive history. Note that Git is a *distributed* source -code management tool. This means that by cloning the Temoa repository, you have -your own copy to which you are welcome (and encouraged!) to alter and make -commits to. It will not affect the source repository. - -Though this is not a Git manual, we recognize that many readers of this manual -may not be software developers, so we offer a few quick pointers to using Git -effectively. - -If you want to see the log of commits, use the command git log: - -.. code:: - - $ git log -1 - commit b5bddea7312c34c5c44fe5cce2830cbf5b9f0f3b - Date: Thu Jul 5 03:23:11 2012 -0400 - - Update two APIs - - * I had updated the internal global variables to use the _psditvo - naming scheme, and had forgotten to make the changes to _graphviz.py - * Coopr also updated their API with the new .sparse_* methods. - -You can also explore the various development branches in the repository: - -.. code:: - - $ ls - data_files stochastic temoa_model create_archive.sh README.txt - - $ git branch -a - * energysystem - remotes/origin/HEAD -> origin/energysystem - remotes/origin/energysystem - remotes/origin/exp_electric_load_duration_reorg - remotes/origin/exp_electricity_sector - remotes/origin/exp_energysystem_flow_based - remotes/origin/exp_energysystem_match_markal - remotes/origin/exp_energysystem_test_framework - remotes/origin/misc_scripts - remotes/origin/old_energysystem_coopr2 - remotes/origin/temoaproject.org - - $ git checkout exp_energysystem_match_markal - Branch exp_energysystem_match_markal set up to track remote branch - exp_energysystem_match_markal from origin. - Switched to a new branch 'exp_energysystem_match_markal' - - $ ls - temoa_model create_archive.sh utopia-markal-20.dat - compare_with_utopia-15.py README.txt - compare_with_utopia-20.py utopia-markal-15.dat - -To view exactly what changes you have made since the most recent commit to the -repository use the ``diff`` command to ``git``: - -.. code:: - - $ git diff - diff --git a/temoa_model/temoa_lib.py b/temoa_model/temoa_lib.py - index 4ff9b30..0ba15b0 100644 - --- a/temoa_model/temoa_lib.py - +++ b/temoa_model/temoa_lib.py - @@ -246,7 +246,7 @@ def InitializeProcessParameters ( M ): - if l_vin in M.vintage_exist: - if l_process not in l_exist_indices: - msg = ('Warning: %s has a specified Efficiency, but does not ' - - 'have any existing install base (ExistingCapacity)\n.') - + 'have any existing install base (ExistingCapacity).\n') - SE.write( msg % str(l_process) ) - continue - if 0 == M.ExistingCapacity[ l_process ]: - [ ... ] - -For a crash course on git, here is a handy `quick start guide`_. - - -====================== -Temoa Code Style Guide -====================== - -It is an open question in programming circles whether code formatting actually -matters. The Temoa Project developers believe that it does for these main -reasons: - - * Consistently-formatted code reduces the cognitive work required to understand - the structure and intent of a code base. Specifically, we believe that - before code is to be executed, it is to be understood by other humans. The - fact that it makes the computer do something useful is a (happy) coincidence. - * Consistently-formatted code helps identify `code smell`_\ . - * Consistently-formatted code helps one to spot code bugs and typos more - easily. - -Note, however, that this is a style `guide`, not a strict ruleset. There will -also be corner cases to which a style guide does not apply, and in these cases, -the judgment of what to do is left to the implementers and maintainers of the -code base. To this end, the Python project has a well-written treatise in `PEP -8`_\ : - - **A Foolish Consistency is the Hobgoblin of Little Minds** - - One of Guido's key insights is that code is read much more often than it is - written. The guidelines provided here are intended to improve the - readability of code and make it consistent across the wide spectrum of Python - code. As PEP 20 says, "Readability counts". - - A style guide is about consistency. Consistency with this style guide is - important. Consistency within a project is more important. Consistency - within one module or function is most important. - - But most importantly: know when to be inconsistent -- sometimes the style - guide just doesn't apply. When in doubt, use your best judgment. Look at - other examples and decide what looks best. And don't hesitate to ask! - - Two good reasons to break a particular rule: - - 1. When applying the rule would make the code less readable, even for - someone who is used to reading code that follows the rules. - 2. To be consistent with surrounding code that also breaks it (maybe for - historic reasons) -- although this is also an opportunity to clean up - someone else's mess (in true XP style). - -Ruff Formatting ---------------- - -The project has shifted to using Ruff (`ruff`_) as a formatter / linter. Commits to -the project should use Ruff to format any changed code. Ruff relies on -settings in the :code:`pyproject.toml` file. Contributors should be able -to apply ruff to any changed files with the command :code:`ruff format `. -If there is *specific* need to disable Ruff for a particular table or equation, -contributors can sparingly turn off Ruff formatting for sections of code using -comments. (See the Ruff documentation.) - -Indentation: Tabs and Spaces ----------------------------- - -The project is standardized to using spaces for indentation in accordance with -PEP-8 standards. Ruff will convert tabs to spaces. - - -End of Line Whitespace ----------------------- - -Remove it. Many editors have plugins or builtin functionality that will take -care of this automatically when the file is saved. - - -Maximum Line Length -------------------- - -(Similar to `PEP 8`_\ ) Limit all lines to a maximum of 100 characters. - -Historically, 80 characters was the width (in monospace characters) that a -terminal had to display output. With the advent of graphical user interfaces -with variable font-sizes, this technological limit no longer exists. While -80 characters remains an excellent metric of what constitutes a "long line" most -modern wide-screen displays can comfortably show side-by-side difference files with -100 characters per side, and 100 characters better accommodates some long equations. A -long line in this sense is one that is not as transparent as to its intent as it -could be. **Ruff will enforce 100 character line length**, in accordance with the settings -in the ``pyproject.toml`` file - -Slightly adapted from `PEP 8`_\ : - - The preferred way of wrapping long lines is by using Python's implied line - continuation inside parentheses, brackets and braces. Long lines can be - broken over multiple lines by wrapping expressions in parentheses. These - should be used in preference to using a backslash for line continuation. - Make sure to indent the continued line appropriately. The preferred place to - break around a binary operator is after the operator, not before it. Some - examples: - - .. code-block:: python - - class Rectangle ( Blob ): - - def __init__ ( self, width, height, - color='black', emphasis=None, highlight=0 ): - if ( width == 0 and height == 0 and - color == 'red' and emphasis == 'strong' or - highlight > 100 ): - raise ValueError("sorry, you lose") - if width == 0 and height == 0 and (color == 'red' or - emphasis is None): - raise ValueError("I don't think so -- values are {}, {}".format( - (width, height) )) - Blob.__init__( self, width, height, - color, emphasis, highlight ) - - -Blank Lines ------------ - - * Separate logical sections within a single function with a single blank line. - * Separate function and method definitions with two blank lines. - * Separate class definitions with three blank lines. - - -Encodings ---------- - -Following `PEP 3120`, all code files should use UTF-8 encoding. - -.. commented out - Punctuation and Spacing - ----------------------- - - Always put spaces after code punctuation, like equivalence tests, assignments, - and index lookups. - - .. code-block:: python - - a=b # bad - a = b # good - - a==b # bad - a == b # good - - a[b] = c # bad - a[ b ] = c # good - - # exception: if there is more than one index - a[ b, c ] = d # acceptable, but not preferred - a[b, c] = d # good, preferred - - # exception: if using a string literal, don't include a space: - a[ 'x' ] == d # bad - a['x'] == d # good - - When defining a function or method, put a single space on either side of each - parenthesis: - - .. code-block:: python - - def someFunction(a, b, c): # bad - pass - - def someFunction ( a, b, c ): # good - pass - - - Vertical Alignment - ------------------ - - *Where appropriate*, vertically align sections of the code. - - .. code-block:: python - - # bad - M.someVariable = Var( M.someIndex, domain=NonNegativeIntegers ) - M.otherVariable = Var( M.otherIndex, domain=NonNegativeReals ) - - # good - M.someVariable = Var( M.someIndex, domain=NonNegativeIntegers ) - M.otherVariable = Var( M.otherIndex, domain=NonNegativeReals ) - - - Single, Double, and Triple Quotes - --------------------------------- - - Python has four delimiters to mark a string literal in the code: ``"``, ``'``, - ``"""``, and |'''|. Use each as appropriate. One should rarely need to escape - a quote within a string literal, because one can merely alternate use of the - single, double or triple quotes: - - .. code-block:: python - - a = "She said, \"Do not do that!\"" # bad - a = 'She said, "Do not do that!"' # good - - b = "She said, \"Don't do that!\"" # bad - b = 'She said, "Don\'t do that!"' # bad - b = """She said, "Don't do that!\"""" # bad - b = '''She said, "Don't do that!"''' # good - - -.. _naming_conventions: - -Naming Conventions ------------------- - -All constraints attached to a model should end with ``Constraint``. Similarly, -the function they use to define the constraint for each index should use the -same prefix and ``Constraint`` suffix, but separate them with an underscore -(e.g. ``M.somenameConstraint = Constraint( ..., rule=somename_Constraint``): - -.. code-block:: python - - M.CapacityConstraint = Constraint( M.CapacityVar_tv, rule=Capacity_Constraint ) - -When providing the implementation for a constraint rule, use a consistent naming -scheme between functions and constraint definitions. For instance, we have -already chosen ``M`` to represent the Pyomo model instance, ``t`` to represent -*technology*, and ``v`` to represent *vintage*: - -.. code-block:: python - - def Capacity_Constraint ( M, t, v ): - ... - -The complete list we have already chosen: - - * :math:`p` to represent a period item from :math:`time\_optimize` - * :math:`s` to represent a season item from :math:`time\_season` - * :math:`d` to represent a time of day item from :math:`time\_of\_day` - * :math:`i` to represent an input to a process, an item from - :math:`commodity\_physical` - * :math:`t` to represent a technology from :math:`tech\_all` - * :math:`v` to represent a vintage from :math:`vintage\_all` - * :math:`o` to represent an output of a process, an item from - :math:`commodity\_carrier` - -Note also the order of presentation, even in this list. In order to reduce the -number mental "question marks" one might have while discovering Temoa, we -attempt to rigidly reference a mental model of "left to right". Just as the -entire energy system that Temoa optimizes may be thought of as a left-to-right -graph, so too are the individual processes. As mentioned above in `A Word on Index -Ordering`_: - - For any indexed parameter or variable within Temoa, our intent is to enable a - mental model of a left-to-right arrow-box-arrow as a simple mnemonic to - describe the "input :math:`\rightarrow` process :math:`\rightarrow` output" - flow of energy. And while not all variables, parameters, or constraints have - 7 indices, the 7-index order mentioned here (p, s, d, i, t, v, o) is the - canonical ordering. If you note any case where, for example, d comes before - s, that is an oversight. - - -In-line Implementation Conventions ----------------------------------- - -Wherever possible, implement the algorithm in a way that is *pedagogically* -sound or reads like an English sentence. Consider this snippet: - -.. code-block:: python - - if ( a > 5 and a < 10 ): - doSomething() - -In English, one might translate this snippet as "If a is greater than 5 and less -then 10, do something." However, a semantically stronger implementation might -be: - -.. code-block:: python - - if ( 5 < a and a < 10 ): - doSomething() - -This reads closer to the more familiar mathematical notation of ``5 < a < 10`` -and translates to English as "If a is between 5 and 10, do something." The -semantic meaning that ``a`` should be *between* 5 and 10 is more readily -apparent from just the visual placement between 5 and 10, and is easier for the -"next person" to understand (who may very well be you in six months!). - -Consider the reverse case: - -.. code-block:: python - - if ( a < 5 or a > 10 ): - doSomething() - -On the number line, this says that a must fall before 5 or beyond 10. But the -intent might more easily be understood if altered as above: - -.. code-block:: python - - if not ( 5 < a and a < 10 ): - doSomething() - -This last snippet now makes clear the core question that a should ``not`` fall -between 5 and 10. - -Consider another snippet: - -.. code-block:: python - - acounter = scounter + 1 - -This method of increasing or incrementing a variable is one that many -mathematicians-turned-programmers prefer, but is more prone to error. For -example, is that an intentional use of ``acounter`` or ``scounter``? Assuming -as written that it's incorrect, a better paradigm uses the += operator: - -.. code-block:: python - - acounter += 1 - -This performs the same operation, but makes clear that the ``acounter`` variable -is to be incremented by one, rather than be set to one greater than ``scounter``. - -The same argument can be made for the related operators: - -.. code-block:: python - - >>> a, b, c = 10, 3, 2 - - >>> a += 5; a # same as a = a + 5 - 15 - >>> a -= b; a # same as a = a - b - 12 - >>> a /= b; a # same as a = a / b - 4 - >>> a *= c; a # same as a = a * c - 8 - >>> a **= c; a # same as a = a ** c - 64 - - -Miscellaneous Style Conventions -------------------------------- - - * (Same as `PEP 8`_\ ) Do not use spaces around the assignment operator (``=``) - when used to indicate a default argument or keyword parameter: - - .. code-block:: python - - def complex ( real, imag = 0.0 ): # bad - return magic(r = real, i = imag) # bad - - def complex ( real, imag=0.0 ): # good - return magic( r=real, i=imag ) # good - - * (Same as `PEP 8`_\ ) Do not use spaces immediately before the open - parenthesis that starts the argument list of a function call: - - .. code-block:: python - - a = b.calc () # bad - a = b.calc ( c ) # bad - a = b.calc( c ) # good - - * (Same as `PEP 8`_\ ) Do not use spaces immediately before the open - bracket that starts an indexing or slicing: - - .. code-block:: python - - a = b ['key'] # bad - a = b [a, b] # bad - a = b['key'] # good - a = b[a, b] # good - - -Patches and Commits to the Repository -------------------------------------- - -In terms of code quality and maintaining a legible "audit trail," every patch -should meet a basic standard of quality: - - * Every commit to the repository must include an appropriate summary message - about the accompanying code changes. Include enough context that one reading - the patch need not also inspect the code to get a high-level understanding of - the changes. For example, "Fixed broken algorithm" does not convey much - information. A more appropriate and complete summary message might be:: - - Fixed broken storage algorithm - - The previous implementation erroneously assumed that only the energy - flow out of a storage device mattered. However, Temoa needs to know the - energy flow in to all devices so that it can appropriately calculate the - inter-process commodity balance. - - License: GPLv2 - - If there is any external information that would be helpful, such as a bug - report, include a "clickable" link to it, such that one reading the patch as - via an email or online, can immediately view the external information. - - Specifically, commit messages should follow the form:: - - A subject line of 50 characters or less - [ an empty line ] - 1. http://any.com/ - 2. http://relevant.org/some/path/ - 3. http://urls.edu/~some/other/path/ - 4. https://github.com/blog/926-shiny-new-commit-styles - 5. https://help.github.com/articles/github-flavored-markdown - [ another empty line ] - Any amount and format of text, such that it conforms to a line-width of - 72 characters[4]. Bonus points for being aware of the Github Markdown - syntax[5]. - - License: GPLv2 - - * Ensure that each commit contains no more than one *logical* change to the - code base. This is very important for later auditing. If you have not - developed in a logical manner (like many of us don't), :code:`git add -p` is - a very helpful tool. - - * If you are not a core maintainer of the project, all commits must also - include a specific reference to the license under which you are giving your - code to the project. Note that Temoa will not accept any patches that - are not licensed under GPLv2. A line like this at the end of your commit - will suffice:: - - ... the last line of the commit message. - - License: GPLv2 - - This indicates that you retain all rights to any intellectual property your - (set of) commit(s) creates, but that you license it to the Temoa Project - under the terms of the GNU Public License, version 2. If - the Temoa Project incorporates your commit, then Temoa may not relicense - your (set of) patch(es), other than to increase the version number of the - GPL license. In short, the intellectual property remains yours, and the - Temoa Project would be but a licensee using your code similarly under the - terms of GPLv2. - - Executing licensing in this manner -- rather than requesting IP assignment -- - ensures that no one group of code contributers may unilaterally change the - license of Temoa, unless **all** contributers agree in writing in a - publicly archived forum (such as the `Temoa Forum`_). - - * When you are ready to submit your (set of) patch(es) to the Temoa Project, - we will utilize GitHub's `Pull Request`_ mechanism. - - -.. rubric:: Footnotes - -.. [#open_source_realities] The two main goals behind Temoa are transparency and - repeatability, hence the GPLv2 license. Unfortunately, there are some harsh - realities in the current climate of energy modeling, so this license is not a - guarantee of openness. This documentation touches on the issues involved in - the final section. - -.. [#efficiency_table] The efficiency parameter is often referred to as the - efficiency table, due to how it looks after even only a few entries in the - Pyomo input "dot dat" file. - -.. [#glpk_presolve] Circa 2013, GLPK uses more memory than commercial - alternatives and has vastly weaker presolve capabilities. - -.. [#esom_definition] For a more in-depth description of energy system - optimization models (ESOMs) and guidance on how to use them, please see: - DeCarolis et al. (2017) "Formalizing best practice for energy system - optimization modelling", Applied Energy, 194: 184-198. - -.. [#web_browser_svg] SVG support in web browsers is currently hit or miss. The - most recent versions of Chromium, Google Chrome, and Mozilla Firefox support - SVG well enough for Temoa's current use of SVG. - -.. [#return_expression] A word on `return` expressions in Pyomo: in most - contexts a relational expression is evaluated instantly. However, in Pyomo, - a relational expression returns an `expression` object. That is, `'M.aVar >= - 5'` does not evaluate to a boolean *true* or *false*, and Pyomo will - manipulate it into the final LP formulation. - -.. [#abstract_model] In contrast to a 'concrete' model, an abstract algebraic - formulation describes the general equations of the model, but requires - modeler-specified input data before it can compute any results. - -.. |'''| replace:: ``'``\ ``'``\ ``'`` - -.. _GNU Linear Programming Kit: https://www.gnu.org/software/glpk/ -.. _WinGLPK: http://winglpk.sf.net/ -.. _Github repo: https://github.com/TemoaProject/temoa/ -.. _Temoa model: http://temoaproject.org/download/temoa.py -.. _temoaproject.org: http://temoaproject.org/ -.. _example data sets: http://temoaproject.org/download/example_data_sets.zip -.. _mailing list: https://groups.google.com/forum/#!forum/temoa-project -.. _Temoa Forum: https://groups.google.com/forum/#!forum/temoa-project -.. _various: http://xlinux.nist.gov/dads/HTML/optimization.html -.. _available: http://www.stanford.edu/~boyd/cvxbook/ -.. _online: https://en.wikipedia.org/wiki/Optimization_problem -.. _sources: https://en.wikipedia.org/wiki/Mathematical_optimization -.. _GAMS: http://www.gams.com/ -.. _AMPL: http://www.ampl.com/ -.. _PDF: https://temoacloud.com/wp-content/uploads/2020/02/toolsforenergymodeloptimizationandanalysistemoa.pdf -.. _HTML: http://temoaproject.org/docs/ -.. _GitHub Issue tracker: https://github.com/TemoaProject/temoa/issues -.. _HTML version: http://temoaproject.org/docs/ -.. _code smell: https://en.wikipedia.org/wiki/Code_smell -.. _PEP 8: http://www.python.org/dev/peps/pep-0008/ -.. _PEP 3120: http://www.python.org/dev/peps/pep-3120/ -.. _list comprehension: http://docs.python.org/tutorial/datastructures.html#list-comprehensions -.. _lambda function: http://docs.python.org/tutorial/controlflow.html#lambda-forms -.. _generally accepted relative rates: http://www.forecasts.org/inflation.htm -.. _Pull Request: https://help.github.com/articles/using-pull-requests -.. _quick start guide: http://rogerdudler.github.io/git-guide/ -.. _sqlite: https://www.sqlite.org/ -.. _Graphviz: http://www.graphviz.org/ -.. _ruff: https://docs.astral.sh/ruff/ - -.. bibliography:: References.bib diff --git a/docs/source/assets/logo.pdf b/docs/source/assets/logo.pdf new file mode 100644 index 000000000..35933f72a Binary files /dev/null and b/docs/source/assets/logo.pdf differ diff --git a/docs/source/assets/logo.svg b/docs/source/assets/logo.svg new file mode 100644 index 000000000..45bfb806f --- /dev/null +++ b/docs/source/assets/logo.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + diff --git a/docs/source/assets/logo_bottom_text.pdf b/docs/source/assets/logo_bottom_text.pdf new file mode 100644 index 000000000..249bfdcb4 Binary files /dev/null and b/docs/source/assets/logo_bottom_text.pdf differ diff --git a/docs/source/assets/logo_bottom_text.svg b/docs/source/assets/logo_bottom_text.svg new file mode 100644 index 000000000..591069599 --- /dev/null +++ b/docs/source/assets/logo_bottom_text.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/commodity_network.rst b/docs/source/commodity_network.rst new file mode 100644 index 000000000..dcba5b7e3 --- /dev/null +++ b/docs/source/commodity_network.rst @@ -0,0 +1,143 @@ +Commodity Network +================= + +This documentation segment highlights some of the techniques to maintain the integrity of +the commodity flow network within Temoa. The modeler is advised to be aware of how +Temoa processes flow of energy within an energy system and how escapes in network +integrity can lead to erroneous results. + +Network Flow +------------ + +In a standard, static, and lossless ``s-t`` network, flow balance constraints fall into +place naturally. The input flow at the source, ``s`` is set to equal the output flow +at termination point ``t`` and for all intermediary nodes, flow in is constrained to +equal flow out. Temoa's energy network, however, is not static and has losses in the form +of efficiency losses within the ``tech`` processes that serve as the links between +``commodity`` nodes. When a model is solved over several time periods with "perfect foresight" +the network may look quite different in each region and time period based on endogenous selections +of new technologies, lifetime expirations, etc. This diversification of networks is compounded when running the +model myopically where previous non-selections remove options from the decision space in future +periods, outside of the modeler's control. + +When developing data for a model to run with perfect foresight, or any of the more advanced modes, +it is incumbent upon the modeler to provide the vintages, technologies, lifespans, etc. in order +to allow Temoa to build a proper network. Some explanation of how Temoa balances flow is in order +to help avoid pitfalls.... + +The energy network in each region and period is built dynamically from the ``techs`` that are +identified as existing capacity and whatever the model has ability to build from elements in +the optimization window identified in the ``efficiency`` table. In order to enforce conservation of +flow constraints, the model identifies output flows (commodities), including the demand +commodities and inventories all possible ``techs`` (existing or available) that *could* produce that commodity from +any input commodity and enforces flow balance by requiring capacity in one of the available technologies, +so that *some tech* must provide the flow to support that output. +This is the basis for satisfying demands at the termination of the network. + +If at any point along the chain there are no ``techs`` +available to produce the commodity in question, the model *assumes this is a base or source commodity* for +flow balance purposes, which can lead to some intermediate physical commodities erroneously being +provided to the model without a preceding processing chain if none is available. +This can inadvertently come about from a variety of causes including: + +* Failure to provide a linking technology in any region-period +* A technology that expires in a particular period with no replacement/alternate pathway provided +* Non-selection in a prior myopic period + +These failures can be difficult to diagnose in large models with many periods/regions/technologies and serve +as the motivation to provide source-tracing (described in the next section) to help enforce network integrity. + +Source Tracing +-------------- + +Source tracing from Demand back to a labeled "source" capacity is now available in Version 3.0+ of Temoa. +During pre-processing, before the model is built, Temoa can identify breaks in the network and can filter out problematic +data before the model is loaded/built. Temoa can also check network integrity on a built model before solve is initiated. Currently, +discrepancies are noted in the log file for the model. The user can also request network plots, which are browser-capable +html files that can be used in diagnosis of problem areas. The general intent is to ensure that all flows to Demand +commodities can be cleanly traced back to original sources. + +**An example:** + +Consider the simple network below with one source, one +demand, and intermediate physical commodities ``{P1, P2, P3, P4}`` and the connective technologies ``T1`` +through ``T6``. This well-connected network works as intended and the singular demand is traceable via either path +back to source. + +.. figure:: images/commodity_network.png + :alt: Good Network + + Good Network + + +A defective network (shown below) may occur for a several reasons, as cited in the previous section. Suppose that +for some reason ``T3`` is no longer available in this or a subsequent period (never made available, lifetime +expiration earlier than other links, not selected by myopic process--which would normally remove the other links +as well, unless they had replacement vintages and ``T3`` did not, etc.) Several problems now exist: + +1. Supply side orphans. Technology ``T1`` is now a "supply side" orphan, which shouldn't cause model problems, but represents bloat + in the model. Legacy (pre version 3.0) Temoa does screen for unused outputs (like ``P1`` in this case) that are not used by other + processes and are not end demands, but it is currently only done 'globally' in all periods/regions. Resultantly, + this orphan may not trigger a model error if it were used in another region/period. + These will now generate **WARNING** level log entries with source tracing. It *could* be a source of unbounded behavior in the case where the + modeler attempts to use negative values for costs. + +2. Technology ``T5`` and perhaps a now-available new vintage ``T5'`` are now "demand-side" orphans. These are + problematic and will generate **WARNING** level log entries by source tracing because they would allow a + false/unlimited supply of ``P3`` as their inputs. + +3. New technology ``T7`` (and any other linkages that are not reachable from either source or demand) + are complete orphans. + They will generate a **WARNING** level log entry during source tracing. + +.. figure:: images/broken_commodity_network.png + :alt: Bad Network + + Bad Network + +Tech Suppression +---------------- + +When source tracing is used, Temoa will attempt to remedy network escapes as described above by suppressing +problematic ``techs`` or chains of technologies. During runs where source tracing is enabled, Temoa's internal +``Network Data Manager`` will preload essential data in order to analyze all networks (each region/period) within the +current optimization window. Network tracing from demands down and sources up reveals ``techs`` that are "orphaned" +These ``techs`` and any unused commodities, etc. are removed with log entries and the remaining "good" data is +used to filter the ingestion of the full model data to support the build. + +Basic rules for ``tech`` Suppression +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- All demand side orphans (and chains of orphans) are suppressed +- All supply-side orphans (and chains of orphans) are suppressed +- For technologies that have multiple inputs or outputs, EACH commodity is treated separately (as it is + represented in the ``efficiency`` table.) The modeler is advised to screen the log file for these entries if a + particular commodity is essential (a catalyst for example?) to a particular operation. +- For linked technologies, an "all or nothing" logic is implemented such that both the driver and driven + technologies must prove independently viable (without regard for the emission linkage) or the pair will be suppressed. +- Currently, exchange technologies are not checked during source tracing. Any ``tech`` with a region link + (denoted with a dash ``R1-R2``) provided for the region name is assumed good. It behooves the modeler to + ensure that both ends of each link are well-connected in the model and to review their performance in output. + +Views across modeling periods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The approach described above is applied individually to each region-period pair within the optimization window. +Recall, that for perfect foresight runs, this will be all future periods. For myopic or other limited-visibility runs, +this will be applied to each period within view. The orphans are identified on a by period-region basis, but *the +suppression is applied for all periods within the view.* For example, if technology ``power_plant`` has a vintage of +2020 and a lifetime of 40 years covering model periods {2020, 2030, 2040, 2050} and it is not viable in 2030, it will +be suppressed and not available in any of the periods. + +Currently, for myopic applications, this decision is made on a per-iteration basis, so the technology may be +suppressed within the current iteration only. For example, if the myopic view was 1 period, and if the ``power_plant`` +was selected for build in its vintage year, 2020, the modeler may see that it is used in 2020, "suppressed" in 2030 +because it may run freely or cause problems in that period, but available again in 2040-2050. This may/may not be +desired behavior and the modeler should be aware of these behaviors logged in the log file. + +This screening prevents model errors and helps to reduce the size of the model in most cases. These processes +with non-utilized outputs may be the result of erroneous data (perhaps a technology to produce liquid hydrogen +before there is a user of same) or via myopic actions where a technology in a chain is not selected for build, +rendering other processes in the chain irrelevant in a later period. The main goal of using tracing and suppression +is to prevent "free" midstream commodities from erroneously feeding processes and prevent free-running techs that +might have a negative cost from making the model unbounded or infeasible. diff --git a/docs/source/computational_implementation.rst b/docs/source/computational_implementation.rst new file mode 100644 index 000000000..023964f3c --- /dev/null +++ b/docs/source/computational_implementation.rst @@ -0,0 +1,1008 @@ +Computational Implementation +============================ + +We have implemented Temoa within an algebraic modeling environment (AME). AMEs +provide both a convenient way to describe mathematical optimization models +for a computational context, and allow for abstract model\ [abstract_model]_ +formulations :cite:`Kallrath_2004`. In contrast to describing a model in a +formal computer programming language like C or Java, AMEs generally have syntax +that directly translates to standard mathematical notation. Consequently, +models written in AMEs are more easily understood by a wider variety of researchers. +Further, by allowing abstract formulations, a model written with an AME may be +used with many different input data sets. + +Three well-known and popular algebraic modeling environments are the General +Algebraic Modeling System (GAMS) :cite:`Brooke_Rosenthal_2003`, AMPL +:cite:`Fourer_etal_1987`, and GNU MathProg :cite:`Makhorin_2000`. All three +environments provide concise syntax that closely resembles standard (paper) +notation. We decided to implement Temoa within an AME called +Python Optimization Modeling Objects (Pyomo). + +Pyomo provides similar functionality to GAMS, AMPL, and MathProg, but is open +source and written in the Python scripting language. This has two general +consequences of which to be aware: + + * Python is a scripting language; in general, scripts are an order of + magnitude slower than an equivalent compiled program. + * Pyomo provides similar functionality, but because of its Python heritage, is + **much** more verbose than GAMS, AMPL, or MathProg. + +It is our view that the speed penalty of Python as compared to compiled +languages is inconsequential in the face of other large resource bottle necks, +so we omit any discussion of it as an issue. However, the "boiler-plate" code +(verbosity) overhead requires some discussion. We discuss this in the +:ref:`Anatomy of a Constraint `. + + +.. _constraint-anatomy: + +Anatomy of a Constraint +----------------------- + +To help explain the Pyomo implementation, we discuss a single constraint in +detail. Consider the :code:`Demand` :eq:`Demand` constraint: + +.. math:: + \sum_{I, T, V} \textbf{FOA}_{r, p, i, t, v, dem} + = + {DEM}_{r, p, dem} + + \\ + \forall \{r, p, dem\} \in \Theta_{\text{Demand}} + +Implementing this with Pyomo requires two pieces, and optionally a third: + + #. a constraint definition (in ``temoa/core/model.py``), + #. the constraint implementation (in ``temoa/components/``), and + #. (optional) sparse constraint index creation (in ``temoa/components/``). + +We discuss first a straightforward implementation of this constraint, that +specifies the sets over which the constraint is defined. We will follow it with +the actual implementation which utilizes a more computationally efficient but +less transparent constraint index definition (the optional step 3). + +A simple definition of this constraint is: + +.. topic:: in ``temoa/core/model.py`` + + .. code-block:: python + :linenos: + + self.demand_constraint = Constraint( + self.regions, self.time_optimize, self.commodity_demand, + rule=demand_constraint + ) + +In line 1, '``self.demand_constraint =``' creates a place holder in the model object +``self``, called 'demand_constraint'. Like a variable, this is the name through +which Pyomo will reference this class of constraints. ``Constraint(...)`` is a +Pyomo-specific function that creates each individual constraint in the class. +The first arguments (line 2) are the index sets of the constraint class. Line 2 +is the Pyomo method of saying "for all" (:math:`\forall`). Line 3 contains the +final, mandatory argument (``rule=...``) that specifies the name of the +implementation rule for the constraint, in this case ``demand_constraint``. +Pyomo will call this rule with each tuple in the Cartesian product of the index +sets. + +An associated implementation of this constraint based on the definition above +is: + +.. topic:: temoa/components/ + + ... + + .. code-block:: python + :linenos: + + def demand_constraint(model: TemoaModel, r: Region, p: Period, dem: Commodity) -> ExprLike: + if (r, p, dem) in model.singleton_demands: + return Constraint.Skip + + supply_annual = sum( + model.v_flow_out_annual[r, p, s_i, s_t, s_v, dem] + for s_t, s_v in model.commodity_up_stream_process[r, p, dem] + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, dem] + ) + + demand_constraint_error_check(supply_annual, r, p, dem) + + expr = supply_annual == value(model.demand[r, p, dem]) + return expr + ... + +The Python boiler-plate code to create the rule is on line 1. It begins with +:code:`def`, followed by the rule name (matching the :code:`rule=...` argument +in the constraint definition in ``temoa.core.model``), followed by the argument list. +Note that the argument list is strongly typed (each element has an explicit type like +:code:`Region`). Temoa enforces strong typing for code integrity using the :code:`mypy` package. +The argument list will always start with the model (Temoa convention shortens +this to just :code:`model`) followed by local variable names in which to store the +index set elements passed by Pyomo. Note that the ordering is the same as +specified in the constraint definition. Thus the first item after :code:`model` +will be an item from :code:`region`, the second from :code:`time_optimize`, +and the third from :code:`commodity_demand`. Though one could choose :code:`a`, +:code:`b`, and :code:`c` (or any naming scheme), we chose :code:`r`, :code:`p`, +and :code:`dem` as part of a :ref:`naming scheme +` to aid in mnemonic understanding. Consequently, the rule +signature (Line 1) is another place to look to discover what indices define a +constraint. + +Lines 2 and 3 are an indication that this constraint is implemented in a +non-sparse manner. That is, Pyomo does not inherently know the valid indices +for a given model parameter or equation. In ``temoa.core.model``, the constraint definition +listed three index sets, so Pyomo will naively call this function for every +possible combination of tuple :math:`\{r, p, dem\}`. However, as there +may be region-period-demand combinations for which the constraint is unnecessary, +there is no need to create a constraint for such tuples. + +Lines 5 through 9 sum the annual output flow of demand commodity ``dem`` +across all technologies and vintages. The +:code:`supply_annual` is a local variable used in the expression +(:code:`expr`) shown below. Note that the sum is performed with sparse indices, which +are returned from dictionaries created in :code:`temoa/components/`. + +Lines 5 through 9 also showcase a very common idiom in Python: +list-comprehension. List comprehension is a concise and efficient syntax to +create lists. As opposed to building a list element-by-element with for-loops, +list comprehension can convert many statements into a single operation. +Consider a naive approach to calculating the supply:: + + to_sum = list() + for S_t in model.tech_all: + for S_v in model.vintage_all: + for S_i in process_inputs_by_output( r, p, S_t, S_v, dem ): + to_sum.append( model.v_flow_out_annual[r, p, S_i, S_t, S_v, dem] ) + supply_annual = sum( to_sum ) + +This implementation creates an extra list (:code:`to_sum`), then builds the list +element by element with :code:`.append()`, before finally calculating the summation. +This means that the Python interpreter must iterate through the elements of the +summation, not once, but twice. + +A less naive approach would replace the :code:`.append()` call with the +:code:`+=` operator, reducing the number of iterations through the elements to +one:: + + supply_annual = 0 + for S_t in model.tech_all: + for S_v in model.vintage_all: + for S_i in process_inputs_by_output( r, p, S_t, S_v, dem ): + supply_annual += model.v_flow_out_annual[r, p, S_i, S_t, S_v, dem] + +Why is list comprehension necessary? Strictly speaking, it is not, especially +in light of this last example, which may read more familiar to those comfortable +with C, Fortran, or Java. However, due to quirks of both Python and Pyomo, +list-comprehension is preferred both syntactically as "the Pythonic" way, and as +the more efficient route for many list manipulations. (It also *may* seem +slightly more familiar to those used to a more mainstream algebraic modeling +language.) + +With the correct model variables summed and stored in the +``supply_annual`` variable, Line 11 calls a function defined in +:code:`temoa/components/commodities.py` that checks to make sure there is a technology +that can supply each demand commodity ``dem`` in each :math:`\{r, p\}`. + +If no process supplies the demand, then it quits computation immediately rather +than completing a potentially lengthy model generation and waiting for the +solver to recognize the infeasibility of the model. Further, the function +lists potential ways for the modeler to correct the problem. This is one of the +benefits of Temoa: we've incorporated error handling in several places to +try and capture the most common user errors. This capability is subtle, but in +practice extremely useful while building and debugging a model. + +Line 12 creates the actual equality comparison. This line is superfluous, but +we leave it in the code as a reminder that comparison operators (i.e. :code:`==`, +:code:`<=`, :code:`>=`) with a Pyomo object (like supply_annual) generate a Pyomo +*expression object*, not a boolean True or False as one might expect [return_expression]_. +It is this expression object that must be returned to Pyomo, as on Line 13. + +In the above implementation, the constraint is called for every tuple in the +Cartesian product of the indices, and the constraint must then decide whether +each tuple is valid. The below implementation differs from the one above +because it only calls the constraint rule for the valid tuples within the +Cartesian product, which is computationally more efficient than the simpler +implementation above. + +.. topic:: in ``temoa/core/model.py`` (declaration of indices, parameter, and constraint) + + .. code-block:: python + :linenos: + + self.demand_constraint_rpc = Set( + within=self.regions * self.time_optimize * self.commodity_demand + ) + self.demand = Param(self.demand_constraint_rpc) + + ... + + self.demand_constraint = Constraint( + self.demand_constraint_rpc, rule=commodities.demand_constraint + ) + + +As discussed above, the demand_constraint is only valid for certain +:math:`\{r, p, dem\}` tuples. Since not all combinations of region, period, and +demand commodity may be valid, Temoa must ascertain the valid tuples. +Thus, Line 1 tells Pyomo to instantiate +:code:`demand_constraint_rpc` as a Set of 3-length tuple indices +within the dimensions of the :code:`regions`, :code:`time_optimize`, and +:code:`commodity_demand` sets, and populate it only with entries loaded from +the database. Demand values are stored in the parameter :code:`self.demand` declared in line 4. +Data from the database is loaded into this index set and parameter via the +:code:`HybridLoader` class in :code:`temoa/data_io/hybrid_loader.py`, declared in +the :code:`component_manifest.py` file. + +.. topic:: in ``temoa/data_io/hybrid_loader.py`` (telling Temoa to load the indices) + + .. code-block:: python + :linenos: + + def load_param_idx_sets(self, data: dict[str, object]) -> dict[str, object]: + + model = TemoaModel() + param_idx_sets = { + ... + model.demand.name: model.demand_constraint_rpc.name, # param index sets + ... + } + +.. topic:: in ``temoa/data_io/component_manifest.py`` (telling Temoa to load the parameter) + + .. code-block:: python + :linenos: + + def build_manifest(model: TemoaModel) -> list[LoadItem]: + + manifest = [ + ... + LoadItem( + component=model.demand, # param name + table='demand', # database table name + columns=['region', 'period', 'commodity', 'demand'], # indices and value column + ), + ... + ] + +Alternatively, this index set could be populated using an index function +which determines the valid indices from other model components. This second method is needed +when the indices of a constraint do not exactly align with the indices of the database +table. For example, the database table :code:`ramp_up_hourly` is indexed by :code:`region`, +:code:`tech` in the database but one of its inheriting constraints, +:code:`ramp_up_day_constraint`, is indexed by :code:`region`, :code:`period`, +:code:`season`, :code:`time_of_day`, :code:`tech`, :code:`vintage` in the model. In this case, +an index function is needed to determine the valid indices for the constraint. + +.. topic:: in ``temoa/core/model.py`` (index set and constraint declaration) + + .. code-block:: python + :linenos: + + self.ramp_up_day_constraint_rpsdtv = Set( + dimen=6, initialize=operations.ramp_up_day_constraint_indices + ) + self.ramp_up_day_constraint = Constraint( + self.ramp_up_day_constraint_rpsdtv, rule=operations.ramp_up_day_constraint + ) + +.. topic:: in ``temoa/components/operations.py`` (return valid indices) + + .. code-block:: python + :linenos: + + def ramp_up_day_constraint_indices( + model: TemoaModel, + ) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + indices = { + (r, p, s, d, t, v) + for r, p, t in model.ramp_up_vintages + for v in model.ramp_up_vintages[r, p, t] + for s in model.time_season + for d in model.time_of_day + } + + return indices + +With the sparse set (:code:`demand_constraint_rpc` or :code:`ramp_up_day_constraint_rpsdtv`) +created, we can now can use it in place of the sets specified in the non-sparse +implementation. Pyomo will now call the constraint implementation rule the +minimum number of times. + +On the choice of the :code:`_rpc` suffix for the index set name, there is no +Pyomo-enforced restriction. However, use of an index set in place of the +non-sparse specification obfuscates over what indexes a constraint is defined. +While it is not impossible to deduce, either from this documentation +or from looking at the index function or +:code:`demand_constraint` implementations, the Temoa convention includes +index set names that feature the one-character representation of each set dimension. +In this case, the name :code:`demand_constraint_rpc` implies that this set has a +dimensionality of 3, and (following the :ref:`naming scheme +`) the first index of each tuple will be an element of +:code:`region`, the second an element of :code:`time_optimize`, +and third a demand commodity. + + + + + +A Word on Verbosity +------------------- + +Implementing this same constraint in AMPL, GAMS, or MathProg would require only +a single source-line (in a single file). Using MathProg as an example, it might +look like: + +.. code-block:: ampl + + s.t. demand_constraint{(r, p, dem) in sDemand_rpc} : + sum{(r, p, Si, St, Sv, dem) in sFlowVar_rpitvo} + v_flow_out_annual[r, p, Si, St, Sv, dem] + = + pDemand[r, p, dem]; + +While the syntax is not a direct translation, the indices of the constraint +(``r``, ``p``, and ``dem``) are clear, and by inference, so are the +indices of summation (``i``, ``t``, ``v``) and operand (``v_flow_out_annual``). This +one-line definition creates an equality for each region, period, and +demand, ensuring that total annual output meets each demand -- +almost exactly as we have formulated the demand constraint :eq:`Demand`. In +contrast, Temoa's implementation in Pyomo takes many more source-lines (the code +discussed above does not include the function documentation). While some of the +verbosity is inherent to working with a general purpose scripting language, and +most of it is our formatting for clarity, the absolute minimum number of lines a +Pyomo constraint can be is 2 lines, and that likely will be even less readable. + +So why use Python and Pyomo if they are so verbose? In short, for four +reasons: + + * Temoa has the full power of Python, and has access to a rich ecosystem of + tools (e.g. numpy, matplotlib) that are not as cleanly available to other + AMLs. For instance, there is minimal capability in MathProg to error check a + model before a solve, and providing interactive feedback like what Temoa's + :code:`demand_constraint_error_check` function does is difficult, if not impossible. + While a subtle addition, specific and directed error messages are an + effective measure to reduce the learning curve for new modelers. + + * Python has a vibrant community. Whereas mathematical optimization has a + small community, its open-source segment even smaller, and the energy modeling segment + significantly smaller than that, the Python community is huge, and + encompasses many disciplines. This means that where a developer may struggle + to find an answer, implementation, or workaround to a problem with a more + standard AML, Python will likely enable a community-suggested solution. + + * Powerful documentation tools. One of the available toolsets in the Python + world is documentation generators that *dynamically* introspect Python code. + While it is possible to inline and block comment with more traditional AMLs, + the integration with Python that many documentation generators have is much + more powerful. Temoa uses this capability to embed user-oriented + documentation literally in the code, and almost every constraint has a block + comment. Having both the documentation and implementation in one place helps + reduce the mental friction and discrepancies often involved in maintaining + multiple sources of model authority. + + * AMLs are not as concise as thought. + +This last point is somewhat esoteric, but consider the MathProg implementation +of the Demand constraint in contrast with the last line of the Pyomo version:: + + expr = supply_annual == value(model.demand[r, p, dem]) + +While the MathProg version indeed translates more directly to standard notation, +consider that standard notation itself needs extensive surrounding text to +explain the significance of an equation. *Why* does the equation compare the +sum of annual flows to Demand? In Temoa's implementation, a high-level +understanding of what a constraint does requires only the last line of code: +"Supply must meet demand." + + +Execution Flow +-------------- + +The Temoa execution flow is designed to be modular and consistent across different modeling modes. It begins with the Command Line Interface (CLI), which parses user input and sets up the execution environment. The following sequence diagram illustrates the execution flow of a Temoa run, from the initial command-line invocation to the final processing of results. + +.. mermaid:: + :alt: Temoa Execution Flow + :zoom: + + sequenceDiagram + participant User + participant CLI as temoa/cli.py + participant Config as TemoaConfig + participant Sequencer as TemoaSequencer + participant HL as HybridLoader + participant Model as TemoaModel Instance + participant RA as run_actions.py + participant DB as Database + + rect rgb(220,230,250) + Note over CLI: User invokes CLI (run/validate) + User->>CLI: temoa run/validate config.toml + end + + rect rgb(230,250,220) + Note over CLI: Environment Setup + CLI->>CLI: _create_output_folder() + CLI->>CLI: _setup_logging() + CLI->>Config: build_config(config_file, output_path, silent) + Config-->>CLI: TemoaConfig instance + end + + rect rgb(250,240,220) + Note over CLI,Sequencer: Sequencer Initialization + CLI->>Sequencer: TemoaSequencer(config, mode_override?) + Sequencer-->>CLI: Ready + end + + rect rgb(240,220,250) + Note over Sequencer: Model Building & Execution + alt build-only / validate + CLI->>Sequencer: build_model() + Sequencer->>Sequencer: _run_preliminary_checks() + Sequencer->>RA: check_database_version() + Sequencer->>HL: HybridLoader(db_con, config) + HL->>HL: load_data_portal() + Sequencer->>RA: build_instance(data_portal) + RA-->>Sequencer: TemoaModel instance + Sequencer-->>CLI: TemoaModel instance + else full run (e.g., perfect_foresight) + CLI->>Sequencer: start() + Sequencer->>Sequencer: _run_preliminary_checks() + Sequencer->>Sequencer: _run_perfect_foresight() + Sequencer->>HL: HybridLoader(db_con, config) + HL->>HL: load_data_portal() + Sequencer->>RA: build_instance(data_portal) + RA-->>Sequencer: instance + Sequencer->>RA: solve_instance(instance, solver_name) + RA->>RA: check_solve_status(results) + Sequencer->>RA: handle_results(instance, results, config) + RA->>DB: Persist results + Sequencer-->>CLI: Success / Error + end + end + + rect rgb(220,250,240) + CLI->>User: report log path and output folder + end + +The execution flow involves several key stages: + +1. **CLI Entry**: The user interacts with :file:`temoa/cli.py` to initiate a run or validation. +2. **Environment Setup**: The CLI creates a timestamped output directory, initializes logging, and uses :class:`TemoaConfig` to parse the provided TOML configuration file. This includes checking for the availability of the specified optimization solver. +3. **Sequencing**: A :class:`TemoaSequencer` is created. This object is the main coordinator, selecting the appropriate execution path based on the modeling mode (e.g., Perfect Foresight, Myopic, MGA). +4. **Data Loading**: The sequencer uses a :class:`HybridLoader` to pull data from the SQLite database. This data is organized into a Pyomo :class:`DataPortal`. +5. **Model Construction**: Using the :class:`DataPortal`, Temoa constructs a :class:`TemoaModel` instance. This stage builds all the mathematical sets, parameters, variables, and constraints. +6. **Solving**: The model instance is passed to :func:`solve_instance`, which invokes the chosen solver (like HiGHS, CBC, or Gurobi). +7. **Result Processing**: After the solver completes, :func:`handle_results` extracts the solution, checks for optimality, and persists the results back to the database. It also generates any requested auxiliary outputs like Excel files or network plots. + + +Project Structure +----------------- + +The Temoa model code is organized into clear, purpose-driven packages: + +**Core Packages:** + +* ``temoa.core`` - Public API for end users + + * ``model.py`` - Contains the ``TemoaModel`` class, the main entry point for building + and solving energy system models. This class coordinates all model components. + + * ``config.py`` - Contains ``TemoaConfig`` and ``TemoaMode`` for model configuration + and execution mode selection (perfect foresight, myopic, MGA, etc.). + +* ``temoa.cli`` - Command-line interface + + * Provides the ``temoa`` command with subcommands for running models, validating + configurations, migrating databases, and generating tutorial files. + +* ``temoa.components`` - Model components and constraints + + * ``costs.py`` - Objective function implementation (total system cost minimization) + * ``flows.py`` - Commodity flow balance constraints + * ``capacity.py`` - Capacity and activity constraints + * ``emissions.py`` - Emission accounting and constraints + * ``reserves.py`` - Reserve margin requirements + * ``limits.py`` - Various limit constraints (capacity, activity, emissions, etc.) + * ``storage.py`` - Energy storage constraints + * ``operations.py`` - Baseload and ramping constraints for generators + * Additional constraint modules for specific features + +* ``temoa.data_io`` - Data loading and validation + + * ``hybrid_loader.py`` - Main data loading engine using manifest-driven architecture + * ``component_manifest.py`` - Declarative specification of all data components + * ``loader_manifest.py`` - Data structure definitions for the loader + * Database interface and validation logic + +* ``temoa.model_checking`` - Model validation and integrity checking + + * Price checking for cost data consistency + * Source tracing for commodity network validation + * Network visualization tools + +* ``temoa.data_processing`` - Output analysis and visualization + + * ``db_to_excel.py`` - Excel output generation ([!] untested in v4.0) + * ``make_graphviz.py`` - Network diagram generation ([!] untested in v4.0) + * Result processing utilities + +* ``temoa.extensions`` - Optional extensions for advanced analysis + + * ``modeling_to_generate_alternatives`` - :doc:`mga` (MGA analysis for exploring near-optimal solutions) + * ``method_of_morris`` - Sensitivity analysis ([!] untested in v4.0) + * ``monte_carlo`` - :doc:`monte_carlo` (Uncertainty quantification) + * ``myopic`` - :doc:`myopic` (Sequential decision making with limited foresight) + * ``single_vector_mga`` - :doc:`mga` (Focused MGA on specific variables) + * ``stochastics`` - :doc:`stochastics` (Stochastic programming capabilities) + +* ``temoa._internal`` - Internal utilities (not part of public API) + + * ``table_writer.py`` - Database output formatting + * ``table_data_puller.py`` - Result extraction utilities + * Other internal helper modules + +If you are working with a Temoa Git repository, these packages are in the +``temoa/`` subdirectory. For detailed architecture documentation, see the +README.md file in the repository root. + + +The Bleeding Edge +----------------- + +The Temoa Project uses the Git source code management system, and the services +of Github.com. If you are inclined to work with the bleeding edge of the Temoa +Project code base, then take a look at the Temoa repository. To acquire a +copy, make sure you have Git installed on your local machine, then execute this +command to clone the repository: + +.. code:: + + $ git clone git://github.com/TemoaProject/temoa.git + Cloning into 'temoa'... + remote: Counting objects: 2386, done. + remote: Compressing objects: 100% (910/910), done. + remote: Total 2386 (delta 1552), reused 2280 (delta 1446) + Receiving objects: 100% (2386/2386), 2.79 MiB | 1.82 MiB/s, done. + Resolving deltas: 100% (1552/1552), done. + +You will now have a new subdirectory called ``temoa``, that contains the entire +Temoa Project code and archive history. Note that Git is a *distributed* source +code management tool. This means that by cloning the Temoa repository, you have +your own copy to which you are welcome (and encouraged!) to alter and make +commits to. It will not affect the source repository. + +Though this is not a Git manual, we recognize that many readers of this manual +may not be software developers, so we offer a few quick pointers to using Git +effectively. + +If you want to see the log of commits, use the command git log: + +.. code:: + + $ git log -1 + commit b5bddea7312c34c5c44fe5cce2830cbf5b9f0f3b + Date: Thu Jul 5 03:23:11 2012 -0400 + + Update two APIs + + * I had updated the internal global variables to use the _psditvo + naming scheme, and had forgotten to make the changes to _graphviz.py + * Coopr also updated their API with the new .sparse_* methods. + +You can also explore the various development branches in the repository: + +.. code:: + + $ ls + stochastic temoa tests pyproject.toml README.md + + $ git branch -a + * energysystem + remotes/origin/HEAD -> origin/energysystem + remotes/origin/energysystem + remotes/origin/exp_electric_load_duration_reorg + remotes/origin/exp_electricity_sector + remotes/origin/exp_energysystem_flow_based + remotes/origin/exp_energysystem_match_markal + remotes/origin/exp_energysystem_test_framework + remotes/origin/misc_scripts + remotes/origin/old_energysystem_coopr2 + remotes/origin/temoaproject.org + + $ git checkout exp_energysystem_match_markal + Branch exp_energysystem_match_markal set up to track remote branch + exp_energysystem_match_markal from origin. + Switched to a new branch 'exp_energysystem_match_markal' + + $ ls + temoa_model create_archive.sh utopia-markal-20.dat + compare_with_utopia-15.py README.txt + compare_with_utopia-20.py utopia-markal-15.dat + +To view exactly what changes you have made since the most recent commit to the +repository use the ``diff`` command to ``git``: + +.. code:: + + $ git diff + diff --git a/temoa_model/temoa_lib.py b/temoa_model/temoa_lib.py + index 4ff9b30..0ba15b0 100644 + --- a/temoa_model/temoa_lib.py + +++ b/temoa_model/temoa_lib.py + @@ -246,7 +246,7 @@ def InitializeProcessParameters ( M ): + if l_vin in M.vintage_exist: + if l_process not in l_exist_indices: + msg = ('Warning: %s has a specified efficiency, but does not ' + - 'have any existing install base (existing_capacity)\n.') + + 'have any existing install base (existing_capacity).\n') + SE.write( msg % str(l_process) ) + continue + if 0 == M.existing_capacity[ l_process ]: + [ ... ] + +For a crash course on git, here is a handy `quick start guide`_. + + +====================== +Temoa Code Style Guide +====================== + +It is an open question in programming circles whether code formatting actually +matters. The Temoa Project developers believe that it does for these main +reasons: + + * Consistently-formatted code reduces the cognitive work required to understand + the structure and intent of a code base. Specifically, we believe that + before code is to be executed, it is to be understood by other humans. The + fact that it makes the computer do something useful is a (happy) coincidence. + * Consistently-formatted code helps identify `code smell`_\ . + * Consistently-formatted code helps one to spot code bugs and typos more + easily. + +Note, however, that this is a style `guide`, not a strict ruleset. There will +also be corner cases to which a style guide does not apply, and in these cases, +the judgment of what to do is left to the implementers and maintainers of the +code base. To this end, the Python project has a well-written treatise in `PEP +8`_\ : + + **A Foolish Consistency is the Hobgoblin of Little Minds** + + One of Guido's key insights is that code is read much more often than it is + written. The guidelines provided here are intended to improve the + readability of code and make it consistent across the wide spectrum of Python + code. As PEP 20 says, "Readability counts". + + A style guide is about consistency. Consistency with this style guide is + important. Consistency within a project is more important. Consistency + within one module or function is most important. + + But most importantly: know when to be inconsistent -- sometimes the style + guide just doesn't apply. When in doubt, use your best judgment. Look at + other examples and decide what looks best. And don't hesitate to ask! + + Two good reasons to break a particular rule: + + 1. When applying the rule would make the code less readable, even for + someone who is used to reading code that follows the rules. + 2. To be consistent with surrounding code that also breaks it (maybe for + historic reasons) -- although this is also an opportunity to clean up + someone else's mess (in true XP style). + +Ruff Formatting +--------------- + +The project has shifted to using Ruff (`ruff`_) as a formatter / linter. Commits to +the project should use Ruff to format any changed code. Ruff relies on +settings in the :code:`pyproject.toml` file. Contributors should be able +to apply ruff to any changed files with the command :code:`ruff format `. +If there is *specific* need to disable Ruff for a particular table or equation, +contributors can sparingly turn off Ruff formatting for sections of code using +comments. (See the Ruff documentation.) + +Indentation: Tabs and Spaces +---------------------------- + +The project is standardized to using spaces for indentation in accordance with +PEP-8 standards. Ruff will convert tabs to spaces. + + +End of Line Whitespace +---------------------- + +Remove it. Many editors have plugins or builtin functionality that will take +care of this automatically when the file is saved. + + +Maximum Line Length +------------------- + +(Similar to `PEP 8`_\ ) Limit all lines to a maximum of 100 characters. + +Historically, 80 characters was the width (in monospace characters) that a +terminal had to display output. With the advent of graphical user interfaces +with variable font-sizes, this technological limit no longer exists. While +80 characters remains an excellent metric of what constitutes a "long line" most +modern wide-screen displays can comfortably show side-by-side difference files with +100 characters per side, and 100 characters better accommodates some long equations. A +long line in this sense is one that is not as transparent as to its intent as it +could be. **Ruff will enforce 100 character line length**, in accordance with the settings +in the ``pyproject.toml`` file + +Slightly adapted from `PEP 8`_\ : + + The preferred way of wrapping long lines is by using Python's implied line + continuation inside parentheses, brackets and braces. Long lines can be + broken over multiple lines by wrapping expressions in parentheses. These + should be used in preference to using a backslash for line continuation. + Make sure to indent the continued line appropriately. The preferred place to + break around a binary operator is after the operator, not before it. Some + examples: + + .. code-block:: python + + class Rectangle ( Blob ): + + def __init__ ( self, width, height, + color='black', emphasis=None, highlight=0 ): + if ( width == 0 and height == 0 and + color == 'red' and emphasis == 'strong' or + highlight > 100 ): + raise ValueError("sorry, you lose") + if width == 0 and height == 0 and (color == 'red' or + emphasis is None): + raise ValueError("I don't think so -- values are {}, {}".format( + (width, height) )) + Blob.__init__( self, width, height, + color, emphasis, highlight ) + + +Blank Lines +----------- + + * Separate logical sections within a single function with a single blank line. + * Separate function and method definitions with two blank lines. + * Separate class definitions with three blank lines. + + +Encodings +--------- + +Following `PEP 3120`, all code files should use UTF-8 encoding. + +.. _naming_conventions: + +Naming Conventions +------------------ + +All constraints attached to a model should end with ``constraint``. Similarly, +the function they use to define the constraint for each index should use the +same prefix and ``constraint`` suffix, but separate them with an underscore +(e.g. ``self.somename_constraint = Constraint( ..., rule=somename_constraint``): + +.. code-block:: python + + self.capacity_constraint = Constraint( self.CapacityVar_tv, rule=Capacity_constraint ) + +When providing the implementation for a constraint rule, use a consistent naming +scheme between functions and constraint definitions. For instance, we have +already chosen ``model`` to represent the Pyomo model instance, ``t`` to represent +*technology*, and ``v`` to represent *vintage*: + +.. code-block:: python + + def capacity_constraint ( model: TemoaModel, t: Technology, v: Vintage ): + ... + +The complete list we have already chosen: + + * :math:`p` to represent a period item from :math:`time\_optimize` + * :math:`s` to represent a season item from :math:`time\_season` + * :math:`d` to represent a time of day item from :math:`time\_of\_day` + * :math:`i` to represent an input to a process, an item from + :math:`commodity\_physical` + * :math:`t` to represent a technology from :math:`tech\_all` + * :math:`v` to represent a vintage from :math:`vintage\_all` + * :math:`o` to represent an output of a process, an item from + :math:`commodity\_carrier` + +Note also the order of presentation, even in this list. In order to reduce the +number mental "question marks" one might have while discovering Temoa, we +attempt to rigidly reference a mental model of "left to right". Just as the +entire energy system that Temoa optimizes may be thought of as a left-to-right +graph, so too are the individual processes. As mentioned above in `A Word on Index +Ordering`_: + + For any indexed parameter or variable within Temoa, our intent is to enable a + mental model of a left-to-right arrow-box-arrow as a simple mnemonic to + describe the "input :math:`\rightarrow` process :math:`\rightarrow` output" + flow of energy. And while not all variables, parameters, or constraints have + 7 indices, the 7-index order mentioned here (p, s, d, i, t, v, o) is the + canonical ordering. If you note any case where, for example, d comes before + s, that is an oversight. + + +In-line Implementation Conventions +---------------------------------- + +Wherever possible, implement the algorithm in a way that is *pedagogically* +sound or reads like an English sentence. Consider this snippet: + +.. code-block:: python + + if ( a > 5 and a < 10 ): + doSomething() + +In English, one might translate this snippet as "If a is greater than 5 and less +then 10, do something." However, a semantically stronger implementation might +be: + +.. code-block:: python + + if ( 5 < a and a < 10 ): + doSomething() + +This reads closer to the more familiar mathematical notation of ``5 < a < 10`` +and translates to English as "If a is between 5 and 10, do something." The +semantic meaning that ``a`` should be *between* 5 and 10 is more readily +apparent from just the visual placement between 5 and 10, and is easier for the +"next person" to understand (who may very well be you in six months!). + +Consider the reverse case: + +.. code-block:: python + + if ( a < 5 or a > 10 ): + doSomething() + +On the number line, this says that a must fall before 5 or beyond 10. But the +intent might more easily be understood if altered as above: + +.. code-block:: python + + if not ( 5 < a and a < 10 ): + doSomething() + +This last snippet now makes clear the core question that a should ``not`` fall +between 5 and 10. + +Consider another snippet: + +.. code-block:: python + + acounter = scounter + 1 + +This method of increasing or incrementing a variable is one that many +mathematicians-turned-programmers prefer, but is more prone to error. For +example, is that an intentional use of ``acounter`` or ``scounter``? Assuming +as written that it's incorrect, a better paradigm uses the += operator: + +.. code-block:: python + + acounter += 1 + +This performs the same operation, but makes clear that the ``acounter`` variable +is to be incremented by one, rather than be set to one greater than ``scounter``. + +The same argument can be made for the related operators: + +.. code-block:: python + + >>> a, b, c = 10, 3, 2 + + >>> a += 5; a # same as a = a + 5 + 15 + >>> a -= b; a # same as a = a - b + 12 + >>> a /= b; a # same as a = a / b + 4 + >>> a *= c; a # same as a = a * c + 8 + >>> a **= c; a # same as a = a ** c + 64 + + +Miscellaneous Style Conventions +------------------------------- + + * (Same as `PEP 8`_\ ) Do not use spaces around the assignment operator (``=``) + when used to indicate a default argument or keyword parameter: + + .. code-block:: python + + def complex ( real, imag = 0.0 ): # bad + return magic(r = real, i = imag) # bad + + def complex ( real, imag=0.0 ): # good + return magic( r=real, i=imag ) # good + + * (Same as `PEP 8`_\ ) Do not use spaces immediately before the open + parenthesis that starts the argument list of a function call: + + .. code-block:: python + + a = b.calc () # bad + a = b.calc ( c ) # bad + a = b.calc( c ) # good + + * (Same as `PEP 8`_\ ) Do not use spaces immediately before the open + bracket that starts an indexing or slicing: + + .. code-block:: python + + a = b ['key'] # bad + a = b [a, b] # bad + a = b['key'] # good + a = b[a, b] # good + + +Patches and Commits to the Repository +------------------------------------- + +In terms of code quality and maintaining a legible "audit trail," every patch +should meet a basic standard of quality: + + * Every commit to the repository must include an appropriate summary message + about the accompanying code changes. Include enough context that one reading + the patch need not also inspect the code to get a high-level understanding of + the changes. For example, "Fixed broken algorithm" does not convey much + information. A more appropriate and complete summary message might be:: + + Fixed broken storage algorithm + + The previous implementation erroneously assumed that only the energy + flow out of a storage device mattered. However, Temoa needs to know the + energy flow in to all devices so that it can appropriately calculate the + inter-process commodity balance. + + License: MIT + + If there is any external information that would be helpful, such as a bug + report, include a "clickable" link to it, such that one reading the patch as + via an email or online, can immediately view the external information. + + Specifically, commit messages should follow the form:: + + A subject line of 50 characters or less + [ an empty line ] + 1. http://any.com/ + 2. http://relevant.org/some/path/ + 3. http://urls.edu/~some/other/path/ + 4. https://github.com/blog/926-shiny-new-commit-styles + 5. https://help.github.com/articles/github-flavored-markdown + [ another empty line ] + Any amount and format of text, such that it conforms to a line-width of + 72 characters[4]. Bonus points for being aware of the Github Markdown + syntax[5]. + + License: MIT + + * Ensure that each commit contains no more than one *logical* change to the + code base. This is very important for later auditing. If you have not + developed in a logical manner (like many of us don't), :code:`git add -p` is + a very helpful tool. + + * If you are not a core maintainer of the project, all commits must also + include a specific reference to the license under which you are giving your + code to the project. Note that Temoa will not accept any patches that + are not licensed under MIT. A line like this at the end of your commit + will suffice:: + + ... the last line of the commit message. + + License: MIT + + This indicates that you retain all rights to any intellectual property your + (set of) commit(s) creates, but that you license it to the Temoa Project + under the terms of the MIT license. If + the Temoa Project incorporates your commit, then Temoa may not relicense + your (set of) patch(es), other than to increase the version number of the + MIT license. In short, the intellectual property remains yours, and the + Temoa Project would be but a licensee using your code similarly under the + terms of MIT. + + Executing licensing in this manner -- rather than requesting IP assignment -- + ensures that no one group of code contributers may unilaterally change the + license of Temoa, unless **all** contributers agree in writing in a + publicly archived forum (such as the `Temoa Forum`_). + + * When you are ready to submit your (set of) patch(es) to the Temoa Project, + we will utilize GitHub's `Pull Request`_ mechanism. diff --git a/docs/source/conf.py b/docs/source/conf.py index 00bd4b367..0c7b29696 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,8 +1,10 @@ -# -*- coding: utf-8 -*- # import os import sys -import time +from pathlib import Path +from typing import Any, cast + +import tomlkit # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -14,17 +16,27 @@ # this addition provided direct abbreviated link to the modules in the model sys.path.insert(1, os.path.abspath('../../temoa/temoa_model')) +# Import version from source (after sys.path setup) +from temoa.__about__ import __version__ + +# -- Project information from pyproject.toml --------------------------------- -# -- Project information ----------------------------------------------------- +# Read metadata from pyproject.toml +pyproject_path = Path(__file__).parent.parent.parent / 'pyproject.toml' +with open(pyproject_path) as f: + pyproject_data = tomlkit.load(f) +project_metadata = cast('dict[str, Any]', pyproject_data['project']) project = 'Tools for Energy Model Optimization and Analysis (Temoa)' -copyright = '2020, NC State University' -author = 'Joe DeCarolis, Kevin Hunter' +author = ', '.join( + author['name'] for author in cast('list[dict[str, Any]]', project_metadata.get('authors', [])) +) + -# The short X.Y version -version = '3.0' -# The full version, including alpha/beta/rc tags -release = time.strftime('%F', time.gmtime()) +# The short version +version = __version__.rsplit('.', 1)[0] +# The full version +release = __version__ # -- General configuration --------------------------------------------------- @@ -45,6 +57,9 @@ 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', + 'myst_parser', # Enable Markdown support + 'sphinxcontrib.mermaid', # Enable Mermaid diagrams + 'sphinx.ext.imgconverter', # Support SVG to PDF conversion for LaTeX ] # Add any paths that contain templates here, relative to this directory. @@ -53,10 +68,11 @@ bibtex_bibfiles = ['References.bib'] # The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +# Support both reStructuredText and Markdown +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} # The master toctree document. master_doc = 'index' @@ -76,6 +92,13 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' +# Suppress warnings for duplicate labels and objects (intentional due to file inclusion and autodoc) +suppress_warnings = [ + 'ref.duplicate', # Duplicate labels between included RST files + 'autosummary', # Autodoc duplicate object descriptions + 'ref.footnote', # Unreferenced footnotes +] + # -- Options for HTML output ------------------------------------------------- @@ -95,26 +118,25 @@ html_static_path = ['default/static'] -# this stylesheet eliminates fixed width and is located in the _static directory -def setup(app): - app.add_css_file('my_theme.css') - - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} -# import sphinx_rtd_theme - -# extensions = [ -# "sphinx_rtd_theme" -# ] - -html_theme = 'sphinx_rtd_theme' -html_logo = 'images/Temoa_logo_color_small.png' -latex_logo = 'images/TemoaLogo_grayscale.png' +html_theme = 'sphinx_book_theme' +html_theme_options = { + 'repository_url': 'https://github.com/TemoaProject/temoa', + 'use_repository_button': True, + 'use_issues_button': True, + 'use_edit_page_button': True, + 'path_to_docs': 'docs/source', + 'show_navbar_depth': 2, +} +latex_logo = 'assets/logo_bottom_text.pdf' +html_logo = 'assets/logo_bottom_text.svg' + +myst_enable_extensions = ['amsmath', 'colon_fence', 'dollarmath', 'html_image'] +myst_fence_as_directive = ['mermaid'] + +mermaid_d3_zoom = True +mermaid_fullscreen = True +mermaid_include_elk = True +mermaid_include_mindmap = True + +# -- Options for LaTeX output ------------------------------------------------ +latex_engine = 'xelatex' diff --git a/docs/source/database.rst b/docs/source/database.rst new file mode 100644 index 000000000..4cec23d1d --- /dev/null +++ b/docs/source/database.rst @@ -0,0 +1,229 @@ + +===================== +Database Construction +===================== + +Input datasets in Temoa are stored in a relational database management system. +For those unfamiliar with databases, you can think of them as collections of +tables. Within each table, a 'primary key' uniquely identifies each row. A +'foreign key' is a column in one table that references the primary key of +another table, thereby establishing relationships between tables and ensuring +data consistency across the database. + +The following Entity-Relationship (ER) diagram provides a visual overview of the +Temoa v4 database schema and the relationships between its various tables: + +.. mermaid:: database_schema.mmd + :alt: Temoa Database Schema ER Diagram + :align: center + +Temoa uses `sqlite`_, a widely used, self-contained database +system. Building a database first requires constructing a sql file, which is +simply a text file that defines the structure of different database tables and +includes the input data. The snippet below is from the ``time_period`` table +used to define the ``test_system`` dataset: + +.. parsed-literal:: + 1 CREATE TABLE time_period + 2 ( + 3 sequence INTEGER UNIQUE, + 4 period INTEGER + 5 PRIMARY KEY, + 6 flag TEXT + 7 REFERENCES time_period_type (label) + 8 ); + 9 INSERT INTO "time_period" VALUES(1,2015,'e'); + 10 INSERT INTO "time_period" VALUES(2,2020,'f'); + 11 INSERT INTO "time_period" VALUES(3,2025,'f'); + 12 INSERT INTO "time_period" VALUES(4,2030,'f'); + 13 INSERT INTO "time_period" VALUES(5,2035,'f'); + +The first line creates the table. **Lines 3-7** define the columns within this +table. Note that ``period`` is the primary key. Therefore, the same time period +cannot be entered twice; each name must be unique. **Line 7**, which helps +define the ``flag`` column, declares a foreign key reference to the ``label`` +column of the ``time_period_type`` table. As a result, if the user tries to +enter a label in this table that does not exist in the ``time_period_type`` +table, it will fail with an error. This foreign key reference ensures that the +modeler doesn't accidentally type the wrong label in this table. For context, +there are two basic types of time periods in Temoa, ``e``, which defines +pre-existing periods, and ``f``, which defines future time periods that are to +be optimized. + +This enforcement of names across tables using foreign keys helps immediately +catch typos. As you can imagine, typos happen in plain text files and Excel when +defining thousands of rows of data. Another big advantage of using databases is +that the model run outputs are stored in separate database output tables. The +outputs by model run are indexed by a scenario name, which makes it possible to +perform thousands of runs, programmatically store all the results, and execute +arbitrary queries that instantaneously return the requested +data. + +Because some database table elements serve as foreign keys in other tables, we +recommend that you populate input tables in the following order: + +**Group 1: labels used for internal database processing** + * commodity_type: Need to identify which type of commodity. Do NOT change these abbreviations. + * technology_type: Need to identify which type of technology. Do NOT change these abbreviations. Categorizing + and sub-categorizing can be done in the Technology table itself. + * time_period_type: Used to distinguish which time periods are simply used to specify pre-existing + vintages and which represent future optimization periods. + + +**Group 2: sets used within Temoa** + * commodity: list of commodities used within the database + * technology: list of technologies used within the database + * time_period: list of both past and future time periods considered in the database + * time_season: seasons modeled in the database (also contains segment fractions) + * time_of_day: time of day segments modeled in the database + + +**Group 3: parameters used to define processes within Temoa** + * metadata_real (global_discount_rate) + * demand + * demand_specific_distribution + * efficiency + * existing_capacity + * capacity_factor_tech + * capacity_factor_process (only if CF varies by vintage; overwrites capacity_factor_tech) + * capacity_to_activity + * cost_fixed + * cost_invest + * cost_variable + * emission_activity + * lifetime_tech + * lifetime_process (only if LT varies by vintage; overwrites lifetime_tech) + + +**Group 4: parameters used to define constraints within Temoa** (non-exhaustive) + * limit_activity + * limit_capacity + * limit_emission + * limit_growth_capacity + * limit_new_capacity + * limit_resource + * limit_tech_input_split + * limit_tech_output_split + + +For help getting started, consider using the ``temoa tutorial`` +command to generate a template project or inspect the example SQL file at +``temoa/tutorial_assets/utopia.sql``. To begin building your own database file, use +``temoa/db_schema/temoa_schema_v4.sql``, which is a database file with the requisite +structure but no data added. We recommend leaving the database structure intact, +and simply adding data to the schema file, or constructing an empty database +from the schema file and then using a script or database editor to import data. +For existing databases from older versions, use the ``temoa migrate`` command +to safely transition your data to the V4 schema. +Once the sql file is complete, you can convert it into a binary sqlite file by +installing sqlite3 and executing the following command: + +.. parsed-literal:: + $ sqlite3 my_database.sqlite < my_database.sql + +.. note:: + The command above using the ``<`` operator for input redirection is supported + in Linux, macOS, and the Windows Command Prompt. Note that PowerShell does not + support the Unix-style ``<`` redirection operator. In PowerShell, you can + achieve the same result by piping the file content into ``sqlite3``: + + .. code-block:: powershell + + Get-Content my_database.sql | sqlite3 my_database.sqlite + + Alternatively, you can load a SQL file from within the ``sqlite3`` interactive + interface using the ``.read`` command: + + .. code-block:: none + + sqlite3 my_database.sqlite + sqlite> .read my_database.sql + + When running on Windows, be sure to use the correct path separators (``\``) if you + provide absolute paths to your files. + +Now you can specify this database as the source for both input and output data +in the config file. + +============ +Data Quality +============ + +Both of the checks below can be run to QA data by using the ``temoa validate`` +command (which runs the model in ``BUILD_ONLY`` mode) and inspecting the log file. +During validation runs, no solve is attempted on the model. + +Price Checking +-------------- +The "price checker" reviews cost data in the 3 cost tables and considers technology lifetime. It +screens for possible inconsistencies that would corrupt output quality. Larger models may have +well over 100K cost entries and an overlooked investment cost for a particular vintage tech in +a particular region could easily be overlooked. Price checks performed/reported: + +1. **Missing Costs (Check 0)**: This check looks for technologies that have no fixed/invest/variable + costs at all. Other checks are more discriminating, so this check is only reported when Temoa is run + in `debug` mode by using the `-d` flag on the run command. +2. **Missing Fixed/Investment Costs (Check 1a)**: This check identifies technologies that are *not* + flagged as `uncapacitated` with neither a fixed or investment cost associated. These *might* be + problematic for solve because the model minimizes cost, so capacity in these technologies would be + free. `uncapacitated` technologies have no capacity measure, so fixed/investment costs are prohibited + for them and that is checked elsewhere. +3. **Inconsistent Fixed/Investment Cost (Check 1b)**: This check looks for inconsistent application + of fixed or base costs in the "base" or vintage year across all vintages and regions. So, if a tech has + a fixed cost in some particular region and vintage year, but not in all, it will be flagged as a likely + omission. +4. **Inconsistent Fixed & Variable Costs (Check 2)**: This check identifies techs that have + inconsistencies in the application of fixed - variable costs. Techs that have *any* fixed cost for + a particular [region, tech, vintage] process, but do not have entries that match the variable cost + entries for the same process are flagged, and vice-versa. This would hopefully identify an + accidental omission of some of the fixed/var costs for processes that have at least 1 entry for either. +5. **Lifetime Costing (Check 3)**: This check identifies costs that fall short or are missing + during the process's lifetime. If a process has a variable cost in *any* year during the lifetime, but + not all years, it is flagged. Same for fixed cost. +6. **Uncapacitated Tech Costs**: Any technology flagged as `uncapacitated` will trigger warnings here + if it has any fixed/invest costs. + +Source Tracing +-------------- + +Temoa works backwards from demands to identify chains of technologies required to meet the demand. +Source Tracing is designed to ensure that this backward tracing from demands describes a proper +commodity network without gaps that might allow intermediate commodities to be treated as a free +"source" commodity. Further description of possible network problems is included in the +:doc:`commodity_network` section. + +Source Tracing pre-builds the entire commodity network in each region-period contained in the +data and analyzes it for "orphans" which likely represent gaps in the network that would lead +to erroneous output data. The operation is enabled by tagging foundational commodities for which +there are no predecessors as "source" commodities in the `Commodity` database table with an `s` tag. +Orphans (or chains of orphans) on either the demand or supply side are reported and *suppressed* in +the data to prevent network corruption. Additionally, Temoa performs cycle detection on the commodity +network to identify circular dependencies that could lead to non-convergence or erroneous results. +Users can configure the cycle detection behavior using the following settings: + +* **cycle_count_limit**: Limits the number of cycles reported in the log. A value of `-1` allows + unbounded detection, `0` causes the system to log an error on the first detected cycle and then + suppresses further cycle reports for the remainder of the run (without terminating execution), + and a positive integer sets a specific limit. Default is 100. +* **cycle_length_limit**: Minimum length of cycles to report. This can be used to filter out small, + expected circularities if necessary. Default is 1. The length limit is inclusive, so a cycle of + length 1 is a self-loop, and a cycle of length `n` has `n` unique nodes. + +Note that the myopic mode *requires* the use of Source Tracing to ensure accuracy as some orphans +may be produced by endogenous decisions in myopic runs. + +SQLite Performance Tuning +------------------------- + +For large-scale models or long-running simulation modes (such as myopic or MGA), database I/O can become a performance bottleneck. Temoa allows you to tune the SQLite connection parameters in your configuration file under the ``[sqlite]`` section. + +The following settings are available: + +* **journal_mode**: Sets the SQLite journaling mode. Default is ``WAL`` (Write-Ahead Logging), which provides better performance and concurrency. Note that this will create temporary ``-wal`` and ``-shm`` files alongside your database during execution. +* **synchronous**: Controls how frequently SQLite flushes data to disk. Default is ``NORMAL``, which provides a good balance between speed and safety. +* **mmap_size**: The maximum number of bytes for memory-mapped I/O. Default is 8GB (``8589934592``). This allows SQLite to access the database file directly from memory, significantly speeding up reads for large databases. +* **cache_size**: The number of pages or the size in KiB for the SQLite page cache. If negative, it specifies size in KiB. Default is 500MiB (``-512000``). + +These settings are especially impactful in **myopic mode**, where Temoa frequently updates and queries the database between period iterations. By default, Temoa also disables the per-period ``VACUUM`` operation in myopic runs to avoid redundant and expensive full-database rewrites. + +.. _sqlite: https://www.sqlite.org/ diff --git a/docs/source/database_schema.mmd b/docs/source/database_schema.mmd new file mode 100644 index 000000000..0bc047252 --- /dev/null +++ b/docs/source/database_schema.mmd @@ -0,0 +1,875 @@ +erDiagram +capacity_credit { + INTEGER period PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL credit + TEXT notes +} +technology { + TEXT tech PK + INTEGER annual + TEXT category + INTEGER curtail + TEXT description + INTEGER exchange + TEXT flag + INTEGER flex + INTEGER reserve + INTEGER retire + INTEGER seas_stor + TEXT sector + TEXT sub_category + INTEGER unlim_cap +} +technology_type { + TEXT label PK + TEXT description +} +time_period { + INTEGER period PK + TEXT flag + INTEGER sequence +} +time_period_type { + TEXT label PK + TEXT description +} +capacity_factor_process { + INTEGER period PK + TEXT region PK + TEXT season PK + TEXT tech PK + TEXT tod PK + INTEGER vintage PK + REAL factor + TEXT notes +} +time_of_day { + TEXT tod PK + INTEGER sequence +} +season_label { + TEXT season PK + TEXT notes +} +capacity_factor_tech { + INTEGER period PK + TEXT region PK + TEXT season PK + TEXT tech PK + TEXT tod PK + REAL factor + TEXT notes +} +capacity_to_activity { + TEXT region PK + TEXT tech PK + REAL c2a + TEXT notes +} +commodity { + TEXT name PK + TEXT description + TEXT flag +} +commodity_type { + TEXT label PK + TEXT description +} +construction_input { + TEXT input_comm PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + TEXT notes + TEXT units + REAL value +} +cost_emission { + TEXT emis_comm PK + INTEGER period PK + TEXT region PK + REAL cost + TEXT notes + TEXT units +} +cost_fixed { + INTEGER period PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL cost + TEXT notes + TEXT units +} +cost_invest { + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL cost + TEXT notes + TEXT units +} +cost_variable { + INTEGER period PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL cost + TEXT notes + TEXT units +} +demand { + TEXT commodity PK + INTEGER period PK + TEXT region PK + REAL demand + TEXT notes + TEXT units +} +demand_specific_distribution { + TEXT demand_name PK + INTEGER period PK + TEXT region PK + TEXT season PK + TEXT tod PK + REAL dsd + TEXT notes +} +efficiency { + TEXT input_comm PK + TEXT output_comm PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL efficiency + TEXT notes +} +efficiency_variable { + TEXT input_comm PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT season PK + TEXT tech PK + TEXT tod PK + INTEGER vintage PK + REAL efficiency + TEXT notes +} +emission_activity { + TEXT emis_comm PK + TEXT input_comm PK + TEXT output_comm PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL activity + TEXT notes + TEXT units +} +emission_embodied { + TEXT emis_comm PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + TEXT notes + TEXT units + REAL value +} +emission_end_of_life { + TEXT emis_comm PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + TEXT notes + TEXT units + REAL value +} +end_of_life_output { + TEXT output_comm PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + TEXT notes + TEXT units + REAL value +} +existing_capacity { + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL capacity + TEXT notes + TEXT units +} +lifetime_process { + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL lifetime + TEXT notes +} +lifetime_survival_curve { + INTEGER period PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL fraction + TEXT notes +} +lifetime_tech { + TEXT region PK + TEXT tech PK + REAL lifetime + TEXT notes +} +limit_activity { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT tech_or_group PK + REAL activity + TEXT notes + TEXT units +} +operator { + TEXT operator PK + TEXT notes +} +limit_activity_share { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT sub_group PK + TEXT super_group PK + TEXT notes + REAL share +} +limit_annual_capacity_factor { + TEXT operator PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT tech PK + REAL factor + TEXT notes +} +limit_capacity { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT tech_or_group PK + REAL capacity + TEXT notes + TEXT units +} +limit_capacity_share { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT sub_group PK + TEXT super_group PK + TEXT notes + REAL share +} +limit_degrowth_capacity { + TEXT operator PK + TEXT region PK + TEXT tech_or_group PK + TEXT notes + REAL rate + REAL seed + TEXT seed_units +} +limit_degrowth_new_capacity { + TEXT operator PK + TEXT region PK + TEXT tech_or_group PK + TEXT notes + REAL rate + REAL seed + TEXT seed_units +} +limit_degrowth_new_capacity_delta { + TEXT operator PK + TEXT region PK + TEXT tech_or_group PK + TEXT notes + REAL rate + REAL seed + TEXT seed_units +} +limit_emission { + TEXT emis_comm PK + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT notes + TEXT units + REAL value +} +limit_growth_capacity { + TEXT operator PK + TEXT region PK + TEXT tech_or_group PK + TEXT notes + REAL rate + REAL seed + TEXT seed_units +} +limit_growth_new_capacity { + TEXT operator PK + TEXT region PK + TEXT tech_or_group PK + TEXT notes + REAL rate + REAL seed + TEXT seed_units +} +limit_growth_new_capacity_delta { + TEXT operator PK + TEXT region PK + TEXT tech_or_group PK + TEXT notes + REAL rate + REAL seed + TEXT seed_units +} +limit_new_capacity { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT tech_or_group PK + REAL new_cap + TEXT notes + TEXT units +} +limit_new_capacity_share { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT sub_group PK + TEXT super_group PK + TEXT notes + REAL share +} +limit_resource { + TEXT operator PK + TEXT region PK + TEXT tech_or_group PK + REAL cum_act + TEXT notes + TEXT units +} +limit_seasonal_capacity_factor { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT season PK + TEXT tech PK + REAL factor + TEXT notes +} +region { + TEXT region PK + TEXT notes +} +limit_storage_level_fraction { + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT season PK + TEXT tech PK + TEXT tod PK + INTEGER vintage PK + REAL fraction + TEXT notes +} +limit_tech_input_split { + TEXT input_comm PK + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT tech PK + TEXT notes + REAL proportion +} +limit_tech_input_split_annual { + TEXT input_comm PK + TEXT operator PK + INTEGER period PK + TEXT region PK + TEXT tech PK + TEXT notes + REAL proportion +} +limit_tech_output_split { + TEXT operator PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT tech PK + TEXT notes + REAL proportion +} +limit_tech_output_split_annual { + TEXT operator PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT tech PK + TEXT notes + REAL proportion +} +linked_tech { + TEXT emis_comm PK + TEXT primary_region PK + TEXT primary_tech PK + TEXT driven_tech + TEXT notes +} +loan_lifetime_process { + TEXT region PK + TEXT tech PK + INTEGER vintage PK + REAL lifetime + TEXT notes +} +loan_rate { + TEXT region PK + TEXT tech PK + INTEGER vintage PK + TEXT notes + REAL rate +} +metadata { + TEXT element PK + TEXT notes + INTEGER value +} +metadata_real { + TEXT element PK + TEXT notes + REAL value +} +myopic_efficiency { + TEXT input_comm PK + TEXT output_comm PK + TEXT region PK + TEXT tech PK + INTEGER vintage PK + INTEGER base_year + REAL efficiency + INTEGER lifetime +} +output_built_capacity { + TEXT region PK + TEXT scenario PK + TEXT tech PK + INTEGER vintage PK + REAL capacity + TEXT sector +} +sector_label { + TEXT sector PK + TEXT notes +} +output_cost { + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT tech PK + INTEGER vintage PK + REAL d_emiss + REAL d_fixed + REAL d_invest + REAL d_var + REAL emiss + REAL fixed + REAL invest + TEXT sector + REAL var +} +output_curtailment { + TEXT input_comm PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT season PK + TEXT tech PK + TEXT tod PK + INTEGER vintage PK + REAL curtailment + TEXT sector +} +output_dual_variable { + TEXT constraint_name PK + TEXT scenario PK + REAL dual +} +output_emission { + TEXT emis_comm PK + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT tech PK + INTEGER vintage PK + REAL emission + TEXT sector +} +output_flow_in { + TEXT input_comm PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT season PK + TEXT tech PK + TEXT tod PK + INTEGER vintage PK + REAL flow + TEXT sector +} +output_flow_out { + TEXT input_comm PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT season PK + TEXT tech PK + TEXT tod PK + INTEGER vintage PK + REAL flow + TEXT sector +} +output_flow_out_summary { + TEXT input_comm PK + TEXT output_comm PK + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT tech PK + INTEGER vintage PK + REAL flow + TEXT sector +} +output_net_capacity { + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT tech PK + INTEGER vintage PK + REAL capacity + TEXT sector +} +output_objective { + TEXT objective_name + TEXT scenario + REAL total_system_cost +} +output_retired_capacity { + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT tech PK + INTEGER vintage PK + REAL cap_early + REAL cap_eol + TEXT sector +} +output_storage_level { + INTEGER period PK + TEXT region PK + TEXT scenario PK + TEXT season PK + TEXT tech PK + TEXT tod PK + INTEGER vintage PK + REAL level + TEXT sector +} +planning_reserve_margin { + TEXT region PK + REAL margin + TEXT notes +} +ramp_down_hourly { + TEXT region PK + TEXT tech PK + TEXT notes + REAL rate +} +ramp_up_hourly { + TEXT region PK + TEXT tech PK + TEXT notes + REAL rate +} +reserve_capacity_derate { + INTEGER period PK + TEXT region PK + TEXT season PK + TEXT tech PK + INTEGER vintage PK + REAL factor + TEXT notes +} +rps_requirement { + TEXT notes + INTEGER period + TEXT region + REAL requirement + TEXT tech_group +} +tech_group { + TEXT group_name PK + TEXT notes +} +storage_duration { + TEXT region PK + TEXT tech PK + REAL duration + TEXT notes +} +tech_group_member { + TEXT group_name PK + TEXT tech PK +} +time_season { + INTEGER period PK + TEXT season PK + INTEGER sequence PK + TEXT notes +} +time_season_all { + INTEGER period PK + TEXT season PK + INTEGER sequence PK + TEXT notes +} +time_season_sequential { + INTEGER period PK + TEXT seas_seq PK + TEXT season PK + INTEGER sequence PK + TEXT notes + REAL num_days +} +time_season_to_sequential { + INTEGER period PK + TEXT seas_seq PK + TEXT season PK + INTEGER sequence PK + TEXT notes + REAL num_days +} +time_segment_fraction { + INTEGER period PK + TEXT season PK + TEXT tod PK + TEXT notes + REAL segment_fraction +} +technology one or zero--0+ capacity_credit : has +time_period one or zero--0+ capacity_credit : has +technology_type 1--0+ technology : has +time_period_type one or zero--0+ time_period : has +technology one or zero--0+ capacity_factor_process : has +time_of_day one or zero--0+ capacity_factor_process : has +time_period one or zero--0+ capacity_factor_process : has +season_label one or zero--0+ capacity_factor_process : has +season_label one or zero--0+ capacity_factor_tech : has +time_period one or zero--0+ capacity_factor_tech : has +time_of_day one or zero--0+ capacity_factor_tech : has +technology one or zero--0+ capacity_factor_tech : has +technology one or zero--0+ capacity_to_activity : has +commodity_type one or zero--0+ commodity : has +time_period one or zero--0+ construction_input : has +commodity one or zero--0+ construction_input : has +technology one or zero--0+ construction_input : has +time_period one or zero--0+ cost_emission : has +commodity 1--0+ cost_emission : has +time_period 1--0+ cost_fixed : has +technology 1--0+ cost_fixed : has +time_period 1--0+ cost_fixed : has +technology one or zero--0+ cost_invest : has +time_period one or zero--0+ cost_invest : has +time_period 1--0+ cost_variable : has +time_period 1--0+ cost_variable : has +technology 1--0+ cost_variable : has +time_period one or zero--0+ demand : has +commodity one or zero--0+ demand : has +time_period one or zero--0+ demand_specific_distribution : has +season_label one or zero--0+ demand_specific_distribution : has +time_of_day one or zero--0+ demand_specific_distribution : has +commodity one or zero--0+ demand_specific_distribution : has +commodity one or zero--0+ efficiency : has +technology one or zero--0+ efficiency : has +time_period one or zero--0+ efficiency : has +commodity one or zero--0+ efficiency : has +time_period one or zero--0+ efficiency_variable : has +season_label one or zero--0+ efficiency_variable : has +time_of_day one or zero--0+ efficiency_variable : has +commodity one or zero--0+ efficiency_variable : has +technology one or zero--0+ efficiency_variable : has +time_period one or zero--0+ efficiency_variable : has +commodity one or zero--0+ efficiency_variable : has +commodity one or zero--0+ emission_activity : has +commodity one or zero--0+ emission_activity : has +technology one or zero--0+ emission_activity : has +time_period one or zero--0+ emission_activity : has +commodity one or zero--0+ emission_activity : has +commodity one or zero--0+ emission_embodied : has +technology one or zero--0+ emission_embodied : has +time_period one or zero--0+ emission_embodied : has +time_period one or zero--0+ emission_end_of_life : has +commodity one or zero--0+ emission_end_of_life : has +technology one or zero--0+ emission_end_of_life : has +technology one or zero--0+ end_of_life_output : has +commodity one or zero--0+ end_of_life_output : has +time_period one or zero--0+ end_of_life_output : has +time_period one or zero--0+ existing_capacity : has +technology one or zero--0+ existing_capacity : has +technology one or zero--0+ lifetime_process : has +time_period one or zero--0+ lifetime_process : has +time_period 1--0+ lifetime_survival_curve : has +technology 1--0+ lifetime_survival_curve : has +technology one or zero--0+ lifetime_tech : has +time_period one or zero--0+ limit_activity : has +operator 1--0+ limit_activity : has +time_period one or zero--0+ limit_activity_share : has +operator 1--0+ limit_activity_share : has +operator 1--0+ limit_annual_capacity_factor : has +time_period one or zero--0+ limit_annual_capacity_factor : has +technology one or zero--0+ limit_annual_capacity_factor : has +commodity one or zero--0+ limit_annual_capacity_factor : has +operator 1--0+ limit_capacity : has +time_period one or zero--0+ limit_capacity : has +time_period one or zero--0+ limit_capacity_share : has +operator 1--0+ limit_capacity_share : has +operator 1--0+ limit_degrowth_capacity : has +operator 1--0+ limit_degrowth_new_capacity : has +operator 1--0+ limit_degrowth_new_capacity_delta : has +commodity one or zero--0+ limit_emission : has +operator 1--0+ limit_emission : has +time_period one or zero--0+ limit_emission : has +operator 1--0+ limit_growth_capacity : has +operator 1--0+ limit_growth_new_capacity : has +operator 1--0+ limit_growth_new_capacity_delta : has +time_period one or zero--0+ limit_new_capacity : has +operator 1--0+ limit_new_capacity : has +operator 1--0+ limit_new_capacity_share : has +time_period one or zero--0+ limit_new_capacity_share : has +operator 1--0+ limit_resource : has +region one or zero--0+ limit_seasonal_capacity_factor : has +time_period one or zero--0+ limit_seasonal_capacity_factor : has +season_label one or zero--0+ limit_seasonal_capacity_factor : has +technology one or zero--0+ limit_seasonal_capacity_factor : has +operator 1--0+ limit_seasonal_capacity_factor : has +time_of_day one or zero--0+ limit_storage_level_fraction : has +technology one or zero--0+ limit_storage_level_fraction : has +time_period one or zero--0+ limit_storage_level_fraction : has +operator 1--0+ limit_storage_level_fraction : has +time_period one or zero--0+ limit_storage_level_fraction : has +season_label one or zero--0+ limit_storage_level_fraction : has +technology one or zero--0+ limit_tech_input_split : has +operator 1--0+ limit_tech_input_split : has +time_period one or zero--0+ limit_tech_input_split : has +commodity one or zero--0+ limit_tech_input_split : has +technology one or zero--0+ limit_tech_input_split_annual : has +operator 1--0+ limit_tech_input_split_annual : has +time_period one or zero--0+ limit_tech_input_split_annual : has +commodity one or zero--0+ limit_tech_input_split_annual : has +commodity one or zero--0+ limit_tech_output_split : has +operator 1--0+ limit_tech_output_split : has +time_period one or zero--0+ limit_tech_output_split : has +technology one or zero--0+ limit_tech_output_split : has +commodity one or zero--0+ limit_tech_output_split_annual : has +operator 1--0+ limit_tech_output_split_annual : has +time_period one or zero--0+ limit_tech_output_split_annual : has +technology one or zero--0+ limit_tech_output_split_annual : has +commodity one or zero--0+ linked_tech : has +technology one or zero--0+ linked_tech : has +technology one or zero--0+ linked_tech : has +technology one or zero--0+ loan_lifetime_process : has +time_period one or zero--0+ loan_lifetime_process : has +technology one or zero--0+ loan_rate : has +time_period one or zero--0+ loan_rate : has +technology one or zero--0+ myopic_efficiency : has +sector_label one or zero--0+ output_built_capacity : has +technology one or zero--0+ output_built_capacity : has +time_period one or zero--0+ output_built_capacity : has +time_period one or zero--0+ output_cost : has +sector_label one or zero--0+ output_cost : has +time_period one or zero--0+ output_cost : has +technology one or zero--0+ output_cost : has +time_of_day one or zero--0+ output_curtailment : has +commodity one or zero--0+ output_curtailment : has +technology one or zero--0+ output_curtailment : has +time_period one or zero--0+ output_curtailment : has +commodity one or zero--0+ output_curtailment : has +time_period one or zero--0+ output_curtailment : has +time_period one or zero--0+ output_curtailment : has +sector_label one or zero--0+ output_emission : has +time_period one or zero--0+ output_emission : has +commodity one or zero--0+ output_emission : has +technology one or zero--0+ output_emission : has +time_period one or zero--0+ output_emission : has +sector_label one or zero--0+ output_flow_in : has +time_period one or zero--0+ output_flow_in : has +season_label one or zero--0+ output_flow_in : has +time_of_day one or zero--0+ output_flow_in : has +commodity one or zero--0+ output_flow_in : has +technology one or zero--0+ output_flow_in : has +time_period one or zero--0+ output_flow_in : has +commodity one or zero--0+ output_flow_in : has +technology one or zero--0+ output_flow_out : has +time_period one or zero--0+ output_flow_out : has +commodity one or zero--0+ output_flow_out : has +sector_label one or zero--0+ output_flow_out : has +time_period one or zero--0+ output_flow_out : has +season_label one or zero--0+ output_flow_out : has +time_of_day one or zero--0+ output_flow_out : has +commodity one or zero--0+ output_flow_out : has +technology 1--0+ output_flow_out_summary : has +technology one or zero--0+ output_net_capacity : has +time_period one or zero--0+ output_net_capacity : has +sector_label one or zero--0+ output_net_capacity : has +time_period one or zero--0+ output_net_capacity : has +technology one or zero--0+ output_retired_capacity : has +time_period one or zero--0+ output_retired_capacity : has +sector_label one or zero--0+ output_retired_capacity : has +time_period one or zero--0+ output_retired_capacity : has +technology one or zero--0+ output_storage_level : has +time_period one or zero--0+ output_storage_level : has +sector_label one or zero--0+ output_storage_level : has +time_period one or zero--0+ output_storage_level : has +season_label one or zero--0+ output_storage_level : has +time_of_day one or zero--0+ output_storage_level : has +region one or zero--1 planning_reserve_margin : has +technology one or zero--0+ ramp_down_hourly : has +technology one or zero--0+ ramp_up_hourly : has +technology one or zero--0+ reserve_capacity_derate : has +time_period one or zero--0+ reserve_capacity_derate : has +season_label one or zero--0+ reserve_capacity_derate : has +time_period 1--0+ rps_requirement : has +tech_group 1--0+ rps_requirement : has +region 1--0+ rps_requirement : has +tech_group one or zero--0+ tech_group_member : has +technology one or zero--0+ tech_group_member : has +time_period one or zero--0+ time_season : has +season_label one or zero--0+ time_season : has +season_label one or zero--0+ time_season_all : has +time_period one or zero--0+ time_season_all : has +time_period one or zero--0+ time_season_sequential : has +season_label one or zero--0+ time_season_sequential : has +season_label one or zero--0+ time_season_to_sequential : has +time_period one or zero--0+ time_season_to_sequential : has +time_period one or zero--0+ time_segment_fraction : has +season_label one or zero--0+ time_segment_fraction : has +time_of_day one or zero--0+ time_segment_fraction : has diff --git a/docs/source/default/static/Network_Graph_utopia_1990.html b/docs/source/default/static/Network_Graph_utopia_1990.html new file mode 100644 index 000000000..f4089ae42 --- /dev/null +++ b/docs/source/default/static/Network_Graph_utopia_1990.html @@ -0,0 +1,62 @@ + + + + + + + Network Graphs - utopia 1990 + + + + + +
+
+

Configuration & Legend

+ +
+
+
+

Visual Settings

+
+ + +
+
+
+

Style Legend

+
+
+
+

Sector Legend

+
+
+ Show Advanced Physics Controls + +
+
+
+ +
+
+ Filter Sectors: +
+
+ + + +
+
+ Tip: Double-click a node to isolate. Single-click to select. Use 'Reset View' to clear. +
+
+ + + + + + + diff --git a/docs/source/default/static/graph_script.js b/docs/source/default/static/graph_script.js new file mode 100644 index 000000000..78e77b915 --- /dev/null +++ b/docs/source/default/static/graph_script.js @@ -0,0 +1,260 @@ +document.addEventListener('DOMContentLoaded', function () { + // --- Master Datasets (Read embedded JSON safely) --- + let data = null; + const dataEl = document.getElementById('graph-data'); + if (dataEl && dataEl.textContent) { + try { + data = JSON.parse(dataEl.textContent); + } catch (e) { + console.error('Failed to parse graph data JSON:', e); + } + } + + // If data failed to load, stop immediately to prevent further errors. + if (!data) { + console.error('Could not find or parse GRAPH_DATA. Halting script.'); + return; + } + + const { + nodes_json_primary: allNodesPrimary, + edges_json_primary: allEdgesPrimary, + nodes_json_secondary: allNodesSecondary, + edges_json_secondary: allEdgesSecondary, + options_json_str: optionsRaw, + sectors_json_str: allSectors, + color_legend_json_str: colorLegendData, + style_legend_json_str: styleLegendData, + primary_view_name: primaryViewName, + secondary_view_name: secondaryViewName, + } = data; + const optionsObject = (typeof optionsRaw === 'string') ? JSON.parse(optionsRaw) : optionsRaw; + // --- State --- + let currentView = 'primary'; + let primaryViewPositions = null; + let secondaryViewPositions = null; + + // --- DOM Elements --- + const configWrapper = document.getElementById('config-panel-wrapper'); + const configHeader = document.querySelector('.config-panel-header'); + const configToggleButton = document.querySelector('.config-toggle-btn'); + const advancedControlsToggle = document.getElementById('advanced-controls-toggle'); + const visConfigContainer = document.getElementById('vis-config-container'); + const searchInput = document.getElementById('search-input'); + const resetButton = document.getElementById('reset-view-btn'); + const sectorTogglesContainer = document.getElementById('sector-toggles'); + const viewToggleButton = document.getElementById('view-toggle-btn'); + const graphContainer = document.getElementById('mynetwork'); + + // --- Config Panel Toggle --- + if (optionsObject.configure && optionsObject.configure.enabled) { + optionsObject.configure.container = visConfigContainer; + configHeader.addEventListener('click', () => { + const isCollapsed = configWrapper.classList.toggle('collapsed'); + configToggleButton.setAttribute('aria-expanded', !isCollapsed); + }); + advancedControlsToggle.addEventListener('click', function(e) { + e.preventDefault(); + const isHidden = visConfigContainer.style.display === 'none'; + visConfigContainer.style.display = isHidden ? 'block' : 'none'; + this.textContent = isHidden ? 'Hide Advanced Physics Controls' : 'Show Advanced Physics Controls'; + }); + } + + // --- Vis.js Network Initialization --- + const nodes = new vis.DataSet(allNodesPrimary); + const edges = new vis.DataSet(allEdgesPrimary); + const network = new vis.Network(graphContainer, { nodes, edges }, optionsObject); + + // --- Core Functions --- + function applyPositions(positions) { + if (!positions) return; + const updates = Object.keys(positions).map(nodeId => ({ + id: nodeId, x: positions[nodeId].x, y: positions[nodeId].y, + })); + if (updates.length > 0) nodes.update(updates); + } + + function switchView() { + if (currentView === 'primary') { + primaryViewPositions = network.getPositions(); + } else { + secondaryViewPositions = network.getPositions(); + } + nodes.clear(); edges.clear(); + + if (currentView === 'primary') { + nodes.add(allNodesSecondary); edges.add(allEdgesSecondary); + currentView = 'secondary'; + viewToggleButton.textContent = `Switch to ${primaryViewName}`; + viewToggleButton.setAttribute('aria-pressed', 'true'); + applyPositions(secondaryViewPositions); + } else { + nodes.add(allNodesPrimary); edges.add(allEdgesPrimary); + currentView = 'primary'; + viewToggleButton.textContent = `Switch to ${secondaryViewName}`; + viewToggleButton.setAttribute('aria-pressed', 'false'); + applyPositions(primaryViewPositions); + } + applyAllFilters(); + network.fit(); + } + + function applyAllFilters() { + // Preserve current positions so filtering doesn't reset layout + const currentPositions = network.getPositions(); + const checkedSectors = new Set(Array.from(sectorTogglesContainer.querySelectorAll('input:checked')).map(c => c.value)); + const searchQuery = searchInput.value; + let regex = null; + if (searchQuery) { + try { regex = new RegExp(searchQuery, 'i'); } catch (e) { console.error("Invalid Regex:", e); return; } + } + const activeNodesData = (currentView === 'primary') ? allNodesPrimary : allNodesSecondary; + const activeEdgesData = (currentView === 'primary') ? allEdgesPrimary : allEdgesSecondary; + const sectorFilteredNodes = activeNodesData.filter(node => { + let match = node.group === null || node.group === undefined || checkedSectors.has(node.group); + if (currentView === 'primary' && node.alwaysVisible === true) { + match = true; + } + return match; + }); + let visibleNodes, visibleEdges; + if (regex) { + const seedNodes = sectorFilteredNodes.filter(node => regex.test(node.label || node.id)); + const seedNodeIds = new Set(seedNodes.map(n => n.id)); + const nodesToShowIds = new Set(seedNodeIds); + activeEdgesData.forEach(edge => { + if (seedNodeIds.has(edge.from)) nodesToShowIds.add(edge.to); + if (seedNodeIds.has(edge.to)) nodesToShowIds.add(edge.from); + }); + visibleNodes = activeNodesData.filter(node => nodesToShowIds.has(node.id)); + visibleEdges = activeEdgesData.filter(edge => nodesToShowIds.has(edge.from) && nodesToShowIds.has(edge.to)); + } else { + visibleNodes = sectorFilteredNodes; + const visibleNodeIds = new Set(visibleNodes.map(n => n.id)); + visibleEdges = activeEdgesData.filter(edge => visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to)); + } + nodes.clear(); edges.clear(); + nodes.add(visibleNodes); edges.add(visibleEdges); + applyPositions(currentPositions); + } + + function createStyleLegend() { + const container = document.getElementById('style-legend-container'); + if (!container || !styleLegendData || styleLegendData.length === 0) return; + styleLegendData.forEach(itemData => { + const item = document.createElement('div'); + item.className = 'legend-item'; + const swatch = document.createElement('div'); + swatch.className = 'legend-color-swatch'; + if (itemData.borderColor) swatch.style.borderColor = itemData.borderColor; + if (itemData.borderWidth) swatch.style.borderWidth = itemData.borderWidth + 'px'; + const label = document.createElement('span'); + label.className = 'legend-label'; + label.textContent = itemData.label; + item.append(swatch, label); + container.appendChild(item); + }); + } + + function createSectorLegend() { + const container = document.getElementById('legend-container'); + if (!container || !colorLegendData || Object.keys(colorLegendData).length === 0) return; + Object.keys(colorLegendData).sort().forEach(key => { + const item = document.createElement('div'); + item.className = 'legend-item'; + const swatch = document.createElement('div'); + swatch.className = 'legend-color-swatch'; + swatch.style.backgroundColor = colorLegendData[key]; + const label = document.createElement('span'); + label.className = 'legend-label'; + label.textContent = key; + item.append(swatch, label); + container.appendChild(item); + }); + } + + function createSectorToggles() { + if (!allSectors || allSectors.length === 0) { + sectorTogglesContainer.style.display = 'none'; + return; + } + allSectors.forEach(sector => { + const item = document.createElement('div'); + item.className = 'sector-toggle-item'; + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `toggle-${sector}`; + checkbox.value = sector; + checkbox.checked = true; + checkbox.addEventListener('change', applyAllFilters); + const swatch = document.createElement('div'); + swatch.className = 'toggle-color-swatch'; + swatch.style.backgroundColor = colorLegendData[sector] || '#ccc'; + const label = document.createElement('label'); + label.htmlFor = checkbox.id; + label.textContent = sector; + item.append(swatch, checkbox, label); + item.addEventListener('click', e => { + if (e.target.tagName === 'INPUT') return; + e.preventDefault(); + checkbox.checked = !checkbox.checked; + checkbox.dispatchEvent(new Event('change', { bubbles: true })); + }); + sectorTogglesContainer.appendChild(item); + }); + } + + function resetView() { + searchInput.value = ""; + primaryViewPositions = null; + secondaryViewPositions = null; + if (currentView !== 'primary') { + switchView(); // This will switch back to primary and apply null positions + } else { + // If already on primary, just reload the original data + nodes.clear(); edges.clear(); + nodes.add(allNodesPrimary); edges.add(allEdgesPrimary); + applyPositions(primaryViewPositions); // Apply null to reset + network.fit(); + } + sectorTogglesContainer.querySelectorAll('input[type=checkbox]').forEach(c => c.checked = true); + applyAllFilters(); + } + + function showNeighborhood(nodeId) { + const activeNodes = (currentView === 'primary') ? allNodesPrimary : allNodesSecondary; + const activeEdges = (currentView === 'primary') ? allEdgesPrimary : allEdgesSecondary; + searchInput.value = ""; + const nodesToShow = new Set([nodeId]); + activeEdges.forEach(edge => { + if (edge.from === nodeId) nodesToShow.add(edge.to); + else if (edge.to === nodeId) nodesToShow.add(edge.from); + }); + const filteredNodes = activeNodes.filter(node => nodesToShow.has(node.id)); + const filteredEdges = activeEdges.filter(edge => nodesToShow.has(edge.from) && nodesToShow.has(edge.to)); + nodes.clear(); edges.clear(); + nodes.add(filteredNodes); + edges.add(filteredEdges); + network.fit(); + } + + // --- Event Listeners & Initial Setup --- + if (allNodesSecondary && allNodesSecondary.length > 0) { + viewToggleButton.textContent = `Switch to ${secondaryViewName}`; + viewToggleButton.addEventListener('click', switchView); + } else { + document.getElementById('view-toggle-panel').style.display = 'none'; + } + resetButton.addEventListener('click', resetView); + searchInput.addEventListener('input', applyAllFilters); + network.on("doubleClick", params => { + if (params.nodes.length > 0) { + showNeighborhood(params.nodes[0]); + } + }); + + createStyleLegend(); + createSectorLegend(); + createSectorToggles(); +}); diff --git a/docs/source/default/static/graph_styles.css b/docs/source/default/static/graph_styles.css new file mode 100644 index 000000000..0402c090e --- /dev/null +++ b/docs/source/default/static/graph_styles.css @@ -0,0 +1,29 @@ +body, html { + margin: 0; padding: 0; width: 100%; height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: #f4f6f8; display: flex; flex-direction: column; overflow: hidden; +} +.config-panel-wrapper { width: 100%; background-color: #ffffff; box-shadow: 0 2px 4px rgba(0,0,0,0.1); z-index: 10; flex-shrink: 0; } +.config-panel-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; border-bottom: 1px solid #e0e0e0; cursor: pointer; background-color: #f9f9f9; } +.config-panel-header h3 { margin: 0; font-size: 16px; font-weight: 600; } +.config-toggle-btn::after { content: '\25BC'; display: inline-block; transition: transform 0.2s ease-in-out; } +.collapsed .config-toggle-btn::after { transform: rotate(-90deg); } +#config-container-content { max-height: 40vh; overflow-y: auto; padding: 15px; box-sizing: border-box; background-color: #ffffff; transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out; } +.collapsed #config-container-content { max-height: 0; padding-top: 0; padding-bottom: 0; overflow: hidden; } +#mynetwork { width: 100%; flex-grow: 1; min-height: 0; } +.filter-panel { display: flex; align-items: center; gap: 10px; padding: 8px 15px; background-color: #e9ecef; border-bottom: 1px solid #dee2e6; } +.filter-panel input[type=text] { flex-grow: 1; padding: 6px 8px; border: 1px solid #ced4da; border-radius: 4px; } +.filter-panel button { padding: 6px 12px; border-radius: 4px; border: 1px solid #6c757d; background-color: #6c757d; color: white; cursor: pointer; } +.info-panel { padding: 8px 15px; background-color: #f8f9fa; font-size: 13px; text-align: center; border-bottom: 1px solid #dee2e6; } +.sector-toggles { padding: 10px 15px; background-color: #f8f9fa; border-bottom: 1px solid #dee2e6; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } +.sector-toggles-title { font-weight: 600; } +.sector-toggle-item { display: flex; align-items: center; gap: 8px; padding: 5px 10px; border: 1px solid #ccc; border-radius: 16px; background-color: #fff; cursor: pointer; user-select: none; } +.toggle-color-swatch { width: 14px; height: 14px; border-radius: 50%; } +.legend-section { margin-bottom: 15px; } +.legend-section h4 { margin-top: 0; margin-bottom: 10px; font-size: 14px; font-weight: 600; } +.legend-container { display: flex; flex-wrap: wrap; gap: 15px; } +.legend-item { display: flex; align-items: center; margin-bottom: 6px; } +.legend-color-swatch { width: 18px; height: 18px; margin-right: 8px; flex-shrink: 0; border: 1px solid #ccc; background-color: #f0f0f0; box-sizing: border-box; } +.legend-label { font-size: 13px; } +#advanced-controls-toggle { font-size: 12px; color: #007bff; cursor: pointer; text-decoration: none; margin-top: 15px; display: block; } +.view-toggle-panel { padding: 8px 15px; background-color: #343a40; color: white; display: flex; justify-content: center; align-items: center; } +.view-toggle-panel button { font-size: 14px; font-weight: 600; padding: 8px 16px; border-radius: 5px; border: 1px solid #6c757d; background-color: #495057; color: white; cursor: pointer; } diff --git a/docs/source/images/adjusted_capacity_plf.pdf b/docs/source/images/adjusted_capacity_plf.pdf new file mode 100644 index 000000000..6170567f1 Binary files /dev/null and b/docs/source/images/adjusted_capacity_plf.pdf differ diff --git a/docs/source/images/adjusted_capacity_plf.png b/docs/source/images/adjusted_capacity_plf.png new file mode 100644 index 000000000..36b4cac56 Binary files /dev/null and b/docs/source/images/adjusted_capacity_plf.png differ diff --git a/docs/source/images/adjusted_capacity_plf.svg b/docs/source/images/adjusted_capacity_plf.svg new file mode 100644 index 000000000..400831421 --- /dev/null +++ b/docs/source/images/adjusted_capacity_plf.svg @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + PE + P0 + Process reaches EOL mid-period + process_life_fraction adjustment + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/images/adjusted_capacity_sc.pdf b/docs/source/images/adjusted_capacity_sc.pdf new file mode 100644 index 000000000..0282149cb Binary files /dev/null and b/docs/source/images/adjusted_capacity_sc.pdf differ diff --git a/docs/source/images/adjusted_capacity_sc.png b/docs/source/images/adjusted_capacity_sc.png new file mode 100644 index 000000000..89d5b3369 Binary files /dev/null and b/docs/source/images/adjusted_capacity_sc.png differ diff --git a/docs/source/images/adjusted_capacity_sc.svg b/docs/source/images/adjusted_capacity_sc.svg new file mode 100644 index 000000000..96c253874 --- /dev/null +++ b/docs/source/images/adjusted_capacity_sc.svg @@ -0,0 +1,1246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PE + P0 + Survival curve in each integer year + Averaged over each period + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/images/coal_process.dot b/docs/source/images/coal_process.dot index 421423baa..fd33519b0 100644 --- a/docs/source/images/coal_process.dot +++ b/docs/source/images/coal_process.dot @@ -7,6 +7,6 @@ digraph coal { coal_plant [ shape="box", color="darkseagreen", label="coal_plant\n\ninstalled capacity\nefficiency\ninstall cost\nfixed cost\nmarginal cost\nemission per unit activity\nuseful life\nloan life\n..." ] ; electricity [ label="electricity" ]; - coal -> coal_plant [ label="Input, (V_FlowIn)" ]; - coal_plant -> electricity [ label="Output, (V_FlowOut)"]; + coal -> coal_plant [ label="Input, (v_flow_in)" ]; + coal_plant -> electricity [ label="Output, (v_flow_out)"]; } diff --git a/docs/source/images/daily_storage_representation.pdf b/docs/source/images/daily_storage_representation.pdf new file mode 100644 index 000000000..d208c75b7 Binary files /dev/null and b/docs/source/images/daily_storage_representation.pdf differ diff --git a/docs/source/images/daily_storage_representation.png b/docs/source/images/daily_storage_representation.png new file mode 100644 index 000000000..e7e750ab3 Binary files /dev/null and b/docs/source/images/daily_storage_representation.png differ diff --git a/docs/source/images/daily_storage_representation.svg b/docs/source/images/daily_storage_representation.svg new file mode 100644 index 000000000..f20578aee --- /dev/null +++ b/docs/source/images/daily_storage_representation.svg @@ -0,0 +1,188 @@ + + + + + + + + + + Adjustedupper bound(x 3 days) + No seasonaldelta allowed + + + + Represents + + + + + + + + + + + + + + Daily (non-seasonal) storage + + diff --git a/docs/source/images/graphviz_examples/Network_Graph_utopia_1990.html b/docs/source/images/graphviz_examples/Network_Graph_utopia_1990.html new file mode 100644 index 000000000..243036757 --- /dev/null +++ b/docs/source/images/graphviz_examples/Network_Graph_utopia_1990.html @@ -0,0 +1,55 @@ + + + + + + + Network Graphs - utopia 1990 + + + + + +
+
+

Configuration & Legend

+ +
+
+
+

Style Legend

+
+
+
+

Sector Legend

+
+
+ Show Advanced Physics Controls + +
+
+
+ +
+
+ Filter Sectors: +
+
+ + + +
+
+ Tip: Double-click a node to isolate. Single-click to select. Use 'Reset View' to clear. +
+
+ + + + + + + diff --git a/docs/source/images/graphviz_examples/results1990.pdf b/docs/source/images/graphviz_examples/results1990.pdf new file mode 100644 index 000000000..02d6839b8 Binary files /dev/null and b/docs/source/images/graphviz_examples/results1990.pdf differ diff --git a/docs/source/images/graphviz_examples/results1990.svg b/docs/source/images/graphviz_examples/results1990.svg new file mode 100644 index 000000000..ba1615a8a --- /dev/null +++ b/docs/source/images/graphviz_examples/results1990.svg @@ -0,0 +1,533 @@ + + + + + + +model + +Results for 1990 + + +E21 + +E21 + + + +ELC + + +ELC + + + + + +E21->ELC + + + + + +IMPDSL1 + + +IMPDSL1 +Capacity: 99999.00 + + + + + +DSL + + +DSL + + + + + +IMPDSL1->DSL + + +38.60 + + + +co2 + +co2 + + + +IMPDSL1->co2 + + +2.89 + + + +IMPFEQ + +IMPFEQ + + + +FEQ + +FEQ + + + +IMPFEQ->FEQ + + + + + +IMPGSL1 + + +IMPGSL1 +Capacity: 99999.00 + + + + + +GSL + + +GSL + + + + + +IMPGSL1->GSL + + +19.91 + + + +IMPGSL1->co2 + + +1.49 + + + +IMPHCO1 + + +IMPHCO1 +Capacity: 99999.00 + + + + + +HCO + + +HCO + + + + + +IMPHCO1->HCO + + +14.70 + + + +IMPHCO1->co2 + + +1.31 + + + +IMPHYD + + +IMPHYD +Capacity: 99999.00 + + + + + +HYD + + +HYD + + + + + +IMPHYD->HYD + + +3.52 + + + +IMPOIL1 + +IMPOIL1 + + + +OIL + +OIL + + + +IMPOIL1->OIL + + + + + +IMPURN1 + +IMPURN1 + + + +URN + +URN + + + +IMPURN1->URN + + + + + +RHE + +RHE + + + +RH + + +RH + + + + + +RHE->RH + + + + + +TXE + +TXE + + + +TX + + +TX + + + + + +TXE->TX + + + + + +FEQ->E21 + + + + + +SRE + + +SRE +Capacity: 0.10 + + + + + +OIL->SRE + + + + + +URN->E21 + + + + + +E01 + + +E01 +Capacity: 0.50 + + + + + +E01->ELC + + +4.70 + + + +E31 + + +E31 +Capacity: 0.13 + + + + + +E31->ELC + + +1.13 + + + +E51 + + +E51 +Capacity: 0.50 + + + + + +E51->ELC + + +0.59 + + + +E70 + + +E70 +Capacity: 0.30 + + + + + +E70->ELC + + + + + +RHO + + +RHO +Capacity: 41.33 + + + + + +RHO->RH + + +25.20 + + + +RL1 + + +RL1 +Capacity: 8.40 + + + + + +RL + + +RL + + + + + +RL1->RL + + +5.60 + + + +SRE->DSL + + + + + +SRE->GSL + + + + + +TXD + + +TXD +Capacity: 0.60 + + + + + +TXD->TX + + +0.60 + + + +nox + +nox + + + +TXD->nox + + +0.60 + + + +TXG + + +TXG +Capacity: 4.60 + + + + + +TXG->TX + + +4.60 + + + +TXG->nox + + +4.60 + + + +DSL->E70 + + + + + +DSL->RHO + + +36.00 + + + +DSL->TXD + + +2.60 + + + +ELC->RHE + + + + + +ELC->TXE + + + + + +ELC->E51 + + +0.82 + + + +ELC->RL1 + + +5.60 + + + +GSL->TXG + + +19.91 + + + +HCO->E01 + + +14.70 + + + +HYD->E31 + + +3.52 + + + diff --git a/docs/source/images/ldes_chain.pdf b/docs/source/images/ldes_chain.pdf new file mode 100644 index 000000000..83ddc5177 Binary files /dev/null and b/docs/source/images/ldes_chain.pdf differ diff --git a/docs/source/images/ldes_chain.png b/docs/source/images/ldes_chain.png new file mode 100644 index 000000000..a2884a6b6 Binary files /dev/null and b/docs/source/images/ldes_chain.png differ diff --git a/docs/source/images/ldes_chain.svg b/docs/source/images/ldes_chain.svg new file mode 100644 index 000000000..129f8a652 --- /dev/null +++ b/docs/source/images/ldes_chain.svg @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/images/ldes_delta_problem.pdf b/docs/source/images/ldes_delta_problem.pdf new file mode 100644 index 000000000..6d1dad0ca Binary files /dev/null and b/docs/source/images/ldes_delta_problem.pdf differ diff --git a/docs/source/images/ldes_delta_problem.png b/docs/source/images/ldes_delta_problem.png new file mode 100644 index 000000000..704f6e64b Binary files /dev/null and b/docs/source/images/ldes_delta_problem.png differ diff --git a/docs/source/images/ldes_delta_problem.svg b/docs/source/images/ldes_delta_problem.svg new file mode 100644 index 000000000..7bceb363b --- /dev/null +++ b/docs/source/images/ldes_delta_problem.svg @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Adjustedupper bound(x 3 days) + Upper boundviolated + Allowedpositiveseasonaldelta + + + + + + Represents + + + + + + + + + Adjustedupper bound(x 3 days) + Non-negativeviolated + Allowednegativeseasonaldelta + + + + + + Represents + + + + + + + diff --git a/docs/source/images/ldes_delta_representation.pdf b/docs/source/images/ldes_delta_representation.pdf new file mode 100644 index 000000000..dda470803 Binary files /dev/null and b/docs/source/images/ldes_delta_representation.pdf differ diff --git a/docs/source/images/ldes_delta_representation.png b/docs/source/images/ldes_delta_representation.png new file mode 100644 index 000000000..f48cd7806 Binary files /dev/null and b/docs/source/images/ldes_delta_representation.png differ diff --git a/docs/source/images/ldes_delta_representation.svg b/docs/source/images/ldes_delta_representation.svg new file mode 100644 index 000000000..dfec1a541 --- /dev/null +++ b/docs/source/images/ldes_delta_representation.svg @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + No adjustment + Allowedpositiveseasonaldelta + + + + + + + Represents + + + + + Seasonal storage + + diff --git a/docs/source/images/results1990.pdf b/docs/source/images/results1990.pdf new file mode 100644 index 000000000..02d6839b8 Binary files /dev/null and b/docs/source/images/results1990.pdf differ diff --git a/docs/source/images/results1990.svg b/docs/source/images/results1990.svg new file mode 100644 index 000000000..ba1615a8a --- /dev/null +++ b/docs/source/images/results1990.svg @@ -0,0 +1,533 @@ + + + + + + +model + +Results for 1990 + + +E21 + +E21 + + + +ELC + + +ELC + + + + + +E21->ELC + + + + + +IMPDSL1 + + +IMPDSL1 +Capacity: 99999.00 + + + + + +DSL + + +DSL + + + + + +IMPDSL1->DSL + + +38.60 + + + +co2 + +co2 + + + +IMPDSL1->co2 + + +2.89 + + + +IMPFEQ + +IMPFEQ + + + +FEQ + +FEQ + + + +IMPFEQ->FEQ + + + + + +IMPGSL1 + + +IMPGSL1 +Capacity: 99999.00 + + + + + +GSL + + +GSL + + + + + +IMPGSL1->GSL + + +19.91 + + + +IMPGSL1->co2 + + +1.49 + + + +IMPHCO1 + + +IMPHCO1 +Capacity: 99999.00 + + + + + +HCO + + +HCO + + + + + +IMPHCO1->HCO + + +14.70 + + + +IMPHCO1->co2 + + +1.31 + + + +IMPHYD + + +IMPHYD +Capacity: 99999.00 + + + + + +HYD + + +HYD + + + + + +IMPHYD->HYD + + +3.52 + + + +IMPOIL1 + +IMPOIL1 + + + +OIL + +OIL + + + +IMPOIL1->OIL + + + + + +IMPURN1 + +IMPURN1 + + + +URN + +URN + + + +IMPURN1->URN + + + + + +RHE + +RHE + + + +RH + + +RH + + + + + +RHE->RH + + + + + +TXE + +TXE + + + +TX + + +TX + + + + + +TXE->TX + + + + + +FEQ->E21 + + + + + +SRE + + +SRE +Capacity: 0.10 + + + + + +OIL->SRE + + + + + +URN->E21 + + + + + +E01 + + +E01 +Capacity: 0.50 + + + + + +E01->ELC + + +4.70 + + + +E31 + + +E31 +Capacity: 0.13 + + + + + +E31->ELC + + +1.13 + + + +E51 + + +E51 +Capacity: 0.50 + + + + + +E51->ELC + + +0.59 + + + +E70 + + +E70 +Capacity: 0.30 + + + + + +E70->ELC + + + + + +RHO + + +RHO +Capacity: 41.33 + + + + + +RHO->RH + + +25.20 + + + +RL1 + + +RL1 +Capacity: 8.40 + + + + + +RL + + +RL + + + + + +RL1->RL + + +5.60 + + + +SRE->DSL + + + + + +SRE->GSL + + + + + +TXD + + +TXD +Capacity: 0.60 + + + + + +TXD->TX + + +0.60 + + + +nox + +nox + + + +TXD->nox + + +0.60 + + + +TXG + + +TXG +Capacity: 4.60 + + + + + +TXG->TX + + +4.60 + + + +TXG->nox + + +4.60 + + + +DSL->E70 + + + + + +DSL->RHO + + +36.00 + + + +DSL->TXD + + +2.60 + + + +ELC->RHE + + + + + +ELC->TXE + + + + + +ELC->E51 + + +0.82 + + + +ELC->RL1 + + +5.60 + + + +GSL->TXG + + +19.91 + + + +HCO->E01 + + +14.70 + + + +HYD->E31 + + +3.52 + + + diff --git a/docs/source/images/survival_curve_discounting.pdf b/docs/source/images/survival_curve_discounting.pdf new file mode 100644 index 000000000..e2943efff Binary files /dev/null and b/docs/source/images/survival_curve_discounting.pdf differ diff --git a/docs/source/images/survival_curve_discounting.png b/docs/source/images/survival_curve_discounting.png new file mode 100644 index 000000000..b27171970 Binary files /dev/null and b/docs/source/images/survival_curve_discounting.png differ diff --git a/docs/source/images/survival_curve_discounting.svg b/docs/source/images/survival_curve_discounting.svg new file mode 100644 index 000000000..444209930 --- /dev/null +++ b/docs/source/images/survival_curve_discounting.svg @@ -0,0 +1,1139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Factor = + Survival curve + PE + P0 + Area + Area + Defined in each period + Intermediate years linearly interpolated + Each year discounted to P0 + + + diff --git a/docs/source/index.rst b/docs/source/index.rst index 777d8885c..f9043f39d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,6 +1,35 @@ Temoa Project Documentation -======================================================= +=========================== .. toctree:: + :maxdepth: 2 + :caption: Getting Started - Documentation + preface + quick_start + database + commodity_network + visualization + +.. toctree:: + :maxdepth: 2 + :caption: Core Concepts + + mathematical_formulation + computational_implementation + +.. toctree:: + :maxdepth: 2 + :caption: Advanced Topics + + mga + monte_carlo + myopic + stochastics + unit_checking + +.. toctree:: + :maxdepth: 1 + :caption: Appendix + + references diff --git a/docs/source/mathematical_formulation.rst b/docs/source/mathematical_formulation.rst new file mode 100644 index 000000000..cc4d3da10 --- /dev/null +++ b/docs/source/mathematical_formulation.rst @@ -0,0 +1,1465 @@ + +======================== +Mathematical Formulation +======================== + +To understand this section, the reader will need at least a cursory +understanding of mathematical optimization. We omit here that introduction, +and instead refer the reader to `various`_ `available`_ `online`_ `sources`_. +Temoa is formulated as an algebraic model that requires information organized +into sets, parameters, variables, and equation +definitions. + +The heart of Temoa is a technology explicit energy system optimization model. +It is an algebraic network of linked processes -- where each process is defined +by a set of engineering characteristics (e.g. capital cost, efficiency, capacity +factor, emission rates) -- that transform raw energy sources into end-use +demands. The model objective function minimizes the present-value cost of +energy supply by optimizing installed capacity and its utilization over time. + +.. _simple_system: + +.. figure:: images/simple_system2.* + :align: center + :width: 100% + :alt: A simple energy system, with energy sources on the left and energy + sinks (end-use demands) on the right. + :figclass: align-center + :figwidth: 70% + + A common visualization of energy system models is a directed network graph, + with energy sources on the left and end-use demands on the right. The + modeler must specify the end-use demands to be met, the technologies defined + within the system (rectangles), and the inputs and outputs of each (red and green + arrows). The circles represent distinct energy carriers that connect + technologies within the energy system network. + +The most fundamental tenet of the model is the understanding of energy flow, +treating all processes as black boxes that take inputs and produce outputs. +Specifically, Temoa does not care about the inner workings of a process, only +its global input and output characteristics. In this vein, the above graphic +can be broken down into process-specific elements. For example, the coal power +plant takes as input coal and produces electricity, and is subject to various +costs (e.g. variable costs) and constraints (e.g. efficiency) along the way. + +.. figure:: images/coal_process.png + :align: center + :figclass: center + :figwidth: 60% + + +The modeler defines the processes and engineering characteristics through a +combination of sets and parameters, described in the next few sections. Temoa then +utilizes these parameters, along with the associated technology-specific decision +variables for capacity and activity, to create the objective function and +constraints that are used during the optimization process. + + +Temoa Notation +-------------- + +In the mathematical notation, we use CAPITALIZATION to denote a container, +like a set, indexed variable, or indexed parameter. Sets use only a single +letter, so we use the lower case to represent an item from the set. For +example, :math:`T` represents the set of all technologies and :math:`t` +represents a single item from :math:`T`. + + * Variables are named V\_VarName within the code to aid readability. However, + in the documentation where there is benefit of italics and other font + manipulations, we elide the 'V\_' prefix. + + * In all equations, we **bold** variables to distinguish them from parameters. + Take, for example, this excerpt from the Temoa default objective function: + + .. math:: + C_{variable} = \sum_{r, p, s, d, i, t, v, o \in \Theta_{VC}} \left ( + {VC}_{r, p, t, v} + \cdot R_p + \cdot \textbf{FO}_{r, p, s, d, i, t, v, o} + \right ) + + Note that :math:`C_{variable}` is not bold, as it is a temporary variable + used for clarity while constructing the objective function. It is not a + structural variable and the solver never sees it. + + * Where appropriate, we put the variable on the right side of the coefficient. + In other words, this is not a preferred form of the previous equation: + + .. math:: + + C_{variable} = \sum_{r, p, s, d, i, t, v, o \in \Theta_{VC}} \left ( + \textbf{FO}_{r, p, s, d, i, t, v, o} + \cdot {VC}_{r, p, t, v} + \cdot R_p + \right ) + + * We generally put the limiting or defining aspect of an equation on the right + hand side of the relational operator, and the aspect being limited or defined + on the left hand side. For example, equation :eq:`Capacity` defines Temoa's + mathematical understanding of a process capacity (:math:`\textbf{CAP}`) in + terms of that process' activity (:math:`\textbf{ACT}`): + + .. math:: + + \left ( + \text{CFP}_{r, s, d, t, v} + \cdot \text{C2A}_{r, t} + \cdot \text{SEG}_{s, d} + \right ) + \cdot \textbf{CAP}_{r, t, v} + = + \sum_{I, O} \textbf{FO}_{r, p, s, d,i, t, v, o} + + + \sum_{I, O} \textbf{CUR}_{r, p, s, d, i, t, v, o} + + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{FO}} + + * We use the word 'slice' to refer to the tuple of season and time of day + :math:`\{s,d\}`. Note that these time slices are user-defined, and can + represent time ranging large blocks of time (e.g., winter-night) to every + hour in a given season. + + * We use the word 'process' to refer to the tuple of technology and vintage + (:math:`\{t,v\}`). For example, solar PV (technology) installed in 2030 + (vintage). + + +Treatment of Time +----------------- +Temoa's conceptual model of *time* is broken up into three levels, and energy supply +and demand is balanced at each of these levels: + + * **Periods** - consecutive blocks of years, marked by the first year in the + period. For example, a two-period model might consist of :math:`\text{P}^f = + \{2010, 2015, 2025\}`, representing the two periods of years from 2010 + to 2015, and from 2015 to 2025. It is up to the model builder whether this represents start + of year to start of year or end of year to end of year. Note that the last period element + (2025) does not represent a new time period, but rather defines the end of the second time + period and therefore the planning horizon. + + * **Seasonal** - Each year is divided into one or more seasons. What a "season" + represents depends on the :code:`time_sequencing` mode chosen in the configuration + file (see below). Each season carries a :code:`segment_fraction` value in the + :code:`time_season` table specifying the fraction of the year it represents. + + * **Daily** - Within each season, the day is subdivided into one or more + time-of-day segments. Each segment has an associated :code:`hours` value in the + :code:`time_of_day` table indicating how many hours it represents (e.g. 8 hours for + "Day", 16 hours for "Night"). Less detailed databases may use a single segment + covering the full day. + +We use the word 'slice' or 'timeslice' to refer to the tuple of season and time of day +:math:`\{s,d\}`. The fraction of a year represented by each slice is computed +automatically: + +.. math:: + + SEG_{s,d} = \text{segment\_fraction\_per\_season}(s) \;\times\; \frac{\text{hours}(d)} + {\sum_{d'} \text{hours}(d')} + +The sum of :math:`SEG_{s,d}` over all :math:`(s,d)` pairs must equal 1. + +Time Sequencing Modes +~~~~~~~~~~~~~~~~~~~~~ + +Temoa v4 supports four modes for interpreting the meaning and ordering of seasons, +controlled by the :code:`time_sequencing` setting in the configuration TOML file: + + * **consecutive_days** — Each season represents a single day, and the seasons are + treated as consecutive days in order. Use this when modeling e.g., a representative + week (7 seasons) or a full year (365 seasons). Inter-season state (e.g. for + storage) carries forward naturally from one season to the next, so the + :code:`time_season_sequential` table can be left empty (more on that below). + + * **seasonal_timeslices** *(default)* — Each season represents a sequential slice of + the year containing one or many days (e.g. Winter, Spring, Summer, Fall). The + true chronological sequence is assumed to follow the :code:`time_season` ordering, + so the :code:`time_season_sequential` table can be left empty. This is the + traditional Temoa time representation. + + * **representative_periods** — Each season represents a block of days that may not + be contiguous in time (e.g. a "typical summer weekday" drawn from several months). + If the model uses inter-season constraints such as seasonal storage or + inter-season ramping, the true chronological sequence must be defined in the + :code:`time_season_sequential` table. Technologies using seasonal storage must + also be flagged in the :code:`technology` table. + + * **manual** — The sequence of time slices is defined explicitly by the modeler in + an optional :code:`time_manual` table. This is an advanced feature provided in + case none of the above modes are suitable, and is not recommended for most users. + +The selected mode, in combination with the sequence of the :code:`time_season` and +:code:`time_of_day` tables, is +used to construct the :code:`time_next` dictionary, which maps each :math:`(s,d)` time +slice to its successor in the chronological sequence, :math:`(s_{next}, d_{next})`. + +The :code:`days_per_period` configuration setting (default: 365) specifies how many +days each planning period's representative year encompasses. This is used to scale +flow variables correctly. For example, reduce it to 7 when modeling a single +representative week. + +Time Season Sequential +~~~~~~~~~~~~~~~~~~~~~~ + +When using the **representative_periods** time sequencing mode, the seasons in +:code:`time_season` may represent non-contiguous blocks of time (e.g. a "typical +summer weekday" drawn from several calendar months). For constraints that depend +on inter-season ordering — seasonal storage and inter-season ramping — the model +needs to know the true chronological sequence showing how the representative seasons +are stitched together to form a complete year. + +The :code:`time_season_sequential` table provides this mapping. Each row defines +a *sequential season* (:code:`seas_seq`) that references one of the +:code:`time_season` entries and carries its own :code:`segment_fraction` and an +integer :code:`sequence` column that determines the chronological order. Because +the same representative season may appear more than once in the reconstructed year +(e.g. "typical summer weekday" could appear for June, July, and August), the sequential table can +contain multiple entries that map back to the same :code:`time_season` row, each +with its own fraction. + +From this table Temoa builds two internal dictionaries: + +* :code:`time_next_sequential` — maps each sequential season to its successor, + forming a circular chain that represents the annual cycle. +* :code:`sequential_to_season` — maps each sequential season back to its parent + representative season in :code:`time_season`. + +These dictionaries are consumed by: + +* The **seasonal storage energy constraint**, which chains the storage state of + charge across sequential seasons in chronological order. +* The **ramp up/down season constraints**, which limit the rate of activity change + at season boundaries that are adjacent in the sequential ordering but not + necessarily adjacent in the :code:`time_season` set. + +For the **consecutive_days** and **seasonal_timeslices** modes, the +:code:`time_season_sequential` table may be left empty — the model derives the +chronological order directly from the :code:`time_season` set. + + +Periods +~~~~~~~ +There are two specifiable period sets: :code:`time_exist` (:math:`\text{P}^e`) +and :code:`time_future` (:math:`\text{P}^f`). The :code:`time_exist` set +contains periods before :code:`time_future`. Its primary purpose is to specify +the vintages for capacity that exists prior to the model optimization. +The :code:`time_future` set contains the future periods that the model will +optimize. As this set must contain only integers, Temoa interprets the elements +to be the boundaries of each period of interest. Thus, this is an ordered set +and Temoa uses its elements to automatically calculate the length of each +optimization period; modelers may exploit this to create variable period lengths +within a given input database. Temoa "names" each optimization period by the first +year, and makes them easily accessible via the :code:`time_optimize` set. This final +"period" set is not user-specifiable, but is an exact duplicate of +:code:`time_future`, less the largest element. In the above example, since +:math:`\text{P}^f = \{2010, 2015, 2025\}`, :code:`time_optimize` does not +contain 2025: :math:`\text{P}^o =\{2010, 2015\}`. + +Temoa assumes that all elements of the :code:`time_exist` and +:code:`time_future` sets are integers. Further, these sets are assumed to be +ordered, such that the minimum element is "naught". For example, if +:math:`\text{P}^f = \{2015, 2020, 2030\}`, then :math:`P_0 = 2015`. In +other words, the capital :math:`\text{P}` with the naught subscript indicates +the first element in the :code:`time_future` set. + +One final note on periods: rather than optimizing each year within a period +individually, Temoa makes the simplifying assumption that each time period contains +:math:`n` copies of a single, representative year. Temoa optimizes capacity +and activity for just this characteristic year within each time period, assuming +the results for different years in the same time period are identical. The Temoa +objective function, however, accounts for the total cost across all years in all +model time periods. Figure 3.3 gives a graphical explanation of the annual +delineation. + +.. _FigureObjectiveComparison: + +.. figure:: images/ObjectiveUsageVsCostComparison.png + :align: center + :width: 100% + :alt: Energy use same each year; time-value of annual costs reduced each year + :figclass: align-center + :figwidth: 60% + + The left graph is of energy, while the right graph is of the annual costs. + The energy used in a period by a process is the same for all + years (with exception for those processes that cease their useful life + mid-period). However, even though the costs incurred will be the same, the + time-value of money changes due to the discount-rate. As the fixed costs of + a process are tied to the length of its useful life, those processes that do + not fall on a period boundary require unique time-value multipliers in the + objective function. + +As noted above, Temoa allows the modeler to subdivide each year into a set of time +slices, comprised of a season and a time of day. Unlike :code:`time_future`, there +is no restriction on what labels the modeler may assign to the :code:`time_season` +and :code:`time_of_day` set elements. Each season carries a +:code:`segment_fraction` (fraction of the year), and each time-of-day segment +carries an :code:`hours` value. These are combined to compute the +:code:`segment_fraction` for each :math:`(s,d)` slice as described above. + +Sets +---- + +.. include:: set_desc_and_tables.rst + +Parameters +---------- +A summary table of input parameters is provided below, followed by a more +detailed description of each. + +.. include:: param_desc_and_tables.rst + +efficiency +~~~~~~~~~~ + +:math:`{EFF}_{r \in R, i \in C_p, t \in T, v \in V, o \in C_c}` + +We present the efficiency (:math:`EFF`) parameter first as it is one of the most +critical model parameters. Beyond defining the conversion efficiency of each +process, Temoa also utilizes the indices to understand the valid input +:math:`\rightarrow` process :math:`\rightarrow` output paths for energy. For +instance, if a modeler does not specify an efficiency for a 2020 vintage coal +power plant, then Temoa will recognize any mention of a 2020 vintage coal power +plant elsewhere as an error. Generally, if a process is not specified in the +efficiency table,\ [efficiency_table]_ Temoa assumes it is not a valid process +and will provide the user a warning with pointed debugging information. + + +efficiency_variable +~~~~~~~~~~~~~~~~~~~ + +:math:`{EFF}_{r \in R, s \in S, d \in D, i \in C_p, t \in T, v \in V, o \in C_c}` + +When a technology's conversion efficiency varies by time of day or season — for +example, a heat pump whose efficiency differs with temperature — the +modeler can use :code:`efficiency_variable` to specify these time-slice-dependent +efficiency values. If not specified for a given process, it defaults to 1, +meaning the base :code:`efficiency` value applies uniformly. Note that there is +no period index: the time-varying efficiency applies to all periods. + + +.. _capacity_factor_tech: + +capacity_credit +~~~~~~~~~~~~~~~ + +:math:`{CC}_{r \in R, p \in P, t \in T, v \in V}` + +The capacity credit represents the fraction of total installed capacity of +a process that can be relied upon during the time slice in which peak +electricity demand occurs. This parameter is used in the 'static' version of +the :math:`reserve_margin` constraint. + +capacity_factor_tech +~~~~~~~~~~~~~~~~~~~~ + +:math:`{CFT}_{r \in R, s \in S, d \in D, t \in T}` + +Temoa indexes the :code:`capacity_factor_tech` parameter by season, time-of-day, +and technology. + +capacity_factor_process +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{CFP}_{r \in R, s \in S, d \in D, t \in T, v \in V}` + +In addition to :ref:`capacity_factor_tech`, there may be cases where different +vintages of the same technology have different capacity factors. For example, +newer vintages of wind turbines may have higher capacity factors. So, +:code:`capacity_factor_process` allows users to specify the capacity factor by +season, time-of-day, technology, and vintage. + + +capacity_to_activity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{C2A}_{r \in R, t \in T}` + +Capacity and Activity are inherently two different units of measure. Capacity +represents the maximum flow of energy per time (:math:`\frac{energy}{time}`), +while Activity is a measure of total energy actually produced. However, there are +times when one needs to compare the two, and this parameter makes those +comparisons more natural. For example, a capacity of 1 GW for one year works +out to an activity of + +.. math:: + + {1 GW} \cdot {8,760 \tfrac{hr}{yr}} \cdot {3,600 \tfrac{sec}{hr}} \cdot + {10^{-6} \tfrac{P}{G}} = {31.536 \tfrac{PJ}{yr}} + +.. centered:: + or + +.. math:: + + {1 GW} \cdot {8,760 \tfrac{hr}{yr}} \cdot {10^{-3} \tfrac{T}{G}} = {8.75 TWh} + +When comparing one capacity to another, the comparison is easy, unit wise. +However, when one *needs* to compare capacity and activity, how does one +reconcile the units? One way to think about the utility of this parameter is in +the context of the question: "How much activity would this capacity create, if +used 100% of the time?" + + +cost_fixed +~~~~~~~~~~ + +:math:`{CF}_{r \in R, p \in P, t \in T, v \in V}` + +The :code:`cost_fixed` parameter specifies the fixed cost associated with any +process. Fixed costs are those that must be paid, regardless of how much the +process is utilized. For instance, if the model decides to build a nuclear +power plant, even if it decides not utilize the plant, the model must pay the +fixed costs. These costs are in addition to the capital cost, so once the +capital is paid off, these costs are still incurred every year the process +exists. + +Temoa's default objective function assumes the modeler has specified this +parameter in units of currency per unit capacity (:math:`\tfrac{Dollars}{Unit +Cap}`). + + +cost_invest +~~~~~~~~~~~ + +:math:`{CI}_{r \in R, t \in T, v \in P}` + +The :code:`cost_invest` parameter specifies the process-specific investment cost. +Unlike the :code:`cost_fixed` and :code:`cost_variable` parameters, +:code:`cost_invest` only applies to vintages of technologies within the model +optimization horizon (:math:`\text{P}^o`). Like :code:`cost_fixed`, +:code:`cost_invest` is specified in units of cost per unit of capacity and is +only used in the default objective function (:math:`\tfrac{Dollars}{Unit Cap}`). + + +cost_variable +~~~~~~~~~~~~~ + +:math:`{CV}_{r \in R, p \in P,t \in T,v \in V}` + +The :code:`cost_variable` parameter represents the cost of a process-specific unit +of activity. Thus the incurred variable costs are proportional to the activity +of the process. + + +cost_emission +~~~~~~~~~~~~~ + +:math:`{CE}_{r \in R, p \in P, e \in C^e}` + +The :code:`cost_emission` parameter specifies a cost per unit of emission +for a given emissions commodity in each period. This allows the modeler to +penalize emission production, for example via a carbon tax. The cost is +applied to total emissions of commodity :math:`e` in period :math:`p`. + + +construction_input +~~~~~~~~~~~~~~~~~~ + +:math:`{CON}_{r \in R, i \in C^p,t \in T \setminus T^u,v \in V}` + +The :code:`construction_input` parameter allows the modeller to attach commodity +input flows to the production of new capacity, in units of activity per unit +capacity. Assumes that capacity is produced evenly over years in its vintage +period. + + +.. _demand: + +demand +~~~~~~ + +:math:`{DEM}_{r \in R, p \in P, c \in C^d}` + +The :code:`demand` parameter allows the modeler to define the total end-use +demand levels for all periods. In combination with the :code:`efficiency` +parameter, this parameter is the most important because without it, the rest of +model has no incentive to build anything. This parameter specifies the end-use +demands that appear at the far right edge of the system diagram. + +To specify a non-uniform distribution of demand across time slices, use the +:code:`demand_specific_distribution` (DSD) parameter, described next. + + +demand_specific_distribution +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{DSD}_{r \in R, s \in S, d \in D, c \in C^d}` + +If there is an end-use demand that varies over the course of a day or across +seasons -- for example, heating or cooling in the summer or winter -- the +modeler may specify the fraction of annual demand occurring in each time slice. +The sum of DSD for each demand commodity :math:`c` must be 1. +If the modeler does not define DSD for a demand commodity, Temoa automatically +populates it using the :code:`segment_fraction` values, resulting in a uniform +(flat) distribution proportional to the length of each time slice. + + +emission_activity +~~~~~~~~~~~~~~~~~ + +:math:`{EAC}_{e \in C_e,\{r,i,t,v,o\} \in \Theta_{\text{efficiency}}}` + +Temoa currently has two methods for enabling a process to produce an output: the +:code:`efficiency` parameter, and the :code:`emission_activity` parameter. Where +the :code:`efficiency` parameter defines the amount of output energy a process +produces per unit of input, the :code:`emission_activity` parameter allows for +secondary outputs. As the name suggests, this parameter was originally intended +to account for emissions per unit activity, but it more accurately describes +*parallel* activity. It is restricted to emissions accounting (by the +:math:`e \in C^e` set restriction). + + +emission_embodied +~~~~~~~~~~~~~~~~~ + +:math:`{EE}_{r \in R,t \in T \setminus T^u, v \in V,e \in C_e}` + +Like the emission_activity parameter, but attaches emission outputs to the creation +of capacity instead of activity flows. Assumes that capacity is produced evenly +over each year in the deployment vintage. + + +emission_end_of_life +~~~~~~~~~~~~~~~~~~~~ + +:math:`{EEOL}_{r \in R,t \in T \setminus T^u, v \in V,e \in C_e}` + +Like emission_embodied, but attaches emissions to the retirement/end of life of +capacity rather than production of capacity. Assumes that retirement or end of +life occur evenly over years in that period. + + +end_of_life_output +~~~~~~~~~~~~~~~~~~ + +:math:`{EOLO}_{r \in R,t \in T \setminus T^u, v \in V,o \in C_p}` + +Like construction_input, but attaches flows to the retirement/end of life of +capacity rather than production of capacity. Assumes that retirement or end of +life occur evenly over years in that period. + + +existing_capacity +~~~~~~~~~~~~~~~~~ + +:math:`{ECAP}_{r \in R, t \in T, v \in \text{P}^e}` + +The :code:`existing_capacity` parameter defines the capacity installed prior to the +beginning of :code:`time_optimize`. Note that processes with existing capacity that +would survive into future periods require all of the engineering-economic +characteristics of a standard process, with the exception of an investment cost. + +.. _GDR: + +global_discount_rate +~~~~~~~~~~~~~~~~~~~~ + +:math:`{GDR}` + +The :code:`global_discount_rate` parameter represents the global discount rate used to convert +cash flows in future model time periods into a present value. The net present value +(NPV) of a cashflow is related to its future value (FV) via the +formula: + +.. math:: + + \text{NPV} = \frac{\text{FV}}{(1 + GDR)^n} + +where :math:`n` is in years. This parameter is used to calculate all discounted +costs, which are the basis of the objective function. Costs are discounted to the +first future time period in the model. + +The output in the :code:`output_cost` table shows both discounted and non-discounted (raw) +values for all model costs. + +The :code:`global_discount_rate` is entered in the metadata_real table in the database. + +loan_lifetime_process +~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LLP}_{r \in R, t \in T, v \in P}` + +Temoa gives the modeler the ability to separate the loan lifetime from the +useful life of a process. This parameter specifies the loan term associated +with capital investment in a process, in years. If not specified, the model +assigns the technology lifetime to the loan period. + + +lifetime_process +~~~~~~~~~~~~~~~~ + +:math:`{LTP}_{r \in R, t \in T, v \in P}` + +This parameter specifies the total useful life of a given process in years. + + +lifetime_survival_curve +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LSC}_{r \in R, p \in P, t \in T, v \in V}` + +The :code:`lifetime_survival_curve` parameter represents the surviving fraction +of original capacity for a given process at a given period. It allows the modeler +to specify gradual capacity loss over time rather than abrupt retirement +at end of life. Values should be between 0 and 1, where 1 means full survival. + + +lifetime_tech +~~~~~~~~~~~~~ + +:math:`{LTT}_{r \in R, t \in T}` + +Similar to :code:`lifetime_process`, this parameter specifies the total useful life +of a given technology in years, with a default of 40 years. If all vintages of a +given technology have the same lifetime, then :code:`lifetime_tech` can be used +instead of :code:`lifetime_process`. + + +linked_techs +~~~~~~~~~~~~ + +:math:`{LIT}_{r \in R, t \in T, e \in C^e, t' \in T}` + +In power-to-gas pathways, :math:`CO2` is an input to some processes, including +synthetic natural gas production and liquid fuel production via Fischer-Tropsch. +Within the model, :math:`CO2` must be converted from an emissions commodity to a +physical commodity that can be included in the :code:`efficiency` table. The +:code:`linked_techs` parameter specifies the dummy technology used to convert an +emissions commodity to a physical commodity. Note that the first :code:`t` +represents the primary upstream technology linked to the dummy linked technology, +which is represented by the second :code:`t'` index. + + +loan_rate +~~~~~~~~~ + +:math:`{LR}_{r \in r, t \in T, v \in V}` + +The interest rate used for loans supporting investment costs. The default +loan rate is accessible in the metadata_real table in the database. + + +default_loan_rate +~~~~~~~~~~~~~~~~~ + +:math:`{DLR}` + +A scalar parameter specifying the default interest rate applied to loans +when no process-specific :code:`loan_rate` is defined. This value is read +from the :code:`metadata_real` table in the database. + + +limit_activity +~~~~~~~~~~~~~~ + +:math:`{LA}_{r \in R, p \in P, t \in T}` + +The :code:`limit_activity` parameter places an upper or lower bound on the total +activity (energy production) from a technology or technology group in each model +time period. The bound direction is controlled by the :code:`operator` column +(:code:`le`, :code:`ge`, or :code:`eq`). The :code:`tech_or_group` column +accepts either a single technology name or a group name defined in +:code:`tech_group_names`. + + +limit_activity_share +~~~~~~~~~~~~~~~~~~~~ + +:math:`{LAS}_{r \in R, p \in P, g_1, g_2}` + +The :code:`limit_activity_share` parameter constrains the activity of one +technology or group as a share of another technology or group's activity. +For example, it can enforce a minimum renewable generation share. The +operator column controls whether the share is an upper bound, lower bound, +or equality. + + +limit_annual_capacity_factor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LACF}_{r \in R, t \in T, v \in V, o \in C_c}` + +The :code:`limit_annual_capacity_factor` parameter limits the annual capacity +factor of a process — that is, the ratio of actual annual output to maximum +possible output. The :code:`tech_or_group` column accepts a technology name or +group name. Note this is indexed by vintage, not period, and will be applied to +all valid time periods for that process. + + +limit_capacity +~~~~~~~~~~~~~~ + +:math:`{LC}_{r \in R, p \in P, t \in T}` + +The :code:`limit_capacity` parameter places an upper or lower bound on the +total available (retirement-adjusted) capacity of a technology or technology +group in each model timeperiod. The :code:`operator` column controls the bound +direction. The :code:`tech_or_group` column accepts either a single technology +name or a group name. + + +limit_capacity_share +~~~~~~~~~~~~~~~~~~~~ + +:math:`{LCS}_{r \in R, p \in P, g_1, g_2}` + +The :code:`limit_capacity_share` parameter constrains the capacity of one +technology group as a share of another group's capacity. The operator column +controls the bound direction. The group columns accept either +a single technology name or a technology group name. + + +limit_degrowth_capacity +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LDGC}_{r \in R, t \in T}` + +The :code:`limit_degrowth_capacity` parameter defines the maximum annual rate +at which the total capacity of a technology (or group) can shrink between +periods. The :code:`tech_or_group` column accepts a technology name or group name. + + +limit_degrowth_new_capacity +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LDGNC}_{r \in R, t \in T}` + +The :code:`limit_degrowth_new_capacity` parameter constrains the rate of decrease +in new capacity deployment between consecutive periods. + + +limit_degrowth_new_capacity_delta +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`\mathrm{LDGNC}_{\Delta,r \in R, t \in T}` + +The :code:`limit_degrowth_new_capacity_delta` parameter constrains the +deceleration of new capacity degrowth between periods, essentially adding +"intertia" to the degrowth of new capacity deployment. + + +limit_emission +~~~~~~~~~~~~~~ + +:math:`{LE}_{r \in R, p \in P, e \in C^e}` + +The :code:`limit_emission` parameter ensures that Temoa finds a solution that +fits within the modeler-specified limit on emission :math:`e` in time period +:math:`p`. The operator column controls whether this is an upper bound, lower +bound, or equality. + + +limit_growth_capacity +~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LGC}_{r \in R, t \in T}` + +The :code:`limit_growth_capacity` parameter defines the maximum annual rate at +which the total capacity of a technology (or group) can grow between periods. +The :code:`tech_or_group` column accepts a technology name or group name. + + +limit_growth_new_capacity +~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LGNC}_{r \in R, t \in T}` + +The :code:`limit_growth_new_capacity` parameter constrains the rate of increase +in new capacity deployment between consecutive periods. + + +limit_growth_new_capacity_delta +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`\mathrm{LGNC}_{\Delta,r \in R, t \in T}` + +The :code:`limit_growth_new_capacity_delta` parameter constrains the acceleration +of new capacity growth between periods. This essentially adds "inertia" to the +growth of new capacity deployment. + + +limit_new_capacity +~~~~~~~~~~~~~~~~~~ + +:math:`{LNC}_{r \in R, t \in T, v \in V}` + +The :code:`limit_new_capacity` parameter constrains the amount of new capacity +that can be deployed for a given technology or group in a specific vintage. +The :code:`tech_or_group` column accepts a technology name or group name. + + +limit_new_capacity_share +~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LNCS}_{r \in R, g_1, g_2, v \in V}` + +The :code:`limit_new_capacity_share` parameter constrains the new capacity of one +technology or group as a share of another technology or group's new capacity. + + +limit_resource +~~~~~~~~~~~~~~ + +:math:`{LS}_{r \in R, t \in T}` + +The :code:`limit_resource` parameter represents a bound on the cumulative +amount of commodity that can be produced by a technology or group over the entire +model time horizon. The :code:`tech_or_group` column accepts a technology name or +group name. Note that this is *not* supported in myopic mode as the cumulative +limit would need to decline as the horizon moves forward, which has not been added +yet. + + +limit_seasonal_capacity_factor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LSCF}_{r \in R, s \in S, t \in T}` + +The :code:`limit_seasonal_capacity_factor` parameter limits the capacity +factor of a technology in a specific season. There is no period index: the +limit applies across all periods. The :code:`tech_or_group` column accepts +a technology name or group name. + + +limit_storage_fraction +~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{LSF}_{r \in R, s \in S, d \in D, t \in T^{S}}` + +The :code:`limit_storage_fraction` parameter constrains the storage level of a +storage technology in any time slice to be at or above a specified fraction of +its maximum charge. Values should be between 0 and 1. Note that this constraint +will be applied to all valid :code:`period` and :code:`vintage` combinations for +the technology. + + +limit_tech_input_split +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{TIS}_{r \in R, p \in P, i \in C_p, t \in T}` + +Some technologies have a single output but have multiple input fuels. The +:code:`limit_tech_input_split` parameter constrains the shares of commodity +input to a specific technology in a given period in every time slice. The +:code:`operator` column controls whether the share is an upper bound, lower bound, +or equality. + + +limit_tech_input_split_annual +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{TISA}_{r \in R, p \in P, i \in C_p, t \in T}` + +Similar to :code:`limit_tech_input_split`, but constrains the average input +commodity shares at the annual level, allowing the shares at the time slice +level to vary. + + +limit_tech_output_split +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{TOS}_{r \in R, p \in P, t \in T, o \in C_c}` + +Some technologies have a single input fuel but have multiple outputs. The +:code:`limit_tech_output_split` parameter constrains the shares of commodity +output from a specific technology in a given period by time slice. + + +limit_tech_output_split_annual +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{TOSA}_{r \in R, p \in P, t \in T, o \in C_c}` + +Similar to :code:`limit_tech_output_split`, but constrains the average output +commodity shares at the annual level, allowing the shares at the time slice +level to vary. + + +planning_reserve_margin +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{PRM}_{r \in R}` + +The :code:`planning_reserve_margin` parameter specifies the capacity reserve margin +in the electric sector by region. The capacity reserve margin represents the +installed generating capacity — expressed as a share of peak load — that must be +available in reserve to meet contingencies. Temoa estimates peak demand from electricity +production by time slice. + + +ramp_down_hourly +~~~~~~~~~~~~~~~~ + +:math:`{RDH}_{r \in R, t \in T}` + +The :code:`ramp_down_hourly` parameter specifies the fraction of installed capacity +by which a technology can ramp output down per hour. This is used in the +:code:`ramp_down_day_constraint` (between time-of-day slices) and +:code:`ramp_down_season_constraint` (between the last time-of-day slice in one +season and the first in the next). + + +ramp_up_hourly +~~~~~~~~~~~~~~ + +:math:`{RUH}_{r \in R, t \in T}` + +The :code:`ramp_up_hourly` parameter specifies the fraction of installed capacity +by which a technology can ramp output up per hour. This is used in the +:code:`ramp_up_day_constraint` (between time-of-day slices) and +:code:`ramp_up_season_constraint` (between seasons). + + +reserve_capacity_derate +~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{RCD}_{r \in R, s \in S, t \in T^{res}, v \in V}` + +The :code:`reserve_capacity_derate` parameter allows the modeler to derate +the capacity of a reserve technology in specific seasons — for example, to +account for seasonal availability. Values default to 1 (no derate). This +parameter is used in the 'dynamic' version of the :code:`reserve_margin` +constraint. + + +.. _segment_fraction: + +segment_fraction +~~~~~~~~~~~~~~~~ + +:math:`{SEG}_{s \in S,d \in D}` + +The :code:`segment_fraction` parameter specifies the fraction of the year represented by +each combination of season and time of day. In v4, this parameter is **computed +automatically** from two user-specified inputs: + + * :code:`segment_fraction` column in the :code:`time_season` table — the fraction + of the year each season represents (e.g. 0.25 for each quarter). + * :code:`hours` column in the :code:`time_of_day` table — the number of hours each + time-of-day segment represents (e.g. 8 for "Day", 16 for "Night"). + +The computation is: + +.. math:: + + SEG_{s,d} = \text{segment\_fraction\_per\_season}(s) \;\times\; \frac{\text{hours}(d)} + {\sum_{d'} \text{hours}(d')} + +The sum of all :math:`SEG_{s,d}` values must equal 1, representing 100% of a year. + + +segment_fraction_per_season +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{SFS}_{s \in S}` + +The per-season share of the year, loaded directly from the :code:`segment_fraction` +column of the :code:`time_season` database table. These values are the first factor +in the :ref:`segment_fraction` computation and must sum to 1. + + +segment_fraction_per_sequential_season +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`{SFS}_{s^{seq} \in S^{seq}}` + +The per-sequential-season share of the year, loaded from the +:code:`segment_fraction` column of the :code:`time_season_sequential` database table. +Used to compute the :code:`days_adjust` ratio that rescales storage levels and flows +between non-sequential and sequential season representations. + + +storage_duration +~~~~~~~~~~~~~~~~ + +:math:`{SD}_{r \in R, t \in T^{S}}` + +The :code:`storage_duration` parameter represents the number of hours over which +storage can discharge if it starts at full charge and produces maximum output +until empty. The parameter value defaults to 4 hours if not specified by the user. + + +\*loan_annualize +~~~~~~~~~~~~~~~~ + +:math:`{LA}_{r \in R, t \in T, v \in P}` + +This is a model-calculated parameter based on the process-specific loan length +(its indices are the same as the :code:`loan_lifetime_process` parameter), and +process-specific interest rate (the :code:`loan_rate` parameter). It is +calculated via the formula: + +.. math:: + + LA_{t,v} = \frac{DR_{r,t,v}}{1 - (1 + DR_{r,t,v})^{{}^- LLN_{r,t,v}}} + + \forall \{t, v\} \in \Theta_{\text{cost\_invest}} + + +\*period_length +~~~~~~~~~~~~~~~ + +:math:`{LEN}_{p \in P}` + +Given that the modeler may specify arbitrary time period boundaries, this +parameter specifies the number of years contained in each period. The final year +is the largest element in :code:`time_future` which is specifically not included +in the list of periods in :code:`time_optimize` (:math:`\text{P}^o`). The length +calculation for each period then exploits the fact that the ``time`` sets are +ordered: + +.. math:: + + \text{LET boundaries} & = \text{sorted}(\text{P}^f) \\ + \text{LET I(p)} & = \text{index of p in boundaries} \\ + & \therefore \\ + {LEN}_p & = \text{boundaries}[ I(p) + 1 ] - p + + \forall p \in P + +The first line creates a sorted array of the period boundaries, called +*boundaries*. The second line defines a function `I` that finds the index of +period :math:`p` in boundaries. The third line then defines the length of period +:math:`p` to be the number of years between period :math:`p` and the next +period. For example, if :math:`\text{P}^f = \{2015, 2020, 2030, 2045\}`, +then *boundaries* would be :code:`[2015, 2020, 2030, 2045]`. For 2020, I(2020) +would return 2. Similarly, boundaries[ 3 ] = 2030. Then, + +.. math:: + {LEN}_{2020} & = \text{boundaries}[I(2020) + 1] - (2020) \\ + & = \text{boundaries} [2 + 1] - 2020 \\ + & = \text{boundaries} [3] - 2020 \\ + & = 2030 - 2020 \\ + & = 10 + +Note that LEN is only defined for elements in :math:`\text{P}^o`, and is +specifically not defined for the final element in :math:`\text{P}^f`. + + +\*process_life_frac +~~~~~~~~~~~~~~~~~~~ + +:math:`{PLF}_{r \in R, p \in P,t \in T,v \in P}` + +The modeler may specify a useful lifetime of a process such that the process +will be decommissioned part way through a period. Rather than attempt to +delineate each year within that final period, Temoa averages the total output +of the process over the entire period but limits the available capacity and +output of the decommissioning process by the ratio of how long through the +period the process is active. This parameter is that ratio, formally defined +as: + +.. math:: + + PLF_{p,t,v} = \frac{v + LTP_{t,v} - p}{LEN_p} + + \\ + \forall \{p,t,v\} & \in \Theta_\text{Activity by PTV} | \\ + v + LTP_{t,v} & \notin P, \\ + v + LTP_{t,v} & \le max(F), \\ + p & = max(P | p < v + LTP_{t,v}) + +Note that this parameter is defined over the same indices as +:code:`cost_variable` -- the active periods for each process :math:`\{p, t, +v\}`. As an example, if a model has :math:`P = \{2010, 2012, +2020, 2030\}`, and a process :math:`\{t, v\} = \{car, 2010\}` has a useful +lifetime of 5 years, then this parameter would include only the first two +activity indices for the process. Namely, :math:`p \in \{2010, 2012\}` as +:math:`\{p, t, v\} \in \{\{2010, car, 2010\}, \{2012, car, +2010\}\}`. The values would be :math:`{TLF}_{2010, car, 2010} = 1`, and +:math:`{TLF}_{2012, car, 2010} = \frac{3}{8}`. + +Decision Variables +------------------ +Decision variables are the quantities optimized by the model. A summary table of +decision variables is provided below, followed by a more detailed description of +each. + +.. _table_variable: +.. csv-table:: Temoa's Main Variables + :header: "Variable","Temoa Name","Short Description" + :widths: 18, 22, 50 + + ":math:`FO_{r,p,s,d,i,t,v,o}`",":code:`v_flow_out`","Commodity flow by time slice out of a tech based on a given input" + ":math:`FOA_{r,p,i,t,v,o}`",":code:`v_flow_out_annual`","Annual commodity flow out of a tech based on a given input" + ":math:`FIS_{r,p,s,d,i,t,v,o}`",":code:`v_flow_in`","Commodity flow into a storage tech to produce a given output" + ":math:`FLX_{r,p,s,d,i,t,v,o}`",":code:`v_flex`","The portion of commodity production exceeding demand" + ":math:`FLXA_{r,p,i,t,v,o}`",":code:`v_flex_annual`","The portion of commodity production from constant production techs exceeding demand" + ":math:`CUR_{r,p,s,d,i,t,v,o}`",":code:`v_curtailment`","Commodity flow out of a tech that is curtailed" + ":math:`CAP_{r,p,t,v}`",":code:`v_capacity`","Required tech capacity to support associated activity" + ":math:`CAPAVL_{r,p,t}`",":code:`v_capacity_available_by_period_and_tech`","Derived variable representing the capacity of technology :math:`t` available in period :math:`p`" + ":math:`SI_{r,p,s,t,v}`",":code:`v_storage_init`","Hub variable for the initial charge level of each daily storage cycle" + ":math:`SL_{r,p,s,d,t,v}`",":code:`v_storage_level`","Charge level each time slice associated with storage techs" + ":math:`SSL_{r,p,s,t,v}`",":code:`v_seasonal_storage_level`","Base charge level of sequential seasons for seasonal storage" + ":math:`RCAP_{r,p,t,v}`",":code:`v_retired_capacity`","Capacity retired before end of life" + ":math:`ART_{r,p,t,v}`",":code:`v_annual_retirement`","Annualised capacity retiring or reaching end of life" + ":math:`NCAP_{r,t,v}`",":code:`v_new_capacity`","New deployed capacity" + +v_flow_out +~~~~~~~~~~ + +:math:`FO_{r,p,s,d,i,t,v,o}` + +The most fundamental variable in the Temoa formulation is the +:code:`v_flow_out` variable. It describes the commodity flow out of a +process in a given time slice. To balance input and output flows in the +:code:`CommodityBalance_constraint`, the commodity flow into a given +process can be calculated as +:math:`\sum_{T, V, O} \textbf{FO}_{p, s, d, c, t, v, o} +/EFF_{c,t,v,o}`. + +v_flow_out_annual +~~~~~~~~~~~~~~~~~ + +:math:`FOA_{r,p,i,t,v,o}` + +Similar to :code:`v_flow_out`, but used for technologies that are members +of the :code:`tech_annual` set, whose output does not vary across seasons +and times-of-day. Eliminating the :code:`s,d` indices for these technologies +improves computational performance. + + +v_flex +~~~~~~ + +:math:`FLX_{r,p,s,d,i,t,v,o}` + +In some cases, the overproduction of a commodity may be required, such +that supply exceeds the endogenous demand. Refineries represent a +common example, where the share of different refined products are governed +by tech_output_split, but total production is driven by a particular commodity. +For example, gasoline production may be artificially constrained in order to +ensure the appropriate balance for lower demand fuels such as propane or +kerosene. Instead, we allow overproduction, i.e., production exceeding +endogenous demand, for commodities produced by technologies belonging to +the :code:`tech_flex` set. In the example above, adding the refinery to +the :code:`tech_flex` set allows for the overproduction of propane and +kerosene, allowing the model to fulfill the endogenous demand +for gasoline. This flexible technology designation activates a slack +variable (:math:`\textbf{FLX}_{r, p, s, d, i, t, v, c}`)representing +the excess production in the :code:`CommodityBalanceAnnual_constraint`. + + +v_flex_annual +~~~~~~~~~~~~~ + +:math:`FLXA_{r,p,i,t,v,o}` + +Similar to :code:`v_flex`, but used for technologies that are members +of the :code:`tech_flex` set, whose output does not vary across seasons +and times-of-day. Eliminating the :code:`s,d` indices for these technologies +improves computational performance. + + +v_curtailment +~~~~~~~~~~~~~ + +:math:`CUR_{r,p,s,d,i,t,v,o}` + +The :code:`v_curtailment` variable is an accounting tool to help calculate +the unused production capacity of technologies annotated in the Technology +database table as curtailable technologies belonging to the +:code:`tech_curtailment` set. +Renewables such as wind and solar are often placed in this set. While we +used to simply formulate the :code:`Capacity` and :code:`CommodityBalance` +constraints as inequalities that implicitly allowed for curtailment, this +simpler approach does not work with renewable targets because the curtailed +portion of the electricity production counts towards the target, and there is +no way to distinguish it from the useful production. Including an explicit +curtailment term addresses the issue. Curtailment in the model is simply +the production activity that is not used in the model and is reported as +such in the output_curtailment table. Note: Outputs presented in the +`output_curtailment` table for curtailment (the table separately includes +flex outputs) are limited by Capacity Factor. Meaning: if a tech has a +capacity of 10 units, and a CF of 0.8 and a usage of 5 units, then the reported +curtailment is 3 units (0.8 x 10 - 5). + + +v_flow_in_storage +~~~~~~~~~~~~~~~~~ + +:math:`FIS_{r,p,s,d,i,t,v,o}` + +Because the production and consumption associated with storage techs occur +across different time slices, the comodity flow into a storage technologiy +cannot be discerned from :code:`v_flow_out`. Thus an explicit :math:`flow_in` +variable is required for storage. + +v_capacity +~~~~~~~~~~ + +:math:`CAP_{r,p,t,v}` + +The :code:`v_capacity` variable represents the available capacity of a process +:math:`(r, t, v)` in period :math:`p`, adjusted for retirements or end of life. +Temoa constrains the capacity variable to be able to meet the total +commodity flow out of that process in all time slices in which it is active +:eq:`Capacity`. + +v_capacity_available_by_period_and_tech +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`CAPAVL_{r,p,t}` + +:code:`CapacityAvailableByPeriodAndTech` is a convenience variable that is +not strictly necessary, but used where the individual vintages of a technology +are not warranted (e.g. in calculating the maximum or minimum total capacity +allowed in a given time period). + +v_storage_init +~~~~~~~~~~~~~~ + +:math:`SI_{r,p,s,t,v}` + +The :code:`v_storage_init` variable replaces the :code:`v_storage_level` variable +for the initial storage charge level of each season. This is purely for solver optimisation +and has no impact on model structure or results. This 1:1 swap measurably improves +presolve time in large models with storage. The mechanism behind +this is not understood. + +v_storage_level +~~~~~~~~~~~~~~~ + +:math:`SL_{r,p,s,d,t,v}` + +The :code:`v_storage_level` variable tracks the storage charge level across ordered +time slices and is critical to ensure that storage charge and dispatch is constrained +by the energy available in the storage units. + + + +v_seasonal_storage_level +~~~~~~~~~~~~~~~~~~~~~~~~ + +:math:`SSL_{r,p,s,t,v}` + +The :code:`v_seasonal_storage_level` variable tracks the base charge level at +the boundary between sequential seasons for technologies flagged as seasonal +storage (:code:`tech_seasonal_storage`). It is used in the +:code:`seasonal_storage_energy_constraint` to chain the storage state of charge +across seasons defined in the :code:`time_season_sequential` table. + +v_retired_capacity +~~~~~~~~~~~~~~~~~~ + +:math:`RCAP_{r,p,t,v}` + +The :code:`v_retired_capacity` variable tracks the cumulative capacity of a +process that has been retired before its natural end of life, up to and including +period :math:`p`. Only technologies in the :code:`tech_retirement` set may +retire early in this manner. This variable is non-decreasing across periods. + +v_annual_retirement +~~~~~~~~~~~~~~~~~~~ + +:math:`ART_{r,p,t,v}` + +The :code:`v_annual_retirement` variable represents the annualised capacity +that retires or reaches end of life in period :math:`p`. It accounts for both +early retirements (via :code:`v_retired_capacity`) and natural end-of-life. + +v_new_capacity +~~~~~~~~~~~~~~ + +:math:`NCAP_{r,t,v}` + +The :code:`v_new_capacity` variable represents the newly deployed capacity of a +technology in its vintage period. It is the primary investment decision +variable and drives the capital cost terms in the objective function. Unlike +:code:`v_capacity`, it has no period index — capacity is built once in its +vintage year. + +We explain the equations governing these variables in the :ref:`Constraints` +section. + + +.. _constraints: + +Equations +--------- + +There are four main equations that govern the flow of energy through the model +network. The :code:`Demand_Constrant` :eq:`Demand` ensures that the supply meets +demand in every time slice. For each process, the :code:`Capacity_constraint` :eq:`Capacity` +ensures that there is sufficient capacity to meet the optimal commodity flows across all +time slices. Between processes, the :code:`CommodityBalance_constraint` :eq:`CommodityBalance` +ensures that global commodity production across the energy system is sufficient to meet the +endogenous demands for that commodity. Finally, the objective function :eq:`obj_invest` drives +the model to minimize the system-wide cost of energy supply by optimizing the deployment and +utilization of energy technologies across the system. + +One additional point regarding the model formulation. Technologies that +produce constant annual output can be placed in the :code:`tech_annual` set. +While not required, doing so improves computational performance by eliminating the +season and time of day :code:`(s,d)` indices associated with these technologies. +In order to ensure the model functions correctly with these simplified technologies, +slightly different formulations of the capacity and commodity balance constraints +are required. See the :code:`AnnualCommodityBalance_constraint` and +:code:`CapacityAnnual_constraint` :eq:`CapacityAnnual` below for details. + +The rest of this section defines each model constraint, with a rationale for +existence. We use the implementation-specific names for the constraints to +highlight the organization of the functions within the actual code. Note that +the definitions below are pulled directly from the docstrings embedded in +:code:`temoa/components/`. + +A couple notes on the notation below. First, in all equations, we **bold** +variables to distinguish them from parameters. Second, you will notice the use +of the Theta superset (:math:`\Theta`). The Temoa code makes heavy use of +sparse sets, for both correctness and efficient use of computational resources. +For brevity, and to avoid discussion of implementation details, we do not +enumerate their logical creation here. Instead, we rely on the reader's general +understanding of the context. The use of (:math:`\Theta`) means that the +constraint is only defined for the exact indices that the modeler specified. + +Capacity-Defining Constraints +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We begin with the :code:`Capacity_constraint` and :code:`CapacityAnnual_constraint`, +which are particularly important because they define the relationship between +installed capacity and allowable commodity flow. + +.. autofunction:: temoa.components.capacity.capacity_constraint + +.. autofunction:: temoa.components.capacity.capacity_annual_constraint + +.. autofunction:: temoa.components.capacity.adjusted_capacity_constraint + +.. autofunction:: temoa.components.capacity.annual_retirement_constraint + +.. autofunction:: temoa.components.capacity.capacity_available_by_period_and_tech_constraint + + +.. _NetworkConstraints: + +Network Constraints +~~~~~~~~~~~~~~~~~~~ + +These three constraints define the core of the Temoa model; together, they +define the algebraic energy system network. + +.. autofunction:: temoa.components.commodities.demand_constraint + +.. autofunction:: temoa.components.commodities.commodity_balance_constraint + +.. autofunction:: temoa.components.commodities.annual_commodity_balance_constraint + + +Physical and Operational Constraints +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These constraints fine-tune the model formulation to account for +various physical and operational real-world phenomena. + +.. autofunction:: temoa.components.operations.baseload_diurnal_constraint + +.. autofunction:: temoa.components.commodities.demand_activity_constraint + +.. autofunction:: temoa.components.storage.storage_energy_constraint + +.. autofunction:: temoa.components.storage.seasonal_storage_energy_constraint + +.. autofunction:: temoa.components.storage.storage_energy_upper_bound_constraint + +.. autofunction:: temoa.components.storage.seasonal_storage_energy_upper_bound_constraint + +.. autofunction:: temoa.components.storage.storage_charge_rate_constraint + +.. autofunction:: temoa.components.storage.storage_discharge_rate_constraint + +.. autofunction:: temoa.components.storage.storage_throughput_constraint + +.. autofunction:: temoa.components.operations.ramp_up_day_constraint + +.. autofunction:: temoa.components.operations.ramp_down_day_constraint + +.. autofunction:: temoa.components.operations.ramp_up_season_constraint + +.. autofunction:: temoa.components.operations.ramp_down_season_constraint + +.. autofunction:: temoa.components.reserves.reserve_margin_static + +.. autofunction:: temoa.components.reserves.reserve_margin_dynamic + +.. autofunction:: temoa.components.emissions.linked_emissions_tech_constraint + + +Objective Function +~~~~~~~~~~~~~~~~~~ + +.. autofunction:: temoa.components.costs.annuity_to_pv + +.. autofunction:: temoa.components.costs.pv_to_annuity + +.. autofunction:: temoa.components.costs.fv_to_pv + +.. autofunction:: temoa.components.costs.total_cost_rule + + +User-Specific Constraints +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. commented out... not used? + .. autofunction:: temoa.components.capacity.existing_capacity_constraint + +.. autofunction:: temoa.components.limits.limit_emission_constraint + +.. autofunction:: temoa.components.limits.limit_activity_constraint + +.. autofunction:: temoa.components.limits.limit_activity_share_constraint + +.. autofunction:: temoa.components.limits.limit_capacity_constraint + +.. autofunction:: temoa.components.limits.limit_new_capacity_constraint + +.. autofunction:: temoa.components.limits.limit_capacity_share_constraint + +.. autofunction:: temoa.components.limits.limit_new_capacity_share_constraint + +.. autofunction:: temoa.components.limits.limit_resource_constraint + +.. autofunction:: temoa.components.limits.limit_tech_input_split_constraint + +.. autofunction:: temoa.components.limits.limit_tech_output_split_constraint + +.. autofunction:: temoa.components.limits.limit_tech_input_split_annual_constraint + +.. autofunction:: temoa.components.limits.limit_tech_output_split_annual_constraint + +.. autofunction:: temoa.components.limits.limit_tech_input_split_average_constraint + +.. autofunction:: temoa.components.limits.limit_tech_output_split_average_constraint + +.. autofunction:: temoa.components.limits.limit_annual_capacity_factor_constraint + +.. autofunction:: temoa.components.limits.limit_seasonal_capacity_factor_constraint + +.. autofunction:: temoa.components.storage.limit_storage_fraction_constraint + +.. autofunction:: temoa.components.limits.limit_growth_capacity + +.. autofunction:: temoa.components.limits.limit_growth_new_capacity + +.. autofunction:: temoa.components.limits.limit_growth_new_capacity_delta + +General Caveats +--------------- + +Temoa does not currently provide an easy avenue to track multiple concurrent +energy flows through a process. Consider a cogeneration plant. Where a +conventional power plant might simply emit excess heat as exhaust, a +cogeneration plant harnesses some or all of that heat for heating purposes, +either very close to the plant, or generally as hot water for district heating. +Temoa's flow variables can track both flows through a process, but each flow +will have its own efficiency from the efficiency parameter. This implies that +to produce 1 unit of electricity will require :math:`\frac{1}{elc eff}` units of +input. At the same time, to produce 1 unit of heat will require units of input +energy, and to produce both output units of heat and energy, both flows must be +active, and the desired activity will be double-counted by Temoa. + +To model a parallel output device (c.f., a cogeneration plant), the modeler must +currently set up the process with the :code:`tech_input_split` and +:code:`tech_output_split` parameters, appropriately adding each flow to the +efficiency parameter and accounting for the overall process efficiency through +all flows. diff --git a/docs/source/mga.rst b/docs/source/mga.rst new file mode 100644 index 000000000..5ad0a8c5b --- /dev/null +++ b/docs/source/mga.rst @@ -0,0 +1,187 @@ +.. _mga: + +Modeling to Generate Alternatives (MGA) +======================================= + +Temoa provides two extensions for Modeling to Generate Alternatives (MGA), an +algorithm to explore the near-optimal solution space: hull expansion and +single-vector MGA. Both are described in more detail below. + +Hull Expansion +-------------- + +Hull expansion creates the convex hull containing all near-optimal solutions +bound by the extreme points along a particular solution axis and then samples +the continuum of solutions. The size of the convex hull is determined by the +degree of cost relaxation (``cost_epsilon``). The method is described in +`Pedersen et al. (2021) `_ + +Configuration +~~~~~~~~~~~~~ + +To enable hull expansion, set the ``scenario_mode`` to ``"mga"`` in your +configuration TOML file and provide an ``[MGA]`` section: + +.. code-block:: toml + + scenario_mode = "mga" + + [MGA] + cost_epsilon = 0.05 # Relax cost by 5% + iteration_limit = 20 # Maximum number of MGA iterations + time_limit_hrs = 12 # Stop after 12 hours + axis = "TECH_CATEGORY_ACTIVITY" + weighting = "HULL_EXPANSION" + +Options +^^^^^^^ + +* **cost_epsilon**: The fraction by which the optimal cost is allowed to increase + (e.g., ``0.05`` for 5%). +* **iteration_limit**: The maximum number of alternative solutions to generate. +* **time_limit_hrs**: The maximum wall-clock time for the entire MGA run. +* **axis**: The dimension along which to optimize. Supported values: + * ``TECH_CAPACITY`` + * ``TECH_CATEGORY_CAPACITY`` + * ``TECH_CATEGORY_ACTIVITY`` (Default) + * ``EMISSION_ACTIVITY`` +* **weighting**: The algorithm used to select the next optimization vector. + Currently, only ``HULL_EXPANSION`` is supported. + +Single-Vector MGA (SVMGA) +------------------------- + +Single-Vector MGA is a simplified, two-stage process. First, it solves the base +model to find the minimum cost. Second, it adds a cost relaxation constraint and +creates a new objective function that minimizes user-specified quantities, such +as technology-specific installed capacity or total carbon dioxide emissions. + + +Configuration +~~~~~~~~~~~~~ + +To enable SVMGA, set the ``scenario_mode`` to ``"svmga"`` and provide an +``[SVMGA]`` section: + +.. code-block:: toml + + scenario_mode = "svmga" + + [SVMGA] + cost_epsilon = 0.05 + capacity_labels = ["solar_pv", "wind_onshore"] + # emissions_labels = ["CO2"] + # activity_labels = ["coal_power"] + +Options +^^^^^^^ + +* **cost_epsilon**: Same as in hull expansion. +* **capacity_labels**: A list of technology names whose total capacity should be + maximized in the second stage. Matching is **exact and case-sensitive** against + the identifiers in the ``tech_all`` set. Example: ``["solar_pv", "wind_onshore"]``. +* **emissions_labels**: A list of emission commodities whose total emissions + should be minimized. Matching is **exact and case-sensitive** against + identifiers in the ``commodity_emissions`` set. Example: ``["CO2"]``. +* **activity_labels**: A list of technology names whose total activity + (energy flow out) should be maximized. Matching is **exact and case-sensitive**. + Example: ``["coal_power"]``. + +Note: SVMGA will construct an unweighted sum of all variables matching these +labels as the new objective function. Be careful not to mix different units. In +addition, note that the MGA objective function is set to minimize regardless of +the label choice. + +Parallel Execution and Solver Options +------------------------------------- + +Standard MGA supports parallel execution of iterative solves to maximize +performance. **Note: SVMGA executes sequentially and does not utilize parallel +workers.** + +The number of worker processes and solver-specific settings are defined in a +``MGA_solver_options.toml`` file. By default, Temoa looks for this file in the +same directory as your main configuration file. + +.. code-block:: toml + + # Global setting at the top level of the file + num_workers = 4 + + [gurobi] + Method = 2 + Threads = 4 # Threads per solver instance + BarConvTol = 0.01 + +.. tip:: + When choosing ``num_workers``, a good rule of thumb is to set it to the + number of available CPU cores minus one. This leaves room for the main + orchestration process and ensures that the system remains responsive. Also, + be mindful of the ``Threads`` setting within solver blocks, as the total + thread count will be ``num_workers * Threads``. + +Outputs +------- + +MGA results are stored in the same output database specified in your +configuration. Each iteration is saved as a unique scenario to allow for easy +comparison and analysis. + +Scenario Naming Convention +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each run is saved under a unique scenario name in the output tables, following +the format: ``-``. + +* **Iteration 0**: The original baseline solve (optimal solution). +* **Iterations 1-N**: The alternative solutions generated by the MGA algorithm. + +For example, if your base scenario is ``utopia_mga``, the results for the base +case will be found under scenario ``utopia_mga-0``, and the first alternative +will be under ``utopia_mga-1``. + +Key Database Tables +~~~~~~~~~~~~~~~~~~~ + +The results are spread across several tables, consistent with standard Temoa runs: + +* **output_objective**: Stores the total system cost and MGA optimization objective for each iteration. +* **output_net_capacity**: Stores the installed capacity for each technology, period, and iteration. +* **output_flow_out** / **output_flow_out_summary**: Stores energy flows between technologies. +* **output_emission**: Stores emission results per commodity and technology. +* **output_cost**: Stores detailed cost breakdowns (investment, fixed, variable). + +Comparing Iterations +~~~~~~~~~~~~~~~~~~~~ + +You can use SQL queries to compare results across different MGA iterations. + +**Comparing Total System Cost:** + +.. code-block:: sql + + SELECT scenario, total_system_cost + FROM output_objective + WHERE scenario LIKE 'utopia_mga-%' + ORDER BY scenario; + +**Comparing Capacity for a Specific Technology:** + +.. code-block:: sql + + SELECT scenario, tech, period, capacity, units + FROM output_net_capacity + WHERE scenario LIKE 'utopia_mga-%' + AND tech = 'solar_pv' + ORDER BY scenario, period; + +**Analyzing Diversity (SQL Join Example):** + +.. code-block:: sql + + SELECT a.scenario, a.tech, a.capacity as cap_a, b.capacity as cap_b, (a.capacity - b.capacity) as diff + FROM output_net_capacity a + JOIN output_net_capacity b ON a.tech = b.tech AND a.period = b.period + WHERE a.scenario = 'utopia_mga-1' + AND b.scenario = 'utopia_mga-0' + AND a.tech = 'solar_pv'; diff --git a/docs/source/monte_carlo.rst b/docs/source/monte_carlo.rst new file mode 100644 index 000000000..5bb4296ad --- /dev/null +++ b/docs/source/monte_carlo.rst @@ -0,0 +1,124 @@ +Monte Carlo Simulation +====================== + +Temoa provides a Monte Carlo simulation framework that allows users to perform +probabilistic analysis by executing multiple model runs with varying input parameters. +This feature is useful for characterizing the distribution of outcomes under +uncertainty or performing sensitivity analysis. + +Overall Framework +----------------- + +The Monte Carlo extension in Temoa is designed to: + +* Execute an arbitrary number of structured runs. +* Provide a clean interface to specify parameter deviations for each run. +* Support multiple deviations (tweaks) per individual run. +* Record all parameter adjustments in the output database for verification. +* Utilize parallel processing to speed up the execution of multiple runs. + +Configuration +------------- + +To enable Monte Carlo mode, set the ``scenario_mode`` to ``"monte_carlo"`` in +your configuration TOML file and provide the path to the run settings file +under a ``[monte_carlo]`` section: + +.. code-block:: toml + + scenario_mode = "monte_carlo" + + [monte_carlo] + run_settings = "path/to/mc_settings.csv" + solver_options = "mc_solver_options.toml" # Optional + +Settings +~~~~~~~~ + +* **run_settings**: Path to the CSV file containing the parameter deviations. +* **solver_options** (Optional): Path to a TOML file containing worker counts and solver-specific options. If not provided, Temoa uses the default options file in ``temoa/extensions/monte_carlo/MC_solver_options.toml``. + +Input File Format +----------------- + +The Monte Carlo settings are defined in a CSV file. The file must contain a header +and follow this structure: + +.. code-block:: none + + run,param,index,mod,value,notes + 1,cost_invest,utopia|TXD|2010,a,-44.0,reduce invest cost to 1000 + 2,demand,utopia|2010|RH,r,0.1,increase demand by 10% + 2,cost_fixed,utopia|TXD|2010|2010,s,15.0,set fixed cost to 15.0 + +Columns +~~~~~~~ + +* **run**: An integer representing the run index. Multiple lines with the same run index will be applied to the same model instance. +* **param**: The name of the Temoa parameter to adjust (e.g., ``cost_invest``, ``demand``). +* **index**: The index of the parameter, using the pipe (``|``) character as a separator. +* **mod**: The adjustment type (see below). +* **value**: The numeric value used for the adjustment. +* **notes**: A free-text field for user notes. + +Adjustment Types +~~~~~~~~~~~~~~~~ + +The ``mod`` column supports three types of adjustments: + +* **a (Absolute Change)**: Adds the ``value`` to the baseline parameter value. + Formula: :math:`new\_value = old\_value + value` +* **r (Relative Change)**: Adjusts the baseline value by a percentage. + Formula: :math:`new\_value = old\_value \times (1 + value)` +* **s (Substitution)**: Replaces the baseline value with the provided ``value``. + Formula: :math:`new\_value = value` + +Advanced Indexing +~~~~~~~~~~~~~~~~~ + +* **Wildcards**: You can use an asterisk (``*``) as a wildcard in any position of the ``index`` to apply the adjustment to all matching elements. + Example: ``utopia|*|2010`` would match all regions for the given tech/commodity in 2010. +* **Multi-tokens**: You can specify multiple identifiers for a single index position by separating them with a forward slash (``/``). This will create a Cartesian product of all specified combinations. + Example: ``utopia/usa|TXD|2010`` would apply the tweak to both ``utopia|TXD|2010`` and ``usa|TXD|2010``. + +Parallel Execution +------------------ + +Monte Carlo runs are executed in parallel to maximize performance. The number of +worker processes and solver-specific options for these workers are controlled +by a configuration file. + +By default, Temoa uses the ``MC_solver_options.toml`` file located in the +``temoa/extensions/monte_carlo/`` directory. However, you can provide your own +file by specifying the ``solver_options`` path in your configuration TOML. + +.. tip:: + The default configuration uses 11 worker processes. If you provide a custom + ``solver_options`` file, you can adjust the ``num_workers`` setting and add + solver-specific parameters (e.g., threads, tolerances) for each solver. + +Outputs +------- + +The results of each Monte Carlo run are stored in the output database specified +in your configuration. + +Model Scenarios +~~~~~~~~~~~~~~~ + +Each run is saved under a unique scenario name in the output tables, following +the format: ``-``. For example, if your base scenario +is ``utopia_mc``, the results for the first run will be labeled ``utopia_mc-1``. + +Tweak Log Table +~~~~~~~~~~~~~~~ + +The specific adjustments made for each run are recorded in the ``output_mc_delta`` +table. This table contains the following columns: + +* **scenario**: The unique scenario name for the run. +* **run**: The run index. +* **param**: The parameter that was adjusted. +* **param_index**: The specific index that was tweaked. +* **old_val**: The original value from the baseline data. +* **new_val**: The new value after the adjustment was applied. diff --git a/docs/source/myopic.rst b/docs/source/myopic.rst new file mode 100644 index 000000000..d0c6fbc70 --- /dev/null +++ b/docs/source/myopic.rst @@ -0,0 +1,162 @@ + +.. _myopic: + +Myopic Optimization +=================== + +Myopic (or sequential) optimization in Temoa provides a way to solve the model in a series of smaller, sequential time windows. This is often used to simulate decision-making with limited foresight, where the model only "sees" a limited number of future time periods at any given step. + +Overall Framework +----------------- + +The Myopic extension in Temoa is designed to: + +* Solve the model for a sequence of overlapping or non-overlapping time windows. +* Limit the foresight of the model to a user-specified number of future periods. +* Fix the capacity investment decisions from previous windows as "initial capacity" for subsequent windows. +* Automatically manage the ``myopic_efficiency`` table to filter active technologies based on their remaining lifetime and previous investment decisions. + +Configuration +------------- + +To enable Myopic mode, set the ``scenario_mode`` to ``"myopic"`` in your configuration TOML file and provide the required settings under a ``[myopic]`` section: + +.. code-block:: toml + + scenario_mode = "myopic" + + [myopic] + view_depth = 2 + step_size = 1 + evolving = false + evolution_script = "temoa/extensions/myopic/evolution_updater.py" + +Settings +~~~~~~~~ + +* **view_depth**: The number of future time periods visible to the model in each iteration. For example, a ``view_depth`` of 2 means the model will optimize over two periods at a time. +* **step_size**: The number of periods to advance the "base year" in each subsequent iteration. A ``step_size`` of 1 means the base year will move forward by one time period at a time. +* **evolving**: A boolean flag (``true`` or ``false``) that selects between evolving and non-evolving myopic mode. Defaults to ``false``. See :ref:`myopic-modes` below. +* **evolution_script**: Path to a Python script containing an ``iterate()`` function that is called between iterations in evolving mode. Ignored when ``evolving = false``. + +.. important:: + Myopic mode requires that the ``input_database`` and ``output_database`` in your configuration point to the same file. This is because the sequencer needs to write intermediate results (like the ``myopic_efficiency`` table and capacity decisions) back to the database to inform subsequent windows. + +.. _myopic-modes: + +Evolving vs. Non-Evolving Mode +------------------------------ + +Temoa supports two myopic sub-modes that control how iterations are generated and whether the +database can be modified between solves. + +Non-Evolving Mode (default) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When ``evolving = false``, the sequencer eliminates **redundant iterations** near the end of the +planning horizon. If multiple consecutive iterations would all see the same final period, only +the first such iteration is kept. This avoids re-solving windows that cannot produce new information. + +For example, consider a model with future periods ``[2025, 2030, 2035, 2040, 2045]``, +``view_depth = 3``, and ``step_size = 1``: + +* Without pruning, the base years would be ``[2025, 2030, 2035, 2040]``. +* The iterations starting at 2035 and 2040 both see period 2045 as the last demand year, so only the first is kept. +* **Result**: 3 iterations instead of 4. + +Non-evolving mode is appropriate when the model structure (technologies, constraints, costs, etc.) does +not change between iterations and you want to minimize computation. + +Evolving Mode +~~~~~~~~~~~~~ + +When ``evolving = true``, the sequencer keeps **all** calculated iterations, stepping forward by exactly +``step_size`` until the planning horizon is exhausted. Between each iteration, the ``evolution_script`` +is called, giving the modeler an opportunity to modify the database before the next window is solved. + +Using the same example (periods ``[2025, 2030, 2035, 2040, 2045]``, ``view_depth = 3``, ``step_size = 1``): + +* Base years are ``[2025, 2030, 2035, 2040]``. +* **All 4 iterations** are kept, because the evolution script may change data between them. + +This mode is designed for scenarios where model parameters—such as technology costs, demand levels, or +policy constraints—need to evolve over time based on previous results or external logic. + +.. note:: + In evolving mode, the view depth may shorten near the end of the planning horizon so that the window + does not extend beyond the available periods. All iterations are still performed. + +Writing an Evolution Script +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The evolution script must define an ``iterate()`` function with the following signature: + +.. code-block:: python + + import sqlite3 + from temoa.extensions.myopic.myopic_index import MyopicIndex + + def iterate( + idx: MyopicIndex | None = None, + prev_base_year: int | None = None, + last_instance_status: str | None = None, + db_con: sqlite3.Connection | None = None, + ) -> None: + """Called between myopic iterations (evolving mode only).""" + # Use db_con to read/write the database as needed. + pass + +Parameters: + +* **idx**: The ``MyopicIndex`` for the current iteration, containing the base year, step year, last demand year, and last year. +* **prev_base_year**: The base year of the previous iteration. +* **last_instance_status**: ``'optimal'`` if the previous solve succeeded, or ``'roll_back'`` if it failed and triggered a rollback. +* **db_con**: An open SQLite connection to the myopic database, which can be used to read results from the prior iteration and modify data for the next one. + +The function is called *after* results from the current iteration have been written to the database and *before* +the myopic efficiency table is updated for the next iteration. A template is provided at +``temoa/extensions/myopic/evolution_updater.py``. + +How it Works +------------ + +When running in Myopic mode, Temoa performs the following steps: + +1. **Initialization**: It identifies all future time periods and sets up a queue of "optimization windows" based on the ``view_depth`` and ``step_size``. +2. **Initial Setup**: It creates the ``myopic_efficiency`` table and pre-loads it with any existing capacity from the input database. +3. **Sequential Solve**: For each window in the queue: + a. **Update Efficiency Table**: The ``myopic_efficiency`` table is updated to include only technologies that are still within their lifetime or were built in previous windows. Any "planned" technologies not built in the previous window's look-ahead are removed to prevent "ghost" capacity. + b. **Data Loading**: Data is loaded specifically for the current window's time range, using the ``myopic_efficiency`` table as a filter. + c. **Model Solve**: A standard Temoa model instance is built and solved for the current window. + d. **Result Persistence**: Investment decisions (built and retired capacity) are saved back to the database. These decisions are treated as fixed constraints in all future windows. +4. **Final Reporting**: Once all windows are solved, a total system cost is calculated as the sum of discounted costs across all periods. + +Myopic Efficiency Table +----------------------- + +The ``myopic_efficiency`` table is a key internal component of the myopic sequencer. It acts as a dynamic version of the standard ``efficiency`` table, filtered for each optimization window. + +* It is "actively maintained" during the run. +* Items not built in a previous time window are removed. +* Items that reach the end of their technical lifetime are retired and removed from the table. +* The table includes a computed ``lifetime`` field used internally to screen active technologies. + +Backtracking and Roll-back +-------------------------- + +If the solver fails to find an optimal solution for a given window (e.g., due to infeasibility caused by previous decisions), the Myopic sequencer has a built-in roll-back mechanism. It will attempt to back up to the previous window and re-solve with an expanded ``view_depth`` to find a feasible path forward. If it cannot back up further, the run will abort with an error. + +Notes and Caveats +----------------- + +Performance and Discounting +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* **Reduced Compute**: Myopic mode dramatically reduces compute requirements compared to perfect foresight by using a "divide and conquer" approach, solving several smaller problems instead of one large global optimization. +* **Discounting**: All costs are discounted to the first optimization period in the database (period ``p0`` of the first myopic window). + +Modeling Considerations +~~~~~~~~~~~~~~~~~~~~~~~ + +* **Investment Amortization**: Investment costs in Temoa are amortized over the useful life of a built technology. This harmonizes how the model perceives costs and benefits within the ``view_depth``: since the model only sees the benefits of a technology for the portion of its life that falls within the current window, the costs are similarly distributed. Consequently, discounted investment costs reported in summary logs may be significantly higher than those reported in the ``output_cost`` table. +* **Limited Foresight**: Myopic mode cannot see beyond the specified ``view_depth``, including constraints. If a constraint only becomes active later in the planning horizon, the model may make sub-optimal decisions in early time windows. This can lead to unexpected rollbacks when those constraints finally enter the visibility window, which can complicate the interpretation of the results. Modeler caution is advised when setting the ``view_depth`` relative to known future constraints. diff --git a/docs/source/param_desc_and_tables.rst b/docs/source/param_desc_and_tables.rst new file mode 100644 index 000000000..40ff0737d --- /dev/null +++ b/docs/source/param_desc_and_tables.rst @@ -0,0 +1,191 @@ +Parameters are indexed by the sets defined above and used to specify input data. +In the leftmost column below, the subscripts indicate the sets used to index the +parameter. As with sets, we categorize parameters thematically and present them +below in separate tables. + +Below are the **time-related** parameters. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{SFS}_s`", ":code:`time_season`", ":code:`segment_fraction_per_season`", "per-season year fraction; loaded from the :code:`segment_fraction` column of :code:`time_season`" + "", ":code:`time_of_day`", ":code:`time_of_day_hours`", "hours per time-of-day segment; loaded from the :code:`hours` column of :code:`time_of_day` (default 1)" + ":math:`\text{SEG}_{s,d}`", "*(computed)*", ":code:`segment_fraction`", "fraction of year represented by each (s, d) tuple; computed as :code:`segment_fraction_per_season(s) × hours(d) / Σ hours`" + "", "*(config)*", ":code:`time_sequencing`", "time sequencing mode: ``consecutive_days``, ``seasonal_timeslices`` (default), ``representative_periods``, or ``manual``" + "", "*(config)*", ":code:`days_per_period`", "number of days per period (default: 365); set as a top-level key in the configuration TOML file, not in the database" + ":math:`\text{SFS}_{s^{seq}}`", ":code:`time_season_sequential`", ":code:`segment_fraction_per_sequential_season`", "per-sequential-season year fraction; loaded from the :code:`segment_fraction` column of :code:`time_season_sequential`" + +Parameters in the table below relate to **capacity and its performance +characteristics**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{C2A}_{r,t}`", ":code:`capacity_to_activity`", ":code:`capacity_to_activity`", "converts from capacity to activity units" + ":math:`\text{CC}_{r,p,t,v}`", ":code:`capacity_credit`", ":code:`capacity_credit`", "process-specific capacity credit used in the static reserve margin constraint" + ":math:`\text{CFT}_{r,s,d,t}`", ":code:`capacity_factor_tech`", ":code:`capacity_factor_tech`", "technology-specific capacity factor" + ":math:`\text{CFP}_{r,s,d,t,v}`", ":code:`capacity_factor_process`", ":code:`capacity_factor_process`", "process-specific capacity factor; allows capacity factor to change with technology vintage" + ":math:`\text{ECAP}_{r,t,v}`", ":code:`existing_capacity`", ":code:`existing_capacity`", "installed capacity that exists prior to first model time period" + ":math:`\text{PRM}_{r}`", ":code:`planning_reserve_margin`", ":code:`planning_reserve_margin`", "planning reserve margin used to ensure sufficient generating capacity" + ":math:`\text{RCD}_{r,s,t,v}`", ":code:`reserve_capacity_derate`", ":code:`reserve_capacity_derate`", "capacity derate factor for dynamic reserve margin constraint" + ":math:`\text{RUH}_{r,t}`", ":code:`ramp_up_hourly`", ":code:`ramp_up_hourly`", "hourly rate at which generation techs can ramp output up" + ":math:`\text{RDH}_{r,t}`", ":code:`ramp_down_hourly`", ":code:`ramp_down_hourly`", "hourly rate at which generation techs can ramp output down" + +Parameters in the table below relate to the specification of **costs**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{CF}_{r,p,t,v}`", ":code:`cost_fixed`", ":code:`cost_fixed`", "fixed operations & maintenance cost" + ":math:`\text{CI}_{r,t,v}`", ":code:`cost_invest`", ":code:`cost_invest`", "tech-specific investment cost" + ":math:`\text{CV}_{r,p,t,v}`", ":code:`cost_variable`", ":code:`cost_variable`", "variable operations & maintenance (O&M) cost" + ":math:`\text{CE}_{r,p,e}`", ":code:`cost_emission`", ":code:`cost_emission`", "emission costs" + ":math:`\text{GDR}`", ":code:`metadata_real`", ":code:`global_discount_rate`", "global rate used to convert future time period costs to the present cost" + ":math:`\text{DLR}`", ":code:`metadata_real`", ":code:`default_loan_rate`", "default loan rate used to amortize investment costs" + ":math:`\text{LLP}_{r,t,v}`", ":code:`loan_lifetime_process`", ":code:`loan_lifetime_process`", "process-specific loan term (default=lifetime_process)" + ":math:`\text{LR}_{r,t,v}`", ":code:`loan_rate`", ":code:`loan_rate`", "process-specific interest rate on investment cost" + +Parameters in the table below relate to the specification of **technology efficiency +and performance**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{EFF}_{r,i,t,v,o}`", ":code:`efficiency`", ":code:`efficiency`", "technology- and commodity-specific conversion efficiency" + ":math:`\text{EFFV}_{r,s,d,i,t,v,o}`", ":code:`efficiency_variable`", ":code:`efficiency_variable`", "optional time slice multiplier on efficiency (no period index; applies to all periods)" + ":math:`\text{LTT}_{r,t}`", ":code:`lifetime_tech`", ":code:`lifetime_tech`", "technology-specific lifetime (default=40 years)" + ":math:`\text{LTP}_{r,t,v}`", ":code:`lifetime_process`", ":code:`lifetime_process`", "tech- and vintage-specific lifetime (default=lifetime_tech)" + ":math:`\text{LSC}_{r,p,t,v}`", ":code:`lifetime_survival_curve`", ":code:`lifetime_survival_curve`", "surviving fraction of original capacity" + ":math:`\text{SD}_{r,t}`", ":code:`storage_duration`", ":code:`storage_duration`", "storage duration per technology, specified in hours" + +Parameters in the table below relate to the specification of **end-use demands**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{DEM}_{r,p,c}`", ":code:`demand`", ":code:`demand`", "end-use demand by region-period-commodity" + ":math:`\text{DSD}_{r,s,d,c}`", ":code:`demand_specific_distribution`", ":code:`demand_specific_distribution`", "fractional annual demand by time slice (s,d)" + +Parameters in the table below relate to the specification of **emissions**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{EAC}_{r,e,i,t,v,o}`", ":code:`emission_activity`", ":code:`emission_activity`", "activity-based emissions rate" + ":math:`\text{EE}_{r,t,v,e}`", ":code:`emission_embodied`", ":code:`emission_embodied`", "emissions associated with the creation of capacity; embodied emissions" + ":math:`\text{EEOL}_{r,t,v,e}`", ":code:`emission_end_of_life`", ":code:`emission_end_of_life`", "emissions associated with the retirement/end of life of capacity" + +Parameters in the table below relate to the specification of **absolute limits on +capacity, activity and emissions**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{LC}_{r,p,t}`", ":code:`limit_capacity`", ":code:`limit_capacity`", "limit on capacity by period; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LNC}_{r,t,v}`", ":code:`limit_new_capacity`", ":code:`limit_new_capacity`", "limit on new capacity deployment by vintage; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LA}_{r,p,t}`", ":code:`limit_activity`", ":code:`limit_activity`", "limit on activity by region and period; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LE}_{r,p,e}`", ":code:`limit_emission`", ":code:`limit_emission`", "limit on emissions by region and period" + ":math:`\text{LS}_{r,t}`", ":code:`limit_resource`", ":code:`limit_resource`", "cumulative activity limit across time periods (not supported in myopic); :code:`tech_or_group` column accepts a technology name or group name" + +Parameters in the table below relate to the specification of **growth and degrowth +limits**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{LGC}_{r,t}`", ":code:`limit_growth_capacity`", ":code:`limit_growth_capacity`", "capacity growth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LDGC}_{r,t}`", ":code:`limit_degrowth_capacity`", ":code:`limit_degrowth_capacity`", "capacity degrowth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LGNC}_{r,t}`", ":code:`limit_growth_new_capacity`", ":code:`limit_growth_new_capacity`", "new capacity growth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LDGNC}_{r,t}`", ":code:`limit_degrowth_new_capacity`", ":code:`limit_degrowth_new_capacity`", "new capacity degrowth rate limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\mathrm{LGNC}_{\Delta,r,t}`", ":code:`limit_growth_new_capacity_delta`", ":code:`limit_growth_new_capacity_delta`", "new capacity growth acceleration limits; :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\mathrm{LDGNC}_{\Delta,r,t}`", ":code:`limit_degrowth_new_capacity_delta`", ":code:`limit_degrowth_new_capacity_delta`", "new capacity degrowth deceleration limits; :code:`tech_or_group` column accepts a technology name or group name" + +Parameters in the table below relate to the specification of **operational and +split limits**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{LACF}_{r,t,v,o}`", ":code:`limit_annual_capacity_factor`", ":code:`limit_annual_capacity_factor`", "annual capacity factor limits; indexed by vintage (not period); :code:`tech_or_group` column accepts a technology name or group name" + ":math:`\text{LSCF}_{r,s,t}`", ":code:`limit_seasonal_capacity_factor`", ":code:`limit_seasonal_capacity_factor`", "seasonal capacity factor limits (no period index; applies to all periods)" + ":math:`\text{LSF}_{r,s,d,t}`", ":code:`limit_storage_level_fraction`", ":code:`limit_storage_fraction`", "limit on storage level in any time slice" + ":math:`\text{TIS}_{r,p,i,t}`", ":code:`limit_tech_input_split`", ":code:`limit_tech_input_split`", "technology input split constraints specifying input fuel ratio at time slice level" + ":math:`\text{TISA}_{r,p,i,t}`", ":code:`limit_tech_input_split_annual`", ":code:`limit_tech_input_split_annual`", "technology input split constraints specifying input fuel ratio at average annual level" + ":math:`\text{TOS}_{r,p,t,o}`", ":code:`limit_tech_output_split`", ":code:`limit_tech_output_split`", "technology split constraints specifying output fuel ratio at time slice level" + ":math:`\text{TOSA}_{r,p,t,o}`", ":code:`limit_tech_output_split_annual`", ":code:`limit_tech_output_split_annual`", "technology split constraints specifying output fuel ratio at average annual level" + +Parameters in the table below relate to the specification of **share limits**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{LCS}_{r,p,g_1,g_2}`", ":code:`limit_capacity_share`", ":code:`limit_capacity_share`", "capacity share limits between technology groups" + ":math:`\text{LAS}_{r,p,g_1,g_2}`", ":code:`limit_activity_share`", ":code:`limit_activity_share`", "activity share limits between technology groups" + ":math:`\text{LNCS}_{r,g_1,g_2,v}`", ":code:`limit_new_capacity_share`", ":code:`limit_new_capacity_share`", "new capacity share limits" + +Parameters in the table below relate to the specification of **policy**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + "", ":code:`rps_requirement`", ":code:`renewable_portfolio_standard`", "**[Deprecated]** RPS requirements; use :code:`limit_activity_share` instead" + ":math:`\text{LIT}_{r,t,e,t'}`", ":code:`linked_tech`", ":code:`linked_techs`", "dummy techs used to convert CO2 emissions to physical commodity" + +Parameters in the table below relate to the specification of **construction and +end-of-life**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{CON}_{r,i,t,v}`", ":code:`construction_input`", ":code:`construction_input`", "construction input requirements that represent commodities consumed by creation of process capacity" + ":math:`\text{EOLO}_{r,t,v,o}`", ":code:`end_of_life_output`", ":code:`end_of_life_output`", "end-of-life outputs that represent commodities produced by retirement/end of life of capacity" + +Parameters in the table below are **computed in code (i.e., model-derived) and +therefore do not appear in the database**. + +.. csv-table:: + :header: "Parameter", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`{}^*\text{LEN}_p`", "", ":code:`period_length`", "number of years in period :math:`p`; computed from time periods" + ":math:`{}^*\text{LA}_{t,v}`", "", ":code:`loan_annualize`", "loan amortization by tech and vintage; based on :math:`DR_t`; computed from loan rate and lifetime" + ":math:`{}^*\text{PLF}_{r,p,t,v}`", "", ":code:`process_life_frac`", "fraction of available process capacity by region and period; computed from process life fraction" + +The tables below specify **model outputs.** When constructing a new database, +they are left blank. These tables are auto-populated by the model after +successful run completion. + +- :code:`output_dual_variable` +- :code:`output_objective` +- :code:`output_curtailment` +- :code:`output_net_capacity` +- :code:`output_built_capacity` +- :code:`output_retired_capacity` +- :code:`output_flow_in` +- :code:`output_flow_out` +- :code:`output_flow_out_summary` +- :code:`output_storage_level` +- :code:`output_emission` +- :code:`output_cost` + +Finally, the database tables below do not have a directed mapping to the model +code. + +.. csv-table:: + :header: "Database Table", "Purpose", "Notes" + :widths: 30, 30, 40 + + ":code:`myopic_efficiency`", "Myopic mode efficiency", "alternative efficiency for myopic optimization" + ":code:`sector_label`", "Sectoral classification", "used in output tables only" diff --git a/docs/source/preface.rst b/docs/source/preface.rst new file mode 100644 index 000000000..16b8261df --- /dev/null +++ b/docs/source/preface.rst @@ -0,0 +1,126 @@ + + +======= +Preface +======= + +This manual, in both `PDF`_ and `HTML`_ form, is the official documentation of +Tools for Energy Model Optimization and Analysis (Temoa). It describes all +functionality of the Temoa model, and provides a mathematical description of +the implemented equations. + +Besides this documentation, there are a couple other sources for Temoa-oriented +information. For questions, bug reports, or feature requests, please use our +`GitHub Issues`_. Publications are good introductory resources, but are not +guaranteed to be the most up-to-date as information and implementations evolve +quickly. As with many software-oriented projects, even before this manual, +`the code is the most definitive resource`. That said, please let us know +(via `GitHub Issues`_) of any discrepancies you find, and we will fix it as +soon as possible. + +What is Temoa? +-------------- +Temoa is an energy system optimization model (ESOM) developed over many years +to support transparent, data-driven analysis of energy systems. ESOMs +serve as an important planning tool because they allow users to examine energy +futures using a self-consistent framework for evaluation. Temoa is implemented +as a linear program that minimizes the total cost of energy supply by optimizing +the installation and operation of energy technologies over a user-defined +planning horizon. Energy supply must meet end-use demands subject to physical +and technical constraints governing system operation, along with user-defined +policy scenarios. + +The energy system on which Temoa and other ESOMs operate can be visualized as +a directed graph. Primary energy sources represent the points of origin, which +are transformed by a network of energy conversion and delivery technologies, and +ultimately produce consumable energy commodities that satisfy end-use demands. +[esom_definition]_. Temoa provides tools to explicitly represent this network, +visualize system structure, and trace energy flows through time. + +A defining strength of Temoa is its flexible treatment of time. Users may +define arbitrary model periods of varying length and represent intra-period +operations using seasonal and time-of-day slices, full chronological hours, or +representative days. Capacity expansion can be solved under perfect foresight +or using a rolling-horizon. In addition, Temoa supports technology +vintaging, separate loan periods and physical lifetimes, and both global and +technology-specific discount rates. Beyond deterministic optimization, Temoa +supports stochastic optimization as well as modeling-to-generate alternatives +(MGA) to explore near-optimal solution spaces. All of Temoa's features were +driven by specific analytic needs over a decade of model development and +policy-focused application. + +Temoa is implemented within an open-source software stack and is released under +the MIT license, with source code available on GitHub [open_source_realities]_. +The model is written in Python and seamlessly integrates with the broader Python +ecosystem. Input data are stored in a relational SQLite database, enabling +transparency, reproducibility, and easy modification. The model maintains a +strict distinction between source code and the input data on which it operates. +The model can be executed on single machines, multi-core systems, or +high-performance computing environments. + +The name Temoa (Tools for Energy Model Optimization and Analysis) reflects the +project's broader scope. The platform comprises four interrelated components: +the underlying mathematical formulation, its software implementation, a suite +of supporting tools for data management, analysis, and visualization, and an +online presence that supports documentation, dissemination, and community +engagement. Together, these elements are designed to foster collaboration, +extensibility, and trust in energy system modeling results. + + + + + +Why Temoa? +---------- +In 2009, when the idea for Temoa was born, most options for energy systems +modeling were geared towards government institutions that could afford the +expensive commercial software licenses. Closed source code and data also +meant that it was impossible for third parties to verify published model +results, even though those results were being used to inform public policy +decisions involving significant transfers of wealth and direct consequences for +people's lives. In addition, models were typically used to run a limited number +of scenarios that did not address the true underlying uncertainty about the +future. + +Today's vibrant open source energy modeling community did not exist at +that time. We were motivated to build Temoa around three high-level objectives: +(1) make the model code and data open source to enable third party replication +of results, (2) use an open source software stack to minimize the barriers to +entry in energy modeling, and (3) build a toolkit to evaluate future uncertainty +in different ways, depending on the question at hand. + +Temoa remains one of the most fully-featured, open source energy system models +focused on projecting changes across the whole energy system. + + + +Temoa Origin and Pronunciation +------------------------------ + +While we use 'Temoa' as an acronym, it is an actual word in the Nahuatl (Aztec) +language, meaning "to seek something." + +.. figure:: images/temoa_definition.* + :align: center + :figclass: center + :figwidth: 50% + +One pronounces the word 'Temoa' as "teh", "moe", "uh". Though TEMOA is an acronym +for 'Tools for Energy Model Optimization and Analysis', we generally use 'Temoa' +as a proper noun, and so forgo the need for all-caps. + + +Bug Reporting +------------- + +Temoa strives for correctness. Unfortunately, as an energy system model and software +project there are plenty of levels and avenues for error. If you spot a bug, +inconsistency, or general "that could be improved", we want to hear about it. + +If you are a software developer-type, feel free to open an issue on our `GitHub +Issue tracker`_\ . + +.. _PDF: https://assets.temoaproject.org/pdf_docs/temoa_docs.pdf +.. _HTML: https://docs.temoaproject.org/en/latest/ +.. _GitHub Issues: https://github.com/TemoaProject/temoa/issues +.. _GitHub Issue tracker: https://github.com/TemoaProject/temoa/issues diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst new file mode 100644 index 000000000..369289e0b --- /dev/null +++ b/docs/source/quick_start.rst @@ -0,0 +1,141 @@ + + +=========== +Quick Start +=========== + +Installing Software Elements +---------------------------- + +Temoa is implemented in `Pyomo `_, which is in turn +written in `Python `_. Consequently, Temoa will run on +Linux, Mac, Windows, or any operating system that Pyomo supports. There are +several open source software elements required to run Temoa. + +**Using pip**: + +The standard way to install Temoa: + +First, it is highly recommended to use a Python virtual environment to manage dependencies: + +.. parsed-literal:: + # Linux / macOS + $ python -m venv temoa_env + $ source temoa_env/bin/activate + + # Windows (Command Prompt) + > python -m venv temoa_env + > temoa_env\\Scripts\\activate + + # Windows (PowerShell) + PS> python -m venv temoa_env + PS> .\\temoa_env\\Scripts\\Activate.ps1 + +Then, install Temoa: + +.. parsed-literal:: + # Install from PyPI + $ pip install temoa + + # Get started + $ temoa tutorial + $ temoa run tutorial_config.toml + +**Using uv (Alternative)**: + +For faster dependency resolution: + +.. parsed-literal:: + # Install uv (Linux / macOS) + $ curl -LsSf https://astral.sh/uv/install.sh | sh + + # Install uv (Windows) + PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + + # Create a uv initialized virtual environment + $ uv init + $ cd + + # add Temoa from PyPI to pyproject.toml + $ uv add temoa + + # Get started + $ uv run temoa tutorial + $ uv run temoa run tutorial_config.toml + +**For Contributors (Development Installation)**: + +If you want to contribute to Temoa or modify the code: + +.. parsed-literal:: + # Clone the repository + $ git clone https://github.com/TemoaProject/temoa.git + $ cd temoa + + # Install in development mode with uv (recommended) + $ uv sync --all-extras --dev + + # Install pre-commit hooks + $ uv run pre-commit install + +For detailed contribution guidelines, see CONTRIBUTING.md in the repository. + +Solvers +------- + +A few notes for on the choice of solvers. Different solvers +have widely varying solution times. If you plan to run Temoa with large datasets +and/or conduct uncertainty analysis, you may want to consider installing +commercial linear solvers such as `CPLEX +`_ or `Gurobi +`_. Both offer free academic licenses. + +For smaller models, Temoa has been tested with the `HiGHS `_ +solver. HiGHS is automatically available when you install Temoa and requires no additional setup. + + +Running Temoa +------------- + +To run the model, a configuration (``config``) file is needed. While an +example config file called :code:`config_sample.toml` is available as a package resource +in :code:`temoa/tutorial_assets`, it is not directly runnable on its own. The tutorial +flow also generates the SQLite database and copies :code:`mc_settings.csv` alongside it. +Users should follow the **temoa tutorial** command (see below) to obtain a fully runnable +setup. Running the model with a config file allows +the user to (1) use a sqlite database for storing input and output data, (2) +create a formatted Excel output file, (2) specify the solver to use, (3) return +the log file produced during model execution, (4) return the lp file utilized by +the solver, and (5) to execute several of the modeling extensions. + +.. parsed-literal:: + $ temoa run tutorial_config.toml + +**For general help, use --help:** + +.. parsed-literal:: + $ **temoa --help** + Usage: temoa [OPTIONS] COMMAND [ARGS]... + + Tools for Energy Model Optimization and Analysis (Temoa) + + Options: + -v, --version Show Temoa version and exit. + --how-to-cite Show citation information and exit. + -h, --help Show this message and exit. + + Commands: + validate Validates a configuration file and database. + run Builds and solves a Temoa model. + check-units Check units consistency in a Temoa database. + migrate Migrate a Temoa database file or directory. + tutorial Create tutorial configuration and database files. + +.. + dated references, preserved as comment here: + + To supplement this documentation, we have also created a + `YouTube video tutorial ` that explains + how to run Temoa from the command line. There is also an option to run + `Temoa on the cloud `, which + is explained in `this video tutorial `. diff --git a/docs/source/references.rst b/docs/source/references.rst new file mode 100644 index 000000000..1fe8c27f5 --- /dev/null +++ b/docs/source/references.rst @@ -0,0 +1,72 @@ + +========== +References +========== + +.. rubric:: Footnotes + +.. [open_source_realities] The two main goals behind Temoa are transparency and + repeatability, hence the MIT license. Unfortunately, there are some harsh + realities in the current climate of energy modeling, so this license is not a + guarantee of openness. This documentation touches on the issues involved in + the final section. + +.. [efficiency_table] The efficiency parameter is often referred to as the + efficiency table, due to how it looks after even only a few entries in the + Pyomo input "dot dat" file. + +.. [glpk_presolve] Circa 2013, GLPK uses more memory than commercial + alternatives and has vastly weaker presolve capabilities. + +.. [esom_definition] For a more in-depth description of energy system + optimization models (ESOMs) and guidance on how to use them, please see: + DeCarolis et al. (2017) "Formalizing best practice for energy system + optimization modelling", Applied Energy, 194: 184-198. + +.. [web_browser_svg] SVG support in web browsers is currently hit or miss. The + most recent versions of Chromium, Google Chrome, and Mozilla Firefox support + SVG well enough for Temoa's current use of SVG. + +.. [return_expression] A word on `return` expressions in Pyomo: in most + contexts a relational expression is evaluated instantly. However, in Pyomo, + a relational expression returns an `expression` object. That is, `'M.aVar >= + 5'` does not evaluate to a boolean *true* or *false*, and Pyomo will + manipulate it into the final LP formulation. + +.. [abstract_model] In contrast to a 'concrete' model, an abstract algebraic + formulation describes the general equations of the model, but requires + modeler-specified input data before it can compute any results. + +.. |'''| replace:: ``'``\ ``'``\ ``'`` + +.. _GNU Linear Programming Kit: https://www.gnu.org/software/glpk/ +.. _WinGLPK: https://winglpk.sourceforge.net/ +.. _Github repo: https://github.com/TemoaProject/temoa/ +.. _Temoa model: https://github.com/TemoaProject/temoa/ +.. _temoaproject.org: https://temoaproject.org/ +.. _example data sets: https://datadocs.openenergyoutlook.org/en/latest/datasets.html + +.. _various: http://xlinux.nist.gov/dads/HTML/optimization.html +.. _available: https://web.stanford.edu/\~boyd/cvxbook/ +.. _online: https://en.wikipedia.org/wiki/Optimization_problem +.. _sources: https://en.wikipedia.org/wiki/Mathematical_optimization +.. _GAMS: http://www.gams.com/ +.. _AMPL: http://www.ampl.com/ +.. _PDF: https://assets.temoaproject.org/pdf_docs/temoa_docs.pdf +.. _HTML: https://docs.temoaproject.org/en/latest/ +.. _GitHub Issue tracker: https://github.com/TemoaProject/temoa/issues +.. _HTML version: https://docs.temoaproject.org/en/latest/ +.. _code smell: https://en.wikipedia.org/wiki/Code_smell +.. _PEP 8: http://www.python.org/dev/peps/pep-0008/ +.. _PEP 3120: http://www.python.org/dev/peps/pep-3120/ +.. _list comprehension: http://docs.python.org/tutorial/datastructures.html\#list-comprehensions +.. _lambda function: http://docs.python.org/tutorial/controlflow.html\#lambda-forms +.. _generally accepted relative rates: http://www.forecasts.org/inflation.htm +.. _Pull Request: https://help.github.com/articles/using-pull-requests +.. _quick start guide: https://rogerdudler.github.io/git-guide/ +.. _sqlite: https://www.sqlite.org/ +.. _Graphviz: https://graphviz.org/ +.. _ruff: https://docs.astral.sh/ruff/ + +.. bibliography:: References.bib +.. _GitHub Issues: https://github.com/TemoaProject/temoa/issues diff --git a/docs/source/set_desc_and_tables.rst b/docs/source/set_desc_and_tables.rst new file mode 100644 index 000000000..29818d487 --- /dev/null +++ b/docs/source/set_desc_and_tables.rst @@ -0,0 +1,113 @@ +In a mathematical model like Temoa, sets are used to index parameters and +variables. To enable the modeler to translate between the algebraic formulation, +input database, and model code, we provide the mathematical notation used in the +model formulation, as well as the associated names in the +Python code and database schema in the tables below. The code representation is +more verbose than the algebraic version, using full words. + +Our discussion of sets is broken down into several logical categories to make it +easier to parse. The **first group of sets pertains to Temoa's treatment of time**, +as shown below. **Note:** Some entries in the "Database Table" column are +comma-separated. In those cases, the first element refers to the name of the database +table, and the second refers to specific column within the database table used to +identify a specific subset. For example, in the first table below, :code:`time_period` +is the master set and :code:`time_exist` is a subset that defines the user-defined +model time periods to define historical technology vintages that exist prior to +the model's time horizon for optimization. + + +.. csv-table:: + :header: "Set", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{P}^e`", ":code:`time_period, flag = e`", ":code:`time_exist`", "model periods prior to the beginning of the optimization time horizon" + ":math:`\text{P}^f`", ":code:`time_period, flag = f`", ":code:`time_future`", "model periods within the optimization time horizon" + ":math:`{}^*\text{P}^o`", ":code:`time_period`", ":code:`time_optimize`", "model time periods to optimize, (:math:`\text{P}^f - \text{max}(\text{P}^f)`); the last time period is removed as it represents the end of the final period" + ":math:`{}^*\text{V}`", ":code:`time_period`", ":code:`vintage_exist`, :code:`vintage_optimize`, :code:`vintage_all`", "tech vintages, (:math:`\text{P}^e \cup \text{P}^o`), derived from time period set and used to track technology vintages across time periods" + ":math:`\text{D}`", ":code:`time_of_day`", ":code:`time_of_day`", "intraday time divisions; each entry carries an :code:`hours` value (default 1) specifying how many hours it represents" + ":math:`\text{S}`", ":code:`time_season`", ":code:`time_season`", "intra-annual divisions (e.g. seasons or representative days); each entry carries a :code:`segment_fraction` giving its share of the year; ordered by :code:`sequence` column" + "", ":code:`time_season_sequential`", ":code:`time_season_sequential, ordered_season_sequential`", "superimposed sequential seasons used for seasonal storage and inter-season ramping; only required when :code:`time_sequencing = 'representative_periods'`" + +The sets in the table below define Temoa's representation of **regions**. + +.. csv-table:: + :header: "Set", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{R}`", ":code:`region`", ":code:`regions`", "distinct geographical regions" + "", ":code:`region`", ":code:`regional_indices`", "set of all the possible combinations of interregional exchanges plus original region indices" + "", "", ":code:`regional_global_indices`", "set of all used combinations of interregional exchanges plus original region indices and groups" + +The sets below define how technologies are represented within Temoa. Because technologies +can serve many different functions across an energy system, we need to define a large +number of **technology subsets**. + +.. csv-table:: + :header: "Set", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`{}^*\text{T}`", ":code:`technology`", ":code:`tech_all`", "all technologies to be modeled (in v4, all technologies are production-type: :math:`T = T^p`)" + ":math:`\text{T}^p`", ":code:`technology, flag LIKE 'p%'`", ":code:`tech_production`", "all production technologies, including baseload and storage (:math:`{T}^b \cup {T}^s \subset {T}^p`)" + ":math:`\text{T}^b`", ":code:`technology, flag = pb`", ":code:`tech_baseload`", "baseload electric generators, which have constant output across intraday time segments (:math:`{T}^b \subset T`)" + ":math:`\text{T}^s`", ":code:`technology, flag = ps`", ":code:`tech_storage`", "all storage technologies (:math:`{T}^s \subset T`)" + ":math:`\text{T}^a`", ":code:`technology, annual = 1`", ":code:`tech_annual`", "technologies that produce constant annual output (:math:`{T}^a \subset T`)" + ":math:`\text{T}^{res}`", ":code:`technology, reserve = 1`", ":code:`tech_reserve`", "electric generators contributing to the reserve margin requirement (:math:`{T}^{res} \subset T`)" + ":math:`\text{T}^c`", ":code:`technology, curtail = 1`", ":code:`tech_curtailment`", "technologies with curtailable output and no upstream cost (:math:`{T}^c \subset (T - T^{res})`)" + ":math:`\text{T}^f`", ":code:`technology, flex = 1`", ":code:`tech_flex`", "technologies producing excess commodity flows (:math:`{T}^f \subset T`)" + ":math:`\text{T}^x`", ":code:`technology, exchange = 1`", ":code:`tech_exchange`", "technologies used for interregional commodity flows (:math:`{T}^x \subset T`)" + ":math:`\text{T}^{ur}`", ":code:`ramp_up_hourly`", ":code:`tech_upramping`", "electric generators with a ramp up hourly rate limit; derived from :code:`ramp_up_hourly` table (:math:`{T}^{ur} \subset T`)" + ":math:`\text{T}^{dr}`", ":code:`ramp_down_hourly`", ":code:`tech_downramping`", "electric generators with a ramp down hourly rate limit; derived from :code:`ramp_down_hourly` table (:math:`{T}^{dr} \subset T`)" + ":math:`\text{T}^{ret}`", ":code:`technology, retire = 1`", ":code:`tech_retirement`", "technologies allowed to retire before end of life (:math:`{T}^{ret} \subset (T - T^{u})`)" + ":math:`\text{T}^u`", ":code:`technology, unlim_cap = 1`", ":code:`tech_uncap`", "technologies that have no bound on capacity (:math:`{T}^u \subset (T - T^{res})`)" + ":math:`\text{T}^{ss}`", ":code:`technology, flag = 'ps' AND seas_stor = 1`", ":code:`tech_seasonal_storage`", "seasonal storage technologies; requires both storage flag and seas_stor column (:math:`{T}^{ss} \subset T^s`)" + "", ":code:`tech_group`", ":code:`tech_group_names`", "named groups for use in group constraints" + "", ":code:`tech_group_member`", ":code:`tech_group_members`", "each technology belonging to each group" + ":math:`\text{T}^e`", ":code:`existing_capacity`", ":code:`tech_exist`", "existing technologies with a past vintage (:math:`{T}^e \subset T`)" + +The sets below define **commodities** that are consumed and produced by different energy +technologies. + +.. csv-table:: + :header: "Set", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + ":math:`\text{C}^d`", ":code:`commodity, flag = d`", ":code:`commodity_demand`", "end-use demand commodities, representing the final consumer demands" + ":math:`\text{C}^e`", ":code:`commodity, flag = e`", ":code:`commodity_emissions`", "emission commodities (e.g., :math:`\text{CO}_\text{2}` :math:`\text{NO}_\text{x}`); filtered by flag" + ":math:`\text{C}^p`", ":code:`commodity, flag IN (p, wp, s, a, wa)`", ":code:`commodity_physical`", "superset of physical, source, annual, and their waste variants; includes all non-demand, non-emission commodities" + ":math:`\text{C}^w`", ":code:`commodity, flag IN (w, wa , wp)`", ":code:`commodity_waste`", "commodity whose production can be greater than its consumption; can be physical, annual, or neither (not balanced, all wasted)" + ":math:`\text{C}^a`", ":code:`commodity, flag IN (a, wa)`", ":code:`commodity_annual`", "commodities whose flows are only balanced over each period, not per-timeslice (:math:`\text{C}^a \subset \text{C}^p`)" + ":math:`{}^*\text{C}^l`", "", ":code:`commodity_flex`", "derived set of commodities produced by a flex technology (:math:`\text{C}^l \subset \text{C}^p`); auto-populated from :code:`tech_flex` outputs" + ":math:`\text{C}^s`", ":code:`commodity, flag = s`", ":code:`commodity_source`", "primary source commodities, not balanced by :code:`CommodityBalance_constraint`" + ":math:`{}^*\text{C}^c`", "", ":code:`commodity_carrier`", "union of physical, demand, and waste commodities, (:math:`\text{C}_p \cup \text{C}_d \cup \text{C}^w`)" + ":math:`{}^*\text{C}`", "", ":code:`commodity_all`", "union of all commodity sets; union of carrier and emissions commodities" + +There is an additional set that defines **operators (=, <, >)**. While not strictly +necessary, defining these operators as a set allows modelers to express constraints +more efficiently. + +.. csv-table:: + :header: "Set", "Database Table", "Model Element", "Notes" + :widths: 15, 20, 25, 40 + + "", ":code:`operator`", ":code:`operator`", "constraint operators" + +As indicated below, there are additional sets that are derived within the model +code and thus do not appear in the database schema. + +.. csv-table:: + :header: "Set", "Model Element", "Notes" + :widths: 15, 25, 60 + + "", ":code:`tech_with_capacity`", "technologies eligible for capacitization; computed as tech_all - tech_uncap" + "", ":code:`tech_or_group`", "technologies or groups combined; union of tech_group_names | tech_all" + ":math:`{}^*\text{C}^c`", ":code:`commodity_carrier`", "physical energy carriers and end-use demands; union of physical, demand, and waste commodities" + ":math:`{}^*\text{C}`", ":code:`commodity_all`", "union of all commodity sets; union of carrier and emissions commodities" + ":math:`\text{T}^e`", ":code:`tech_exist`", "technologies with existing capacity; derived from existing_capacity table" + +There are also python dictionaries and sets used to specify internal data +structures during model construction and are not considered formal model elements: + +- :code:`process_inputs`, :code:`process_outputs`, :code:`process_loans` +- :code:`active_flow_rpsditvo`, :code:`active_flow_rpitvo` +- Various vintage and operational tracking dictionaries +- Time sequencing dictionaries (:code:`time_next`, :code:`time_next_sequential`) diff --git a/docs/source/stochastics.rst b/docs/source/stochastics.rst new file mode 100644 index 000000000..6f01ab88e --- /dev/null +++ b/docs/source/stochastics.rst @@ -0,0 +1,107 @@ + +.. _stochastics: + +Stochastic Programming +====================== + +The Stochastics extension in Temoa v4 provides support for stochastic programming using the `mpi-sppy `_ library. This allows for decision-making under uncertainty by considering multiple scenarios simultaneously and finding an optimal "first-stage" decision that minimizes the expected cost over all scenarios. + +Stochastic programming is particularly useful for modeling uncertainties in future costs, demands, or resource availability. + +Dependencies +------------ + +The stochastics extension requires the ``mpi-sppy`` package. You can install it using ``uv``: + +.. code-block:: bash + + uv add mpi-sppy + +Or using ``pip``: + +.. code-block:: bash + + pip install mpi-sppy + +Configuration +------------- + +To run Temoa in stochastic mode, you need to modify your main configuration TOML file and provide an additional stochastic configuration file. + +Main Configuration TOML +~~~~~~~~~~~~~~~~~~~~~~~ + +Set the ``scenario_mode`` to ``"stochastic"`` and add a ``[stochastic]`` section: + +.. code-block:: toml + + scenario_mode = "stochastic" + + # ... other standard options ... + + [stochastic] + # Path to the stochastic configuration file, relative to this file + stochastic_config = "stochastic_config.toml" + +Stochastic Configuration TOML +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The stochastic configuration file defines the scenarios, their probabilities, and the data perturbations associated with each scenario. + +.. code-block:: toml + + # Define the scenarios + [scenarios] + # Each key is a scenario name, and the value is its probability + # Probabilities must sum to 1.0 + low_cost = 0.5 + high_cost = 0.5 + + # Define perturbations for a specific scenario + [[perturbations]] + scenario = "low_cost" + table = "cost_variable" + # Filter specifies which rows in the table to perturb + filter = { tech = "IMPHCO1" } + # Action can be "multiply", "add", or "set" (defaults to "set") + action = "multiply" + value = 0.5 + + [[perturbations]] + scenario = "high_cost" + table = "cost_variable" + filter = { tech = "IMPHCO1" } + action = "multiply" + value = 1.5 + +Perturbation Options +^^^^^^^^^^^^^^^^^^^^ + +Currently, the following fields are required for each perturbation: + +* **scenario**: The name of the scenario to which this perturbation applies. +* **table**: The Temoa parameter (database table) to perturb (e.g., ``cost_variable``, ``demand``, ``capacity_factor_process``). +* **filter**: A dictionary of column-value pairs used to identify specific rows. Since the extension uses the dynamic manifest from ``HybridLoader``, any column belonging to the table's index can be used for filtering. +* **action**: The operation to perform. Supported values: + * ``multiply``: Multiply the base value by ``value``. + * ``add``: Add ``value`` to the base value. + * ``set``: Replace the base value with ``value``. +* **value**: The numeric value used in the perturbation action. + +How it Works +------------ + +When running in stochastic mode, Temoa: + +1. Loads the base data from the input database. +2. Identifies the "first-stage" variables. In the current implementation, all decisions in the first time period are considered first-stage. +3. Orchestrates multiple scenario runs using the ``mpi-sppy`` Extensive Form (EF) solver. +4. For each scenario, the ``scenario_creator`` applies the specified perturbations to the base data and builds a Pyomo model instance. +5. The EF solver binds the first-stage variables across all scenarios (non-anticipativity constraints) and optimizes the total expected cost. +6. The terminal output reports the Stochastic Expected Value. + +Limitations +----------- + +* **Two-Stage Only**: While ``mpi-sppy`` supports multi-stage stochastic programming, the current Temoa integration is tailored for two-stage problems where the first time period constitutes the first stage. +* **Result Persistence**: Currently, only the expected objective value and summary logs are produced. Detailed per-scenario result persistence to the database is under development. diff --git a/docs/source/unit_checking.rst b/docs/source/unit_checking.rst new file mode 100644 index 000000000..79ec5c1fb --- /dev/null +++ b/docs/source/unit_checking.rst @@ -0,0 +1,426 @@ +.. _unit_checking: + +Units Checking +============== + +The Temoa v4.0 database schema includes comprehensive units checking capabilities to ensure consistency +and accuracy throughout the model. Unit checking validates that all units are properly formatted, +dimensionally consistent, and align correctly across related tables. + +.. important:: + The units expressed and checked via ``pint`` do not "follow the values" through the mathematics + of the model. Unit checking is a **pre-processing validation layer** used to support documentation + and catch input errors. **The units are not used in the model calculations themselves.** + +Unit Propagation to Outputs +--------------------------- + +Starting with v4.0, units defined in input tables are **automatically propagated** to output tables +when model results are written. This provides traceability of units from inputs through results. + +**How It Works**: + +- ``output_flow_out`` / ``output_flow_in``: Units from ``commodity.units`` for the output/input commodity +- ``output_built_capacity`` / ``output_net_capacity`` / ``output_retired_capacity``: Units from ``existing_capacity.units`` +- ``output_emission``: Units from ``commodity.units`` for the emission commodity +- ``output_cost``: Common currency unit extracted from cost input tables +- ``output_storage_level``: Units from the stored commodity + +**Backward Compatibility**: + +- Databases without unit data in input tables will write ``NULL`` units to outputs +- Databases with older schemas (no ``units`` column on output tables) continue to work normally +- Unit propagation is automatic and requires no configuration + +Overview +-------- + +The unit checking system uses the Python package ``pint`` to perform unit validation and dimensional +analysis. This leverages pint's built-in unit registry to validate units with varying prefixes +(e.g., PJ, TJ, GJ) and enables future extensions. + +The basis for most unit comparisons comes from: + +- **commodity table**: Defines native units for each commodity (energy network nodes) +- **efficiency table**: Infers technology units via input/output ratios +- **capacity_to_activity table**: Provides conversion factors for capacity-based measures + +Enabling Unit Checking +----------------------- + +Via Configuration File +~~~~~~~~~~~~~~~~~~~~~~ + +Add to your ``config.toml``: + +.. code-block:: toml + + check_units = true + +Via CLI +~~~~~~~ + +Standalone unit checking for any database: + +.. code-block:: bash + + # Basic usage + temoa check-units path/to/database.sqlite + + # Custom output directory + temoa check-units database.sqlite --output ./reports + + # Silent mode (for scripting) + temoa check-units database.sqlite --silent + +How It Works +------------ + +The unit checker performs five sequential tests: + +1. **Database Version Check**: Ensures database is v4.0+ (checks ``metadata`` table) +2. **Units Entry Validation**: Checks for illegal characters, proper formatting, registry membership +3. **Technology I/O Alignment**: Validates ``efficiency`` table units match commodities +4. **Related Tables**: Checks tables referencing technologies for unit consistency +5. **Cost Tables**: Validates cost units and dimensional alignment + +Expressing Units +---------------- + +Format Requirements +~~~~~~~~~~~~~~~~~~~ + +.. warning:: + **CRITICAL**: The unit checker uses regex parsing with strict format requirements! + +Units in **efficiency** and **cost** tables MUST use ratio format: + +.. code-block:: text + + Numerator / (Denominator) + + [V] CORRECT: PJ / (PJ) + [V] CORRECT: Mdollar / (PJ^2 / GW) + [X] WRONG: PJ/PJ (no parentheses) + [X] WRONG: Mdollar * GW / (PJ^2) (denominator incomplete) + +The denominator **MUST be fully enclosed in parentheses**. The regex only captures content +within ``( )`` after the ``/``. + +Other tables should use plain entries: + +.. code-block:: text + + [V] PJ + [V] petajoules + [V] GW + [V] Mt / (GW) (if ratio needed) + +Custom Units Registry +~~~~~~~~~~~~~~~~~~~~~ + +Temoa extends pint's default registry with domain-specific units: + +- ``dollar`` (or ``USD``) +- ``euro`` (or ``EUR``) +- ``passenger`` +- ``seat`` (for passenger-miles, seat-miles) +- ``ethos`` (dimensionless source commodity) + +Common Footguns and Pitfalls +----------------------------- + +1. Missing Parentheses in Ratios +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: Forgetting to parenthesize the denominator + +.. code-block:: text + + [X] cost_invest units: "Mdollar / PJ^2 / GW" + + Error: RATIO_ELEMENT regex doesn't match, only "PJ^2" captured + +**Solution**: Always use parentheses + +.. code-block:: text + + [V] cost_invest units: "Mdollar / (PJ^2 / GW)" + +2. Capacity vs Energy Units +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: Using energy units (GW*year, kWh) in capacity tables + +.. code-block:: text + + [X] existing_capacity units: "GW * year" + + Error: Energy units (not capacity) in capacity table + +**Solution**: Use power units (negative time dimension) + +.. code-block:: text + + [V] existing_capacity units: "GW" ([time]^-3 = power) + [X] existing_capacity units: "GWh" ([time]^-2 = energy) + +**Physics**: +- Capacity = Power (W, kW, MW, GW) -> ``[time]^-3`` +- Energy = Power × Time (Wh, kWh) -> ``[time]^-2`` + +3. Cost Table Units and C2A +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: Not accounting for capacity_to_activity conversion + +For capacity-based costs, the expected denominator is: + +.. math:: + + \text{expected\_measure} = \text{output\_units} \times \text{C2A} \times \text{year} + +**Example**: +- Output commodity: ``ELC`` = ``PJ`` +- C2A: ``PJ / (GW * year)`` +- Expected: ``PJ * [PJ/(GW*year)] * year`` = ``PJ^2/GW`` + +.. code-block:: text + + [V] cost_invest: "Mdollar / (PJ^2 / GW)" + [V] cost_fixed: "Mdollar / (PJ^2 / GW / year)" (adds /year for period-based) + +4. period_based Flag +~~~~~~~~~~~~~~~~~~~~ + +**Problem**: Misunderstanding period_based vs capacity_based + +These flags are **orthogonal**: + +- ``capacity_based``: Multiply expected units by C2A +- ``period_based``: Divide expected units by year + +``cost_fixed`` is **BOTH** capacity_based AND period_based! + +.. csv-table:: + :header: "Table", "capacity_based", "period_based", "Units Example" + + "cost_invest", "True", "False", "Mdollar / (PJ^2 / GW)" + "cost_fixed", "True", "True", "Mdollar / (PJ^2 / GW / year)" + "cost_variable", "False", "False", "Mdollar / (PJ)" + "cost_emission", "False", "False", "Mdollar / (Mt)" + +5. Schema Variations: tech vs tech_or_group +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: v4.0 limit tables use ``tech_or_group`` not ``tech`` + +The unit checker automatically detects which column exists, but be aware: + +.. code-block:: sql + + -- v3.1 schema + SELECT tech, units FROM limit_capacity... + + -- v4.0 schema + SELECT tech_or_group, units FROM limit_capacity... + +Tables affected: ``limit_activity``, ``limit_capacity``, ``limit_new_capacity`` + +6. Technology Output Uniformity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Problem**: Same technology with multiple output commodities using different units + +.. warning:: + All instances of a technology MUST produce output in the **same units**, even if + output commodities differ! + +**Why**: Constraints span regions and output commodities, requiring a single unit of measure. + +.. code-block:: text + + [X] WRONG: + tech E01, output ELC: PJ + tech E01, output HEAT: GJ (different units!) + + [V] CORRECT: + tech E01, output ELC: PJ + tech E01, output HEAT: PJ (same units) + +Testing and Troubleshooting +---------------------------- + +Testing Units Manually +~~~~~~~~~~~~~~~~~~~~~~~ + +Test unit validity outside the model: + +.. code-block:: python + + from temoa.model_checking.unit_checking import ureg + + # Check if units exist in registry + print('PJ' in ureg) # True + print('catfood' in ureg) # False + + # Parse and check units + u = ureg('Mdollar / (PJ^2 / GW)') + print(u.dimensionality) # {[currency]: 1, [time]: 1, ...} + + # Check currency dimension (for cost tables) + print('[currency]' in u.dimensionality) # True + +Common Error Messages +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + "Units lack currency dimension" + -> Cost table units don't contain currency (dollar/euro) + + "Energy units (not capacity) in capacity table" + -> Using energy units (kWh, GWh, etc.) instead of capacity units (GW, MW) + + "Non-matching measure unit" + -> Units don't match expected format after accounting for C2A/period + + "failed to process query: no such column: tech" + -> Using old SQL queries on v4.0 schema (should auto-fix now) + +Reading Unit Check Reports +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Reports are saved to ``output_path/unit_check_reports/units_check_TIMESTAMP.txt``: + +.. code-block:: text + + ======== Units Check 1 (DB Version): Started ======== + Units Check 1 (DB Version): Passed + + ======== Units Check 2 (Units Entries in Tables): Started ======== + existing_capacity: Energy units (not capacity) in capacity table: GW*year at rows: 1, 2, 3 + + ======== Units Check 3 (Tech I/O via Efficiency Table): Started ======== + Efficiency units conflict with associated commodity for Technology E01 near row 1 + + ======== Units Check 4 (Related Tables): Started ======== + limit_capacity: Non-standard units for tech E01 (expected GW) got: MW at rows: 5, 6 + + ======== Units Check 5 (Cost Tables): Started ======== + cost_invest: Non-matching measure unit for tech/comm: E01 + Table entry: Mdollar / (PJ) + Expected: petajoule ** 2 / gigawatt + Found: petajoule at rows: 1, 2, 3 + +Each check section shows: +- Which table/tech has issues +- What was expected vs what was found +- Row numbers for easy correction + +Tables Checked +-------------- + +v4.0 Schema Coverage +~~~~~~~~~~~~~~~~~~~~ + +.. csv-table:: + :header: "Table", "Has Units", "Check 2", "Check 3", "Check 4", "Check 5" + :widths: 25, 12, 10, 12, 15, 10 + + "capacity_to_activity", "[V]", "[V]", "", "(used)", "(used)" + "commodity", "[V]", "[V]", "[V]", "", "" + "construction_input", "[V]", "[V]", "", "", "" + "cost_emission", "[V]", "[V]", "", "", "[V]" + "cost_fixed", "[V]", "[V]", "", "", "[V]" + "cost_invest", "[V]", "[V]", "", "", "[V]" + "cost_variable", "[V]", "[V]", "", "", "[V]" + "demand", "[V]", "[V]", "", "[V]", "" + "efficiency", "[V]", "[V]", "[V]", "", "" + "emission_activity", "[V]", "[V]", "", "", "" + "emission_embodied", "[V]", "[V]", "", "", "" + "emission_end_of_life", "[V]", "[V]", "", "", "" + "end_of_life_output", "[V]", "[V]", "", "", "" + "existing_capacity", "[V]", "[V]", "", "[V]", "" + "lifetime_process", "[V]", "[V]", "", "", "" + "lifetime_tech", "[V]", "[V]", "", "", "" + "loan_lifetime_process", "[V]", "[V]", "", "", "" + "limit_activity", "[V]", "[V]", "", "[V]", "" + "limit_capacity", "[V]", "[V]", "", "[V]", "" + "limit_emission", "[V]", "[V]", "", "", "" + "limit_new_capacity", "[V]", "[V]", "", "[V]", "" + "limit_resource", "[V]", "[V]", "", "", "" + +**Check Legend**: +- Check 2: Standard validation (format, characters, registry) +- Check 3: Technology I/O alignment via Efficiency table +- Check 4: Related tables consistency +- Check 5: Cost tables validation + +Best Practices +-------------- + +1. **Start with commodities**: Define commodity units first, then build tech efficiency ratios +2. **Use standard prefixes**: Stick to k, M, G, T prefixes (kilo, mega, giga, tera) +3. **Be consistent**: Use the same unit style across your database (e.g., always "PJ" not mix of "PJ"/"petajoule") +4. **Test early**: Run unit checker on partial databases during development +5. **Document assumptions**: Use notes fields to explain unusual unit choices +6. **Reference implementation**: See ``temoa/tutorial_assets/utopia.sql`` for a fully compliant example + +Quick Reference +--------------- + +Common Unit Patterns +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: text + + # Energy commodities + commodity units: PJ, TJ, GJ, MWh + + # Capacity + existing_capacity units: GW, MW, kW + + # Efficiency (energy tech) + efficiency units: PJ / (PJ) (dimensionless) + efficiency units: PJ / (Mt) (energy from mass) + + # C2A conversion + capacity_to_activity units: PJ / (GW * year) + + # Costs (capacity-based tech with PJ output, PJ/(GW*year) C2A) + cost_invest units: Mdollar / (PJ^2 / GW) + cost_fixed units: Mdollar / (PJ^2 / GW / year) + cost_variable units: Mdollar / (PJ) + + # Emissions + emission_activity units: Mt / (PJ) + emission_embodied units: Mt / (GW) + +Dimension Reference +~~~~~~~~~~~~~~~~~~~ + +Pint tracks seven base dimensions: + +- ``[length]``: meter, km, mile +- ``[mass]``: kg, tonne, Mt +- ``[time]``: second, year +- ``[current]``: ampere +- ``[temperature]``: kelvin +- ``[substance]``: mole +- ``[luminosity]``: candela +- ``[currency]``: dollar, euro (Temoa extension) + +Derived dimensions: + +- Energy: ``[length]^2 * [mass] / [time]^2`` (joule, kWh) +- Power: ``[length]^2 * [mass] / [time]^3`` (watt, GW) +- Force: ``[length] * [mass] / [time]^2`` (newton) + +See Also +-------- + +- :ref:`database_schema` - v4.0 schema reference +- :ref:`configuration` - Configuration file format +- ``temoa/model_checking/unit_checking/`` - Source code +- ``tests/test_unit_checking.py`` - Unit tests with examples diff --git a/docs/source/visualization.rst b/docs/source/visualization.rst new file mode 100644 index 000000000..f8d29b4ef --- /dev/null +++ b/docs/source/visualization.rst @@ -0,0 +1,76 @@ +============= +Visualization +============= + +Network Diagrams +---------------- + +Since the Temoa model consists of an energy network in which technologies are connected +by the flow of energy commodities, a directed network graph represents an excellent way +to visualize a given energy system representation in a Temoa-compatible input database. + +Temoa provides two types of network visualizations: + +1. **Interactive HTML Network Graphs** - Dynamic, explorable visualizations showing commodity flows and technology connections +2. **Graphviz Diagrams** - Static SVG/DOT format diagrams showing the energy system structure + +Generating Network Visualizations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The easiest way to generate these diagrams is to enable visualization options in your +configuration TOML file. Add the following to your config file: + +.. parsed-literal:: + # Enable interactive HTML network graphs (requires source_trace = true) + source_trace = true + plot_commodity_network = true + + # Enable Graphviz static diagrams + graphviz_output = true + +When these options are enabled, Temoa will automatically generate visualization files +in the output directory during model execution. + +**Interactive Network Graphs** will be created as HTML files (one per time period) that +you can open in a web browser. These provide an interactive view where you can: + +- Pan and zoom the network +- Click on nodes to see details +- Toggle between commodity-centric and technology-centric views +- Filter by sector using color-coded legends + +**Graphviz Diagrams** will be created as both ``.dot`` (source) and ``.svg`` (rendered) +files in a subdirectory within your output folder. These provide static visualizations +showing: + +- Full energy system maps +- Capacity and activity results per model time period +- Technology interconnections via commodity flows + +Example Visualizations +~~~~~~~~~~~~~~~~~~~~~~ + +**Interactive Network Graph** + +The interactive HTML network graphs provide dynamic exploration with pan, zoom, and filtering capabilities: + +.. raw:: html + + + +*Interactive network graph for the 'utopia' test system in 1990. You can pan, zoom, click nodes for details, and toggle between commodity-centric and technology-centric views. These files are automatically generated when* ``source_trace = true`` *and* ``plot_commodity_network = true`` *are set in the configuration file.* + +**Static Graphviz Diagram** + +Graphviz also generates static SVG diagrams showing the energy system structure: + +.. figure:: images/results1990.* + :align: center + :figclass: center + :width: 100% + + Static Graphviz diagram showing the optimal installed capacity and commodity flows + for the 'utopia' test system in 1990. Technologies are shown as boxes, + commodities as circles, with arrows indicating energy flows. These diagrams + are automatically generated when ``graphviz_output = true`` is set in the + configuration file. diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 2d4c34998..000000000 --- a/environment.yml +++ /dev/null @@ -1,49 +0,0 @@ -# NOTE: This environment is UNTESTED. It is just a couple version updates on the old conda environment -# file. Development has moved forward with pip and the requirements.txt -# file without using conda. Feedback/corrections/pull requests to tighten up this file for conda -# users are welcome. - -name: temoa-py3 -channels: - - defaults - - conda-forge -dependencies: - # Requirements for core model functionality - - python=3.12 - - pyomo=6.7 -# - pyomo.extras - - xlwt - - ipython - - matplotlib - - pandas - - numpy - - scipy - - joblib - - salib - - pydoe - - pyutilib -# - glpk - - python-graphviz - - ipykernel - - jupyter - - jupyter_contrib_nbextensions - - seaborn - - tabulate - - xlsxwriter - - plotly - - pyam - # cbc solver below cannot be installed via Conda on Windows -# - coincbc - - pytest - - deprecated - - openpyxl - - networkx - - gravis - - gurobi - - # Below required to update documentation - - sphinx - - sphinx_rtd_theme - - sphinxcontrib-htmlhelp - - sphinxcontrib-serializinghtml - - sphinxcontrib-bibtex diff --git a/environment_minimal.yml b/environment_minimal.yml deleted file mode 100644 index a6b2c257d..000000000 --- a/environment_minimal.yml +++ /dev/null @@ -1,49 +0,0 @@ -# NOTE: This environment is UNTESTED. It is just a couple version updates on the old conda environment -# file. Development has moved forward with pip and the requirements.txt -# file without using conda. Feedback/corrections/pull requests to tighten up this file for conda -# users are welcome. - -name: temoa_min -channels: - - defaults - - conda-forge -dependencies: - # Requirements for core model functionality - - python=3.12 - - pyomo=6.7 -# - pyomo.extras - - xlwt -# - ipython -# - matplotlib - - pandas - - numpy - - scipy - - joblib - - salib - - pydoe - - pyutilib -# - glpk -# - python-graphviz -# - ipykernel -# - jupyter -# - jupyter_contrib_nbextensions -# - seaborn - - tabulate - - xlsxwriter -# - plotly - - pyam - # cbc solver below cannot be installed via Conda on Windows -# - coincbc - - pytest - - deprecated - - openpyxl - - networkx - - gravis - - gurobi - - # Below required to update documentation -# - sphinx -# - sphinx_rtd_theme -# - sphinxcontrib-htmlhelp -# - sphinxcontrib-serializinghtml -# - sphinxcontrib-bibtex diff --git a/main.py b/main.py deleted file mode 100644 index 9ad4b4e85..000000000 --- a/main.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -Entry point for running the model. -""" -import argparse -import logging -import os -import sys -from datetime import datetime -from pathlib import Path - -from deprecated import deprecated - -import definitions -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_model import TemoaModel -from temoa.temoa_model.temoa_sequencer import TemoaMode, TemoaSequencer -from temoa.version_information import TEMOA_MAJOR, TEMOA_MINOR - -# Written by: J. F. Hyink -# jeff@westernspark.us -# https://westernspark.us -# Created on: 7/18/23 - -logger = logging.getLogger(__name__) - - -@deprecated('currently deprecated functionality') -def runModelUI(config_filename): - """This function launches the model run from the Temoa GUI""" - raise NotImplementedError - # solver = TemoaSolver(model, config_filename) - # for k in solver.createAndSolve(): - # yield k - # # yield " " * 1024 - - -def runModel(arg_list: list[str] | None = None) -> TemoaModel | None: - """ - Start the program - :param arg_list: optional arg_list - :return: A TemoaModel instance (if asked for), more likely None - """ - options = parse_args(arg_list=arg_list) - mode = TemoaMode.BUILD_ONLY if options.build_only else None - ts = TemoaSequencer( - config_file=options.config_file, - output_path=options.output_path, - mode_override=mode, - silent=options.silent, - ) - result = ts.start() - return result - - -def parse_args(arg_list: list[str] | None) -> argparse.Namespace: - """ - Parse the command line args (CLA) if None is passed in (normal operation) or the arg_list, - if provided :param arg_list: default None --> process sys.argv :return: options Namespace - """ - parser = argparse.ArgumentParser() - parser.add_argument( - '--config', - help='Path to file containing configuration information.', - action='store', - dest='config_file', - default=None, - ) - parser.add_argument( - '-b', - '--build_only', - help='Build and return an unsolved TemoaModel instance.', - action='store_true', - dest='build_only', - ) - parser.add_argument( - '-s', '--silent', help='Silent run. No prompts.', action='store_true', dest='silent' - ) - parser.add_argument( - '-d', - '--debug', - help='Set logging level to DEBUG to see debugging output in log file.', - action='store_true', - dest='debug', - ) - parser.add_argument( - '-o', - '--output_path', - help='Set the path for log and program outputs to an existing directory. ' - 'Default is time-stamped folder in output_files.', - action='store', - dest='output_path', - ) - parser.add_argument( - '--how_to_cite', - help='Show citation information for publishing purposes.', - action='store_true', - dest='how_to_cite', - ) - parser.add_argument( - '-v', '--version', help='Show current Temoa version', action='store_true', dest='version' - ) - - options = parser.parse_args(args=arg_list) # dev note: The default (if None) is sys.argv - - # handle the non-execution options and quit - if options.how_to_cite or options.version: - if options.version: - version = f'{TEMOA_MAJOR}.{TEMOA_MINOR}' - print(f'Temoa Version: {version}') - if options.how_to_cite: - raise NotImplementedError('Need this information...') - sys.exit() - - # validate the output folder if provided, or make the default - output_path: Path - if options.output_path: - if not Path(options.output_path).is_dir(): - raise FileNotFoundError( - f'The selected output path directory {options.output_path} ' - f'could not be located.' - ) - else: - output_path = Path(options.output_path) - else: - output_path = create_output_folder() - # capture it in options - options.output_path = output_path - definitions.set_OUTPUT_PATH(options.output_path) - - # initialize the logging now that option & path are known... - setup_logging(output_path=output_path, debug_level=options.debug) - - # check for config file existence - if not options.config_file: - logger.error( - 'No config file found in CLA. ' - 'Temoa needs a config file to operate, see documentation.' - ) - raise AttributeError('no config file provided.') - else: - # convert it to a Path, if it isn't one already - options.config_file = Path(options.config_file) - if not options.config_file.is_file(): - logger.error('Config file provided: %s is not valid', options.config_file) - raise FileNotFoundError('Config file not found. See log for info.') - - logger.debug('Received Command Line Args: %s', sys.argv[1:]) - - if options.build_only: - logger.info('Build-only selected.') - return options - - -def create_output_folder() -> Path: - """ - create a time-stamped folder as the default catch-all for outputs - :return: Path to default folder - """ - output_path = Path(PROJECT_ROOT, 'output_files', datetime.now().strftime('%Y-%m-%d %H%Mh')) - if not output_path.is_dir(): - output_path.mkdir() - return output_path - - -def setup_logging(output_path: Path, debug_level=False): - # set up logger - if debug_level: - level = logging.DEBUG - else: - level = logging.INFO - logging.getLogger('pyomo').setLevel(logging.WARNING) - logging.getLogger('matplotlib').setLevel(logging.WARNING) - filename = 'log.log' - logging.basicConfig( - filename=os.path.join(output_path, filename), - filemode='w', - format='%(asctime)s | %(module)s | %(levelname)s | %(message)s', - datefmt='%d-%b-%y %H:%M:%S', - level=level, - ) - logger.info('*** STARTING TEMOA ***') - - -if __name__ == '__main__': - options = runModel() diff --git a/notebooks/Network_diagrams.ipynb b/notebooks/Network_diagrams.ipynb deleted file mode 100644 index cf8cf4417..000000000 --- a/notebooks/Network_diagrams.ipynb +++ /dev/null @@ -1,476 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "source": [ - "# NOTE: This is preserved, but needs updating!\n", - "## It needs updating for:\n", - "- location (the imports need to be aligned to this file location)\n", - "- V3 database naming. Opening alongside a version 3 database should help ID erroneous names." - ], - "metadata": { - "collapsed": false - } - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Modify path to database in the cell below and run all cells" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "hide_input": false, - "init_cell": true - }, - "outputs": [], - "source": [ - "db_path = '../data_files/temoa_utopia.sqlite'" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "hide_input": true, - "init_cell": true, - "jupyter": { - "source_hidden": true - } - }, - "outputs": [], - "source": [ - "import nbformat as nbf\n", - "import pandas as pd\n", - "import numpy as np\n", - "import os\n", - "import sqlite3\n", - "import tabulate\n", - "import shutil\n", - "import IPython\n", - "from IPython.display import HTML, display, Markdown, Image\n", - "import ipywidgets as widgets\n", - "from ipywidgets import HBox, VBox, Layout\n", - "import graphviz\n", - "from GraphVizUtil import *\n", - "from GraphVizFormats import *\n", - "import warnings\n", - "warnings.filterwarnings('ignore')\n", - "\n", - "def filter_descriptions(tech_comm_desc):\n", - " try:\n", - " tech_comm_desc = tech_comm_desc.values[0][0].replace('#', '').replace('\"','').replace(\"\\n\",'').strip()\n", - " except:\n", - " tech_comm_desc = 'No description provided'\n", - " return tech_comm_desc\n", - " \n", - "\n", - "def create_args_flowd(df_graph):\n", - " nodes, tech, ltech, to_tech, from_tech = set(), set(), set(), set(), set()\n", - " for ind,row in df_graph.iterrows():\n", - " #descriptions:\n", - " input_comm_des = filter_descriptions(pd.read_sql(\"SELECT comm_desc FROM commodities WHERE comm_name='\" + row['input_comm'] + \"'\", con))\n", - " output_comm_des = filter_descriptions(pd.read_sql(\"SELECT comm_desc FROM commodities WHERE comm_name='\" + row['output_comm'] + \"'\", con))\n", - " tech_des = filter_descriptions(pd.read_sql(\"SELECT tech_desc FROM technologies WHERE tech='\" + row['tech'] + \"'\", con))\n", - "\n", - " if 'ethos' in row['input_comm']:\n", - " ltech.add('\"' + row['tech'] + '\"' + ' [tooltip = \"' + tech_des + '\"]')\n", - " else :\n", - " nodes.add('\"' + row['input_comm'] + '\"' + ' [tooltip = \"' + input_comm_des + '\"]')\n", - " nodes.add('\"' + row['output_comm'] + '\"' + ' [tooltip = \"' + output_comm_des + '\"]')\n", - " tech.add('\"' + row['tech'] + '\"' + ' [tooltip = \"' + tech_des + '\"]')\n", - "\n", - " if row['input_comm'] != 'ethos':\n", - " to_tech.add('\"%s\"' % row['input_comm'] + '\\t->\\t\"%s\"' % row['tech']) \n", - " from_tech.add('\"%s\"' % row['tech'] + '\\t->\\t\"%s\"' % row['output_comm'])\n", - " args = dict(\n", - " enodes = \"\".join('%s;\\n\\t\\t' % x for x in nodes),\n", - " tnodes = \"\".join('%s;\\n\\t\\t' % x for x in tech),\n", - " iedges = \"\".join('%s;\\n\\t\\t' % x for x in to_tech),\n", - " oedges = \"\".join('%s;\\n\\t\\t' % x for x in from_tech),\n", - " snodes = \";\".join('%s' %x for x in ltech),\n", - " )\n", - " return args\n", - "\n", - "def return_format_colors():\n", - " colors = {}\n", - " colors.update(getColorConfig(False))\n", - " return colors, quick_run_dot_fmt\n", - "\n", - "def return_flowd_table(final_dem, level):\n", - " df = pd.read_sql(\"SELECT * FROM Efficiency\", con)\n", - " df_sel = df[df['output_comm']==final_dem]\n", - " if len(df_sel)==0:\n", - " df_sel = df[df['tech']==final_dem]\n", - " inputs = df_sel['input_comm'].unique()\n", - " iterval=0\n", - " while len(inputs)>0:\n", - " df_append = df[df['output_comm'].isin(inputs)]\n", - " df_sel = pd.concat([df_sel, df_append])\n", - " inputs = df_append['input_comm'].unique()\n", - " iterval+=1\n", - " if iterval>level:\n", - " break\n", - " df_graph = df_sel[['input_comm', 'tech', 'output_comm']].drop_duplicates()\n", - " return df_graph\n", - "\n", - "\n", - "con = sqlite3.connect(db_path) #change path to database\n", - "cur = con.cursor() \n", - "con.text_factory = str \n", - "\n", - "def controls_rows(w):\n", - " controls = HBox(w.children[:-1], layout = Layout(flex_flow='row wrap', width='max-content'))\n", - " output = w.children[-1]\n", - " display(VBox([controls, output], layout = Layout(flex_flow='columns wrap', width='max-content', height='max-content')))\n", - "\n", - " display(HTML(\"\"))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "hide_input": false - }, - "source": [ - "### Network diagrams for commodities and technologies\n", - "Select a table, followed by a sector to view the available commodities and technologies. Then select a specific technology/commodity to generate the network diagram. Finally, select the level (upstream connections) of the network to be displayed. Hovering the cursor over each node in the diagram will display the associated description from the commodities or technologies tables." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": { - "hide_input": true, - "init_cell": true, - "jupyter": { - "source_hidden": true - }, - "scrolled": false - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "16320f95465b4393b8734889efd08d68", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(HBox(children=(Select(description='Table', layout=Layout(height='50px', width='200px'), options…" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def network_sector(sector, table_name):\n", - " if table_name=='commodities':\n", - " query = \"SELECT * FROM commodities WHERE flag='p' AND comm_name IN \\\n", - " (SELECT DISTINCT(output_comm) FROM efficiency \\\n", - " WHERE tech IN (SELECT DISTINCT(tech) FROM technologies WHERE sector=\"\n", - " elif table_name=='technologies':\n", - " query = \"SELECT * FROM technologies WHERE sector=\"\n", - "\n", - " if sector=='Industry':\n", - " query += \"'industrial'\"\n", - " elif sector=='Transport':\n", - " query += \"'transport'\"\n", - " elif sector=='Residential':\n", - " query += \"'residential'\"\n", - " elif sector=='Commercial':\n", - " query += \"'commercial'\"\n", - " elif sector=='Supply':\n", - " query += \"'supply'\"\n", - " elif sector=='Electric':\n", - " query += \"'electric'\"\n", - " elif sector=='Electric Misc':\n", - " query += \"'electric_misc'\"\n", - " \n", - " if (table_name=='commodities'):\n", - " query +=\"))\"\n", - "\n", - " if (sector=='Final demands') & (table_name=='commodities'):\n", - " query = \"SELECT * FROM commodities WHERE flag='d'\"\n", - " \n", - " if (sector=='Final demands') & (table_name=='technologies'):\n", - " df_demands = pd.DataFrame(columns = pd.read_sql(\"SELECT * FROM technologies\", con).columns)\n", - " else:\n", - " df_demands = pd.read_sql(query, con)\n", - " \n", - " if table_name=='commodities':\n", - " col_filter = 'comm_name'\n", - " elif table_name=='technologies':\n", - " col_filter = 'tech'\n", - "\n", - " df_demands[col_filter] = df_demands[col_filter].str.replace('#', '').str.strip()\n", - "\n", - " def show_desc(desc, level):\n", - " if desc!='':\n", - " col = col_filter\n", - " final_dem = df_demands.loc[df_demands[col_filter]==desc, col].values[0]\n", - " df_graph = return_flowd_table(final_dem, level)\n", - " args = create_args_flowd(df_graph)\n", - " colors, quick_run_dot_fmt = return_format_colors()\n", - " args.update(colors)\n", - " o_str = 'rankdir = \"LR\" ;'\n", - " r_str = 'rankdir = \"LR\" ; \\n\\t size=\"12,12\";'\n", - " quick_run_dot_fmt = quick_run_dot_fmt.replace(o_str, r_str)\n", - " dot_graph = quick_run_dot_fmt % args\n", - " graph = graphviz.Source(dot_graph)\n", - " display(Markdown('Network diagram for ' + final_dem))\n", - " display(graph)\n", - " \n", - " layout = widgets.Layout(width='500px', height='150px')\n", - " df_demands = df_demands.dropna(subset=[col_filter])\n", - " select_options = df_demands[col_filter].unique()\n", - " if len(select_options)==1:\n", - " select_options = list(select_options) + [''] \n", - "\n", - " w1 = widgets.Select(options=np.sort(select_options), description=table_name.replace('ies','y').capitalize() , layout=layout)\n", - " w2 = widgets.IntSlider(\n", - " value=2,\n", - " min=0,\n", - " max=10,\n", - " step=1,\n", - " description='Level:',\n", - " disabled=False,\n", - " continuous_update=False,\n", - " orientation='horizontal',\n", - " readout=True,\n", - " readout_format='d')\n", - " w = widgets.interactive(show_desc, desc = w1, level=w2)\n", - " controls_rows(w)\n", - "\n", - "#selects particular table\n", - "def select_tech_comm():\n", - " def single_table(table_val, sector):\n", - " if table_val=='Technologies':\n", - " network_sector(sector, 'technologies')\n", - " elif table_val=='Commodities':\n", - " network_sector(sector, 'commodities')\n", - " \n", - " layout = widgets.Layout(width='200px', height = '50px')\n", - " w1 = widgets.Select(options=['Technologies', 'Commodities'], \\\n", - " description='Table', layout=layout)\n", - " layout = widgets.Layout(width='200px', height = '150px')\n", - " w2 = widgets.Select(options=['Final demands','Supply','Transport','Residential','Commercial','Industry', 'Electric', 'Electric Misc',], \\\n", - " description='Sector', layout=layout)\n", - " \n", - "\n", - " w = widgets.interactive(single_table, table_val = w1, sector = w2)\n", - " controls_rows(w)\n", - "\n", - "#main function\n", - "select_tech_comm()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Technology data look-up\n", - "Use the tool below to select any technology within the database. Data tables specific to the selected technology will be displayed." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "hide_input": true, - "init_cell": true - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "f2f5bde2f6d944d2be7cce49dc2f2c99", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Text(value='E21')" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "cf9961ed99dd4a6780256239662828bf", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "w = widgets.Text(value='E21')\n", - "display(w)\n", - "def f(w):\n", - " df = pd.read_sql(\"SELECT * FROM technologies WHERE tech='\" + w + \"'\", con)\n", - "\n", - " if len(df)>0:\n", - " \n", - " df_efficiency= pd.read_sql(\"SELECT regions, input_comm, tech, vintage, output_comm, efficiency FROM Efficiency WHERE tech='\" + w + \"'\", con)\n", - " df_existing_capacity = pd.read_sql(\"SELECT regions, tech, vintage, exist_cap FROM ExistingCapacity WHERE tech='\" + w + \"'\", con)\n", - " df_lifetime = pd.read_sql(\"SELECT regions, tech, life FROM LifetimeTech WHERE tech='\" + w + \"'\", con)\n", - " df_cost_invest = pd.read_sql(\"SELECT regions, tech, vintage, cost_invest FROM CostInvest WHERE tech='\" + w + \"'\", con)\n", - "\n", - " df_all = df_efficiency.merge(df_existing_capacity, on = ['regions','tech','vintage'], how='left')\n", - " df_all = df_all.merge(df_lifetime, on = ['regions','tech'], how='left')\n", - " df_all = df_all.merge(df_cost_invest, on = ['regions','tech','vintage'], how='left')\n", - "\n", - " df_all.fillna(0, inplace=True)\n", - " display(\n", - " HTML(\n", - " tabulate.tabulate(df_all.set_index('regions'),['region'] + list(df_all.set_index('regions').columns.values),tablefmt='html')))\n", - " \n", - " df_costfixed= pd.read_sql(\"SELECT regions, periods, tech, vintage, cost_fixed FROM CostFixed WHERE tech='\" + w + \"'\", con)\n", - " df_costvariable= pd.read_sql(\"SELECT regions, periods, tech, vintage, cost_variable FROM CostVariable WHERE tech='\" + w + \"'\", con)\n", - " df_costsannual = df_costfixed.merge(df_costvariable, on = ['regions','periods','tech','vintage'], how='outer')\n", - " df_costsannual.fillna(0, inplace=True)\n", - " df_costsannual = df_costsannual[['regions', 'periods', 'tech', 'vintage', 'cost_fixed', 'cost_variable']]\n", - " if len(df_costsannual)>0:\n", - " display(\n", - " HTML(\n", - " tabulate.tabulate(df_costsannual.set_index('regions'),['region'] + list(df_costsannual.set_index('regions').columns.values),tablefmt='html')))\n", - "\n", - " else:\n", - " print('')\n", - "\n", - "\n", - "out = widgets.interactive_output(f, {'w': w})\n", - "display(out)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "hide_input": true - }, - "source": [ - "### Technology/commodity look-up tool\n", - "Use the tool below to provide a description for any technology or commodity within the database. Type the commodity or technology name in the box below to view its description." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "hide_input": true, - "init_cell": true, - "jupyter": { - "source_hidden": true - } - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "ae698f893de748259577009b514828d7", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Text(value='ELC')" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d85d4ca4cf98497ba36a02ad651456e3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "w = widgets.Text(value='ELC')\n", - "display(w)\n", - "def f(w):\n", - " df = pd.read_sql(\"SELECT * FROM commodities WHERE comm_name='\" + w + \"'\", con)\n", - " if len(df)==0:\n", - " df = pd.read_sql(\"SELECT * FROM technologies WHERE tech='\" + w + \"'\", con)\n", - "\n", - " if len(df)>0:\n", - " try:\n", - " display(Markdown(df['comm_desc'].values[0].replace('#', '').strip()))\n", - " except:\n", - " display(Markdown(df['tech_desc'].values[0].replace('#', '').strip()))\n", - " else:\n", - " print('')\n", - "\n", - "\n", - "out = widgets.interactive_output(f, {'w': w})\n", - "display(out)\n" - ] - } - ], - "metadata": { - "celltoolbar": "Initialization Cell", - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": true, - "sideBar": true, - "skip_h1_title": false, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": true - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/notebooks/develop_mitigation_curve copy.ipynb b/notebooks/develop_mitigation_curve copy.ipynb deleted file mode 100644 index fd38d62cf..000000000 --- a/notebooks/develop_mitigation_curve copy.ipynb +++ /dev/null @@ -1,3461 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "bd2fc2aa", - "metadata": {}, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'matplotlib'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[1], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mpandas\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpd\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mmatplotlib\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mpyplot\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mplt\u001b[39;00m\n\u001b[1;32m 3\u001b[0m get_ipython()\u001b[38;5;241m.\u001b[39mrun_line_magic(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mmatplotlib\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[38;5;124m'\u001b[39m\u001b[38;5;124minline\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01msqlite3\u001b[39;00m\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'matplotlib'" - ] - } - ], - "source": [ - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline\n", - "import sqlite3\n", - "import seaborn as sb\n", - "import numpy as np\n", - "from pandas.api.types import CategoricalDtype\n", - "from itertools import cycle, islice\n", - "import os\n", - "import shutil\n", - "import collections\n", - "import warnings\n", - "warnings.filterwarnings(\"ignore\")\n", - "import matplotlib.ticker as tick\n", - "#!pip install mpl-axes-aligner\n", - "\n", - "\n", - "import mpl_axes_aligner\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "d5bb94fa", - "metadata": {}, - "outputs": [], - "source": [ - "#os.mkdir('carbon_tax_figures_updates')" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "bb7bc9a0", - "metadata": {}, - "outputs": [], - "source": [ - "sb.set(style='whitegrid', font_scale=1.4)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "96751de1", - "metadata": {}, - "outputs": [], - "source": [ - "# map tech categories to energy sources\n", - "col_order = ['Biomass','Geothermal','Hydro','Nuclear','Coal','Natural Gas','Solar','Wind', 'Pumped Hydro',\\\n", - " 'Battery', 'Hydrogen']\n", - "#map powerplant categories\n", - "map_plants = dict()\n", - "map_plants['COAL'] = 'Coal'\n", - "map_plants['GEO'] = 'Geothermal'\n", - "map_plants['BE'] = 'Biomass'\n", - "map_plants['BIO'] = 'Biomass'\n", - "map_plants['HYDPS'] = 'Pumped Hydro'\n", - "map_plants['HYDCONV'] = 'Hydro'\n", - "map_plants['HYDSM'] = 'Hydro'\n", - "map_plants['NGA'] = 'Natural Gas'\n", - "map_plants['WND'] = 'Wind'\n", - "map_plants['SOL'] = 'Solar'\n", - "map_plants['URN'] = 'Nuclear'\n", - "map_plants['Batt'] = 'Battery'\n", - "map_plants['H2'] = 'Hydrogen'\n", - "\n", - "color_dict = dict()\n", - "color_dict['Coal'] = 'dimgray'\n", - "color_dict['Geothermal'] = 'pink'\n", - "color_dict['Biomass'] = 'darkseagreen'\n", - "color_dict['Hydro'] = 'b'\n", - "color_dict['Natural Gas'] = 'peru'\n", - "color_dict['Wind'] = 'lightskyblue'\n", - "color_dict['Solar'] = 'lemonchiffon'\n", - "color_dict['Nuclear'] = 'darkorange'\n", - "color_dict['Pumped Hydro'] = 'lightcoral'\n", - "color_dict['Battery'] = 'red'\n", - "color_dict['Hydrogen'] = '#e5ae38'\n", - "color_dict['Curtailment'] = 'darkgrey'\n", - "\n", - "color_dict['commercial'] = 'darkred'\n", - "color_dict['industrial'] = 'blue'\n", - "color_dict['transport'] = 'darkorange'\n", - "color_dict['residential'] = 'lightskyblue'\n", - "\n", - "color_dict['Petroleum'] = '#ABABAB'\n", - "\n", - "\n", - "\n", - "tech_transport = dict()\n", - "tech_transport['T_Liquids'] = 'Synthetic Fuel'\n", - "tech_transport['DSL_EA'] = 'Diesel'\n", - "tech_transport['BIODSL'] = 'Biodiesel'\n", - "tech_transport['MGO_EA'] = 'Diesel'\n", - "tech_transport['RFO_EA'] = 'Other Fossil'\n", - "tech_transport['CNG_EA'] = 'Other Fossil'\n", - "tech_transport['T_LPG_EA'] = 'Other Fossil'\n", - "tech_transport['T_LNG_EA'] = 'Other Fossil'\n", - "tech_transport['ELC'] = 'Electricity'\n", - "tech_transport['ELC_TRN'] = 'Electricity'\n", - "tech_transport['ELC_HDV_CHRG'] = 'drop'\n", - "tech_transport['ELC_LDV_CHRG'] = 'drop'\n", - "tech_transport['BIO_JTF'] = 'Biodiesel'\n", - "tech_transport['LH2'] = 'Hydrogen'\n", - "tech_transport['H2'] = 'Hydrogen'\n", - "tech_transport['JTF_EA'] = 'Jet Fuel'\n", - "tech_transport['ETHANOL'] = 'Ethanol'\n", - "tech_transport['GAS_EA'] = 'Gasoline'\n", - "\n", - "tech_commercial = dict()\n", - "tech_commercial['C_DISTOIL_EA'] = 'Other Fossil'\n", - "tech_commercial['C_NGA_EA'] = 'Natural Gas'\n", - "tech_commercial['ELC_COM'] = 'Electricity'\n", - "tech_commercial['SNG_100'] = 'Synthetic Natural Gas'\n", - "tech_commercial['C_RFO_EA'] = 'Other Fossil'\n", - "\n", - "tech_residential= dict()\n", - "tech_residential['R_BIO_EA'] = 'Biomass'\n", - "tech_residential['R_NGA_EA'] = 'Natural Gas'\n", - "tech_residential['ELC_RES'] = 'Electricity'\n", - "tech_residential['ELCDIST_R'] = 'Electricity'\n", - "tech_residential['SNG_100'] = 'Synthetic Natural Gas'\n", - "tech_residential['R_DISTOIL_EA'] = 'Other Fossil'\n", - "tech_residential['R_LPG_EA'] = 'Other Fossil'\n", - "tech_residential['RWHSOL'] = 'Residential Solar'\n", - "tech_residential['H2_100'] = 'Hydrogen'\n", - "\n", - "\n", - "tech_industrial = dict()\n", - "tech_industrial['ELC'] = 'Electricity'\n", - "tech_industrial['I_COAL'] = 'Coal'\n", - "tech_industrial['I_GSL_EA'] = 'Gasoline'\n", - "tech_industrial['I_NGA'] = 'Natural Gas'\n", - "tech_industrial['I_OTH_EA'] = 'Other Fossil'\n", - "tech_industrial['I_LPG'] = 'Other Fossil'\n", - "tech_industrial['I_DFO'] = 'Diesel'\n", - "tech_industrial['I_RFO'] = 'Residual Fuel Oil'\n", - "tech_industrial['I_REN'] = 'Biomass'\n", - "tech_industrial['SNG_100'] = 'Synthetic Natural Gas'\n", - "tech_industrial['H2_100'] = 'Hydrogen'\n", - "\n", - "tech_colormap = dict()\n", - "tech_colormap['Synthetic Fuel'] = '#595959'\n", - "tech_colormap['Other Fossil'] = '#ABABAB'\n", - "tech_colormap['Jet Fuel'] = '#8172B2'\n", - "tech_colormap['Diesel'] = 'darkorange'\n", - "tech_colormap['Gasoline'] = '#C44E52'\n", - "tech_colormap['Electricity'] = '#55A868'\n", - "tech_colormap['Biodiesel'] = 'steelblue'\n", - "tech_colormap['Ethanol'] = '#64B5CD'\n", - "tech_colormap['Hydrogen'] = '#e5ae38'\n", - "\n", - "tech_colormap['Natural Gas'] = 'peru'\n", - "tech_colormap['Synthetic Natural Gas'] = 'tan'\n", - "\n", - "tech_colormap['Biomass'] = 'darkseagreen'\n", - "tech_colormap['Residential Solar'] = 'lemonchiffon'\n", - "tech_colormap['Coal'] = 'dimgray'\n", - "\n", - "tech_colormap['Residual Fuel Oil'] = 'chocolate'\n", - "tech_colormap['Distillate Oil'] = '#ABABAB'\n", - "tech_colormap['Petroleum'] = '#ABABAB'\n", - "tech_colormap['Geothermal'] = 'pink'\n", - "\n", - "tech_colormap['Hydro'] = 'b'\n", - "tech_colormap['Other Renewables'] = 'b'\n", - "tech_colormap['Wind'] = 'lightskyblue'\n", - "tech_colormap['Solar'] = 'lemonchiffon'\n", - "tech_colormap['Nuclear'] = 'darkorange'\n", - "\n", - "tech_colormap['ICEV-E10'] = '#C44E52'\n", - "tech_colormap['CI-Diesel'] = 'darkorange'\n", - "tech_colormap['ICEV-CNG'] = 'peru'\n", - "tech_colormap['EV'] = '#55A868'\n", - "tech_colormap['ICEV-E85'] = '#64B5CD'\n", - "tech_colormap['PHEV'] = 'dimgray'\n", - "tech_colormap['ICEV-LPG'] = '#ABABAB'\n", - "tech_colormap['CI-B20'] = 'darkseagreen'\n", - "\n", - "\n", - "color_dict = dict()\n", - "color_dict['DAC to sequestration'] = 'darkorange'\n", - "color_dict['CC to sequestration'] = 'b'\n", - "color_dict['DAC to fuels'] = 'darkred'\n", - "color_dict['CC to fuels'] = 'lightskyblue'\n", - "\n", - "color_dict['Coal'] = 'dimgray'\n", - "color_dict['Geothermal'] = 'pink'\n", - "color_dict['Biomass'] = 'darkseagreen'\n", - "color_dict['Hydro'] = 'b'\n", - "color_dict['Natural Gas'] = 'peru'\n", - "color_dict['Wind'] = 'lightskyblue'\n", - "color_dict['Solar'] = 'lemonchiffon'\n", - "color_dict['Nuclear'] = 'darkorange'\n", - "color_dict['Pumped Hydro'] = 'lightcoral'\n", - "color_dict['Battery'] = 'red'\n", - "color_dict['Hydrogen'] = '#e5ae38'\n", - "color_dict['Curtailment'] = 'darkgrey'\n", - "\n", - "color_dict['Commercial'] = '#C44E52'\n", - "color_dict['Industrial'] = 'darkorange'\n", - "color_dict['Transport'] = '#8172B2'\n", - "color_dict['Residential'] = 'lightskyblue'\n", - "color_dict['Supply'] = 'darkgrey'\n", - "color_dict['Electric'] = '#55A868'\n", - "color_dict['Petroleum'] = '#ABABAB'\n", - "color_dict['Other Renewables'] = 'b'\n", - "\n", - "color_dict['NG SMR (CCS) - Hydrogen'] = 'peru'\n", - "color_dict['BECCS - Electricity'] = 'darkseagreen'\n", - "color_dict['BECCS - Hydrogen'] = '#e5ae38'\n", - "color_dict['DAC'] = 'red'\n", - "\n", - "\n", - "color_dict['TotalInvestment_discounted'] = 'b'\n", - "color_dict['TotalInvestment'] = 'b'" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "7dcc11c4", - "metadata": {}, - "outputs": [], - "source": [ - "def func_stacked_plot(df_list, ax = None, col_order = ['Biomass','Geothermal','Hydro','Nuclear',\\\n", - " 'Coal','Natural Gas','Solar','Wind','Pumped Hydro', 'Battery', 'Hydrogen'], loc='default', color_dict= color_dict):\n", - " # df list needs to come from one of the prep functions above\n", - " bar_width = 1\n", - " if ax is None:\n", - " ax = plt.gca() \n", - " # specify order of energy source stack here\n", - " \n", - "# fig, ax = plt.subplots(figsize=(8, 6))\n", - "\n", - " num_dfs = len(df_list)\n", - " col_spacing = [x - (num_dfs / 1.8 - 0.5) for x in np.arange(num_dfs)]\n", - "\n", - " for df_in_list, spacing in zip(df_list, col_spacing):\n", - " col_order_inst = [\n", - " y for y in col_order for x in df_in_list.index if x == y\n", - " ]\n", - " # plotting the first set of bars to cumulatively add on\n", - " df_in_list = df_in_list.loc[col_order_inst, :]\n", - " ax.bar(df_in_list.columns + spacing,\n", - " df_in_list.iloc[0, :],\n", - " label=df_in_list.index[0],\n", - " width=bar_width,\n", - " color=color_dict[df_in_list.index[0]])\n", - "\n", - " cumsum = df_in_list.iloc[0, :]\n", - "\n", - " for i in np.arange(1, len(df_in_list)):\n", - " ax.bar(df_in_list.columns + spacing,\n", - " df_in_list.iloc[i, :],\n", - " bottom=cumsum,\n", - " label=df_in_list.index[i],\n", - " width=bar_width,\n", - " color=color_dict[df_in_list.index[i]])\n", - " cumsum = cumsum + df_in_list.iloc[i, :]\n", - " \n", - " \n", - " handles, labels = ax.get_legend_handles_labels()\n", - " unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - " if loc=='inside_upper_left':\n", - " plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - " elif loc=='inside_upper_right':\n", - " plt.legend(*zip(*unique[::-1]), loc='upper right', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - " else:\n", - " plt.legend(*zip(*unique[::-1]), loc='upper left',bbox_to_anchor=(1.01, 1), frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - " ax.tick_params(axis = 'x', labelrotation=90)\n", - " return ax" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cef70285", - "metadata": {}, - "outputs": [], - "source": [ - "#return fuel consumption dataframe for a sector\n", - "def stacked_penergy_sector(con, scenario, sector, region):\n", - " if sector =='commercial':\n", - " fuels_list = ['C_DISTOIL_EA','C_LPG_EA','C_NGA_EA','C_RFO_EA','ELC_COM','SNG_100','H2_100', 'ELCDIST_R']\n", - " elif sector=='residential':\n", - " fuels_list = ['R_DISTOIL_EA','R_LPG_EA','R_NGA_EA','RWHSOL', 'R_BIO_EA','ELC_RES','SNG_100','H2_100', 'ELCDIST_R']\n", - " elif sector == 'transport':\n", - " fuels_list = ['MGO_EA','DSL_EA','GAS_EA','CNG_EA','ETHANOL', 'JTF_EA', 'RFO_EA', 'T_LPG_EA','MEOH', 'T_Liquids', 'ELC_TRN', 'BIODSL', 'BIO_JTF', 'ELCDIST_R', 'LH2', 'H2']\n", - " elif sector == 'industrial':\n", - " fuels_list = ['I_COAL', 'I_DFO', 'I_GSL_EA', 'I_NGA', 'I_REN', 'I_RFO', 'I_LPG', 'I_OTH_EA', 'SNG_100', 'H2_100', 'ELC', 'ELCDIST_R']\n", - " else:\n", - " print('Sector input is either residential, commercial, industrial, or transport')\n", - " \n", - " if region=='All':\n", - " query = \"SELECT input_comm, t_periods, SUM(vflow_in) AS vflow_in FROM Output_VFlow_In \\\n", - " WHERE scenario='\" + scenario + \"' AND sector='\" + sector + \\\n", - " \"' AND vflow_in > 1e-6 GROUP BY input_comm, t_periods\"\n", - " else:\n", - " query = \"SELECT input_comm, t_periods, SUM(vflow_in) AS vflow_in FROM Output_VFlow_In \\\n", - " WHERE scenario='\" + scenario + \"' AND sector='\" + sector + \"' AND regions='\"+ str(region) +\\\n", - " \"' AND vflow_in > 1e-6 GROUP BY input_comm, t_periods\"\n", - "\n", - " df_sector = pd.read_sql_query(query, con)\n", - " df_sector = df_sector[df_sector['input_comm'].isin(fuels_list)]\n", - " \n", - " if (df_sector['input_comm'].str.contains('ELC_').any()):\n", - " df_sector = df_sector[df_sector['input_comm']!='ELC']\n", - "\n", - " \n", - " df_sector = df_sector.pivot_table(values='vflow_in', index='input_comm', columns='t_periods')\n", - " df_sector.fillna(0, inplace=True)\n", - " return df_sector" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3c1b2e24", - "metadata": {}, - "outputs": [], - "source": [ - "# os.mkdir('carbon_tax_figures_updates')" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "75f3a962", - "metadata": {}, - "outputs": [], - "source": [ - "# list_of_DBs = ['US_9R_8D_CT_neg50.sqlite', 'US_9R_8D_CT_neg10.sqlite', 'US_9R_8D_CT_0.sqlite', \\\n", - "# 'US_9R_8D_CT_10.sqlite','US_9R_8D_CT_50.sqlite','US_9R_8D_CT_100.sqlite',\\\n", - "# 'US_9R_8D_CT_250.sqlite',\\\n", - "# 'US_9R_8D_CT_400.sqlite','US_9R_8D_CT_600.sqlite']\n", - "\n", - "# list_of_DBs = ['US_9R_8D_CT_neg50.sqlite', 'US_9R_8D_CT_neg10.sqlite', 'US_9R_8D_CT_0.sqlite', \\\n", - "# 'US_9R_8D_CT_10.sqlite', 'US_9R_8D_CT_50.sqlite','US_9R_8D_CT_100.sqlite','US_9R_8D_CT_200.sqlite',\\\n", - "# 'US_9R_8D_CT_400.sqlite','US_9R_8D_CT_600.sqlite','US_9R_8D_CT_800.sqlite','US_9R_8D_CT_1000.sqlite']\n", - "\n", - "list_of_DBs = ['US_9R_8D_CTneg10.sqlite','US_9R_8D_CT0.sqlite','US_9R_8D_CT10.sqlite', 'US_9R_8D_CT100.sqlite', 'US_9R_8D_CT200.sqlite', \\\n", - " 'US_9R_8D_CT300.sqlite', 'US_9R_8D_CT400.sqlite', 'US_9R_8D_CT500.sqlite']\n", - "list_of_scenarios = ['test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run', \\\n", - " 'test_run', 'test_run','test_run','test_run','test_run']\n", - "\n", - "list_of_conns = [sqlite3.connect(db) for db in list_of_DBs]" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "b30a0cc0", - "metadata": {}, - "outputs": [], - "source": [ - "query = \"SELECT t_periods, sum(emissions) as emissions FROM Output_Emissions \\\n", - " WHERE emissions_comm='co2' GROUP BY t_periods\"\n", - "emiss_store = dict()\n", - "for conn, db in zip(list_of_conns, list_of_DBs):\n", - " df_read_emissions = pd.read_sql_query(query, conn)\n", - " df_read_emissions['emissions'] /= 1000\n", - " emiss_store[db] = df_read_emissions.set_index('t_periods')\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "eec17e7e", - "metadata": {}, - "outputs": [], - "source": [ - "query = \"SELECT * FROM Output_Duals WHERE constraint_name LIKE '%EmissionLimit%' AND constraint_name LIKE '%co2%' \\\n", - "AND constraint_name LIKE '%global%'\"\n", - "duals_store = dict()\n", - "for conn, db in zip(list_of_conns, list_of_DBs):\n", - " df_duals = pd.read_sql(query, conn)\n", - " df_duals['t_periods'] = [int(x[1]) for x in df_duals['constraint_name'].str.split(',')]\n", - " df_duals['shadow_price'] = -1*df_duals['dual']*1000\n", - " df_duals.set_index('t_periods',inplace=True)\n", - " duals_store[db] = df_duals\n" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "beb32695", - "metadata": {}, - "outputs": [], - "source": [ - "base = 'US_9R_8D_CT0.sqlite'\n", - "emis_reductions = dict()\n", - "for db in list_of_DBs:\n", - " emis_reductions[db] = emiss_store[base] - emiss_store[db]\n" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "5d4e42b9", - "metadata": {}, - "outputs": [], - "source": [ - "map_scenario_names = dict()\n", - "map_scenario_names['neg50.'] = '-50$/tonne'\n", - "map_scenario_names['neg10.'] = '-10$/tonne'\n", - "map_scenario_names['10.'] = '10$/tonne'\n", - "map_scenario_names['50.'] = '50$/tonne'\n", - "map_scenario_names['100.'] = '100$/tonne'\n", - "map_scenario_names['200.'] = '200$/tonne'\n", - "map_scenario_names['300.'] = '300$/tonne'\n", - "map_scenario_names['400.'] = '400$/tonne'\n", - "map_scenario_names['500.'] = '500$/tonne'\n", - "map_scenario_names['600.'] = '600$/tonne'\n", - "map_scenario_names['800.'] = '800$/tonne'\n", - "map_scenario_names['1000.'] = '1000$/tonne'\n", - "\n", - "\n", - "map_scenario_names[''] = 'No carbon tax'" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "d25052b6", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "scen_name_all = []\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - "\n", - " query = \"SELECT t_periods, tech, sum(vflow_out) FROM Output_VFlow_Out WHERE input_comm LIKE 'ethos%' AND tech NOT LIKE '%_emissions%' \\\n", - " GROUP BY t_periods, tech\"\n", - " df_s = pd.read_sql_query(query, conn)\n", - "\n", - " ethos_tech_map = dict()\n", - " ethos_tech_map['E_SOLPV'] = 'Solar'\n", - " ethos_tech_map['SOLELC'] = 'Solar'\n", - " ethos_tech_map['E_WND'] = 'Wind'\n", - " ethos_tech_map['E_OFWND_N'] = 'Wind'\n", - " ethos_tech_map['E_GEO'] = 'Other Renewables'\n", - " ethos_tech_map['E_HYD'] = 'Other Renewables'\n", - " ethos_tech_map['Corn'] = 'Biomass'\n", - " ethos_tech_map['Herbaceous'] = 'Biomass'\n", - " ethos_tech_map['Soybeans'] = 'Biomass'\n", - " ethos_tech_map['Waste'] = 'Biomass'\n", - " ethos_tech_map['Woody'] = 'Biomass'\n", - " ethos_tech_map['DFO'] = 'Petroleum'\n", - " ethos_tech_map['DISTOIL'] = 'Petroleum'\n", - " ethos_tech_map['IMPRESNGA'] = 'Natural Gas'\n", - " ethos_tech_map['IMPELCNGA_S3'] = 'Natural Gas'\n", - " ethos_tech_map['IMPCOMNGA'] = 'Natural Gas'\n", - "# ethos_tech_map['NGA'] = 'Natural Gas'\n", - " ethos_tech_map['INDNG'] = 'Natural Gas'\n", - " ethos_tech_map['CNG'] = 'Natural Gas'\n", - " ethos_tech_map['LNG'] = 'Natural Gas'\n", - " ethos_tech_map['COAL'] = 'Coal'\n", - " ethos_tech_map['COAB'] = 'Coal'\n", - " ethos_tech_map['COAS'] = 'Coal'\n", - " ethos_tech_map['URN'] = 'Nuclear'\n", - " ethos_tech_map['RFO'] = 'Petroleum'\n", - " ethos_tech_map['BIO'] = 'Biomass'\n", - " ethos_tech_map['REN'] = 'Biomass'\n", - " ethos_tech_map['GAS'] = 'Petroleum'\n", - " ethos_tech_map['JTF'] = 'Petroleum'\n", - " ethos_tech_map['LPG'] = 'Petroleum'\n", - " ethos_tech_map['DSL'] = 'Petroleum'\n", - " ethos_tech_map['GSL'] = 'Petroleum'\n", - " ethos_tech_map['MGO'] = 'Petroleum'\n", - " ethos_tech_map['IMPINDOTH'] = 'Petroleum'\n", - "\n", - "\n", - " for key in ethos_tech_map.keys():\n", - " mask = df_s['tech'].str.contains(key)\n", - " df_s.loc[mask,'tech_rev'] = ethos_tech_map[key]\n", - " \n", - " df_s_pivot = df_s.groupby(by=['t_periods', 'tech_rev']).sum().reset_index()\n", - " df_s_pivot = df_s_pivot.pivot_table(index='tech_rev', columns='t_periods')\n", - " df_s_pivot.fillna(0, inplace=True)\n", - " df_s_pivot.columns = [x[1] for x in df_s_pivot.columns]\n", - " \n", - " df_s_pivot = df_s_pivot.loc[['Coal','Petroleum','Natural Gas','Nuclear','Other Renewables','Biomass','Solar','Wind']]\n", - " df_s_pivot /= 1000\n", - " output_list.append(df_s_pivot)\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('CT')[1]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " \n", - " scen_name_all.append(scen_name)" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "de9fa722", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "image/png": "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cIA//vGPbN++nT/96U+9/7aDCYfDXH311Rw4cID3v//9LF26lO7ubp5//nmOHTtGcXExwWCQe+65h3e961188IMfJBqN8ve//50bb7yRX//611x44YX9HvOtt97i8ccf5+qrr6agoIA5c+b03rZ161Yee+wxrrnmGjweD/feey833ngjv/3tb1m1ahUAHR0dbNq0ie7ubq6++mqKiop45JFHuOWWW+ju7uYTn/hEv+d77LHH6Orq4qqrrsLhcPC73/2OG2+8kWeffZacnJxBf+68vDy+/vWv881vfpN3vOMdvOMd7wBg5syZAOzcuZM33niDSy+9lPLyclpaWrjnnnu4+uqreeihhygsLATga1/7Gq+99hr/+q//yn333YfdbueRRx7hkUce4dZbb6WqqmrI370YGzI+i+lE+rOYTqQ/i7Em189y/SxGRsZnMdZkfJbxeVQpMa0cOnRIVVdXqz//+c9KKaUuueSSU57/1ltvqerqanXo0KHeY5/61KcGvd8zzzyjqqur1Y9//ON+x7/zne+o6upq9fLLL/ceu+SSSwYci0ajat26derzn/9877FXX31VVVdXq3e84x2qp6en9/iTTz6pqqur1bPPPtt77JOf/KR65zvfqcLhcL/n/+IXv6hWrFihQqHQKX9WcWbuv/9+VV1drbZu3arq6+vV4sWL1de//vXe20/uN/v371fV1dXq5ptv7vc4v/3tb/v10aH89Kc/VdXV1eqvf/3rgNsMw1BKKZVIJFQ0Gu13WzQaVe985zvVtdde2+94dXW1mj9/vtqzZ8+Ax6uurlbV1dXqrbfe6j3W0dGhVq1apa666qreY9/73vdUdXW12rx5c7/n+9CHPqTOOecc5ff7lVJK1dXVqerqanXeeeep9vb23nP37Nmjqqur1R/+8IdT/uwtLS2qurpa/fSnPx1w28n9Xymlampq1JIlS9QvfvGLfsc3b96s5s+fr370ox+plpYWtXr1anX11Vf3/v7E+JLxWUwn0p/FdCL9WYwVuX6W62dxZmR8FmNFxmcZn8eCpIuZZmbPns2KFSv47ne/y3e+8x1CoRBNTU1Dnv/cc89RUVGR0azPs88+i6ZpfOpTn+p3/B//8R97H6uvyspKzj///N7v7XY7y5cvH3Rb15VXXonD4ej9Pj2zVldXB5jFSzZv3sw73/lOIpEIHR0dvR8XXXQRoVCIXbt2DfsziNHRd7Z3qNxl6f5w3XXX9Tu+adMmvF7voFuV+nr88ceZO3cu733vewfcpmkaABaLBbvdDphbq7q6uggGg6xatYrdu3cPuN+KFStYuHDhoM+3ZMkSVq5c2ft9bm4u73nPe9i6dSvd3d2A+RpYtGgR69at6z3PbrfziU98gnA4PKBa9uWXX05eXl7v9wsXLsTr9fb265FwuVy9X0ciETo7O/H5fFRWVg74mdetW8fVV1/Nr3/9a/7pn/6JeDzObbfd1vv7E+NLxmcxnUh/FtOJ9GcxHuT6Wa6fxemT8VmMBxmfZXweLZIuZprRdZ3f/OY3/PKXv+TRRx+lq6uLDRs2UFVVxec///nerVVpzz33HJdccklGj11fX09BQQFZWVn9jhcVFZGVlUV9fX2/42VlZQMeIzs7e0AOpcHOzc7OBuh98R87dgylFHfccQd33HHHoO1rb2/P6OcQo+P666/ngQce4M477+Q//uM/Btx+/PhxNE3rt2UJzEF7xowZA/rLyWpra9m4ceOw7bjvvvv47W9/y+HDh1FK9R4fbKBNb0kaTGVl5ZDHGhsbyc7Opr6+nksvvXTAeXPnzgXI+DWQ7tcjEY1G+clPfsLf/vY3Wltb+92Wm5s74Pybb76Zp556it27d/PNb36TioqKET+3ODMyPovpRPqzmE6kP4vxItfPJ8j1s8iEjM9ivMj4fIKMzyMnQfZpyOv1ctNNN3HTTTexceNGbrnlFu6++27++Z//GYfD0fvCbmpqYv/+/Xz5y18+4+fs++JP0/XMN0pYLJZTPq5hGABce+21bNiwYdBzq6urM34+ceZOzl12OpRSGc02DnfOQw89xNe+9jU2btzIpz/9afLy8rBardx///089NBDA87vu5og03aeyXnD9euR+M53vsN9993H1VdfzcqVK/H5fOi6zne/+91BH3f//v20tLQAcODAgRE/rxgdMj6L6UT6s5hOpD+L8SDXz8OfJ9fP4mQyPovxIOPz8OfJ+Dw8CbKfBS677DIuuugi1q1bx6OPPtr7R+jZZ5/F7XazZs2afucP9cIvLy/n5Zdfxu/395vtbW1tJRAIUF5ePmY/w4wZMwDzRd13i5aYWH1ne09WUVGBUoojR46wYMGC3uOxWIzjx4+zdu3aUz72zJkzhx00H3nkEWbMmMEvfvGLfv12JBXZa2pqBhw7duwYQG/xk/Lyco4cOTLgvPSx0XoNnOqP7yOPPML73/9+/u3f/q3f8e7u7gEzvT09Pfzrv/4rFRUVbNiwgd///ve8/e1v77cdTEwsGZ/FdCL9WUwn0p/FWJHrZ5NcP4uRkvFZjBUZn00yPo+c5GSfZoLBIIlEYsDxZDKJYRj9Zrqef/551q1b15vzKc3tduP3+wc8xiWXXIJSit/+9rf9jt91110AXHzxxWf+AwwhPz+ftWvX8uc//3nQHFkdHR1j9txiaH1ne0/OjZeekT+5v9x7770Eg8Fh+8tll13GoUOHePjhhwfclp7RTM+k9p3hrKur46mnnjrdH4Vdu3axdevW3u87Ozt56KGHOOecc3q3911yySXs2bOHV199tfe8eDzO7373O1wu14ALupFK5yUbbNuVxWIZMKP70EMP9c7m9vWDH/yA2tpavve97/HlL3+Z6upqvvrVrxIMBkelneL0yPgsphPpz2I6kf4sxpNcP8v1s8icjM9iPMn4LOPzmZKV7NPM7t27+frXv84HP/hBFi9eTCwW48EHH+T3v/898Xi8t8hCNBrl1Vdf5atf/eqAx1iyZAmPP/443/72t1m+fDm6rvPud7+biy++mAsvvJCf//znNDY2smTJErZu3crf//533va2t435DOw3vvENNm3axHvf+14+/OEPU1lZSWdnJ7t37+all15iy5YtY/r8YnDp2d5Dhw71m+mcP38+V111Fffeey+BQIDzzz+fgwcPcu+997J06VLe//73n/Jxr7vuOp544gluvvlmXn75ZZYuXUogEOCFF17gC1/4AqtXr2bjxo088cQT3HDDDWzcuJHm5mb++Mc/UllZyb59+07r56iurub666/n6quvxuPx9P6x/Jd/+Zfecz796U/z8MMPc8MNN3DNNddQWFjII488wrZt27j11lvx+Xyn9ZxD8Xg8zJ49m0ceeYTZs2eTk5NDRUUFy5cvZ+PGjTz44IN4vV7mzZvH3r17efTRR3tXQ6S99tpr/OEPf+C6667rLXjyn//5n3zoQx/iO9/5DrfddtuotFVkTsZnMZ1IfxbTifRnMd7k+lmun0VmZHwW403GZxmfz4QE2aeZhQsX8r73vY/HHnuMX//61/j9fr7zne9QVVXFnXfeyerVqwF49dVXiUQig+b/uvrqqzl06BB///vf+cMf/oBS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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(figsize=(26,6))\n", - "for ind_iter in np.arange(len(output_list)):\n", - " subp = plt.subplot(1,len(output_list)+2,ind_iter+1)\n", - " plt.stackplot(output_list[ind_iter].columns, output_list[ind_iter], alpha = 0.75, labels=output_list[ind_iter].index, \\\n", - " colors = output_list[ind_iter].index.map(tech_colormap))\n", - " \n", - " \n", - " if ind_iter==0:\n", - " plt.ylabel('Primary energy consumption (EJ)')\n", - " else:\n", - " subp.set(yticklabels=[])\n", - "\n", - " plt.grid(axis='x')\n", - " plt.xticks(rotation='vertical')\n", - " plt.xlim([output_list[ind_iter].columns.min(), output_list[ind_iter].columns.max()])\n", - " plt.title(scen_name_all[ind_iter].replace('$/tonne','\\n $/tonne'))\n", - " plt.ylim([0, 100])\n", - "\n", - "# handles, labels = subp.get_legend_handles_labels()\n", - "# unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - "# if l not in labels[:i]]\n", - "# subp = plt.subplot(1,len(output_list)+2,ind_iter+2)\n", - "# subp.legend(handles[::-1], labels[::-1], loc='upper left', frameon=False)\n", - "\n", - "# dummy_df = pd.DataFrame(index=output_list[ind_iter].index, columns=output_list[ind_iter].columns)\n", - "# dummy_df.transpose().plot.bar(kind='stacked',ax=subp, color = output_list[ind_iter].index.map(tech_colormap))\n", - "# subp.set(yticklabels=[],xticklabels=[] )\n", - "# plt.legend(loc='upper left')\n", - "# plt.grid(False)\n", - "# plt.axis('off')\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_all_primaryenergy.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "073b3035", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAAI1CAYAAADsLNpwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8/fFQqAAAACXBIWXMAAAsTAAALEwEAmpwYAACQVklEQVR4nOzdd1gU1/s28Hvo0kUEEbsGMaACKqhYsGDHErsoSlQsiDVYo+arRuwNjA0balRAY2/Ya7AXIsaKDRSVJihlYd8//LGvhDa7LK7K/bkur4uZc+Y8zxCD+zBzzhGkUqkUREREREREBDVVJ0BERERERPS1YIFERERERET0f1ggERERERER/R8WSERERERERP+HBRIREREREdH/YYFERERERET0f1ggERERERER/R8WSERERERERP+HBRIREREREdH/YYFERERERET0f1ggERERERER/R8WSERERERERP+HBRIREREREdH/0ZD3gjdv3iA+Ph6CIKB06dIwNTUtjryIiIiIiIi+uEILpJSUFBw+fBjHjx/HjRs3kJSUlKPd0NAQdnZ2aN26Ndq3bw99ff1iS5aIiIiIiKg4CVKpVJpXQ3x8PNauXYsdO3YgLS0NP/zwA2rXro2KFSvCyMgIUqkUSUlJePbsGe7cuYOHDx9CW1sbvXv3hpeXF0xMTL70vRARERERERVJvgWSg4MDKlSogN69e6Nt27aFvkr35s0bHD16FMHBwXjx4gWuX79eLAkXt6ysLKSkpEBTUxOCIKg6HSIiIqIcpFIpMjIyoKenBzU1TicnUrZ8C6TTp0/DxcVFoUGLcq2qvX//Hvfv31d1GkREREQFsrKygoGBgarTIPru5FsglVSpqan4559/YGVlBS0tLVWnQ0TfiYiICNja2qo6DSL6DqSnp+P+/fuwsbGBjo6OqtMh+u7IvYrd9y77tTotLS1oa2urOBsi+p7wZwoRKROnAhAVjwILpHXr1sk1mLq6OvT19VG9enU4ODjwf1wiIiIiIvqmFFggLV68WKFBBUFA5cqVsWbNGlSuXFmhMYiIiIiIiL60AgukEydOyDWYVCpFSkoKbt++jQULFmDOnDlyP4UiIiIiIiJSlQILJEtLS4UGrVmzJt69e4c1a9YodD0REREREZEqFFggffz4Edra2qLW2E9KSsLz589hY2MDAGjYsCGePn2qnCyJ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3sWIESPybbezs8OqVauUHpeIiIiIiCgvogskR0dHpQf/+PFjoQs/pKSkKD0uERERERFRXtQUuejevXs4efIkTp48iXv37kEqlSoUvGrVqjhz5ky+7WfOnEHlypUVGpuIiIiIiEheop8gAcDBgwexaNEivHr1CgAglUohCALMzc0xYcIEuLm5yRW8Z8+emD17NmbOnIkxY8bAxMQEABAXF4cVK1bg4sWLmDx5slxjEhERERERKUp0gbR7925MnToVVatWha+vL6pUqQKpVIqoqCiEhIRg4sSJyMjIwE8//SQ6uLu7OyIjI7Fz504EBwejTJkyAIB3795BKpWiW7duGDhwoPx3RUREREREpABBKvL9uLZt28LIyAhbtmzJtbx3Wloa3N3d8f79exw9elTuJMLDw3H06FE8f/4cUqkUlStXRtu2bYtl3lNh0tLSEBERAVtbW6Xv90TfqcXK30CZiEqoCYq9sk4lCz+rEBUv0U+QYmJi0L9//zz/R9TW1kaXLl2waNEihZJwcnKCk5OTQtcSEREREREpi+gCqUaNGnj9+nW+7a9evUL16tUVSiI6OhqXL19GXFwc2rdvDwsLC0gkEiQmJsLIyAgaGnJNlSIiIiIiIlKI6Mpj4sSJGDNmDH788cdcm8EePHgQoaGhWLFihdwJ+Pn5YevWrcjMzIQgCKhVqxYsLCyQmpoKV1dXjB49GoMGDZJ7XCIiIiIiInmJLpDWr18PY2NjTJgwAXPnzkXFihUhCAKePXuGd+/eoXLlyggMDERgYKDsGkEQsHbt2nzHDAwMxObNmzF48GA0adIEnp6esjZ9fX24uroiLCyMBRIREREREX0RogukR48eAQAsLCwAQPa6nZaWFiwsLJCeni7rk62wTWBDQkLQuXNn+Pr6Ij4+Ple7lZUVzp8/LzZFIiIiIiKiIhFdIJ08eVLpwaOjozF48OB82/X19ZGUlKT0uERERERERHlRU2VwY2NjxMbG5tt+//59mJubf8GMiIiIiIioJFNpgeTi4oLg4GDExcXlart79y5CQ0PRunVrFWRGREREREQlkUrXzx49ejTOnz+Pzp07w8XFBYIgYNeuXQgODkZYWBgsLS0xYsQIVaZIREREREQliEqfIJUtWxa7du1CixYtEBYWBqlUigMHDuDcuXPo0qULtm/fDiMjI1WmSEREREREJYjKniBl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ERERfdMKLJDMzc3zfb3uv1q3bo3WrVsrJSkiIiIiIiJVKPAVOyIiIiIiopLkqymQYmJiEBERgZSUFFWnQkREREREJZTKC6Tjx4+jTZs2aNmyJXr27Inbt28DAOLi4uDm5oawsDAVZ0hERERERCWFSguk06dPw8fHByYmJvD29s6x55KJiQksLCywe/duFWZIREREREQliUoLpJUrV8LBwQE7duzIczEIOzs7roxHRERERERfjEoLpPv376NDhw75tpctWxbv3r37ghkREREREVFJVuAy3/+VmZmJ8+fP48WLF0hISMjxShwACIIAb29v0eNpaWkhPT093/bo6GgYGBjIkyIREREREZHCRBdIkZGR8Pb2RkxMTK7CKJu8BZKDgwMOHToET0/PXG3JycnYtWsXnJycRI9HRERERERUFKILpP/9739ISUmBv78/HB0dYWhoWOTgo0aNgru7Ozw9PeHm5gYAuHv3LqKiorBhwwa8f/9eroKLiIiIiIioKEQXSHfv3oWPjw9at26ttOC1a9dGYGAgZsyYgalTpwIAFi5cCACoXLkyAgMDUb16daXFIyIiIiIiKojoAsnU1BSamppKT8DR0RFHjhzBvXv38OTJE0ilUlSsWBG2trYQBEHp8YiIiIiIiPIjukAaMGAA9uzZA3d3d6UVSh8/fkSpUqUAANbW1rC2tlbKuERERERERIoQXSCZm5tDXV0dnTp1Qvfu3VG+fHmoqeVeJbygZbv/q1GjRmjRogU6duyIZs2aQUtLS/S1REREREREyia6QBo/frzs6yVLluTZRxAEuQqkn376CceOHcPhw4ehr6+P1q1bo0OHDnB2doa6urrocYiIiIiIiJRBdIEUFBSk9OAzZszAr7/+ir///huHDh1CWFgY9u7dCyMjI7Rp0wYdOnSAk5MT5yIREREREdEXIUjz29RIBSQSCc6fP4+DBw/i5MmT+PDhA0xNTXHu3LkvlkNaWhoiIiJga2sLbW3tLxaXvl3X57qoOgUi+k44TD2t6hToG8DPKkTFS/QTpC9BQ0MDLi4uqFu3LmxsbODv74+3b9+qOi0iIiIiIioh8i2QpkyZAkEQMHv2bKirq2PKlCmFDiYIAubOnatQIsnJyTh27BgOHjyI8PBwZGZmwsrKCh07dlRoPCIiIiIiInnlWyCFh4dDEARkZWVBXV0d4eHhhQ4m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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(12,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(color_dict), legend=False)\n", - "# plt.ylim([-2000, 3000])\n", - "plt.ylabel('Difference in 2050 primary energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend(frameon=False)\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left',bbox_to_anchor=(1.01, 1), frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "# fig, ax = plt.subplots(figsize=(10,8))\n", - "# df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap))\n", - "# handles, labels = subp.get_legend_handles_labels()\n", - "# unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - "# if l not in labels[:i]]\n", - "# plt.legend(*zip(*unique[::-1]), loc='upper left',bbox_to_anchor=(1.01, 1), frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "# plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_all_primaryenergy_diff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "721a6f19", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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h4fLaDSqQoUKat++vT7//HM1bdrUtHZ7yhjt4cOHa9euXTp27JgSExNVqVIldezYUY6Ojlm+D5Zy8OBB/f777/Lz81NQUJDKlSvHJIEAUMBFRETowIEDOnz4sCpVqmTRMcIp17B/69+/v6l3W2hoqPbs2aPIyEjZ29urYsWKatWqlVxcXDJsN+X6/m8p18+UMdqvvfaa1q5dq5MnT8rKykpVq1ZV+/btZWOTuhNkWFiYdu/erWvXrsna2lply5ZVq1atVLRo0UzPL6Mx2rdv39bmzZt14sQJ3b9/X25ubgoODlaDBg1kMBgyjL9Lly769ddf091ObzUAeSnTruP/zsETEhIUFhamO3fu5GpQkBwdHdW+fXutWbNGfn5+qlKliiSpcOHCkh4kuLNnz5a1tbXq1asnOzs7HTx4UAsXLtSzzz5rqp+edu3a6ffff5eDg4MaN24sSbKzs0tVZ9euXUpOTladOnWUnJysnTt3avHixRoxYoTpSfP169c1e/ZsOTo6msaRnTx5UitXrlRcXJzq1auX5XlGRUVpyZIlCg4OVlBQkNzc3NKtZzQatWjRIl24cEHBwcEqXry4zp49q59++inDtn/++We5uLioefPmioyM1N69e2VlZaVnnnnG7Pchp8qXL69atWrpyJEjCgsLk7u7uwIDAxUYGJjhuQIA8p+4uDiFhYXpwIED+vvvv2VjY6MqVaqodu3aFj1OlSpVdOPGDR09elRt27Y1fTns6ekpSdq+fbs2bNigsmXLqnXr1oqOjta+fft04cIFDRkyJMOnvZ6enmratKm2bNmi4OBg02S1Xl5epjpGo1ELFiyQl5eXWrdurb///luhoaFydHRM1VU7JYYqVaqoevXqio+P1/79+zV79mwNHjzYdK9irrt372rWrFlKSkpSzZo15ezsrAsXLujPP//U7du31a5dO3l6eqpbt276448/Ul23PTw8VLt2be3bt880bluSSpcuna0YAMDSMk20kXfs7OxUtWpVrVmzRl5eXqpevXqq8g0bNighIUEvvvii6eJbs2ZNTZ8+XevWrZOfn1+GT079/Pz0559/ysnJKU27KeLi4vTKK6+YvsH29PTU4sWLdfr0aVWuXFmStHbtWjk7O2vw4MGytbWVJNWuXVvLli3Tpk2bFBwcnGXievPmTfXs2VO+vr6Z1jt+/LguXLigli1b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tHz5co0fP16SVL9+ffn6+qpFixamMl9fXx06dEgvvviigoKCTF3BT5w4YdoWHBysl19+2azhP4cPH9YLL7xg2m/EiBG6evWqqXzPnj3y9fXVkSNHUu03btw4derUyfQ6JbajR4/qpZdeUmBgoFq1aqUNGzbIaDTq+++/V+PGjVW3bl29//77pvMFAAAoCMxOtEeOHClra2s99dRT+uyzz2QwGPTzzz9r1KhReu6551S8eHG98sorOQomNDRUycnJKlWqVI7aAYCC7M6dO7p165aKFSumefPmafz48apXr56mT5+u4cOHa8WKFZo4caKkB1+CpvztnTlzphYvXqxp06alam/UqFEKDg7Wt99+q+7du+vKlSvq3bu3rl+/rkmTJmnixIk6f/68evfune6XqSkOHz6s3r17y87OTlOmTNGkSZN07tw5DRo0SElJSQ91rq+//roaNmyoadOmqWzZsnrttdc0efJkHT16VB999JFefvllLVmyRD/++ONDtQ8AAJAXzO46XrRoUf3888/64osvtH79ehmNRv32229ydnZWly5dNGbMGLm5uZl94EGDBqlevXqqVKmS7OzsdOzYMc2aNUu+vr45fjIOAAVNyrKG165d03//+18lJSWpWbNmeuONNzRw4ECNHTvWVNfV1VVjx47V4MGDVapUKdM4bn9//3S7jj/33HN6+eWXTa8nTZqkhIQE/fDDD6b6NWrUUNu2bfXjjz9qxIgR6cb46aefqkqVKpo+fboMBoOkB93727Ztq99++01dunTJ9nn36dNHvXv3liT5+PioZcuW2rFjh1auXCkrKys1adJEu3fv1tq1azVw4MBstw8AAJAXsrWOtoeHhz788EN9+OGHioqKUnJysjw8PGRlZfaDcZPq1atr5cqVpq6RpUqVUq9evTRw4EDZ2dlluz0AKKju3bsnf39/02tXV1e99957cnBw0N27d9WhQwdTIi496CKelJSkY8eOmdUDKKUreYr9+/erXr16qZLykiVLKigoKMPZzmNjYxUSEqI33ngj1dNrLy8vlStXTkeOHHmoRLthw4am30uVKiVbW1vVr18/1XXFx8dHa9asyXbbAAAAeSVbifY/ZTXhTlZeffVVvfrqqzlqAwAeBw4ODlqwYIEMBoMKFy4sb29vWVlZaeXKlZKkp59+Ot39Ll++bFb7np6eqV7HxMSY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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(1,2,figsize=(14,8), sharey='row', gridspec_kw={'width_ratios': [1, 3]})\n", - "ax1 = plt.subplot(1,2,1)\n", - "\n", - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "ind_notax = list_of_DBs.index('US_9R_8D_CT0.sqlite')\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if ('neg' in db) | ('US_9R_8D_CT0' in db):\n", - " if i!=ind_notax:\n", - " df_diff = output_list[i] - output_list[i+1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[ind_notax]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax1, color = df_diff_all.index.map(color_dict), legend=False)\n", - "# ax1.set_ylabel('Difference in 2050 primary energy consumption (EJ) \\n relative to succeeding scenario (to the right)')\n", - "ax1.set_title('relative to scenario \\n to the right -->',color='grey')\n", - "ax2 = plt.subplot(1,2,2)\n", - "\n", - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if 'neg' in db:\n", - " pass\n", - " else:\n", - " if i!=ind_notax:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[ind_notax]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "ax2.set_title('relative to scenario \\n <-- to the left',color='grey')\n", - "\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax2, color = df_diff_all.index.map(color_dict))\n", - "ax1.set_ylabel('Difference in 2050 primary energy consumption (EJ)')\n", - "\n", - "# plt.ylim([-2000, 3000])\n", - "plt.legend(frameon=False)\n", - "\n", - "handles, labels = ax2.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "# # fig, ax = plt.subplots(figsize=(10,8))\n", - "# # df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap))\n", - "# # handles, labels = subp.get_legend_handles_labels()\n", - "# # unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - "# # if l not in labels[:i]]\n", - "# # plt.legend(*zip(*unique[::-1]), loc='upper left',bbox_to_anchor=(1.01, 1), frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "# # plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_all_primaryenergy_diff_alt.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "937d18b9", - "metadata": {}, - "outputs": [], - "source": [ - "df_diff_all" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "26f940d0", - "metadata": {}, - "outputs": [], - "source": [ - "region = 'US'\n", - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " \n", - " if region=='US':\n", - " query = \"SELECT tech, t_periods, sum(vflow_out) as vflow_out FROM Output_VFlow_Out WHERE \\\n", - " sector = 'electric' AND scenario='\" + scenario + \"' \\\n", - " AND vflow_out > 1e-3 GROUP BY tech, t_periods\"\n", - " else:\n", - " query = \"SELECT regions, tech, t_periods, sum(vflow_out) as vflow_out FROM Output_VFlow_Out WHERE \\\n", - " sector = 'electric' AND scenario='\" + scenario + \"' \\\n", - " AND vflow_out > 1e-3 GROUP BY regions, tech, t_periods\"\n", - "\n", - " df_s = pd.read_sql_query(query, conn)\n", - " df_plot = df_s.groupby(['tech' , 't_periods']).sum().pivot_table(values='vflow_out', index='tech', columns='t_periods')\n", - " df_plot = df_plot[~df_plot.index.str.contains('TRANS')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('BLND')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('Batt')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('HYDPS')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('_emissions')]\n", - " \n", - "# df_stor = pd.read_sql_query(\"SELECT DISTINCT tech FROM StorageDuration\", conn)\n", - "# df_plot = df_plot[~df_plot.index.isin(df_stor['tech'])]\n", - " \n", - " df_plot.loc[:,'agg_tech'] = [map_plants[y] for x in df_plot.index for y in map_plants.keys() if y.lower() in x.lower()] #map agg technologies\n", - "\n", - " df_plot = df_plot.groupby('agg_tech').sum()\n", - " df_plot = df_plot.loc[:, df_plot.columns >= 2020]\n", - " df_plot.fillna(0, inplace=True)\n", - " df_plot*=0.277778\n", - "\n", - " df_plot = df_plot[[2020, 2035, 2050]]\n", - "\n", - " output_list.append(df_plot)\n", - "\n", - "\n", - "ax = func_stacked_plot(output_list)\n", - "plt.xticks([2020, 2035, 2050])\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.5) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2035\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - "\n", - "ax.grid(axis='x')\n", - "plt.ylim([0, 12000])\n", - "ax.get_yaxis().set_major_formatter(\n", - "tick.FuncFormatter(lambda x, p: format(int(x), ',')))\n", - "plt.ylabel('Generation (TWh)')\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_gen.jpg')\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e94a7d4", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(12,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(color_dict), legend=False)\n", - "plt.ylim([-2000, 3000])\n", - "plt.ylabel('Difference in 2050 electricity generation (TWh) \\n relative to preceding scenario (to the left)')\n", - "plt.legend(frameon=False)\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left',bbox_to_anchor=(1.01, 1), frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_gen_diff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "67f1dfe8", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'transport', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_transport)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Jet Fuel','Diesel','Gasoline','Biodiesel','Ethanol','Synthetic Fuel','Electricity','Hydrogen']\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2035, 2050]]\n", - "\n", - " \n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = ['Other Fossil', 'Jet Fuel','Diesel','Gasoline','Biodiesel',\\\n", - " 'Ethanol','Synthetic Fuel','Electricity','Hydrogen'], color_dict = tech_colormap)\n", - "plt.xticks([2020, 2035, 2050])\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.5) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2035\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - "\n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,30])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_transport.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "33cda9f2", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_transportdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f14d20e", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'industrial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_industrial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Coal','Diesel','Gasoline','Natural Gas', 'Biomass','Synthetic Natural Gas',\\\n", - " 'Electricity', 'Hydrogen']\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2035, 2050]]\n", - "\n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = ['Other Fossil', 'Coal','Diesel','Gasoline','Natural Gas', 'Biomass','Synthetic Natural Gas',\\\n", - " 'Electricity', 'Hydrogen'], color_dict = tech_colormap)\n", - "plt.xticks([2020, 2035, 2050])\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,30])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_industrial.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e970fb9", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_industrialdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8ec25443", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'residential', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_residential)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Biomass','Natural Gas','Residential Solar','Synthetic Natural Gas', 'Electricity', 'Hydrogen']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_res = df_plot_copy[[2020, 2035, 2050]]\n", - " \n", - " df_plot = stacked_penergy_sector(conn, scenario, 'commercial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_commercial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Natural Gas','Synthetic Natural Gas','Electricity']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_comm = df_plot_copy[[2020, 2035, 2050]]\n", - " \n", - " df_plot_bld = pd.concat([df_plot_res, df_plot_comm]).groupby('input_comm').sum()\n", - " output_list.append(df_plot_bld)\n", - " \n", - "fuels_order = ['Other Fossil', 'Biomass','Natural Gas','Residential Solar','Synthetic Natural Gas', 'Electricity', 'Hydrogen']\n", - "ax = func_stacked_plot(output_list, col_order = fuels_order, color_dict = tech_colormap)\n", - "plt.xticks([2020, 2035, 2050])\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,25])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_buildings.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f5007e5a", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_all_scens_buildingsdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "id": "975c8af2", - "metadata": {}, - "outputs": [], - "source": [ - "def plot_mit_curve(merge_dbs, xlim, ylim):\n", - " fig, ax = plt.subplots(figsize=(10,6))\n", - "\n", - " w = merge_dbs['emissions'].diff() \n", - " w.iloc[0] = merge_dbs['emissions'].iloc[0]\n", - " w = list(w)\n", - " xticks=[]\n", - " for n, c in enumerate(w):\n", - " xticks.append(sum(w[:n]) + w[n]/2)\n", - "\n", - " plt.bar(xticks,height=merge_dbs['shadow_price'], width =w)\n", - " plt.ylim([0,ylim])\n", - " plt.xlim([0,xlim])\n", - " plt.xlabel('Avoided annual CO$_2$ emissions (million tonnes)')\n", - " plt.ylabel('Shadow price on abated CO$_2$ emissions ($/tonnene CO$_2$)')\n", - " ax.get_yaxis().set_major_formatter(\n", - " tick.FuncFormatter(lambda x, p: format(int(x), ',')))\n" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "1b60e309", - "metadata": {}, - "outputs": [], - "source": [ - "#os.mkdir('carbon_tax_figures_updates')" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "2f7edebf", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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/u/mjU2bNmHq1Klqt4xJJBLo6urCysoKNjY2mo+UiIiIiEiERiW6I0eOFK6Wq6iowKRJkzB48OAWDYyIiIiIqDlEXwG8aNGiloiDiOrwuLIS2v93hjURERGJIzrRffjwIQoLC+u94vf27dswNDREt27dmh0c0fNOWypFVKhXvfWLwj9rxWiIiIg6FtHngIWHh2PhwoX11vv5+WH9+vXNCoqIiIiIqLlEJ7o///wzJk6cWG/9xIkTcezYsWYFRURERJ1XdUWF8HPtO0BELUH01oX79+/jhRdeqLfexMQE9+7da1ZQRERE1HlpyWT4ecbMBtuM+y65laKhzkz0iq6RkREuX75cb/3ly5ehUCiaFRQRERERUXOJTnQdHR3xzTff4MyZM2p1mZmZ+Oabb/Daa69pJDgiIiIioqYSvXXB398fR48exTvvvIPXXnsN5ubmkEgk+PXXX5Geng5jY2MsXry4JWIlIiIiImo00YmuiYkJkpOT8cknnyA1NRU//fQTAEAul2PGjBkICgqCiYmJpuMkIiIiIhJFdKILAMbGxli3bh1qampQUFCAmpoa9OjRAxKJRNPxERERERE1iehE9/bt23WW37lzR+VzfRdKEBERERG1BtGJrrOzc6NWbrOyspoUEBERERGRJohOdMPCwtQS3aqqKty8eRPfffcdevToAQ8PD40FSERERETUFKITXXd393rr5s+fj1mzZqG0tLRZQRERERERNZfoc3QboqenB3d3d+zZs0eT3RIRERERiabRRBcApFIp7t69q+luiYiIiIhE0Wiim52djYSEBJiZmWmyWyIiIiIi0TR26kJxcTGKi4uhq6uL8PBwjQRHRERERNRUohPdV199tc5E18DAAP369YOrqysUCoVGgiMiIiIiairRie66detaIg4iIiIiIo3S+MtoRERERETtwTNXdOu78vdZeAUwEREREbWlZya6jb3y9894BTARtYbHlVXQlnZp6zCIiKgdemai++crf2tqapCQkIBbt27Bzc0NAwYMQE1NDXJzc/HPf/4Tffr0wdy5c1s0aCKiWtrSLli95GCDbVZtdG2laIiIqD15ZqL75yt/Y2Nj8ejRI3z//fcwNDRUqfP398dbb72FgoICzUZJRERERCSS6JfRvvrqK/z1r39VS3IBwMjICLNnz8aXX36pkeCIiIiIiJpKdKKbn5+P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rru7e4OfiYiIiIjaI9EXRhARERERdQRNOnXhf//3f5GUlIQbN27gwYMHqKmpUamXSCTIyMjQSIBERERERE0hOtGNiopCdHQ0FAoFLCws0L9//5aIi4iIiIioWUQnuomJiRgzZgxiYmIgk8laIiYiIiIiomYTvUf38ePHmDRpEpNcIiIiImrXRCe648aNw4ULF1oiFiIiIiIijRGd6K5atQoXLlxAVFQUbt++rfYiGhERERFReyB6j66RkRGmTp2KyMhIREdH19lGIpHgP//5T7ODIyIiIiJqKtGJ7ieffIJdu3ahb9++sLa2hlwub4m4iIiIiIiaRXSiq1QqMWHCBERFRbVEPEREREREGiF6j25NTQ3s7e1bIhYiIiIiIo0Rneg6OzvjX//6V0vEQkRERESkMaIT3ffeew+5ublYuXIlzp07h3v37iE/P1/tFxERERFRWxK9R3fKlCkAgKysLCQlJdXbLisrq+lR1ePbb79FaGioWrmHhwdWrVolfD569Cg2b96MK1euwNTUFPPmzcPcuXPVntu1axcSExORl5cHMzMzBAcHY8yYMRqPm4iIiIhan+hE18/PDxKJpCViabS4uDjo6+sLn42NjYWfMzMzsXDhQsyYMQPLli3DmTNnEBYWBm1tbbz99ttCu127diEyMhKBgYGwtLSEUqmEr68vlEolXn755Vb9PkRERESkeaITXX9//5aIQ5ShQ4fCyMiozrqoqChYWloiLCwMADB69GjcuXMH0dHRePPNN6GlpYWKigrs2LED7777Lnx8fAAAr776Ktzc3LBjxw5s2bKl1b4LEREREbUM0Xt027OKigqcOHECU6dOVSl3dXXF/fv3cfHiRQDAmTNnUFxcjGnTpgltunTpgilTpiA9PZ23vRERERF1Ak1OdG/fvo19+/Zh9+7duHPnDgDg8ePHyM/Px+PHjzUWYF3c3NwwZMgQODs7IyoqShjv+vXrqKysxKBBg1Tam5ubAwCuXr0KAMjJyQEAtXZmZmZ4+PAh7t6926LxExEREVHLE711AQDCw8PxxRdfoKqqChKJBEOGDEGvXr1QVlYGFxcXBAQEwNPTU8OhAiYmJvD394e1tTW6dOmC9PR0bN++HTdv3sS6devw4MEDAIBCoVB5rvZzbX1RURFkMhl0dHRU2hkYGAAACgsL0bNnT43HT0REREStR3SiGxcX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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(figsize=(10,8))\n", - "x_adj = -1.25\n", - "for db in list_of_DBs:\n", - " if db !=base:\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " ax.bar(emis_reductions[db].index + x_adj,emis_reductions[db]['emissions'], label = scen_name)\n", - " x_adj+=0.5\n", - "plt.ylabel('Annual emissions reductions (million tonnes CO$_2$)')\n", - "plt.legend()\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_mitigation_curve.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "3121b7ad", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
-1010100200300400500
20201.818989e-12-9.094947e-13-9.094947e-13-2.728484e-12-2.728484e-12-4.547474e-129.094947e-13
2025-5.744561e+014.826871e+017.003515e+029.430930e+021.091853e+031.381918e+031.664550e+03
2030-6.422540e+013.668175e+016.532997e+029.211713e+021.230383e+031.650798e+031.956162e+03
2035-5.962839e+016.782927e+017.155486e+021.025245e+031.553076e+031.950417e+032.260195e+03
2040-7.093354e+015.148444e+017.426683e+021.155758e+031.765291e+032.225288e+032.432806e+03
2045-1.064066e+025.571457e+018.379535e+021.363562e+032.064070e+032.507763e+032.626591e+03
2050-1.667094e+028.162671e+018.476383e+021.467175e+032.178318e+032.599885e+033.046109e+03
\n", - "" - ], - "text/plain": [ - " -10 10 100 200 300 \\\n", - "2020 1.818989e-12 -9.094947e-13 -9.094947e-13 -2.728484e-12 -2.728484e-12 \n", - "2025 -5.744561e+01 4.826871e+01 7.003515e+02 9.430930e+02 1.091853e+03 \n", - "2030 -6.422540e+01 3.668175e+01 6.532997e+02 9.211713e+02 1.230383e+03 \n", - "2035 -5.962839e+01 6.782927e+01 7.155486e+02 1.025245e+03 1.553076e+03 \n", - "2040 -7.093354e+01 5.148444e+01 7.426683e+02 1.155758e+03 1.765291e+03 \n", - "2045 -1.064066e+02 5.571457e+01 8.379535e+02 1.363562e+03 2.064070e+03 \n", - "2050 -1.667094e+02 8.162671e+01 8.476383e+02 1.467175e+03 2.178318e+03 \n", - "\n", - " 400 500 \n", - "2020 -4.547474e-12 9.094947e-13 \n", - "2025 1.381918e+03 1.664550e+03 \n", - "2030 1.650798e+03 1.956162e+03 \n", - "2035 1.950417e+03 2.260195e+03 \n", - "2040 2.225288e+03 2.432806e+03 \n", - "2045 2.507763e+03 2.626591e+03 \n", - "2050 2.599885e+03 3.046109e+03 " - ] - }, - "execution_count": 35, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# fig, ax = plt.subplots(figsize=(10,8))\n", - "x_adj = -1.25\n", - "all_emiss_db = pd.DataFrame(index = [2020, 2025, 2030, 2035, 2040, 2045, 2050])\n", - "for db in list_of_DBs:\n", - " if db !=base:\n", - " dummy = emis_reductions[db]\n", - " \n", - " try:\n", - " scen_name = int(map_scenario_names[db.replace('sqlite','').split('CT')[1]].replace('$/tonne',''))\n", - " except:\n", - " scen_name = 0\n", - " dummy.columns = [scen_name]\n", - " all_emiss_db = pd.merge(all_emiss_db, dummy, left_index= True, right_index=True)\n", - "all_emiss_db" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "7c355833", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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QGg0Gt544w0UCoVBcHmrpKQkioqKjIJ5nU7HmTNnyu1SFw8eadEU9z13d3fWrFnDxIkTGTp0qMFuGQkJCcTExFCvXj1mz55NVFQU77//PtOnT2fcuHGo1Wq2bduGl5cX6enpVq2Xpdfq06cPO3bswNXVlaCgIM6ePcuuXbuMxuWVBYnLli0jKioKBwcHunXrVmG5tra2PP7440RHR6NWq5k4caJRnldffZXff/+d0aNHM3z4cIKDg1EqlVy4cIE9e/awZ88evL29K7yGpedXVUFBAb169eKxxx6jVatWuLq6EhcXx6FDh/S7sZiSpzwzZ87k8OHDjBkzhmeeeQYXFxe2b99Oeno6K1asMHuHlKrWozKmvvczZszgl19+4ZlnnmHMmDHY29uzdetWfH19jQIfc36WZ8+ezeHDhxk/frx+eaMbN26wd+9eVq9eTZMmTSr8ma1fv77R/dSk993R0ZGePXty+PBho9103NzcWLt2LZMmTWLo0KEGOwNlZGRw4MABLl26RPv27fXnmPM+gHnfdXBzws7KlSv1+ct+HnJzc/n+++85f/48EydO1E8EvFVRURH/+Mc/mDJlilH3+J9//kl+fr7VVtgQtZsEmuKBEB4eTkxMDJ988gn79+/nhx9+QKfT0axZM0aNGsWzzz4L3PziXbNmDUuWLOHf//43Pj4+PPfcc7i5uVV5iZmKWHqt119/HXt7e3bu3ElhYSFt2rTho48+4l//+pdBvvbt2zNz5kz++9//Mn/+fLRaLZs2bbpj2VFRUWzevBmg3DX2vLy82Lp1K+vWreOnn35i69at+hnv06dPx8PD447lW3p+VTk7O/PMM89w5MgRfv75ZzQaDU2aNGHu3Ln6fZlNyVOeli1bsmXLFpYtW8ann36KWq0mNDSUDz74QD+xytp1rQpT3/vg4GA++eQT3nvvPdauXUv9+vV55plnqF+/vtGkFXN+ln18fIiOjmbFihXs3LkThUKBj48PPXr00A/5qOhntrwAq6a978OHD2fy5MlcvnzZaKxx69at+e6779i0aRP79u3j559/Rq1W06BBA9q1a8ekSZMMFr43530oY+p3XZmOHTvy/fffs379en7++We2bNmCi4sLbdq0Yc6cOeW+hyqVipdffpnAwEAmT55slL57924aNWokuwIJQPY6F0IIIaxGq9UyePBgevbsydy5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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(figsize=(10,8))\n", - "aa = 0.2\n", - "for ind, row in all_emiss_db.iterrows():\n", - " plt.plot(row.values, row.index, color = 'b', alpha=aa, label = ind)\n", - " aa+=0.12\n", - "plt.xlabel('Annual emissions reductions (million tonnes CO$_2$)')\n", - "plt.ylabel('Carbon price (\\$/tonne CO$_2$)')\n", - "\n", - "plt.legend()\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.ylim([-50,1000])\n", - "plt.xlim([-2000, 5000])\n", - "ax2 = ax.twiny()\n", - "sum_emissions = all_emiss_db.sum()/1000*5\n", - "ax2.plot(sum_emissions.values,sum_emissions.index, 'k--', label='Cumulative')\n", - "# ax2.plot(all_emiss_db.loc[2050]*30/1000, sum_emissions.index, edgecolor=\"None\")\n", - "# ax2.plot(all_emiss_db.loc[2020]*30/1000, sum_emissions.index, edgecolor=\"None\")\n", - "\n", - "ax2.set_xlim([-2000/1000*30, 5000/1000*30])\n", - "ax2.set_xlabel('Cumulative emissions reductions (Gt CO$_2$)')\n", - "ax2.legend(loc = 'lower left')\n", - "mpl_axes_aligner.align.xaxes(ax, 0, ax2, 0, 0.4)\n", - "ax.grid(axis='x')\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_mitigation_curve_annual_cumulative_PvsQ.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 37, - "id": "0c8e352a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
index0
0-10-2.626745
1101.708027
210022.487300
320034.380018
430049.414953
540061.580349
650069.932062
\n", - "
" - ], - "text/plain": [ - " index 0\n", - "0 -10 -2.626745\n", - "1 10 1.708027\n", - "2 100 22.487300\n", - "3 200 34.380018\n", - "4 300 49.414953\n", - "5 400 61.580349\n", - "6 500 69.932062" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "dummy =pd.DataFrame(sum_emissions)\n", - "# dummy.loc[0] = 0\n", - "dummy.sort_index(inplace=True)\n", - "# dummy['diff'] = dummy[0].diff()\n", - "# dummy.fillna(0, inplace=True)\n", - "dummy.reset_index(inplace=True)\n", - "dummy" - ] - }, - { - "cell_type": "code", - "execution_count": 38, - "id": "4f75051b", - "metadata": {}, - "outputs": [], - "source": [ - "def resource_curve_plot(df_all, xlabel):\n", - " fig, ax = plt.subplots(figsize=(8,6))\n", - " \n", - " df_sel = df_all[df_all[0]>0]\n", - "\n", - " start =0\n", - " for ind, row in df_sel.iterrows():\n", - " x = start + (row[0]-start)/2\n", - " plt.bar(x, row['index'] , width=row[0]-start, color='b')\n", - " start = row[0]\n", - " \n", - " df_sel = df_all[df_all[0]<0]\n", - " df_sel.sort_index(ascending=False, inplace=True)\n", - " start =0\n", - " for ind, row in df_sel.iterrows():\n", - " x = start + (row[0]-start)/2\n", - " plt.bar(x, row['index'] , width=row[0]-start, color='b')\n", - " start = row[0]\n", - " \n", - " plt.ylabel('Carbon tax (\\$/tonne CO$_2$)')\n", - " plt.xlabel(xlabel)\n", - " plt.tight_layout()\n", - "# plt.savefig(country + '_resource_curve.jpg', dpi=400)" - ] - }, - { - "cell_type": "code", - "execution_count": 39, - "id": "98d2fd2b", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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0m+NHvr55JNPEBsbiy1btqBnz54YNGgQXF1d8eKLL6JNmzaQSqUQi8W4efMmzp8/jzNnzuDChQuwt7fHmjVrnkYfiIiIqIVqNKjIJmxLS0vD/v378eWXX0IikcDAwEChnVQqhYmJCTw9PTFnzhx4eXkptSEiIiJSh0on0xoYGMDb2xve3t6orq7GhQsXkJubi/v37wMAnn/+edjZ2aFHjx68czIRERFpjNpX/RgbG6NPnz7o06ePNuohIiIiklPr7slERERETxODChEREQkWgwoREREJFoMKERERCRaDChEREQkWgwoREREJltpBpaqqqtE2BQUFTSqGiIiI6FFqB5WAgABcvHixwfVJSUkYN25cs4oiIiIiApq4R2XSpElYv349amtr5cuLioowb948LF++HK6urhotkoiIiPST2kHlwIEDmDBhAtavX49JkyYhJycHR44cgZ+fH3744QdERkZi69at2qiViIiI9IzaU+g/99xz+OCDDzB8+HC8++67GD9+PGpra9G3b1+sXr0aL730kjbqJCIiIj2kdlCRMTQ0hIGBAWpqagAAnTp1Qps2bTRWGBEREZHah34qKyuxcuVKhISEoF27djhw4ACWLl2KI0eOwN/fH6dOndJGnURERKSH1A4q48ePx969exEWFoavv/4ajo6OCAkJwf79+9GuXTuEhoYiMjJSG7Uqqa2tRUBAAJycnHD48GGFdWlpaQgICICbmxt8fX2RkJBQ7za2bt0KHx8f9OzZE4GBgcjIyHgapRMREZEK1A4qRkZG2L17N+bPn49Wrf46cmRvb4/ExETMmTMH3377rUaLbMju3btRWFiotDw7Oxvh4eFwdnbG5s2bERgYiKioKOzevVuh3datWxETE4OgoCBs2rQJXbp0QWhoKC5duvRU6iciIqInUzuofPvtt3Bzc6t3nZGREebOnYu9e/c2u7DG3L17F7GxsViyZInSuvXr18PFxQVRUVHw8PBAeHg4Jk6ciA0bNqCurg7Aw8us4+LiEBwcjJCQEAwaNAj/+te/8NJLLyEuLk7r9RMREVHj1A4qJiYmjbZxdnZuUjHq+OSTT/Dqq69i4MCBCsurqqpw9uxZjBkzRmG5n58f7ty5I5+sLisrC6WlpRg7dqy8jZGREUaPHo309HRIpVKt94GIiIierElX/dy7dw/ffPMNLl68CLFYLN9LIWNgYIAdO3ZopMD6nDt3DkeOHEFKSorCpHMAkJeXh+rqatjZ2Sksd3BwAADk5ubCzc0NOTk5AKDUzt7eHhUVFSgoKECnTp201gciIiJqnNpB5erVq5g6dSoqKirQpUsX/PHHH7C3t0dJSQkKCwtha2uLjh07aqNWAEBNTQ1WrlyJ0NBQdO7cGTdv3lRYX1JSAgCwtLRUWC57LFsvFoshEolgamqq0E52iXVxcTGDChERkY6pHVQ+/fRTtGrVCocOHYKZmRkGDx6Md955B4MGDUJycjJWrVqF6OhobdQKANi5cycqKysREhLyxHYGBgaNLq+vjeyQT0PPb8iFCxfUat/SZGZm6roEndP3MdBV/7s794BZa9PGG2pZv379dF2Czt+Dun59IeAYaJ7aQSUzMxPTpk3DSy+9hOLiYgB/fbn7+fkhMzMTn3zyCXbu3KnRQoGH9xNat24d3nvvPVRWVqKyshJlZWUAHs7vUlpaKt8jIttzIiMWiwH8tWfF0tISEokEEolE4bwbWTt1J69zdXVV6fydligzM1MQH9K6pO9joOv++y/5Tmev/SQH145/qq+ny5+Brt8DQqDPYyCRSLT2B7vaQaW6ulp+aEd22KS0tFS+3tnZGUlJSZqp7jEFBQWoqKjAsmXLlNYtW7YMFhYWOHPmDIyNjZGbm4shQ4bI11+9ehUA0K1bNwB/nZuSk5MDFxcXebucnByYmZlp9fAVERERqUbtoPLoeSGmpqZo3749fv75Z4wcORIAcOXKFZiZmWm2yv9na2urtKfm7t27WLx4MebNmwcPDw+IRCJ4eHggNTUV06dPl7dLTk5G+/bt0aNHDwBA3759YWFhgZSUFHlQqa2tRWpqKjw9PdU+9ENERESap3ZQcXd3x/Hjx7Fo0SIAgL+/P3bs2IHS0lLU1dXJ766sDWZmZnB3d1dYJgtN9vb26N+/PwBgzpw5mDp1KlasWAF/f39kZWUhMTERkZGRMDR8eEW2SCRCWFgYYmJiYGVlBRcXFyQmJiIvLw9r167VSv1ERESkHrWDSmhoKM6fPy8/t2PhwoUoKytDamoqDA0NMW7cOLz99tvaqFVlffr0wcaNGxEdHY2kpCR06NABERERmDJlikI72Qm5CQkJuHv3LhwcHBAfH4/u3bvromwiIiJ6jNpBxdraGtbW1vLHIpEIK1euxMqVKzVamKpefPFFXL58WWm5l5cXvLy8Gn1+SEhIo1cQERERkW6oPTPtuXPnUFRU1OD6oqIinDt3rllFEREREQFNCCrBwcE4ffp0g+vPnj2L4ODgZhVFREREBDQhqDR2D5yqqir5CatEREREzaHSOSplZWXyidCAh9PL37p1S6mdWCzGoUOHOAcJERERaYRKQWX79u3YsGEDgIdTy0dFRSEqKqretlKpVH7pMhEREVFzqBRUBg0aBJFIBACIjo7GmDFjlC7hNTAwQOvWreHq6opevXppvlIiIiLSOyoFlX79+snvX1BVVYURI0bA0dFRq4URERERqT2Pyty5c7VRBxEREZESXp5DREREgsWgQkRERILFoEJERESCxaBCREREgsWgQkRERIKl9lU/MmVlZbh9+zZKSkrqnVZ/wIABzSqMiIiISO2gUlJSglWrVuHw4cOora1VWi+VSmFgYIDff/9dIwUSERGR/lI7qERGRuLo0aMICgrCwIEDYWlpqY26iIiIiNQPKunp6XjzzTexfPlybdRDREREJKf2ybQikQgvv/yyNmohIiIiUqB2UBk5ciTS09O1UQsRERGRArWDSkhICAoLC7Fs2TJkZ2ejsLAQ9+7dU/pHRERE1Fxqn6MycuRIGBgY4OLFizhw4ECD7XjVDxERETWX2kFlzpw5MDAw0EYtRERERArUDirz5s3TRh1EpANV1bUQGRs1ezv9+vXTQDVERMqaPDMtAFy+fBn5+fkAABsbGzg5OWmkKCJ6OkTGRvBf8p2uy2iWg2vH67oEItKiJgWVo0ePIioqCrdv3wbw12y01tbWiIiIgK+vr0aLJCIiIv3UpAnf5s+fj06dOmHRokWws7ODVCpFbm4u9uzZgwULFuCLL76Ap6enNuolIiIiPaJ2UNm4cSPs7Oywe/dumJubK6x74403MGXKFGzcuJFBhYiIiJpN7XlULl26hAkTJiiFFAAwNzfHhAkTeGkyERERaYTaQcXY2BgVFRUNri8vL4exsXGziiIiIiICmhBU+vXrh3//+9+4fv260robN25g165d6N+/vyZqIyIiIj2n9jkqS5YsweTJk+Hn5wcfHx907doVAHDt2jWcOHECpqamWLJkicYLJSIiIv2jdlBxcHDAvn37EB0djVOnTuH7778HADz33HMYOnQoFi1aJA8vRERERM3RpHlUunTpgs8//xx1dXUo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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "resource_curve_plot(pd.DataFrame(sum_emissions).reset_index(), xlabel = 'Cumulative emissions reductions (Gt CO$_2$)')\n", - "# plt.plot(sum_emissions.values,sum_emissions.index, 'k--', label='Cumulative')\n", - "plt.xlim([-20, 120])\n", - "plt.ylim([-100, 1100])\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/co2_mitigation_curve_stacked_alt.jpg', dpi=400)" - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "02b01a02", - "metadata": {}, - "outputs": [ - { - "ename": "KeyError", - "evalue": "0", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/mnt/cephfs/cluster/epp/user/avenkate/pwd/envs/temoa-py3/lib/python3.7/site-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 3360\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3361\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcasted_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3362\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/mnt/cephfs/cluster/epp/user/avenkate/pwd/envs/temoa-py3/lib/python3.7/site-packages/pandas/_libs/index.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", - "\u001b[0;32m/mnt/cephfs/cluster/epp/user/avenkate/pwd/envs/temoa-py3/lib/python3.7/site-packages/pandas/_libs/index.pyx\u001b[0m in \u001b[0;36mpandas._libs.index.IndexEngine.get_loc\u001b[0;34m()\u001b[0m\n", - "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.Int64HashTable.get_item\u001b[0;34m()\u001b[0m\n", - "\u001b[0;32mpandas/_libs/hashtable_class_helper.pxi\u001b[0m in \u001b[0;36mpandas._libs.hashtable.Int64HashTable.get_item\u001b[0;34m()\u001b[0m\n", - "\u001b[0;31mKeyError\u001b[0m: 0", - "\nThe above exception was the direct cause of the following exception:\n", - "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/tmp/ipykernel_13746/1202034546.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mresource_curve_plot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mall_emiss_db\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m2020\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'2020 annual emissions reductions (million tonnes CO$_2$/year)'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m/tmp/ipykernel_13746/105789939.py\u001b[0m in \u001b[0;36mresource_curve_plot\u001b[0;34m(df_all, xlabel)\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubplots\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfigsize\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m8\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m6\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mdf_sel\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdf_all\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mdf_all\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m>\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mstart\u001b[0m \u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/mnt/cephfs/cluster/epp/user/avenkate/pwd/envs/temoa-py3/lib/python3.7/site-packages/pandas/core/series.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 940\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 941\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mkey_is_scalar\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 942\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_value\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 943\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 944\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_hashable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/mnt/cephfs/cluster/epp/user/avenkate/pwd/envs/temoa-py3/lib/python3.7/site-packages/pandas/core/series.py\u001b[0m in \u001b[0;36m_get_value\u001b[0;34m(self, label, takeable)\u001b[0m\n\u001b[1;32m 1049\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1050\u001b[0m \u001b[0;31m# Similar to Index.get_value, but we do not fall back to positional\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1051\u001b[0;31m \u001b[0mloc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlabel\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1052\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mindex\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_values_for_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mloc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabel\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1053\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/mnt/cephfs/cluster/epp/user/avenkate/pwd/envs/temoa-py3/lib/python3.7/site-packages/pandas/core/indexes/base.py\u001b[0m in \u001b[0;36mget_loc\u001b[0;34m(self, key, method, tolerance)\u001b[0m\n\u001b[1;32m 3361\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_engine\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_loc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcasted_key\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3362\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mKeyError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3363\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3364\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3365\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_scalar\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0misna\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhasnans\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mKeyError\u001b[0m: 0" - ] - }, - { - "data": { - "image/png": "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U0xMjMaMGRPpzAAAIEwhX7pfsGCB3n77bbndbrndbjU1NamiokKzZs0K+jR+SUmJvF6vjh07JunyS+333HOP1qxZo6amJt1555367LPPtGXLFs2bN0833nijfY8KAABICvM9+u3bt6u8vFyFhYWKj49Xfn6+iouLg44LBALq6urq+To2NlabN2+Wx+PRli1b1NjYqNtvv12LFy/WsmXLBv+RAACAXsL61P24ceO0bdu2fo+pqKhQRUVF0NqPf/xjrV27NvLpAABAVLh6HQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABiP0AAAYjNADAGAwQg8AgMEIPQAABgsr9KdOndKSJUuUnZ0tl8ulsrIyXbhwIaw7aGtr00svvaScnBxlZGTooYceksfjiWpoAAAQnutDHeDz+VRQUKAxY8bI4/GoublZ69atU3NzszZu3NjvuRcvXlRBQYHa29u1cuVKJSUl6cyZMzp79uygPQAAANC3kKHfsWOHfD6fvF6vEhMTJUmxsbEqKiqS2+1WWlpan+du2bJFX3/9tfbs2aPRo0dLku67775BGh0AAIQS8qX76upquVyunshL0owZM+RwOFRdXd3vuVVVVcrLy+uJPAAAGFohQ19fX6/x48cHrTkcDiUnJ6uhoaHP886cOaNz587ptttuU3FxsbKyspSdna1Vq1apubk5+skBAEBIIUPv8/nkdDp7rTudTrW0tPR5XmNjoyRp69atam1t1aZNm7R69Wr985//1LPPPhv5xAAAIGwh36Pvi2VZiomJ6fP7XV1dki7/gaCyslIOh0OSlJCQoMLCQh05ckRZWVlh319dXV2koyJMtbW1wz3CNYF9th97bD/2eOQIGXqn0ymfz9drvbW1VampqX2ed+ut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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "resource_curve_plot(all_emiss_db.loc[2020], '2020 annual emissions reductions (million tonnes CO$_2$/year)')" - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "4cd38242", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAroAAAIqCAYAAADLm+laAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8/fFQqAAAACXBIWXMAAAsTAAALEwEAmpwYAACnoElEQVR4nOzdeViUVf8/8PcMuwyLqKCgiAqYIiEugKlAio+ZII+KpiluJKm4WwmlPpqG5JMLCVIoLpD5VdRcSFIqRXOr4MGSLAVMXFBUxGEdtvn94Y/JcdgGBgbw/bourqs559znfO47l49nzn2OQCqVSkFERERE1MoI1R0AEREREVFjYKJLRERERK0SE10iIiIiapWY6BIRERFRq8REl4iIiIhaJU11B9AaVFRUoKCgAFpaWhAIBOoOh4iIiKjFkEqlKC0thb6+PoRC1c7BMtFVgYKCAly/fl3dYRARERG1WLa2tjAwMFBpn0x0VUBLSwvAs/9B2traao6m9bh69Sr69Omj7jBaFT5T1eMzVS0+T9XjM1U9PlPVKikpwfXr12X5lCox0VWByuUK2tra0NHRUXM0rQufp+rxmaoen6lq8XmqHp+p6vGZql5jLP/ky2hERERE1Cox0SUiIiKiVomJLhERERG1Skx0iYiIiKhVYqJLRERERK0Sd10gIiKiOhOLxcjOzkZpaam6Q1EbTU1NXLt2Td1htBhaWlowNTWFoaFhk4/NRJeIiIjqRCwW48GDB7CwsICent5LexpoQUEB9PX11R1GiyCVSlFUVIS7d+8CQJMnu1y6QERERHWSnZ0NCwsLtGnT5qVNckk5AoEAbdq0gYWFBbKzs5t8fCa6REREVCelpaXQ09NTdxjUAunp6alluQsTXSIiIqozzuRSfajr1w0TXSIiIiJqlZjoEhER0Uvr3LlzmD17NpydndGnTx+4ubnhww8/RHp6urpDk3P58mX07NkTv//+u1LXff/999i7d69C+datW+Ho6Kiq8JotJrpERET0Utq6dSveeecdaGlpYfXq1di1axeWLl2K7OxsTJo0Sd3hqcT333+Pffv2KZRPmDABe/bsUUNETYvbixEREdFL56effkJYWBjeffddLF26VK7O29sbP/zwg5oiaxodO3ZEx44d1R1Go+OMLhEREb10oqKi0K5dOyxYsKDK+uHDhwMAevbsiaioKLm6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QEREjSPnzlX8cToSANB7+DyYWjmqOSIiai1a9BHARETU8j1IuwQd/bbo4xEArTYm6g6HiFoRgbRyGpbqrXJbDG4vplrX/kpDBzOLJh1TIJCirLx5/JbQ1BCirLxCpX3m5j6FsbHqvhLW0hSiQrUhNiktTWGD+ygpKYG2trYKolENLU0hNITNfzIiLy8PT5/monPnLigqKoJUKkWbNm1QXlEBDWHV/18qpICeDndeUBa3wlI9PlPVasw8in9iULNVmP8U7XtaqzuMVuXOzT/Rs7u5usNoVZKSrvIvPCWlpaXBz88PWlpaOHnyJIyNDGR1TCCISJUaPp1BRERUR/Hx8Rg9ejSePHmCjz/+GBoaGuoOiYhaMSa6RETU6MrLyxESEoJ33nkHNjY2+O677/Daa6+pOywiauWY6BIRUaMrLS1FYmIi3n77bRw6dAjm5lxCQ0SNj2t0iYio0fzxxx+wsLCAkZERDh48CH19fXWHREQvEc7oEhFRozh06BC8vLywbt06AGCSS0RNrt4zuvn5+cjKysLTp09R1Q5lAwcObFBgRETUMpWWlmLt2rWIiorCoEGD8MEHH6g7JCJ6SSmd6D59+hRr167Fd999h/LycoV6qVQKgUDQqCehERFR85SdnY05c+bg8uXLeOedd7BixQpoaWmpOywiekkpneiuWrUK33//PaZMmQInJycYGho2RlxERNQClZeX4/79+wgLC8PYsWPVHQ4RveSUTnTPnj0LX19fBAYGNkY8RETUwkilUiQkJGD48OHo1KkTzpw506xOiyOil5fSL6Npa2uja9eujRELERG1MMXFxVi2bBlmzpyJgwcPAgCTXCJqNpROdEeOHImzZ882RixERNSC3L17F+PGjcP+/fuxaNEi+Pj4qDskIiI5Sie6fn5+yM7OxvLly5GSkoLs7Gw8fvxY4YeIiFqvS5cu4Y033kB6ejp27tyJDz74gMf5ElGzo/Qa3ZEjR0IgECA1NRXHjh2rth13XSAiar20tbXRqVMnbNu2DdbW1uoOh4ioSkonugEBARAIBI0RCxERNWOFhYU4efIkxo4di379+uG7776DUMhzh4io+VI60V2wYEFjxEFERM3YzZs38c477+D69euwt7eHtbU1k1wiavbqfTIaAPz111+4e/cuAMDCwgI9e/ZUSVBERNR8fP/991iwYAE0NDSwd+9eLlUgohajXonu999/j+DgYGRlZQH45zQ0c3NzBAUFwcPDQ6VBEhGRemzduhUhISGwt7fH9u3b0aVLF3WHRERUZ/U6MGLhwoXo2LEjlixZgh49ekAqlSIjIwP/93//h0WLFuGLL77A0KFDGyNeIiJqQmZmZpgwYQLWr18PPT09dYdDRKQUpRPdbdu2oUePHti3bx9EIpFc3dtvv43Jkydj27ZtTHSJiFqoP//8Ezdv3sSoUaMwceJETJw4Ud0hERHVi9JvEvz5558YP368QpILACKRCOPHj+fWYkRELdTRo0fh6emJtWvXoqSkRN3hEBE1iNKJrpaWFgoLC6utLygogJaWVoOCIiKiplVWVoaPP/4Y8+bNQ58+ffDNN9/wKF8iavGUTnT79++PvXv34u+//1aou3XrFr7++msMGDBAFbEREVETKC0txdtvv40vv/wSM2fOxIEDB2BmZqbusIiIGkzpNbrLli3DpEmT4OnpiWHDhqFbt24Anu2xePr0aejq6mLZsmUqD5SIiBqHlpYW+vXrBx8fH67HJaJWRelE18bGBocOHcKmTZtw7tw5nDp1CgCgp6eH119/HUuWLJElv0QN0UZkhEe5RY3St0AgRVm5tFH6fp6mhhBl5RWNPk5NtDSFqPj/IbTv2LXRnqkytDRb1kEDWppCaAirPhGyl509JCVlTRxR3VVIAT2dqv+o379/P2xsbNCvXz8EBgY2cWRERI2vXvvoWllZ4fPPP0dFRQVycnIAACYmJjwlh1SqXKqB65lPGqVviw76SLnxqFH6fp6jbQecvHyr0cepiefgbtgV9wcAIDc3F8bGxmqNBwDmjHsVa6MuqTuMOlsfMASz1iVUWVdWVgZNzQadvdOodq4YoVAmkUiwatUqfPXVV/Dx8UG/fv3UEBkRUeNr0J/OQqEQ7du3V1UsRETUyLKysuDv74/k5GQEBARg+fLl6g6JiKjR1DvRvXDhAhITE3Hv3j0AgLm5OVxdXTF48GCVBUdERKrz999/w9vbG4WFhfjyyy/h6emp7pCIiBqV0olufn4+Fi1ahAsXLkAqlcLIyAhSqRRisRjR0dF47bXXEBoaWuU+u0REpD5dunTBqFGjMGvWLNja2qo7HCKiRqf0otqQkBCcP38ec+fOxcWLF3H58mX8/PPPuHjxIubMmYPz588jJCSkMWIlIiIlFRUVYeXKlbh//z40NDQQEhLCJJeIXhpKJ7qnTp3CxIkTsXDhQrRt21ZW3rZtWyxatAgTJkyQ7cRARETqU5T3EBN8xmHXrl04d+6cusMhImpySie6UqkUr7zySrX1r7zyCqTSxt+2iYiIqiaVSvEg/TKSjqzFnTt3EB0djQkTJqg7LCKiJqd0ouvq6oozZ85UW3/mzBm4uro2JCYiImqAu6k/4NqZ7dAz6oijx+IwbNgwdYdERKQWtb6M9vjxY7nP8+bNw5IlS/Duu+9iypQp6Nq1KwQCAW7evIm9e/ciOzubG48TETUxaUUFSorzoNPGCGY2gyDQ0IB5Tzd07dpV3aEREalNrYnu4MGDIRDInwgklUpx/fp1nD17VqEcALy8vPDHH3+oMEwiIqpOfs4d/PXTHkgrytFvzEfQ0tGHRa/X1R0WEZHa1ZroBgQEKCS6RESkfuVlpbiVEofbv30HTR09WLtMhkDAEyqJiCrVmuguWLBA9t/FxcXw9PTEtGnTMG3atEYNjIiIqleU9xC/ndyCoqcPYGY9CD2cJ0Jb10DdYRERNStKHRihq6uLvLw8aGlpNVY8RERUA6lUCoFAAJ02bdHG0Aw2Lm/DpLOdusMiImqWlP6Oy93dHYmJiY0RCxER1eDh38lIPh6MspIiCDU0Yf+vhUxyiYhqoHSi6+/vj7t372LRokW4ePEi7t69i8ePHyv8EBGRakgKcnH1h21I/WEbKsrLUFIsVndIREQtglJLFwBg9OjRAIAbN27UeALatWvX6h8VEQANQTlsLdvW3rAeBAIpXPp0bJS+n6epIcSYod0bfZyaaGkK4f9vewBAQUEB9PX11RoPAGgIBVg9e5C6w6izigopdq4YUWVdeUUFNISN8wKYVCrF/v/7P6xfH4ySEgk+WB4IP793lFo+VsHze4joJaZ0ostdGKipFOY/Rfue1uoOo1W5lf4Huvbvr+4wWpWkpCT0b6RnKpVK8d13J9Cnjx02bNiA7t3V+48mIqKWRulE9/ldGIiISLVKS0vx5ZdfYuzYsbCwsMCXX34JAwMDTjAQEdWD0okuERE1jv/97394//33ce3aNWhoaGDu3LkwNDRUd1hERC0WE10iIjUrKCjAhg0bsHPnTpiammLnzp0YOXKkusMiImrx6pXoPn78GAcPHkRqairEYjEqKirk6gUCAfbs2aOSAImIWrstW7Zgx44dmDZtGoKCgjiLS0SkIkonumlpaZg6dSoKCwthZWWFGzduwNraGk+fPkV2djYsLS3RsWPjv81ORNSSPX78GLm5uejRowcCAgLwr3/9CwMHDlR3WERErYrSe+J89tln0NTUxLfffovdu3dDKpXiww8/xNmzZ/HZZ5/h6dOn+OCDDxojViKiFk8qleLQoUNwc3PDokWLIJVKYWxszCSXiKgRKJ3oJiUlYdKkSejSpQuE/3/vSKn02UaNnp6eePPNN7FhwwbVRklE1Arcvn0bU6dOxcKFC9GtWzd89tln3E2BiKgRKb10obS0FGZmZgAAXV1dAEBeXp6svlevXjhy5IhqoiMiaiWSk5MxceJECIVCrFu3DtOmTYOGhoa6wyIiatWUntHt1KkT7ty5A+BZotuhQwf873//k9Vfv369WZy8RETUHBQXFwMA+vTpg8mTJ+P06dOYOXMmk1wioiag9Iyus7MzfvzxRyxZsgQA4OXlhT179iAvLw8VFRU4duwYxo8fr/JAiYhakuLiYmzZsgVHjhzBqVOnYGhoiLVr16o7LCKil4rSia6/vz9+//13SCQS6OjoYPHixcjPz0d8fDyEQiHGjBnDl9GI6KV28eJFfPDBB8jIyMDEiRNl7zEQEVHTUjrRNTc3h7m5ueyztrY2Pv74Y3z88ccqDYyIqKWRSCRYuXIl9u7dC0tLS+zbtw+urq7qDouI6KVVpzW6Dx8+xBtvvIHNmzfX2G7z5s148803kZOTo5LgiIhaEm1tbWRlZWHu3Ln48ccfmeQSEalZnRLd6Oho5ObmYvbs2TW2mz17NnJychATE6OS4IiImrvHjx9j/vz5uHPnDgQCAXbv3o0VK1ZAT09P3aEREb306pToJiYmYvTo0RCJRDW2E4lE8PT0xI8//qiS4IiImquKigrExMQgICAA8fHx+O233wCAuykQETUjdUp0MzMz0bNnzzp1aGtri1u3bjUoKCKi5iwtLQ0+Pj4IDAyEtbU1vv/+e7z55pvqDouIiF5Qp5fRBAIBKioq6tRhRUUFT/oholZtx44d+PPPP7Fx40ZYW1ujW7du6g6JiIiqUKcZXQsLC9nXcrX5/fffYWFh0aCgiIiam+TkZFy9ehUAEBQUhDNnzmDSpEn8hz0RUTNWp0TX3d0d3377LdLT02tsl56ejri4OLz++usqCY6ISN0KCgqwatUqjBkzBp9++ikAwMjICKampmqOjIiIalOnRHfWrFlo06YNpk+fjri4OJSVlcnVl5WVIS4uDtOnT4dIJMLMmTMbJVgioqb0448/4vXXX8fOnTsxffp0bNu2Td0hERGREuq0RtfExASRkZEICAjA+++/jxUrVqBbt27Q19dHQUEBbt68CYlEAlNTU4SHh8PExKSx4yYialTx8fF45513YGNjg2+++QYDBw5Ud0hERKSkOp+MZm9vj2+//Rb79u3D6dOnkZGRgfz8fIhEIvTq1QvDhg3DpEmTYGBg0JjxEhE1GqlUiqysLJibm2P48OH4+OOPMXXqVOjo6Kg7NCIiqgeljgA2MDCAv78//P39GyseIiK1yMzMxPLly3H9+nWcPn0ahoaG8PPzU3dYRETUAEolukRNqY3ICI9yi1TWn0AgRVm5VCV9aWoIUVZety33lKGlKUQdd/Krl/Ydu+JpvqTxBqiClqYQGkL17UxQIQX0dKr/o66srAxRUVH473//C6FQiA8//LDWw3GIiKhlYKJLzVa5VAPXM5+orD+LDvpIufFIJX052nbAycuqPxjFc3A37Ir7Q+X9VsrNzUXgLFesjbrUaGO8aH3AEMxal9Bk471o54oR1dbl5ubi7bffxpUrV+Dh4YHg4GBuj0hE1IrUadeF5iQ+Ph7z5s2Dq6sr+vbtizFjxiA2NhZSqfxMXWJiIsaOHQt7e3t4eHggJiamyv6ioqIwbNgwvPrqqxg3bhwuXrzYFLdBRGpU+eeFkZERbGxsEBERgd27dzPJJSJqZVpcort7927o6uoiMDAQERERcHNzw6pVq7B161ZZm5SUFMybNw+9evXC9u3bMW7cOAQHB2Pfvn1yfUVFRWHz5s2YMmUKvvzyS1hZWcHf3x9//vlnU98WETWR8+fP44033sCdO3cgEAgQGhqKMWPG8OAHIqJWqMUtXYiIiJDbvmzQoEHIzc3Fnj17MH/+fAiFQoSFhaF3794IDg4GALi4uCArKwvh4eF46623IBQKUVJSgoiICEybNk32womTkxO8vLwQERGB0NBQtdwfETWO3NxcfPLJJ/j6669hZWWFx48fo3PnzuoOi4iIGlGLm9Gtao/eXr16IT8/HxKJBCUlJbh06RLefPNNuTaenp54+PAhUlNTATw7zjMvLw+jR4+WtdHQ0MCoUaNw9uxZhaUQRNRyffvtt3j99dexf/9+zJs3D99//z0cHBzUHRYRETWyFpfoViUpKQkWFhbQ09NDZmYmSktL0aNHD7k2NjY2AICMjAwAkB1n/GI7a2trFBYW4sGDB00QORE1hcTERJiZmeHEiRP46KOPoKenp+6QiIioCbS4pQsv+vXXX3HixAm89957AICnT58CAAwNDeXaVX6urBeLxdDW1oaurq5cOyMjIwDPvubs2LFjo8ZORI3nceZv0BE9+wZo9erV0NbWhqZmi/8jj4iIlNCi/9S/f/8+lixZgoEDB2LGjBlyddW9WPJ8eVVtKpcs1OfFlKtXryp9DVVPR98E6f9/Bl4VDHW74fbt2yrpq3dXQ2Q/yFZJX88rK7dEbm6uyvt9nlQqRUFhYaOOIT/gs71qm2w4qRR3fo/Hrf8dR7uufVFeMR3Xrl1r1DGTkpIatf+XDZ+n6vGZqh6facvQYhNdsViM2bNnw9jYGOHh4dDQ0ADwz4xs5czt8+2Bf2Z2DQ0NIZFIIJFI5I73rGxX2Y8y+vTpw6NCVejqnzfRo3t3lfVnYKCPLl26qKQvHR0dmJqZqqSv52lqaMDY2Fjl/VbKzc2FQCCAfps2jTaGAgGabCa1rLQY18/uwsO/k2Dawxk9h0yDhlCI/v37N9qYSUlJjdr/y4bPU/X4TFWPz1S1JBJJo00Wtsg1usXFxXj33XeRl5eHHTt2wMDAQFZnaWkJLS0t2VrcSmlpaQCA7v8/capcm1u5VrdSeno69PX1YWZm1pi3QEQqJil8iv/FheDhrWR0d5qAXm7vQEOT//AkInqZtbhEt6ysDIsXL0ZGRgZ27NihkJBqa2vDxcUF8fHxcuVxcXHo0KED7OzsAAD9+vWDgYEBTpw4IWtTXl6O+Ph4DB06lHtqErUwWjr60BW1x6v/WgRL+5H8PUxERC1v6cKaNWtw+vRpBAYGIj8/HykpKbI6a2triEQiBAQEYOrUqVixYgW8vLyQnJyM2NhYrFq1CkLhs9xeW1sbc+fOxebNm2FiYoLevXsjNjYWmZmZ2Lhxo5rujoiUIZVKkfXXObS3coS2rgHsR8xXd0hERNSMtLhE9/z58wCAkJAQhbro6Gg4OzvD0dER27Ztw6ZNm3DkyBGYmpoiKCgIkydPlmtfeVBETEwMHj16BBsbG0RGRuKVV15p/BshogYpLyvF9QsxeHDjAkqKnsLK0UvdIRERUTPT4hLdH3/8sU7t3Nzc4ObmVms7Pz8/WcJLRC2DpOAJrv6wDXkPb8LKcQy69h1d+0VERPTSaXGJLhG93PIeZeK3U1tQUSaB3fAAdLByVHdIRETUTDHRpWZLQ1AOW8u2KutPIJDCpY9qDgHR1BBizFDVbX1WSUtTCP9/26u830oFBQXQEAqwevagRhvjRRUVUuxcMUJl/T1+/BiLHv2AVatWw9bWtvbxeZo3EdFLi4kuNVuF+U/Rvqe1usNoVW6l/4GuFi1v78eSkhLs2bMHM2bMgHknM8QeOKDukIiIqAVgoktEzdqjR4/w7rvv4tKlS+jSpQveeOMNdYdEREQtBBNdImq2fv/9d8yaNQs5OTnYunUrk1wiIlJKizswgoheDvHx8fj3v/8NAPjmm28wbtw49QZEREQtDhNdImqWunTpgkGDBiE+Ph6vvvqqusMhIqIWiIkuETUbubm5iI6OBgD06dMHX331Fdq3b6/mqIiIqKXiGl0iahauX7+OmTNn4u7du3jttddgbc0dN4iIqGE4o0tEanfy5El4enqioKAAsbGxTHKJiEglmOgSkVqFh4dj1qxZsLGxwYkTJzBw4EB1h0RERK0EE10iUisrKyv4+Pjg0KFDMDc3V3c4RETUijDRJaImd/PmTRw7dgwAMHr0aISGhkJXV1fNURERUWvDl9GIqEklJiZi7ty50NHRgYeHB9q0aaPukIiIqJXijC4RNQmpVIovvvgCU6dOhbm5OY4cOcIkl4iIGhVndImo0UmlUixatAiHDh3C6NGjsXnzZujr66s7LCIiauWY6BJRoxMIBLCyssIHH3yAhQsXQiAQqDskIiJ6CTDRJaJGc/nyZQCAs7Mzli5dquZoiIjoZcM1ukTUKKKjozFx4kSEhIRAKpWqOxwiInoJcUaXiFSqpKQEK1aswN69ezFs2DCEhYVxqQIREakFE10iUpm8vDz4+vril19+wfz58/HBBx9AQ0ND3WEREdFLiokuEamMvr4+unTpgpkzZ8Lb21vd4RAR0UuOiS4RNdg333yDAQMGoEuXLti6dau6wyEiIgLAl9GIqAHKysqwevVqzJ8/HxEREeoOh4iISA5ndImoXnJycjB37lz89NNPmDVrFlatWqXukIiIiOQw0aVmq43ICI9yi+p1rUAgRVm5cltaaWoIUVZeUa/xAEBLU4iKel6upamaL1e0NIXQEFa/w0EvO3tISspq7adCCujpVP/Hw99//423334bWVlZ2LRpE9566616xUtERNSYmOhSs1Uu1cD1zCf1utaigz5SbjxS6hpH2w44eflWvcYDAM/B3bAr7o96XTtn3KtYG3Wp3mNXWh8wBLPWJVRbX1ZWBk3N2n/b71wxosb6Dh06oHv37ti6dSv69++vdJxERERNgWt0iahOKioqsHPnThQWFkJfXx9fffUVk1wiImrWOKNLRLXKy8vDggULkJCQAC0tLfj6+qo7JCIiolox0SWiGqWnp2PWrFm4efMm1q1bh6lTp6o7JCIiojphoktE1bpw4QL8/PygoaGBffv2YfDgweoOiYiIqM64RpeIqtWlSxc4ODggPj6eSS4REbU4THSJSE55qQS7du1ERUUFunTpgv/7v/9Dly5d1B0WERGR0rh0gYgAAEV5D3HvWiKyrv+En0oK0L+fIwYOHKjusIiIiOqNiS7RS66kOA9/nt2FnNu/AwIB2nfti23//ZBJLhERtXhMdIleQqWSfBQ8yYJxRxtoaeujrDgfXfuORqdXXKGrb4IBA5jkEhFRy8dEl+glkv/4Fu7/dRbZGT9DQ0sXgyb/F0KhJvqN+VDdoREREakcE12il0Bu1nWk/xKLvIc3IdTUhpnNa7B4xR1CIf8IICKi1ot/yxG1UkV5DyEQakBX3wQAUFZShO5OE2Hecwg0tduoOToiIqLGx0SXqBWpqKjA49u/4+6108i5/TvMe7nD9rUpMOpoA6fxa1FeXg5NTf62JyKilwP/xiNqJdL+l4Dhwz5E5q1b0NIzlL1cBgACgUDN0RERETU9JrpELdjTh7dh2L4zBAIBnmT/DdMOptDvNhIdrPpBqMHf3kRE9HLj34TUbGkIymFr2bZe1woEUrj06ajUNZoaQowZ2r1e4wGAlqYQ/v+2r9e1GkIBVs8eVKe2EokE38V/i6/3xuD3365g34HDcHDoi9IZA9BGTwcawupnb8srKqAhrP1AxAppnUMnIiJqtpjoUrNVmP8U7XtaqzuMZuPp06cICwvDvn378OTJE1hbW2Pt2rVwfLU3DEQ6AHRq7SMpKQn9+/dv/GCJiIiaASa6RM1YRUUFsrKyYGFhAW1tbezfvx+DBg3CtGnTMGTIEK69JSIiqgETXaJm6MmTJ9i/fz9iYmIgFAqRmJgIPT09XLp0CW3acGswIiKiumCiS9SM/PXXX/jyyy9x9OhRFBcXw8nJCTNmzIBU+mzRLJNcIiKiumOiS6RmxcXFKCsrg0gkwvXr13H8+HH4+Phg+vTp6N27t7rDIyIiarGY6BKpSWZmJmJiYrBv3z74+flhyZIleOONN+Dm5gZDQ0N1h0dERNTiMdElamJnzpzBzp078eOPP0IoFGLkyJEYPHgwAEBLSwtaWlpqjpCIiKh1YKJL1AQKCgqgr68PANizZw9+++03LFq0CFOmTIG5ubmaoyMiImqdmOgSNaIrV65g9+7dOH78OL7//ntYWVnh008/hbGxMbS1tdUdHhERUavGRJdIxYqLi3Hs2DFER0fjf//7H9q0aYPx48fLliSYmpqqOUIiIqKXAxNdIhUpLS2FlpYWCgoKsHz5clhaWmLt2rXw8fHhy2VERERqwESXqAEqKipw5swZ7N69GwUFBTh06BDatWuHkydPwsbGhieXERERqRETXaJ6yMnJwYEDBxAdHY1bt26hQ4cOmDJlCsrLy6GhoQFbW1t1h0hERPTSY6JLpISKigoIhUIcO3YMa9euhZOTE5YvX45Ro0bx5TIiIqJmhokuUS0qXy7bs2cPJk2aBF9fX/j4+MDJyYknlxERETVjTHSJqvH8yWVPnjyBtbU1jIyMAAAikYhJLhERUTPHRJfoOVKpVPYC2YIFC/C///0PI0eOxPTp0zF48GC+XEZERNSCMNElwj8vlx04cAD/r707D4uqbv8H/mYblE1DIVdcEUUWNRVccMENAfVBUVEUNS9RSUhJQ8rUvqJpuURoKW65PEpumSJaiArl0uNaZpkKKokJPCwOIAwDc35/+GMexhl0BgYnhvfrurxqPudzzrnPzeHMzTmfc86hQ4dgbW2NlStXwtramm8uIyIiqqNY6FK9VvHmsmPHjqGkpARubm7IycmBtbU1nJycdB0eERER1QALXfrHMrNohDxxMUrLZDAxNoSRoeH//68BZALQ0LRmu+9ff/0Fb29vmJmZYfz48QgKCuK4WyIiIj3CQpf+scoFIzyTlCPhwn349msHU5EBTA0N8HZUInYsGabx8ipuLsvPz8dnn32G1q1bY+vWrejfvz/fXEZERKSHWOiSXpPJZDh79ix27dqFM2fOwNDQEN7e3vLn4Xp7e+s6RCIiIqolLHRJr23ZsgVRUVGwsbHBu+++i8DAQN5cRkREVE+w0CW9cuPGDezatQve3t4YNmwY/Pz80KJFC765jIiIqB5ioUt1XuU3l924cQNmZmZwcXEBADRr1gxjxozRcYRERESkCyx0qc7z9/fH9evXYW9vj6ioKIwbN443lxEREREL3QcPHmDFihW4du0aTE1N4ePjg4ULF6Jhw4a6Do1UkMlkyHn0G8JCv8Pnn29Aw4YNERYWBnNzc/Tt25dvLiMiIiK5el3oisViBAUFoUWLFoiOjkZubi4++eQT5ObmYsOGDboOT+8US8pgWKkOLZcJkJbJAADSMhkMDQFBAAABgmAAI2NTed+SkhIcO3oc+/Z8jXv37uHvpjZITU2Fk5MThg8f/no3hIiIiOqEel3oxsXFQSwW4+jRo7C2tgYAGBkZYeHChQgJCYG9vb2OI9QvhgbA21GJ8s/blwzD8q0X8dFMd8QevYkZvo6QlskgCAIysovQyOx5v6e52fD1moT8vDx07doVnQfOxNHYxbC0MNPRlhAREVFdYKjrAHQpJSUF7u7u8iIXAEaMGAGRSISUlBQdRkYP0u4gKfEkAMDqjabw8f0Xduzah++On0Czjn34BAUiIiJ6pXp9Rjc1NRXjxo1TaBOJRLCzs0NaWpqOoqq/ZDIZLvyUjP3//hpX/nMJjd+wxugxfjAwMMCChYthKjLmGFwiIiJSW70udMViscq7862srPD06VONl/fbb79pIyy91aWrM8rKyv7XIABFz55BEAT8efMSxm99Dw8fpKFpU1t4jZmMWbNmAgCyMrNQVm4HkWAECEBZWRnKZTJcvXpVR1tStzFv2secahfzqX3MqfYxp3VDvS50qyIIQrXOHDo5OcHU1PTVHespSWkZjI3/t8tlZWcC0iIYGBigqW1zZJg1xPKoz+A5dDgy80phaQaYmIhg+6YtjI2Mnv9MDABjY2MYGRrirbfe0uHW1E1Xr15l3rSMOdUu5lP7mFPtY061SyKR1NrJwnpd6FpZWUEsFiu1FxQUoEOHDjqIqH4oyEnHo98SMWD3FTTr0BMIH4Umb7bBnv1H5DejAaW6DpOIiIjquHpd6Hbo0AGpqakKbaWlpUhPT8fYsWN1FJX++jElBTcS1iL/79swNDbFlMBAZBs76zosIiIi0lP1+qkLAwYMwKVLl5CXlydvS0xMRGlpKQYOHKjDyPTThYsXUCzORPte/ugT8CmWLf8Ylm8003VYREREpKfq9RndgIAA7N27FyEhIQgJCUFOTg5Wr14Nb29vdOzYUdfh6Z133pmHPySuMDRUb7czNgTMTI0w2qM9TIwNYWRoAJlMwI4lwyATajlYIiIiqvPqdaFrZWWFXbt2ISoqCqGhofJXAC9atEjXoeklCwsLtYtcAJCWSvCGFV/FTERERNVTrwtdAGjXrh22b9+u6zCIiIiISMvq9RhdIiIiItJfLHSJiIiISC+x0CUiIiIivcRCl4iIiIj0EgtdIiIiItJL9f6pC/T6yARgx5Jh8s/lMgHLZ/UBAAT/yxmGhoDIxAiAgE52byA7M0NHkRIREZE+YKFLr01DU812t4epT2spEiIiIqoPOHSBiIiIiPQSC10iIiIi0kssdImIiIhIL7HQJSIiIiK9xEKXiIiIiPQSC10iIiIi0kssdImIiIhIL7HQJSIiIiK9xEKXiIiIiPQSC10iIiIi0kssdImIiIhIL7HQJSIiIiK9xEKXiIiIiPQSC10iIiIi0kssdImIiIhIL7HQJSIiIiK9xEKXiIiIiPQSC10iIiIi0kssdImIiIhIL7HQJSIiIiK9xEKXiIiIiPQSC10iIiIi0kssdImIiIhIL7HQJSIiIiK9xEKXiIiIiPSSsa4D0AeCIAAASktLdRyJ/pFIJLoOQe8wp9rHnGoX86l9zKn2MafaU1E/VdRT2mQg1MZS65mCggLcuXNH12EQERER1VmdOnWCpaWlVpfJQlcLZDIZioqKYGJiAgMDA12HQ0RERFRnCIIAqVQKc3NzGBpqd1QtC10iIiIi0ku8GY2IiIiI9BILXSIiIiLSSyx0iYiIiEgvsdAlIiIiIr3EQpeIiIiI9BILXSIiIiLSSyx0iYiIiEgvsdCtgbi4OMycORP9+vVDjx49MH78eCQmJqrse/ToUXh5ecHZ2Rk+Pj5ISEh4zdHWDQ8ePMDMmTPRvXt3uLu7Y8WKFSguLtZ1WHXCyZMnERISggEDBqBbt24YPXo0Dh48qPRKxeTkZPj5+cHZ2RlDhw7Fnj17dBRx3VNeXg4/Pz84ODjg1KlTCtOYV80cP34cY8eOhYuLC9zc3DBjxgzk5ubKpzOfmjl9+jTGjx+PHj16oF+/fggNDcWDBw+U+jGvyh4+fIilS5dizJgxcHR0hK+vr8p+6uZu+/bt8PT0hIuLC8aOHYuLFy/WZvj/SK/KaXl5ObZu3YopU6bAzc0NvXr1wtSpU/Gf//xH5fJqklMWujWwefNmNG/eHMuXL0dMTAw6d+6MefPm4fDhwwr9Tp06hYiICAwbNgxbt25Fnz59EB4ejuTkZB1F/s8kFosRFBSEoqIiREdHY/HixYiPj8cHH3yg69DqhK+//hoNGjTA4sWL8dVXX2HgwIFYunQpYmJi5H1u3LiBkJAQdOnSBVu3bsXYsWOxatUq7N+/X4eR1x379+9HVlaWUjvzqpnY2FhERkbCw8MDsbGxWLlyJezt7SGVSgEwn5q6ePEi5s2bh3bt2iEmJgZLlixBWloaZsyYgcLCQnk/5lW1u3fvIjk5GW3atEGHDh1U9lE3d9u3b8eGDRsQGBiILVu2oG3btggODsbt27dfx6b8Y7wqpyUlJYiNjYWjoyPWrFmDdevWoVGjRpg2bRouXLig0LfGORWo2nJycpTaZsyYIfj6+iq0eXl5CWFhYQptb7/9tjBu3Lhaja+u2bJli+Dq6qqQ12PHjgmdOnUS7ty5o8PI6gZV++OSJUuEHj16COXl5YIgCMLMmTMFf39/pT79+vWT9yHVsrOzhZ49ewqHDx8WOnXqJJw8eVI+jXlVX1pamuDo6CjExcVV2Yf51MwHH3wgDB48WJDJZPK2X375RejUqZNw7tw5eRvzqlrlbY+IiBB8fHyU+qiTO4lEIrz11lvCmjVr5H3KysqEkSNHKtUA+u5VOS0rKxPy8/OV2ry8vITZs2fL27SRU57RrQFra2ulti5duiAnJ0f++a+//kJaWhp8fHwU+vn4+ODmzZsKl+rqu5SUFLi7uyvkdcSIERCJREhJSdFhZHVDVftjYWEhJBIJSktLcenSJXh7eyv08fX1RXZ2Nm7duvW6Qq2TPv30U/Tv3x+9e/dWaGdeNXPkyBGIRCL4+fmpnM58aq6srAzm5uYwMDCQt1laWir0YV6rZmj48lJI3dxdu3YNBQUFCt/3RkZGGDlyJFJSUpSGkemzV+XUyMgIjRo1UmpzcHBQqKG0kVMWulp29epVhdP0aWlpAKB06r5jx44K0wlITU2V56WCSCSCnZ0d81RNV69eRcuWLdGwYUOkp6dDKpUq7Yv29vYAuC++zOXLl5GYmIj3339faRrzqpkbN26gXbt2+PbbbzFo0CA4OjrCz89PfrmS+dScv78/0tLSsGfPHojFYjx69Ahr1qxBhw4d0KdPHwDMa02om7vU1FQAqr/vnz17hszMzNcQbd1VVlaGX375RaEO0EZOWehq0fHjx3H9+nUEBgbK254+fQoAsLKyUuhb8ZdMxXR6Pkb3xTwBz3PHPGnuypUrSEhIkO+PVe2LFZ+ZY9XKysrwf//3fwgODkbz5s2VpjOvmsnOzsb9+/cRExOD+fPnY8uWLbC2tkZwcDAePnzIfFZDr169sHHjRmzYsAG9evXCkCFDkJGRgZ07d0IkEgHgfloT6uZOLBZDJBKhQYMGCv0qvu/z8/NrOdK6bdu2bXjy5AkCAgLkbdrIqbFWo6zjCgoKVN5o8qIWLVqgYcOGCm23b9/GsmXLMGbMGHh5eSnNU/mSEgD56fYX20mZIAjMk4aePHmCBQsWoFevXpg+fbrCtKpyyRyrtnv3bpSUlGDmzJkv7ce8qkcmk+HZs2f4/PPPMXDgQACQF2c7duzA6NGjATCfmrh27RoWLVoEf39/eHp6Ij8/H19++SXmzp2Lffv2KRQJzGv1qZM7VX34ff9q58+fR0xMDObMmQNXV1eFaTXNKQvdShITExEZGfnKfjt37kTfvn3lnzMyMjBr1iy4uLggKipKoW/lM7dNmzaVt4vFYgDKfyHWZ1ZWVvK8VFZQUFDlnbCkTCwWY9asWWjcuDE2bdoEIyMjAFVfReC+WLXc3FzExMRg2bJlKCkpQUlJifwu9pKSEhQUFDCvGqrIl5ubm7ytQYMGcHV1RWpqKvNZDVFRUXB3d1d4Qk23bt0waNAgfPfdd5g4cSLzWgPq5s7KygoSiQQSiQSmpqZK/V4ck0rP3bp1C6GhofDx8UFYWJjCNG3klIVuJWPHjsXYsWM1mic3NxczZ85EkyZNsHHjRvllogrt27cH8HwMT+VirWLcScV0ej4GpyIvFUpLS5Genq7xz6W+KikpwezZs1FQUIBvvvlG4YYUOzs7mJiYIC0tDQMGDJC337t3DwD3RVUyMzPx7NkzREREKE2LiIiApaUlLly4wLxqoGPHjrh586ZSuyAIkEgk3E+rITU1FZ6engptzZo1wxtvvIH09HQA/P2vCXVzV/Edn5qaCkdHR3m/1NRUmJub480333yNUdcNDx8+xKxZs9C9e3esXLlS6QytNnLKMbo1UFRUhFmzZqG0tBSxsbGwsLBQ6tO6dWu0b99e6QUR8fHxcHZ2VnmnfH01YMAAXLp0CXl5efK2xMRElJaWyi9xUtXKysowf/58pKWlYdu2bUoHAJFIBHd3d5w8eVKhPT4+HjY2NujatevrDLdOsLOzw+7duxX+rV+/HgAQGhqKzZs3M68aGjx4MARBUHjge3FxMW7cuIGuXbsyn9XQokULpacmZGRkIC8vDy1btgTA3/+aUDd3PXr0gKWlpcL3fXl5OU6ePAkPDw8OXXhBVlYW3n77bTRv3hxffPEFTExMlPpoI6c8o1sDoaGhuH37NlauXInHjx/j8ePH8mmOjo7ys7thYWFYsGAB7Ozs0LdvXyQlJeH8+fPYsmWLrkL/RwoICMDevXsREhKCkJAQ5OTkYPXq1fD29lZ6GgMp+/jjj3H27FksXrwYhYWFuHHjhnxax44dYWFhgXfeeQdTpkzBkiVLMGrUKFy7dg0HDx7E0qVLX/k4mPrI3Nxc4RI7ADx69AjA85z27NkTAJhXDQwdOhQuLi5YsmQJ3nvvPTRp0gRff/01SkpKMGPGDADMp6YCAwOxYsUKrFixAkOGDEF+fj6++uorWFtbY+TIkfJ+zKtqxcXF8hc4ZWRkoLCwUP7mQ2dnZ7Rs2VKt3IlEIsydOxcbNmyAtbU1HB0dcfDgQaSnp2PdunU62z5deFVOmzRpglmzZiEvLw8ffvgh7t69qzB/t27dAGgnpwZCfXqwm5Y5ODhUOS0pKQmtWrWSf/7222+xefNmZGRkwM7ODu+8847Ss3UJuH//PqKionD16lWYmprCx8cHixYtUrr5j5R5enoiIyND5bTdu3fLC7bk5GSsX78eqampsLW1xfTp0xEUFPQ6Q63THj16hCFDhiA6OlrhxlPmVX25ubn49NNPkZSUBIlEAldXV7z//vtwdnaW92E+1ScIAg4cOIB9+/YhPT0d5ubmcHV1RXh4uNL9DcyrsorfaVU++eQT+dA5dXO3fft27N27F//9739hb2+PRYsWyR/zVl+8Kqe9e/eucjoA/Pnnnwqfa5JTFrpEREREpJfq77UKIiIiItJrLHSJiIiISC+x0CUiIiIivcRCl4iIiIj0EgtdIiIiItJLLHSJiIiISC+x0CUiIiIivcRCl4iIiIj0EgtdonruyJEjcHBwkL/a9p+6zH+a2tzG+pA/VXS93bpePwCcPn0a3bt3R35+vs5i0IW9e/di0KBBKC0t1XUopGeMdR0AkT7IycnBzp07cfbsWWRkZEAQBNjZ2WHgwIEICgqCra2trkOsFVeuXMHFixcxbdo0WFlZ6Tocolf6J++zMpkM0dHRmDRpEho3bqwwLT8/H7t27cLZs2eRnp6O0tJS2NjYwNnZGaNGjcLQoUNhYGAAoPrbqOlxLDMzE1u2bEFKSgoyMzNhZmYGFxcXTJ06FQMGDNBo2/39/bFp0ybExcXV+1cSk3bxFcBENXTz5k0EBwejsLAQvr6+cHZ2hqGhIf78808kJCSgcePG+P7773UdZpWOHDmCyMhIJCUloVWrVhrNGxsbi3Xr1inNW15ejrKyMohEIvmXr76pzW2sD/lTpSb7orqq2mcB3ef93LlzmD17Nk6fPo3WrVvL23///XcEBwcjPz8fXl5e6NatGxo0aIAnT54gOTkZv/76K5YuXYrAwEAAL9/Gqmh6HLtx4waCg4MhlUoxbtw4ODg4IC8vD8ePH8edO3cwe/ZshIeHa7T9a9aswalTp5CUlARDQ15wJu3gGV2iGhCLxXjnnXdgYGCAI0eOwN7eXmF6eHg4YmNjdRSd7hgZGcHIyEjXYdSq2tzGupa/4uJiNGzYUNdh1Jiu83748GG4uLgoFLkFBQWYO3cuBEHAkSNH0KlTJ4V55s2bh0uXLqGgoKDa69X0OCYWixEaGgojIyPExcWhffv28mlvv/02wsPDsWXLFnTp0gUjR45UO46RI0dix44duHTpEvr27Vvt7SGqjH8yEdVAXFwcMjMzERERofTlAACWlpZ47733AACLFy+Gp6enUh9V4wJjYmLg4OCAtLQ0LF68GL169YKbmxs+++wzyGQy5OTk4N1330XPnj3h7u6OjRs3KixTk3WpkpGRgeXLl8PLywuurq7o2bMn5syZg7t37yrEuG7dOgDAkCFD4ODgAAcHB/z8889K6zl16hQcHBxw8eJFpXWpmpaVlYWPPvoI/fv3h5OTE4YPH46tW7dC3QtQ6sxf0xyrymVRURHWrFkDT09PODs7o2/fvggKCsLPP/+sUR9Vy75z5w7mzJmDnj17wtXVFRMnTkRycrLStlds14MHD7B06VK4ubmhe/fuCAsLQ15enkZxqFKx/Hv37iEiIgJubm7w8fHRKPfA8zOCAQEBcHZ2xqBBgxAbG6vy56vpvpyVlYWlS5diwIABcHJygqenJ5YsWYLCwsKX7rOvK+9VKS0tRXJyslKBFxcXhydPnmDx4sVKRW4Fd3d3DBs2TB7Hy7ZRFU2OYxX9s7KysGjRIoUiFwCMjY0RFRUFS0tLxMTEvHK7K3N2doaVlRUSExM1mo/oZXhGl6gGzpw5A1NTU43OWmgiPDwcbdu2xYIFC/DTTz9h27ZtaNSoEU6cOIGuXbsiPDwcP/zwA2JiYtC5c2cMHTpUK+u9efMmLl++jOHDh6Nly5bIyspCXFwcpkyZgvj4eNjY2GDYsGFIS0tDQkICIiMj8cYbbwAAOnTogIyMDIXlDR48GObm5jhx4gT69OmjMC0hIQFNmzZF7969ATwfJzhx4kRIpVJMnDgRNjY2uHLlCtauXYusrCx8+OGHL41d0/m1mePly5fj5MmTCAwMRMeOHSEWi/HLL7/gjz/+gJubm9p9XnT//n1MmjQJIpEI06dPh5mZGY4cOYI5c+bgiy++kBc5L26Xra0twsLC8PDhQ+zduxcmJibyIqg6cVQ2f/58tGzZEmFhYZBKpRrl/t69e5gxYwbMzc0xd+5cmJiY4MCBAzAzM3vlel8mOzsb48ePR25uLiZMmAB7e3tkZ2cjMTER+fn5L91nVamNvFflt99+g0QigZOTk0L7mTNn0KBBA4wYMUKtHGi6jRXr0OQ4dubMGYhEIoU/cCqzsrLCkCFDcPToUaSnp8POzk6t5RoYGKBr1664evWqWv2J1MFCl6gG0tLS0K5dO4hEolpZvqOjI1atWgUAmDRpEoYNG4b169dj7ty5ePfddwEA48aNg4eHBw4dOqS1QnfgwIHw8vJSaBs9ejR8fX1x6NAhzJ07F507d0aXLl2QkJCAoUOHvnQsoKmpKYYMGYLExEQsW7YMJiYmAJ6fVUxOToa/v7/8kvHnn38OiUSCY8eOoWnTpgCAgIAA2NraYufOnZg2bdpL16Xp/NrM8blz5zBhwgRERkbWqM+LNmzYgJKSEhw4cEBesEyYMAGjRo3CqlWrMGTIEKUxje3bt8fatWvlnwVBwL///W8sX74clpaW1Yqjsnbt2imdsVM399HR0ZBKpdi3b5+8CBo3bhyGDx9erVgqrFu3DpmZmdi7dy969uwpbw8NDYUgCDAwMFB7nwVqJ+9VSUtLAwClmFJTU9G2bVulY8yzZ89QUlIi/2xiYgJLS0uNfi8rr1uT41hqairatWsHU1PTKvt06dIFR48exb1799QudAGgdevWuHLlitr9iV6FQxeIaqCwsBDm5ua1tvzx48fL/9/AwAAuLi4QBAHjxo2Tt5uamsLBwQHp6elaW2/l8ZbFxcXIy8uDpaUl2rZti1u3blVrmT4+PsjPz8eFCxfkbUlJSSgpKYG3tzeA50XB999/j0GDBsHQ0BC5ubnyfx4eHpDJZLh8+XKV66jO/NrMsYWFBX799VdkZmbWqE9l5eXl+PHHHzF48GCFs3IWFhYICAjA48ePcefOHaX5Jk+erPC5d+/eKC8vx+PHj6sVx4smTZqk8Fnd3FfensoFkLW1NUaNGlWtWIDnTyxITEyEh4eHQpFbQdOby2or71WpGN7w4lMSqjrGrF27Fn369JH/CwkJUXvbXqTpcayoqAgWFhYv7VOxvMLCQo1iadSoEaRSqcbzEVWFZ3SJasDCwgJFRUW1tvwWLVoorQ8AmjdvrtBuaWmJ+/fva229EokE0dHROHbsGLKzsxWmVVwK1VS/fv3QuHFjnDhxAgMHDgTwfNhC8+bN0aNHDwBAbm4unj59isOHD+Pw4cMql5OTk1PlOqozvzZzvHDhQkRGRmLQoEHo0qULPDw8MHr0aIVCSZ0+L27Ts2fPlMZCAv+7HP3o0SN07txZYVrLli0VPlcUUE+fPq1WHC+qfMNURZzq5D43NxfFxcVo166d0nRVberKzc1FYWFhleNYq7O82sj7q7w4TrmqY8yUKVPkVxc++OADtZZdFU2PY+bm5q8sRCuWZ25ujtLSUixbtgwXL16EWCxGx44dERkZie7duyvNxwdBkbax0CWqgfbt2+P3339HaWnpKy/7VXVGqby8vMp5qnrEjqo7wyt/QVRnXZWtXLkSBw8exJQpU9CjRw9YWlrC0NAQq1atqvYXkYmJCYYPH46EhARIJBJIJBL89NNPmDp1qjxemUwGAPD19VU4o1pZmzZtqlxHdeavbo5V8fHxQa9evXDmzBmcP38ee/bswbZt27Bq1SqMGTNG7T7aUNV2VWxDTeNo0KCBwmd1c1+xflX7qKr8qrsvv2y5r9Or8l6Vij8gxWKxQnuHDh1w69YtpWNM+/bt5UX4iz8LTWlyHKsck0QiqXL4wu3btwEA9vb2KCsrQ8uWLbFv3z40a9YM3333HebMmYNz584pPa1DLBbDxMTklWeMidTFQpeoBjw9PXH9+nWcOnUKo0ePfmlfKysrpS8xAEo3bmlDTdeVkJCAf/3rX0o3bj19+rTaZ3QBwNvbGwcOHEBycjKePn0KqVSqcEOLtbU1LCwsUFZWVq3HC9V0fm2wtbVFQEAAAgICIBaLMWHCBGzatEmheFSnTwVra2uYmZnJx3BWVtW4Tm3Gqi51c19eXo6GDRuq3J4HDx4otam7Lzdp0gQWFhYqhxNUR23mXZXKZ4kdHR3l7Z6enrh27Zpax5jq0uQ4Bjy/ufT69etISEiAn5+f0vSCggIkJSWhQ4cO8uEp8+bNk0/38/PD6tWr8fDhQ6Uz4unp6WpfVSBSB8foEtVAQEAA3nzzTaxZswapqalK0wsLC7F+/XoAgJ2dHQoKCvD777/LpxcVFeHo0aNaj6um6zIyMlI6AxUfH4+srCyFtoq75FUVIqq4ubnBxsYGCQkJOHnyJNq0aaNwl7mRkRFGjBiB06dPqxwLXFBQIL/Dv6q4azJ/TZSXlys9y9TKygqtWrWSX7ZWp8+LjIyM4OHhgXPnzikMnSgsLERcXBxatGih8eX66sTxKurm3sjICP3795e/4atCbm4u4uPjleZTd182NDTEsGHDkJKSgmvXriktp2J/VnefrY28v0zXrl1hamqK3377TaE9ICAAzZo1w+rVqxUe71fZi7+rmv5eanIcq+hvY2ODtWvXKv1xUl5ejo8++ghisVihuK0sNTUVxcXFSn8oCIKAW7duqRzSQFRdPKNLVANWVlbYtGkTgoOD4efnp/BGoTt37iA+Ph6NGzdGeHg4fH19sW7dOsybNw9BQUGQSqU4fPgwrK2t8ffff2s1rpquy9PTE0ePHoWFhQXs7e3xxx9/4OTJk0rjMiuK1PXr18PX1xcmJiZwd3evcrmGhobw8vLCwYMHIZVKERwcrNRn4cKFuHz5MiZNmgR/f3906tQJhYWFuHv3Ln744Qf88MMPsLGxqXIdNZ2/uoqKijBgwAAMHz4cnTt3hoWFBa5du4Yff/xR/sYqdfqoMn/+fJw/fx6BgYGYPHkyzM3NceTIEfz999+Ijo7W+C1S1Y3jVdTNfVhYGH766SdMnjwZgYGBMDY2xoEDB9CiRQul4kyTfTk8PBznz5/H9OnT5Y8X++9//4vExERs3LgRrVq1qnKfbdKkidL2aDvvLyMSieDh4YHz588rvFHM0tISX375JWbPng0/Pz+FN6NlZWXh3LlzePDgAbp16yafR5NtBDQ7jgHPbxj74osv5P0rftb5+fk4fvw4/vzzTwQHB8tvMq2suLgY77//PubOnas0POHXX39FQUGB1p4eQwSw0CWqMWdnZ8THx2PHjh04e/YsTpw4AUEQ0KZNGwQEBGDq1KkAnn85bNq0CatXr8batWtha2uLadOmwdLSstqPeKpKTdf14YcfwtjYGAkJCXj27BmcnJywdetWfPbZZwr9unXrhvnz5+Obb75BZGQkZDIZdu/e/dJl+/r6Ys+ePQCg8jmc1tbWOHDgAL766iucPn0aBw4ckD/xYd68eWjUqNFLl1/T+aurQYMGmDx5Mi5cuICkpCSUl5ejVatWiIiIQFBQkNp9VGnfvj3279+P9evXY+fOnZBKpejSpQs2b94sv7FP27FWh7q579SpE3bs2IE1a9bgyy+/RJMmTTB58mQ0adJE6cYqTfZlW1tbHDx4ENHR0UhISIBYLIatrS369+8vH3JT1T6rqgjUdt5fxd/fH3PmzMHDhw8VxpJ37doVx44dw+7du3HmzBkkJSVBKpWiadOmcHV1xezZsxVeqqHJNlZQ9zhWoUePHjh+/DhiY2ORlJSE/fv3w9zcHE5OTnjvvfdU5qe0tBShoaHo2LEj5syZozT91KlTaNasGd+KRlplIPAWRyIiIp2TyWQYPXo0PDw8EBERoetwtKq8vBwLFiyAVCpFTEwMjI0Vz7OVlJRg8ODBmD17NqZPn66bIEkvcYwuERHRP4ChoSHmz5+PuLg45Ofn6zocrfroo4+Ql5eHzz//XKnIBYBDhw5BJBIpPYeYqKZ4RpeIiIhqTUZGBjw9PWFqaqrw2L6PP/641p4kQVSBhS4RERER6SUOXSAiIiIivcRCl4iIiIj0EgtdIiIiItJLLHSJiIiISC+x0CUiIiIivcRCl4iIiIj0EgtdIiIiItJLLHSJiIiISC/9P8m40tOjRo9XAAAAAElFTkSuQmCC\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(figsize=(10,8))\n", - "all_emiss_db_cumu = all_emiss_db*5/1000\n", - "# all_emiss_db_cumu = all_emiss_db_cumu.loc[:,[100, 250, 400, 600]]\n", - "bottom = np.zeros(len(all_emiss_db_cumu.columns))\n", - "aa = 0.2\n", - "for ind, row in all_emiss_db_cumu.iterrows():\n", - " ax.barh(y = row.index, width = row.values,height = 40, left=bottom, label=str(ind) + '-' + str(ind+4), color = 'b', alpha=aa)\n", - " bottom += row.values\n", - " aa+=0.12\n", - "ax.plot(sum_emissions.values,sum_emissions.index, 'k--', label='Cumulative')\n", - "plt.xlabel('Cumulative emissions reduction (Gt CO$_2$)')\n", - "plt.ylabel('Carbon price (\\$/tonne CO$_2$)')\n", - "plt.xlim([-20, 120])\n", - "plt.ylim([-100, 1100])\n", - "plt.legend()\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_mitigation_curve_stackedbarh.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "68034ef9", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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nTsXgwcPRkZGBhISErBy5Uo88cQTAABLS0sAbZsbTca25Jw15zNfN14ikSj9oVafiYkJJkyYgOPHjyMrKwvm5uYabVdHRwf29va4ePGiRuOJujoWukTUbBkZGRgyZIhG60o9PT0xadIkpbYpU6bA19cXhw8fxltvvaXyniFDhijNdNX9d//+/bFz507o6OgAAPr27Ytt27bh559/xrhx4wAAW7ZsQWVlJQ4ePKgosF599VW8/PLL+OijjzBhwgSltY0WFhb4+OOPFa8FQcB//vMfvPfeezA2Nm7y+DSJqbHjaswnn3yC3Nxc7N+/H87Ozor20NBQxczjp59+iqqqKnz77bd48sknAQABAQHo168f9u7di7lz52LgwIFqt5+RkQEAavv19PQgl8s1vrL/p59+gq6uLsaNGwcjIyPY2toiISEBEydOVNl+W+ZGk7EtPWeafuYBID09HUOGDIG+vn6jY2xtbXH8+HHcuXNH40IXAJ555hn8+uuvGo8n6sq4dIGImq2srAyGhoYaje3Zs6fivysqKlBUVARjY2MMHjwYN27cUPuemTNnqm2fPXu2oqAEgDfeeAMAFF9919bW4qeffoK3t7eikAIAIyMjBAQE4P79+7h9+7bSNl9//XWl18899xxqa2sV61Ob0lRMmhxXfXK5HElJSXB3d1cqcuvo6OhAEAR8//338PLygq6uLgoLCxU/7u7ukMvluHDhQqP7qPtKX93FYi+//DJqa2sREBCAb775BiUlJaisrGx0W8nJyXBycoKRkdEjj6utc9PU2Jaes+Z85gHgwYMHTZ6Tuu2pW37zKL169UJNTU2z30fUFXFGl4iazcjICA8ePNBobFVVFSIjI/Htt9+q3Ae37qvthtRdIAUAgwYNUnptamqKXr16KYqZwsJClJeXq6yJBP739Xl2djaGDRumaDczM1MaV1f8lZSUPOqwNI6pvsaOq77CwkKUlZXB2tr6kWNKSkpw5MgRHDlyRO2YgoKCJvelbl2qjY0NDh48iG3btuHkyZN48OABnJ2d4enpiYiICKWZx9raWvz8888ICQnR6LjaMjdNjW3pOWvOZx54WMQ2VYjWbc/Q0BDV1dV49913ce7cOUilUlhZWWHlypUYNWqUyvuaWk9MRP/DQpeIms3CwgK///47qqurm/wqd8OGDTh06BBmz54NJycnGBsbQ1dX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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(figsize=(10,8))\n", - "all_emiss_db_cumu = all_emiss_db*5/1000\n", - "bottom = np.zeros(len(all_emiss_db_cumu.columns))\n", - "aa = 0.2\n", - "for ind, row in all_emiss_db_cumu.iterrows():\n", - " ax.bar(x = row.index, height = row.values,width = 40, bottom=bottom, label=str(ind) + '-' + str(ind+4), color = 'b', alpha=aa)\n", - " bottom += row.values\n", - " aa+=0.12\n", - "plt.ylim([-20, 120])\n", - "plt.xlim([-100, 1100])\n", - "ax.plot(sum_emissions.index, sum_emissions.values, 'k--', label='Cumulative')\n", - "\n", - "plt.legend()\n", - "plt.ylabel('Cumulative emissions reduction (Gt CO$_2$)')\n", - "plt.xlabel('Carbon price (\\$/tonne CO$_2$)')\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_mitigation_curve_stackedbarv.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "ddfa2ee8", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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iIiIyFQeV4JcoVDAzEzv8BgA81QTERER0ROsoKAAMplM+zovL6/SLCAymQy//vor3NzcDLoP81QTERER0RNr48aNWL16NQBAJBIhMjISkZGRlbYVBAEzZ8406D7MU01ERERET6zQ0FBtBcXPP/8cYWFhaNq0qU4bkUgEGxsbtGzZUmfvoD6MmqfaxsaGeaqJiIiIqM5o37492rdvD6BszfSAAQMQHBxs9PsYdfGzmZkZ7OzsjNklEREREZFRTJs2zWR9PzaorsnyjkREREREppSdnY3t27fj8uXLkMlk0Gg0OudFIhGio6P17vexQXVNlnckIiIiIjKVxMREjBs3DkVFRfD398f169cRGBiI/Px8ZGZmwtfXF+7u7gb1/dig+sHyjuWviYiIiIjqk+XLl8PCwgK//vorbG1t0bVrVyxcuBChoaHYvXs3lixZgs8//9ygvh8bVD9Y3vHB10RERERE9UFcXBwmTpwIHx8f5OXlAfi3IMzQoUMRFxeHZcuWYdOmTXr3bVjJGCIiIiKiekapVGqLu5Rnq5PL5drzzZo1w8WLFw3q26DsH4cOHcL27dtx+/Zt5OfnVyj5KBKJcPz4cYMGRERERERkCh4eHkhNTQVQFlS7urri3LlzGDhwIAAgISEBtra2BvWtd1AdFRWF1atXQyqVokmTJvDz8zPoxkRERERENalz5844fPiwtmris88+i+joaMjlcmg0GuzcuRPPP/+8QX3rHVRv3rwZoaGhWLt2rbY6DRERERFRXTdlyhRcvHgRpaWlaNCgAWbMmIGCggLs3bsXZmZmGDZsGObNm2dQ33oH1SqVCgMGDGBATURERET1iqenp04tFbFYjMWLF2Px4sXV7lvvjYrdunXDpUuXqn1jIiIiIqInhd4z1YsWLcKrr76KqKgojBw5Eh4eHsxbTURERER1TlUrgT/IkMrgegfVTk5OCAsLw4oVK7B69epK24hEIly5ckXvwRARERERGUtVK4E/yJDK4HoH1cuXL8f69evh7e2NVq1aQSKR6H1TIiIiIiJTq8lK4HoH1du2bUO/fv0QFRVlivEQERERERlFTVYC13ujoiAI6N69uynGQkRERERUL+k9U923b1+cPn0ao0ePNsV4iIiIiIhMoqobF2tko+Lrr7+OWbNm4b333sOoUaPg4eEBc3PzCu2cnZ31HgwRERERkalU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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, ax = plt.subplots(figsize=(10,8))\n", - "aa = 0.2\n", - "for ind, row in all_emiss_db.iterrows():\n", - " row.plot(ax=ax, color = 'b', alpha=aa, label = ind)\n", - " aa+=0.12\n", - "plt.ylabel('Annual emissions reductions (million tonnes CO$_2$)')\n", - "plt.legend()\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.xlim([-50,1100])\n", - "plt.ylim([-2000, 5000])\n", - "ax2 = ax.twinx()\n", - "sum_emissions = all_emiss_db.sum()/1000*5\n", - "ax2.plot(sum_emissions.index, sum_emissions.values, 'k--', label='Cumulative')\n", - "# ax2.plot(sum_emissions.index, all_emiss_db.loc[2050]*30/1000, edgecolor=\"None\")\n", - "# ax2.plot(sum_emissions.index, all_emiss_db.loc[2020]*30/1000, edgecolor=\"None\")\n", - "\n", - "ax2.set_ylim([-2000/1000*30, 5000/1000*30])\n", - "ax2.set_ylabel('Cumulative emissions reductions (Gt CO$_2$)')\n", - "ax2.legend()\n", - "mpl_axes_aligner.align.yaxes(ax, 0, ax2, 0, 0.3)\n", - "ax.grid(axis='y')\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_mitigation_curve_annual_cumulative.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "83b942db", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "3992.5384187624477\n", - "3825.828976265355\n", - "3744.2022668633163\n", - "2978.190649859003\n", - "2358.654409597703\n", - "1647.5111792278342\n", - "1225.94386844026\n", - "779.7201676758104\n" - ] - }, - { - "data": { - "image/png": "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P/30E9OmTSMlJaXKuaSkJB588EGuuuqqBqucEEIIIYSoWZ0DuSlTpvDRRx9hsVi47rrraNu2LQEBARQVFbF7925+/vlnHnnkEe677z4mTJhwPusshBBCCCGoYyC3cuVKZsyYwbBhw3jppZcIDAysUqawsJCnn36ajz76iB49etCvX78Gr6wQQgghhDilTulH/v3vf9OmTRveeeedaoM4AIvFwttvv03r1q354osvGrSSQgghhBCiqjoFclu3bmXUqFFotbUX12q1jBo1im3btjVI5YQQQgghRM3qFMgVFxcTHBxcpxcMCgqiuLj4nColhBBCCCHOrE6BXEREBHv37q3TC+7du5dmzZqdU6WEEEIIIcSZ1SmQ69+/P//97385ePBgreUOHjzI7NmzGThwYINUTgghhBBC1KxOgdz999+PXq/nzjvvZM6cOdjtdq/zdrudOXPmMHr0aPR6Pffdd995qawQQgghhDilznutbt68mYceeogTJ05gNBpp2bIlFouFwsJC0tLSsNvthIaGMm3aNLp27Xq+6+1zZN86IYQQvkLuWb6jzgmBu3TpwsKFC5k1axa//PILKSkpFBUVERAQQLt27Rg8eDC33347Vqv1fNa30Rw4cIAXXniBP/74A5PJxMiRI3nsscfw8/Nr7KoJIYQQ4hJVry26AgMDue+++y65odP8/HzGjBlDdHQ0U6dOJTs7m1deeYXs7GymTJnS2NUTQgghxCWqTnPksrKyGD58+BmDlilTpnD11VeTnZ3dIJVrKr7++mvy8/N5//33GTBgANdffz1PP/00CxcuZN++fY1dPSGEEEJcouoUyH355Zfk5uYybty4WsuNGzeOnJwc/v3vfzdI5ZqKFStW0KtXL0JCQtRjV111FUajkRUrVjRizYQQQghxKatTILd8+XJGjhyJxWKptZzFYuGaa67h559/bpDKNRUpKSkkJSV5HTMajcTHx5OamtpItRJCCCHEpa5OgdyhQ4do06ZNnV6wdevWZ8w352vy8/OrXcRhtVrJy8trhBoJIYQQQtRxsYNGo8HtdtfpBd1uNxqN5pwq5SsURan3Z92+fft5qo0QQgghLjV1CuRiYmLYunUrt99++xnLbtu2jZiYmHOuWFNitVrJz8+vcrygoIDExMR6vZbk5BFCCNHUVeSRE01fnYZWBw0axIIFC0hJSam1XEpK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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# output_list = []\n", - "fig, ax = plt.subplots(figsize=(9,6))\n", - "\n", - "max_val = -1\n", - "\n", - "n = len(list_of_DBs)\n", - "colors = plt.cm.RdYlGn(np.linspace(0,1,n))\n", - "\n", - "i=0\n", - "for db, conn, scenario in zip(list_of_DBs,list_of_conns, list_of_scenarios):\n", - " query = \"SELECT t_periods, sum(emissions) as emissions FROM Output_Emissions \\\n", - " WHERE emissions_comm='co2'GROUP BY t_periods\"\n", - " df_read_emissions = pd.read_sql_query(query, conn)\n", - " df_read_emissions['emissions']/=1000\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('CT')[1]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.plot(df_read_emissions['t_periods'], df_read_emissions['emissions'],label=scen_name, color = colors[i])\n", - " if scen_name =='100$/tonne':\n", - " plt.plot(df_read_emissions['t_periods'], df_read_emissions['emissions'],label=scen_name, color = 'y')\n", - "\n", - " max_val = max(max_val, df_read_emissions['emissions'].max())\n", - " va = 'center'\n", - " if '-10$' in scen_name:\n", - " va = 'bottom'\n", - " elif '10$' in scen_name:\n", - " va = 'top'\n", - " plt.annotate(scen_name, (2050, df_read_emissions['emissions'].iloc[-1]), \\\n", - " rotation = 'horizontal', \\\n", - " horizontalalignment='left', verticalalignment=va, fontsize=14, color = colors[i])\n", - " if scen_name =='100$/tonne':\n", - " plt.annotate(scen_name, (2050, df_read_emissions['emissions'].iloc[-1]), \\\n", - " rotation = 'horizontal', \\\n", - " horizontalalignment='left', verticalalignment=va, fontsize=14, color = 'y')\n", - " i+=1\n", - " print(df_read_emissions.iloc[-1,-1])\n", - " \n", - "plt.ylim([-1000, 6000])\n", - "# plt.xlim([2020, 2053])\n", - "ax.grid(axis='x')\n", - "plt.xlim([df_read_emissions['t_periods'].min(), df_read_emissions['t_periods'].max()])\n", - "plt.xticks(rotation='vertical')\n", - "ax.get_yaxis().set_major_formatter(\n", - "tick.FuncFormatter(lambda x, p: format(int(x), ',')))\n", - "\n", - "# plt.legend(frameon=False)\n", - "plt.ylabel('CO$_2$ emissions (million tonnes)')\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_co2_emissions.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d1f25e28", - "metadata": {}, - "outputs": [], - "source": [ - "df_sector_emissions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a425988c", - "metadata": {}, - "outputs": [], - "source": [ - "df_sector_emissions_all = pd.DataFrame()\n", - "for db, conn, scenario in zip(list_of_DBs,list_of_conns, list_of_scenarios):\n", - " query = \"SELECT tech, sum(emissions) as emissions FROM Output_Emissions \\\n", - " WHERE emissions_comm='co2' AND t_periods = 2050 AND sector='transport' GROUP BY tech\"\n", - " df_sector_emissions = pd.read_sql_query(query, conn)\n", - " df_sector_emissions['emissions']/=1000\n", - "# mask = (df_sector_emissions['sector'].isin(['commercial','residential']))\n", - "# bldg = df_sector_emissions[mask]\n", - "# df_sector_emissions.loc[len(df_sector_emissions),:] = ['buildings', bldg['emissions'].sum()]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]].replace('$/tonne','')\n", - " except:\n", - " scen_name = 0 #map_scenario_names['']\n", - " df_sector_emissions.loc[:,'db'] = int(scen_name)\n", - " df_sector_emissions_all = pd.concat([df_sector_emissions_all, df_sector_emissions])\n", - "df_sector_emissions_all" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "62aee484", - "metadata": {}, - "outputs": [], - "source": [ - "df_sector_emissions_all[(df_sector_emissions_all['db']==1000) & (df_sector_emissions_all['emissions']>0)].sum()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d6ecf7d9", - "metadata": {}, - "outputs": [], - "source": [ - "df_sector_emissions_all = pd.DataFrame()\n", - "for db, conn, scenario in zip(list_of_DBs,list_of_conns, list_of_scenarios):\n", - " query = \"SELECT sector, sum(emissions) as emissions FROM Output_Emissions \\\n", - " WHERE emissions_comm='co2' AND t_periods = 2050 GROUP BY sector\"\n", - " df_sector_emissions = pd.read_sql_query(query, conn)\n", - " df_sector_emissions['emissions']/=1000\n", - " mask = (df_sector_emissions['sector'].isin(['commercial','residential']))\n", - " bldg = df_sector_emissions[mask]\n", - " df_sector_emissions.loc[len(df_sector_emissions),:] = ['buildings', bldg['emissions'].sum()]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]].replace('$/tonne','')\n", - " except:\n", - " scen_name = 0 #map_scenario_names['']\n", - " df_sector_emissions.loc[:,'db'] = int(scen_name)\n", - " df_sector_emissions_all = pd.concat([df_sector_emissions_all, df_sector_emissions])\n", - "df_sector_emissions_all" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "379fefcc", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(figsize=(7,6))\n", - "for sector in df_sector_emissions_all['sector'].unique():\n", - " if sector in ['buildings','electric','industrial','transport']:\n", - " df_sector_plot = df_sector_emissions_all[df_sector_emissions_all['sector']==sector]\n", - " plt.plot(df_sector_plot['db'], df_sector_plot['emissions'], label=sector)\n", - "\n", - "plt.legend(frameon=False)\n", - "plt.ylim([-400, 1400])\n", - "plt.xlim([-100, 1000])\n", - "plt.ylabel('CO$_2$ emissions in 2050 (million tonnes)')\n", - "plt.xlabel('Carbon tax ($/tonne)')\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/sectoral_co2_20450.jpg', dpi=400)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d163aa73", - "metadata": {}, - "outputs": [], - "source": [ - "list_of_DBs = ['US_9R_8D_CT_neg50.sqlite', 'US_9R_8D_CT_neg10.sqlite', \\\n", - " 'US_9R_8D_CT_0.sqlite', 'US_9R_8D_CT_10.sqlite', 'US_9R_8D_CT_50.sqlite']\n", - "\n", - "list_of_scenarios = ['test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run','test_run']\n", - "\n", - "list_of_conns = [sqlite3.connect(db) for db in list_of_DBs]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "49129fa4", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "scen_name_all = []\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - "\n", - " query = \"SELECT t_periods, tech, sum(vflow_out) FROM Output_VFlow_Out WHERE input_comm LIKE 'ethos%' AND tech NOT LIKE '%_emissions%' \\\n", - " GROUP BY t_periods, tech\"\n", - " df_s = pd.read_sql_query(query, conn)\n", - "\n", - " ethos_tech_map = dict()\n", - " ethos_tech_map['E_SOLPV'] = 'Solar'\n", - " ethos_tech_map['SOLELC'] = 'Solar'\n", - " ethos_tech_map['E_WND'] = 'Wind'\n", - " ethos_tech_map['E_OFWND_N'] = 'Wind'\n", - " ethos_tech_map['E_GEO'] = 'Other Renewables'\n", - " ethos_tech_map['E_HYD'] = 'Other Renewables'\n", - " ethos_tech_map['Corn'] = 'Biomass'\n", - " ethos_tech_map['Herbaceous'] = 'Biomass'\n", - " ethos_tech_map['Soybeans'] = 'Biomass'\n", - " ethos_tech_map['Waste'] = 'Biomass'\n", - " ethos_tech_map['Woody'] = 'Biomass'\n", - " ethos_tech_map['DFO'] = 'Petroleum'\n", - " ethos_tech_map['DISTOIL'] = 'Petroleum'\n", - " ethos_tech_map['IMPRESNGA'] = 'Natural Gas'\n", - " ethos_tech_map['IMPELCNGA_S3'] = 'Natural Gas'\n", - " ethos_tech_map['IMPCOMNGA'] = 'Natural Gas'\n", - "# ethos_tech_map['NGA'] = 'Natural Gas'\n", - " ethos_tech_map['INDNG'] = 'Natural Gas'\n", - " ethos_tech_map['CNG'] = 'Natural Gas'\n", - " ethos_tech_map['LNG'] = 'Natural Gas'\n", - " ethos_tech_map['COAL'] = 'Coal'\n", - " ethos_tech_map['COAB'] = 'Coal'\n", - " ethos_tech_map['COAS'] = 'Coal'\n", - " ethos_tech_map['URN'] = 'Nuclear'\n", - " ethos_tech_map['RFO'] = 'Petroleum'\n", - " ethos_tech_map['BIO'] = 'Biomass'\n", - " ethos_tech_map['REN'] = 'Biomass'\n", - " ethos_tech_map['GAS'] = 'Petroleum'\n", - " ethos_tech_map['JTF'] = 'Petroleum'\n", - " ethos_tech_map['LPG'] = 'Petroleum'\n", - " ethos_tech_map['DSL'] = 'Petroleum'\n", - " ethos_tech_map['GSL'] = 'Petroleum'\n", - " ethos_tech_map['MGO'] = 'Petroleum'\n", - " ethos_tech_map['IMPINDOTH'] = 'Petroleum'\n", - "\n", - "\n", - " for key in ethos_tech_map.keys():\n", - " mask = df_s['tech'].str.contains(key)\n", - " df_s.loc[mask,'tech_rev'] = ethos_tech_map[key]\n", - " \n", - " df_s_pivot = df_s.groupby(by=['t_periods', 'tech_rev']).sum().reset_index()\n", - " df_s_pivot = df_s_pivot.pivot_table(index='tech_rev', columns='t_periods')\n", - " df_s_pivot.fillna(0, inplace=True)\n", - " df_s_pivot.columns = [x[1] for x in df_s_pivot.columns]\n", - " \n", - " df_s_pivot = df_s_pivot.loc[['Coal','Petroleum','Natural Gas','Nuclear','Other Renewables','Biomass','Solar','Wind']]\n", - " df_s_pivot /= 1000\n", - " output_list.append(df_s_pivot)\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " \n", - " scen_name_all.append(scen_name)\n", - "# fig, ax = plt.subplots(figsize=(12,6))\n", - "\n", - "# ax = func_stacked_plot(output_list, col_order = ['Coal','Petroleum','Natural Gas','Nuclear',\\\n", - "# 'Other Renewables','Biomass','Solar','Wind'])\n", - "\n", - "# ax.grid(axis='x')\n", - "\n", - "# plt.ylabel('Primary energy consumption (EJ)')\n", - "# plt.tight_layout()\n", - "# plt.savefig('carbon_tax_figures_updates/'+list_of_DBs[0].replace('.sqlite','_compare_scens_primaryenergy.jpg'))\n", - "\n", - "\n", - "fig, ax = plt.subplots(figsize=(16,6))\n", - "for ind_iter in np.arange(len(output_list)):\n", - " subp = plt.subplot(1,len(output_list)+2,ind_iter+1)\n", - " plt.stackplot(output_list[ind_iter].columns, output_list[ind_iter], alpha = 0.75, labels=output_list[ind_iter].index, \\\n", - " colors = output_list[ind_iter].index.map(tech_colormap))\n", - " \n", - " \n", - " if ind_iter==0:\n", - " plt.ylabel('Primary energy consumption (EJ)')\n", - " else:\n", - " subp.set(yticklabels=[])\n", - "\n", - " plt.grid(axis='x')\n", - " plt.xticks(rotation='vertical')\n", - " plt.xlim([output_list[ind_iter].columns.min(), output_list[ind_iter].columns.max()])\n", - " plt.title(scen_name_all[ind_iter])\n", - " plt.ylim([0, 100])\n", - "\n", - "# handles, labels = subp.get_legend_handles_labels()\n", - "# unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - "# if l not in labels[:i]]\n", - "subp = plt.subplot(1,len(output_list)+2,ind_iter+2)\n", - "# subp.legend(handles[::-1], labels[::-1], loc='upper left', frameon=False)\n", - "\n", - "# dummy_df = pd.DataFrame(index=output_list[ind_iter].index, columns=output_list[ind_iter].columns)\n", - "# dummy_df.transpose().plot.bar(kind='stacked',ax=subp, color = output_list[ind_iter].index.map(tech_colormap))\n", - "# subp.set(yticklabels=[],xticklabels=[] )\n", - "plt.legend(loc='upper left')\n", - "plt.grid(False)\n", - "plt.axis('off')\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_primaryenergy.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5fa547e1", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "plt.ylim([-15, 20])\n", - "plt.ylabel('Difference in primary energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend(frameon=False)\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_primaryenergy_diff.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "68ea9912", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "scen_name_all = []\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - "\n", - " query = \"SELECT t_periods, tech, sum(vflow_out) FROM Output_VFlow_Out WHERE input_comm LIKE 'ethos%' AND tech NOT LIKE '%_emissions%' \\\n", - " AND tech LIKE '%NGA%' GROUP BY t_periods, tech\"\n", - " df_s = pd.read_sql_query(query, conn)\n", - " df_s_pivot = df_s.pivot_table(index='tech', columns='t_periods')\n", - " output_list.append(df_s_pivot)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0b9c326f", - "metadata": {}, - "outputs": [], - "source": [ - "region = 'US'\n", - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " \n", - " if region=='US':\n", - " query = \"SELECT tech, t_periods, sum(vflow_out) as vflow_out FROM Output_VFlow_Out WHERE \\\n", - " sector = 'electric' AND scenario='\" + scenario + \"' \\\n", - " AND vflow_out > 1e-3 GROUP BY tech, t_periods\"\n", - " else:\n", - " query = \"SELECT regions, tech, t_periods, sum(vflow_out) as vflow_out FROM Output_VFlow_Out WHERE \\\n", - " sector = 'electric' AND scenario='\" + scenario + \"' \\\n", - " AND vflow_out > 1e-3 GROUP BY regions, tech, t_periods\"\n", - "\n", - " df_s = pd.read_sql_query(query, conn)\n", - " df_plot = df_s.groupby(['tech' , 't_periods']).sum().pivot_table(values='vflow_out', index='tech', columns='t_periods')\n", - " df_plot = df_plot[~df_plot.index.str.contains('TRANS')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('BLND')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('_emissions')]\n", - " \n", - "# df_stor = pd.read_sql_query(\"SELECT DISTINCT tech FROM StorageDuration\", conn)\n", - "# df_plot = df_plot[~df_plot.index.isin(df_stor['tech'])]\n", - " \n", - " df_plot.loc[:,'agg_tech'] = [map_plants[y] for x in df_plot.index for y in map_plants.keys() if y.lower() in x.lower()] #map agg technologies\n", - "\n", - " df_plot = df_plot.groupby('agg_tech').sum()\n", - " df_plot = df_plot.loc[:, df_plot.columns >= 2020]\n", - " df_plot.fillna(0, inplace=True)\n", - " df_plot*=0.277778\n", - "\n", - " df_plot = df_plot[[2020, 2030, 2040, 2050]]\n", - "\n", - " output_list.append(df_plot)\n", - "\n", - "\n", - "ax = func_stacked_plot(output_list)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.5) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - "\n", - "ax.grid(axis='x')\n", - "plt.ylim([0, 6000])\n", - "ax.get_yaxis().set_major_formatter(\n", - "tick.FuncFormatter(lambda x, p: format(int(x), ',')))\n", - "plt.ylabel('Generation (TWh)')\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_gen.jpg')\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "09395b87", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(color_dict), legend=False)\n", - "# plt.ylim([-15, 20])\n", - "plt.ylabel('Difference in 2050 electricity generation (TWh) \\n relative to preceding scenario (to the left)')\n", - "plt.legend(frameon=False)\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_gen_diff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "92b8c629", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'transport', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_transport)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Jet Fuel','Diesel','Gasoline','Biodiesel','Ethanol','Synthetic Fuel','Electricity','Hydrogen']\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - "\n", - " \n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = ['Other Fossil', 'Jet Fuel','Diesel','Gasoline','Biodiesel',\\\n", - " 'Ethanol','Synthetic Fuel','Electricity','Hydrogen'], color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.5) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - "\n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,30])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_transport.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8af4799", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_transportdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c8a5717", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'industrial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_industrial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Coal','Diesel','Gasoline','Natural Gas', 'Biomass','Synthetic Natural Gas',\\\n", - " 'Electricity', 'Hydrogen']\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - "\n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = ['Other Fossil', 'Coal','Diesel','Gasoline','Natural Gas', 'Biomass','Synthetic Natural Gas',\\\n", - " 'Electricity', 'Hydrogen'], color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,30])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_industrial.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "798ab89f", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_industrialdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1005280", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'residential', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_residential)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Biomass','Natural Gas','Residential Solar','Synthetic Natural Gas', 'Electricity', 'Hydrogen']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_res = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - " \n", - " df_plot = stacked_penergy_sector(conn, scenario, 'commercial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_commercial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Natural Gas','Synthetic Natural Gas','Electricity']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_comm = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - " \n", - " df_plot_bld = pd.concat([df_plot_res, df_plot_comm]).groupby('input_comm').sum()\n", - " output_list.append(df_plot_bld)\n", - " \n", - "fuels_order = ['Other Fossil', 'Biomass','Natural Gas','Residential Solar','Synthetic Natural Gas', 'Electricity', 'Hydrogen']\n", - "ax = func_stacked_plot(output_list, col_order = fuels_order, color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,25])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_buildings.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f6c4cf67", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_buildingsdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0d997945", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'commercial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_commercial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Natural Gas','Synthetic Natural Gas','Electricity']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - "\n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = fuels_order, color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,15])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_commercial.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "47342aca", - "metadata": {}, - "outputs": [], - "source": [ - "list_of_DBs = ['US_9R_8D_CT_0.sqlite', 'US_9R_8D_CT_10.sqlite', 'US_9R_8D_CT_50.sqlite', 'US_9R_8D_CT_100.sqlite', 'US_9R_8D_CT_200.sqlite', \\\n", - " 'US_9R_8D_CT_400.sqlite', 'US_9R_8D_CT_600.sqlite', 'US_9R_8D_CT_800.sqlite', 'US_9R_8D_CT_1000.sqlite']\n", - "\n", - "list_of_scenarios = ['test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run', 'test_run','test_run','test_run', 'test_run','test_run']\n", - "\n", - "list_of_conns = [sqlite3.connect(db) for db in list_of_DBs]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "19d8bd55", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "scen_name_all = []\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - "\n", - " query = \"SELECT t_periods, tech, sum(vflow_out) FROM Output_VFlow_Out WHERE input_comm LIKE 'ethos%' AND tech NOT LIKE '%_emissions%' \\\n", - " GROUP BY t_periods, tech\"\n", - " df_s = pd.read_sql_query(query, conn)\n", - "\n", - " ethos_tech_map = dict()\n", - " ethos_tech_map['E_SOLPV'] = 'Solar'\n", - " ethos_tech_map['SOLELC'] = 'Solar'\n", - " ethos_tech_map['E_WND'] = 'Wind'\n", - " ethos_tech_map['E_OFWND_N'] = 'Wind'\n", - " ethos_tech_map['E_GEO'] = 'Other Renewables'\n", - " ethos_tech_map['E_HYD'] = 'Other Renewables'\n", - " ethos_tech_map['Corn'] = 'Biomass'\n", - " ethos_tech_map['Herbaceous'] = 'Biomass'\n", - " ethos_tech_map['Soybeans'] = 'Biomass'\n", - " ethos_tech_map['Waste'] = 'Biomass'\n", - " ethos_tech_map['Woody'] = 'Biomass'\n", - " ethos_tech_map['DFO'] = 'Petroleum'\n", - " ethos_tech_map['DISTOIL'] = 'Petroleum'\n", - " ethos_tech_map['IMPRESNGA'] = 'Natural Gas'\n", - " ethos_tech_map['IMPELCNGA_S3'] = 'Natural Gas'\n", - " ethos_tech_map['IMPCOMNGA'] = 'Natural Gas'\n", - "# ethos_tech_map['NGA'] = 'Natural Gas' \n", - " ethos_tech_map['INDNG'] = 'Natural Gas'\n", - " ethos_tech_map['CNG'] = 'Natural Gas'\n", - " ethos_tech_map['LNG'] = 'Natural Gas'\n", - " ethos_tech_map['COAL'] = 'Coal'\n", - " ethos_tech_map['COAB'] = 'Coal'\n", - " ethos_tech_map['COAS'] = 'Coal'\n", - " ethos_tech_map['URN'] = 'Nuclear'\n", - " ethos_tech_map['RFO'] = 'Petroleum'\n", - " ethos_tech_map['BIO'] = 'Biomass'\n", - " ethos_tech_map['REN'] = 'Biomass'\n", - " ethos_tech_map['GAS'] = 'Petroleum'\n", - " ethos_tech_map['JTF'] = 'Petroleum'\n", - " ethos_tech_map['LPG'] = 'Petroleum'\n", - " ethos_tech_map['DSL'] = 'Petroleum'\n", - " ethos_tech_map['GSL'] = 'Petroleum'\n", - " ethos_tech_map['MGO'] = 'Petroleum'\n", - " ethos_tech_map['IMPINDOTH'] = 'Petroleum'\n", - "\n", - "\n", - " for key in ethos_tech_map.keys():\n", - " mask = df_s['tech'].str.contains(key)\n", - " df_s.loc[mask,'tech_rev'] = ethos_tech_map[key]\n", - " \n", - " df_s_pivot = df_s.groupby(by=['t_periods', 'tech_rev']).sum().reset_index()\n", - " df_s_pivot = df_s_pivot.pivot_table(index='tech_rev', columns='t_periods')\n", - " df_s_pivot.fillna(0, inplace=True)\n", - " df_s_pivot.columns = [x[1] for x in df_s_pivot.columns]\n", - " \n", - " df_s_pivot = df_s_pivot.loc[['Coal','Petroleum','Natural Gas','Nuclear','Other Renewables','Biomass','Solar','Wind']]\n", - " df_s_pivot /= 1000\n", - " output_list.append(df_s_pivot)\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " \n", - " scen_name_all.append(scen_name)\n", - "# fig, ax = plt.subplots(figsize=(12,6))\n", - "\n", - "# ax = func_stacked_plot(output_list, col_order = ['Coal','Petroleum','Natural Gas','Nuclear',\\\n", - "# 'Other Renewables','Biomass','Solar','Wind'])\n", - "\n", - "# ax.grid(axis='x')\n", - "\n", - "# plt.ylabel('Primary energy consumption (EJ)')\n", - "# plt.tight_layout()\n", - "# plt.savefig('carbon_tax_figures_updates/'+list_of_DBs[0].replace('.sqlite','_compare_scens_primaryenergy.jpg'))\n", - "\n", - "\n", - "fig, ax = plt.subplots(figsize=(16,6))\n", - "for ind_iter in np.arange(len(output_list)):\n", - " subp = plt.subplot(1,len(output_list)+2,ind_iter+1)\n", - " plt.stackplot(output_list[ind_iter].columns, output_list[ind_iter], alpha = 0.75, labels=output_list[ind_iter].index, \\\n", - " colors = output_list[ind_iter].index.map(tech_colormap))\n", - " \n", - " \n", - " if ind_iter==0:\n", - " plt.ylabel('Primary energy consumption (EJ)')\n", - " else:\n", - " subp.set(yticklabels=[])\n", - "\n", - " plt.grid(axis='x')\n", - " plt.xticks(rotation='vertical')\n", - " plt.xlim([output_list[ind_iter].columns.min(), output_list[ind_iter].columns.max()])\n", - " plt.title(scen_name_all[ind_iter])\n", - " plt.ylim([0, 100])\n", - "\n", - "# handles, labels = subp.get_legend_handles_labels()\n", - "# unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - "# if l not in labels[:i]]\n", - "subp = plt.subplot(1,len(output_list)+2,ind_iter+2)\n", - "# subp.legend(handles[::-1], labels[::-1], loc='upper left', frameon=False)\n", - "\n", - "# dummy_df = pd.DataFrame(index=output_list[ind_iter].index, columns=output_list[ind_iter].columns)\n", - "# dummy_df.transpose().plot.bar(kind='stacked',ax=subp, color = output_list[ind_iter].index.map(tech_colormap))\n", - "# subp.set(yticklabels=[],xticklabels=[] )\n", - "plt.legend(loc='upper left')\n", - "plt.grid(False)\n", - "plt.axis('off')\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_highC_primaryenergy.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c31d2500", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "plt.ylim([-15, 20])\n", - "plt.ylabel('Difference in primary energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend(frameon=False)\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_highC_primaryenergy_diff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0feb23d0", - "metadata": {}, - "outputs": [], - "source": [ - "region = 'US'\n", - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " \n", - " if region=='US':\n", - " query = \"SELECT tech, t_periods, sum(vflow_out) as vflow_out FROM Output_VFlow_Out WHERE \\\n", - " sector = 'electric' AND scenario='\" + scenario + \"' \\\n", - " AND vflow_out > 1e-3 GROUP BY tech, t_periods\"\n", - " else:\n", - " query = \"SELECT regions, tech, t_periods, sum(vflow_out) as vflow_out FROM Output_VFlow_Out WHERE \\\n", - " sector = 'electric' AND scenario='\" + scenario + \"' \\\n", - " AND vflow_out > 1e-3 GROUP BY regions, tech, t_periods\"\n", - "\n", - " df_s = pd.read_sql_query(query, conn)\n", - " df_plot = df_s.groupby(['tech' , 't_periods']).sum().pivot_table(values='vflow_out', index='tech', columns='t_periods')\n", - " df_plot = df_plot[~df_plot.index.str.contains('TRANS')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('BLND')]\n", - " df_plot = df_plot[~df_plot.index.str.contains('_emissions')]\n", - " \n", - "# df_stor = pd.read_sql_query(\"SELECT DISTINCT tech FROM StorageDuration\", conn)\n", - "# df_plot = df_plot[~df_plot.index.isin(df_stor['tech'])]\n", - " \n", - " df_plot.loc[:,'agg_tech'] = [map_plants[y] for x in df_plot.index for y in map_plants.keys() if y.lower() in x.lower()] #map agg technologies\n", - "\n", - " df_plot = df_plot.groupby('agg_tech').sum()\n", - " df_plot = df_plot.loc[:, df_plot.columns >= 2020]\n", - " df_plot.fillna(0, inplace=True)\n", - " df_plot*=0.277778\n", - " \n", - " df_plot = df_plot[[2020, 2030, 2040, 2050]]\n", - " output_list.append(df_plot)\n", - "\n", - "\n", - "ax = func_stacked_plot(output_list)\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - "ax.grid(axis='x')\n", - "plt.ylim([0, 12000])\n", - "ax.get_yaxis().set_major_formatter(\n", - "tick.FuncFormatter(lambda x, p: format(int(x), ',')))\n", - "plt.ylabel('Generation (TWh)')\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_highC_gen.jpg')\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b632ba32", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(color_dict), legend=False)\n", - "# plt.ylim([-15, 20])\n", - "plt.ylabel('Difference in 2050 electricity generation (TWh) \\n relative to preceding scenario (to the left)')\n", - "plt.legend(frameon=False)\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_scens_highC_gen_diff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bfd76560", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'transport', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_transport)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Jet Fuel','Diesel','Gasoline','Biodiesel','Ethanol','Synthetic Fuel','Electricity','Hydrogen']\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - "\n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = ['Other Fossil', 'Jet Fuel','Diesel','Gasoline','Biodiesel',\\\n", - " 'Ethanol','Synthetic Fuel','Electricity','Hydrogen'], color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,30])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_transport.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f07f3da8", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_transportdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34207b30", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'industrial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_industrial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Coal','Diesel','Gasoline','Natural Gas', 'Biomass','Synthetic Natural Gas',\\\n", - " 'Electricity', 'Hydrogen']\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - "\n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = ['Other Fossil', 'Coal','Diesel','Gasoline','Natural Gas', 'Biomass','Synthetic Natural Gas',\\\n", - " 'Electricity', 'Hydrogen'], color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,30])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_industrial.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "19578ac7", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_industrialdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6cb57956", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'residential', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_residential)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Biomass','Natural Gas','Residential Solar','Synthetic Natural Gas', 'Electricity', 'Hydrogen']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_res = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - " \n", - " df_plot = stacked_penergy_sector(conn, scenario, 'commercial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_commercial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Natural Gas','Synthetic Natural Gas','Electricity']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_comm = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - " \n", - " df_plot_bld = pd.concat([df_plot_res, df_plot_comm]).groupby('input_comm').sum()\n", - " output_list.append(df_plot_bld)\n", - " \n", - "fuels_order = ['Other Fossil', 'Biomass','Natural Gas','Residential Solar','Synthetic Natural Gas', 'Electricity', 'Hydrogen']\n", - "ax = func_stacked_plot(output_list, col_order = fuels_order, color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,25])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_buildings.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "971a11b7", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_buidlingsdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "17fdc74d", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "fig, ax = plt.subplots(figsize=(10,6))\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_plot = stacked_penergy_sector(conn, scenario, 'commercial', 'All')\n", - " df_plot /=1000\n", - " df_plot_copy = df_plot.copy()\n", - " df_plot_copy.index = df_plot_copy.index.map(tech_commercial)\n", - " df_plot_copy = df_plot_copy.reset_index().groupby(by=['input_comm']).sum()\n", - "\n", - " fuels_order = ['Other Fossil', 'Natural Gas','Synthetic Natural Gas','Electricity']\n", - "\n", - " add_ind = [x for x in fuels_order if x not in df_plot_copy.index]\n", - " for ind in add_ind:\n", - " df_plot_copy.loc[ind,:] = 0\n", - " df_plot_copy = df_plot_copy.loc[fuels_order]\n", - " df_plot_copy = df_plot_copy[[2020, 2030, 2040, 2050]]\n", - "\n", - " output_list.append(df_plot_copy)\n", - "ax = func_stacked_plot(output_list, col_order = fuels_order, color_dict = tech_colormap)\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.4) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2020\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "plt.ylabel('Energy consumption (EJ)')\n", - "plt.grid(axis='x')\n", - "plt.ylim([0,15])\n", - "\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_commercial.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a5f1884", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(tech_colormap), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (EJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_commercialdiff.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ec4ac4a", - "metadata": {}, - "outputs": [], - "source": [ - "output_list = []\n", - "\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - "\n", - " query = \"SELECT t_periods, tech, sum(emissions) as emissions FROM Output_Emissions \\\n", - " WHERE emissions_comm='co2' AND emissions <-0.001 GROUP BY t_periods, tech\"\n", - " df_read_emissions_cdr = pd.read_sql_query(query, conn)\n", - " df_read_emissions_cdr['emissions']/=1000\n", - " df_read_emissions_cdr = df_read_emissions_cdr.pivot_table(index='tech', columns='t_periods', values = 'emissions')\n", - " output_list.append(df_read_emissions_cdr)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "93819ec2", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(figsize=(10,6))\n", - "i=0\n", - "plt.xlim([2020, 2050])\n", - "for db in output_list:\n", - " \n", - " try:\n", - " scen_name = map_scenario_names[list_of_DBs[i].replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.plot(-db.loc['CO2_Capture_ground'], label = scen_name)\n", - " i+=1\n", - " \n", - "plt.legend()\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "ax.tick_params(axis = 'x', labelrotation=90)\n", - "plt.ylim([0, 1400])\n", - "plt.ylabel('CO$_2$ DAC to sequestration (million tonnes)')\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_DAC_ground.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "446e9891", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(figsize=(10,6))\n", - "i=0\n", - "plt.xlim([2020, 2050])\n", - "for db in output_list:\n", - " try:\n", - " scen_name = map_scenario_names[list_of_DBs[i].replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.plot(-db.loc['BECCS_H2_N'] - db.loc['E_BECCS_N'], label = scen_name)\n", - " i+=1\n", - " \n", - "plt.ylim([0, 1400])\n", - "plt.legend()\n", - "\n", - "handles, labels = ax.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='upper left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "ax.tick_params(axis = 'x', labelrotation=90)\n", - "\n", - "plt.ylabel('CO$_2$ BECCS (million tonnes)')\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_highC_BECCS.jpg')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3ecce831", - "metadata": {}, - "outputs": [], - "source": [ - "#compare hydrogen use across sectors\n", - "def return_h2_shares(conn):\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_out) as Transport FROM Output_VFlow_Out WHERE sector = 'transport' AND \\\n", - " (input_comm = 'H2' OR input_comm = 'LH2' OR input_comm='T_Liquids' OR input_comm='SNG_100' OR input_comm = 'MEOH') GROUP BY t_periods, input_comm\"\n", - " df_h2_transport = pd.read_sql_query(query, conn)\n", - "\n", - " query = \"SELECT t_periods,input_comm, sum(vflow_out) as Industrial FROM Output_VFlow_Out WHERE sector = 'industrial' AND \\\n", - " (input_comm = 'H2_100' OR input_comm='SNG_100') GROUP BY t_periods, input_comm\"\n", - " df_h2_industrial = pd.read_sql_query(query, conn)\n", - "\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_out) as Residential FROM Output_VFlow_Out WHERE sector = 'residential' AND \\\n", - " (input_comm = 'SNG_100') GROUP BY t_periods, input_comm\"\n", - " df_h2_residential = pd.read_sql_query(query, conn)\n", - "\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_out) as Commercial FROM Output_VFlow_Out WHERE sector = 'commercial' AND \\\n", - " (input_comm = 'SNG_100') GROUP BY t_periods, input_comm\"\n", - " df_h2_commercial = pd.read_sql_query(query, conn)\n", - " \n", - " query = \"SELECT t_periods, sum(vflow_out) as Electric FROM Output_VFlow_Out WHERE sector LIKE 'electric%' AND \\\n", - " (input_comm = 'H2_100') GROUP BY t_periods\"\n", - " df_h2_electric = pd.read_sql_query(query, conn)\n", - "\n", - " #t_liquids\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_out) AS t_liquids FROM Output_VFlow_Out WHERE \\\n", - " (input_comm = 'H2_100' AND output_comm='T_Liquids' ) GROUP BY t_periods, input_comm\"\n", - " df_h2_t_liq_out = pd.read_sql_query(query, conn)\n", - "\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_in) AS h2_100 FROM Output_VFlow_In WHERE \\\n", - " (input_comm = 'H2_100' AND output_comm='T_Liquids' ) GROUP BY t_periods, input_comm\"\n", - " df_h2_t_liq_in = pd.read_sql_query(query, conn)\n", - "\n", - " df_h2_t_liq = pd.merge(df_h2_t_liq_in, df_h2_t_liq_out)\n", - " df_h2_t_liq['ratio'] = df_h2_t_liq['h2_100']/df_h2_t_liq['t_liquids']\n", - "\n", - " mask = df_h2_transport['input_comm']=='T_Liquids'\n", - " ratios = df_h2_transport.loc[mask].merge(df_h2_t_liq, on='t_periods', how='left')['ratio'].values\n", - " df_h2_transport.loc[mask, 'Transport'] *= ratios\n", - " \n", - " #MEOH\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_out) AS meoh FROM Output_VFlow_Out WHERE \\\n", - " (input_comm = 'H2_100' AND output_comm='MEOH' ) GROUP BY t_periods, input_comm\"\n", - " df_h2_meoh_out = pd.read_sql_query(query, conn)\n", - "\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_in) AS h2_100 FROM Output_VFlow_In WHERE \\\n", - " (input_comm = 'H2_100' AND output_comm='MEOH' ) GROUP BY t_periods, input_comm\"\n", - " df_h2_meoh_in = pd.read_sql_query(query, conn)\n", - "\n", - " df_h2_meoh = pd.merge(df_h2_meoh_in, df_h2_meoh_out)\n", - " df_h2_meoh['ratio'] = df_h2_meoh['h2_100']/df_h2_meoh['meoh']\n", - "\n", - " mask = df_h2_transport['input_comm']=='MEOH'\n", - " ratios = df_h2_transport.loc[mask].merge(df_h2_meoh, on='t_periods', how='left')['ratio'].values\n", - " df_h2_transport.loc[mask, 'Transport'] *= ratios\n", - "\n", - "\n", - " #SNG\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_out) AS sng_20 FROM Output_VFlow_Out WHERE \\\n", - " (input_comm = 'H2_100' AND output_comm='SNG_20' ) GROUP BY t_periods, input_comm\"\n", - " df_h2_sng_100_out = pd.read_sql_query(query, conn)\n", - "\n", - " query = \"SELECT t_periods, input_comm, sum(vflow_in) AS h2_100 FROM Output_VFlow_In WHERE \\\n", - " (input_comm = 'H2_100' AND output_comm='SNG_20' ) GROUP BY t_periods, input_comm\"\n", - " df_h2_sng_100_in = pd.read_sql_query(query, conn)\n", - "\n", - " df_h2_sng_100 = pd.merge(df_h2_sng_100_in, df_h2_sng_100_out)\n", - " df_h2_sng_100['ratio'] = df_h2_sng_100['h2_100']/df_h2_sng_100['sng_20']\n", - "\n", - " mask = df_h2_residential['input_comm']=='SNG_100'\n", - " ratios = df_h2_residential.loc[mask].merge(df_h2_sng_100, on='t_periods', how='left')['ratio'].values\n", - " df_h2_residential.loc[mask, 'Residential'] *= ratios\n", - "\n", - " mask = df_h2_commercial['input_comm']=='SNG_100'\n", - " ratios = df_h2_commercial.loc[mask].merge(df_h2_sng_100, on='t_periods', how='left')['ratio'].values\n", - " df_h2_commercial.loc[mask, 'Commercial'] *= ratios\n", - "\n", - " mask = df_h2_transport['input_comm']=='SNG_100'\n", - " ratios = df_h2_transport.loc[mask].merge(df_h2_sng_100, on='t_periods', how='left')['ratio'].values\n", - " df_h2_transport.loc[mask, 'Transport'] *= ratios\n", - " \n", - " mask = df_h2_industrial['input_comm']=='SNG_100'\n", - " ratios = df_h2_industrial.loc[mask].merge(df_h2_sng_100, on='t_periods', how='left')['ratio'].values\n", - " df_h2_industrial.loc[mask, 'Industrial'] *= ratios\n", - "\n", - " df_h2_transport = df_h2_transport.groupby('t_periods')['Transport'].sum().reset_index()\n", - " df_h2_residential = df_h2_residential.groupby('t_periods')['Residential'].sum().reset_index()\n", - " df_h2_commercial = df_h2_commercial.groupby('t_periods')['Commercial'].sum().reset_index()\n", - " df_h2_industrial = df_h2_industrial.groupby('t_periods')['Industrial'].sum().reset_index()\n", - "\n", - " df_h2 = pd.merge(df_h2_transport, df_h2_industrial, on = 't_periods', how='outer')\n", - " df_h2 = pd.merge(df_h2, df_h2_residential, on = 't_periods', how='outer')\n", - " df_h2 = pd.merge(df_h2, df_h2_commercial, on = 't_periods', how='outer')\n", - " df_h2 = pd.merge(df_h2, df_h2_electric, on = 't_periods', how='outer')\n", - "\n", - " df_h2 = df_h2.sort_values(by='t_periods')\n", - " df_h2.set_index('t_periods',inplace=True)\n", - " df_h2.fillna(0, inplace=True)\n", - " df_h2[df_h2<0]=0\n", - " return df_h2\n", - "\n", - "output_list = []\n", - "for conn, scenario in zip(list_of_conns, list_of_scenarios):\n", - " df_h2 = return_h2_shares(conn)\n", - " output_list.append(df_h2.transpose())\n", - "\n", - "fig, ax = plt.subplots(figsize=(8,6))\n", - "\n", - "func_stacked_plot(output_list, ax= ax, col_order = \\\n", - " ['Electric','Transport', 'Industrial','Residential','Commercial'], loc='inside_upper_left')\n", - "\n", - "num_dfs = len(output_list)\n", - "col_spacing = [x - (num_dfs / 1.8 - 0.5) for x in np.arange(num_dfs)]\n", - "i = 0\n", - "period_placement= 2035\n", - "for spacing in col_spacing:\n", - " db = list_of_DBs[i]\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('.sqlite','').split('_')[3]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " plt.annotate(scen_name, (period_placement+spacing, \\\n", - " output_list[i][period_placement].sum()+ output_list[0][period_placement].sum()*0.05), \\\n", - " rotation = 'vertical', \\\n", - " horizontalalignment='center', verticalalignment='bottom', fontsize=14, color='grey')\n", - " i+=1\n", - " \n", - "ax.grid(axis='x')\n", - "# plt.ylim([0, 5000])\n", - "ax.get_yaxis().set_major_formatter(\n", - "tick.FuncFormatter(lambda x, p: format(int(x), ',')))\n", - "plt.ylabel('Hydrogen consumption (PJ)')\n", - "plt.tight_layout()\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_hydrogen_consumption_highC.jpg')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "16b43746", - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "df_diff_all = pd.DataFrame()\n", - "for conn, scenario, db in zip(list_of_conns, list_of_scenarios, list_of_DBs):\n", - " if i!=0:\n", - " df_diff = output_list[i] - output_list[i-1]\n", - " else:\n", - " df_diff = output_list[i] - output_list[0]\n", - "\n", - " try:\n", - " scen_name = map_scenario_names[db.replace('sqlite','').split('_')[4]]\n", - " except:\n", - " scen_name = map_scenario_names['']\n", - " df_diff_all.loc[:, scen_name] = df_diff.loc[:,2050]\n", - " i+=1\n", - "\n", - "fig, ax = plt.subplots(figsize=(9,8))\n", - "df_diff_all.transpose().plot(kind='bar',stacked=True, ax = ax, color = df_diff_all.index.map(color_dict), legend=False)\n", - "# plt.ylim([-6, 4])\n", - "plt.ylabel('Difference in 2050 energy consumption (PJ) \\n relative to preceding scenario (to the left)')\n", - "plt.legend()\n", - "\n", - "handles, labels = subp.get_legend_handles_labels()\n", - "unique = [(h, l) for i, (h, l) in enumerate(zip(handles, labels))\n", - " if l not in labels[:i]]\n", - "plt.legend(*zip(*unique[::-1]), loc='lower left', frameon=False) # changing plt to ax gives you legends for each of the graphs\n", - "\n", - "plt.tight_layout()\n", - "\n", - "plt.savefig('carbon_tax_figures_updates/carbontax_compare_hydrogen_consumption_highC_diff.jpg')\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/loan_cost_primer.ipynb b/notebooks/loan_cost_primer.ipynb deleted file mode 100644 index 7182558eb..000000000 --- a/notebooks/loan_cost_primer.ipynb +++ /dev/null @@ -1,470 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "source": [ - "## Loan Costing Discussion\n", - "This example uses the cost formulae from the `temoa_rules.py` file in order to verify costs used for loan. The Temoa dox for the loan cost explain the original formula fairly well. The original pointer to the formula is from Kevin Hunter et. all paper in ~2013. See equation (15) on the included document and description. Of particular note, that document uses the loan lifetime in a few spots, and somewhere along the way the tech lifetime crept into the formula in the form of the `lifetime_process` variable, which seems like a mistake\n", - "\n" - ], - "metadata": { - "collapsed": false - }, - "id": "54ecbe852dc90f38" - }, - { - "cell_type": "markdown", - "source": [ - "### Key Questions:\n", - "1. What is `discount_rate`? It looks and smells like the loan rate. The dox are just wrong.\n", - "2. Is the inclusion of the process lifetime in the loan cost calculation a typo? Seems it should be loan life across the board\n", - "3. Is the approach to undiscounted correct?\n", - "4. It is unclear what the plan is for \"hurdle rate\" -- and if any support from me is needed on that\n", - "\n", - "### Ponderings...\n", - "- Note that in none of these approaches renders the \"full cost\" of a scenario visible because of the eclipsing effect of the end of the window in perfect foresight or the end of the shorter myopic windows.\n", - "- A corollary of that (that I stumbled into the other day during testing) is that it is impossible to compare costs when using different myopic view depths. More clearly: When I changed from a view depth of 1 to 2 (seeing further into the future), I expected cost to go down--as they should--but they went up, which is attributable to having more of the loan lives visible to the calculation. This may/may not be important to the team. It is just an observation.\n", - "- It wouldn't be too much work to enable a 3rd mode for these to calculate the \"full cost\" by making the windowing feature conditional on some flag." - ], - "metadata": { - "collapsed": false - }, - "id": "8bb6c026095b6680" - }, - { - "cell_type": "markdown", - "source": [ - "### Desired outcome:\n", - "- Concurrence or edits to formulae\n", - "- About 4 test values that I can incorporate into unit tests that exercise the discount rate, GDR, and eclipsing effect of the period end, and discounted/undiscounted test values (similar to what is used below)" - ], - "metadata": { - "collapsed": false - }, - "id": "fcf4ce5e6305e7ef" - }, - { - "cell_type": "markdown", - "source": [ - "### Formulae\n", - "The below 2 formulae are the current implementations in the model that I have extracted out of the cost computation so that they can be used both by the model and in post-processing--basically single-sourcing the formula. It was replicated in several areas before, which is an invitation for problems." - ], - "metadata": { - "collapsed": false - }, - "id": "e706138552927b3c" - }, - { - "cell_type": "markdown", - "source": [ - "### Notes: \n", - "#### \"Undiscounted\" cost\n", - "This was previously calculated in post-processing (not used at all in the model) was pretty wonky. I have disregarded it and I think the below is a better place to **start** the discussion. The intent of the fomula for loan_cost is to produce a undiscounted cost when GDR==0.\n", - "#### discount_rate\n", - "I have not renamed this, but this is central to the discussion. It is applied as and probably should be renamed as the `loan_rate`. TBD on how you want to handle \"hurdle rate.\"\n" - ], - "metadata": { - "collapsed": false - }, - "id": "2fe340463d16c00b" - }, - { - "cell_type": "code", - "source": [ - "from pyomo.environ import Var, Expression" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.165454Z", - "start_time": "2024-04-09T20:34:40.163675Z" - } - }, - "id": "6786fe13296c8574", - "execution_count": 14, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "def loan_annualization_rate(loan_rate: float | None, loan_life: int | float) -> float:\n", - " \"\"\"\n", - " This calculation is broken out specifically so that it can be used for param creation\n", - " and separately to calculate loan costs rather than rely on fully-built model parameters\n", - " :param loan_rate: The loan rate\n", - " :param loan_life: The term (years) of the loan\n", - "\n", - " \"\"\"\n", - " if not loan_rate:\n", - " # dev note: this should not be needed as the LoanRate param has a default (see the definition)\n", - " return 1.0 / loan_life\n", - " annualized_rate = loan_rate / (1.0 - (1.0 + loan_rate) ** (-loan_life))\n", - " return annualized_rate" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.178102Z", - "start_time": "2024-04-09T20:34:40.176150Z" - } - }, - "id": "6fc96307ef6013fb", - "execution_count": 15, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "def loan_cost(\n", - " capacity: float | Var,\n", - " invest_cost: float,\n", - " loan_annualize: float,\n", - " lifetime_loan_process: float | int,\n", - " P_0: int,\n", - " P_e: int,\n", - " GDR: float,\n", - " vintage: int,\n", - ") -> float | Expression:\n", - " \"\"\"\n", - " function to calculate the loan cost. It can be used with fixed values to produce a hard number or\n", - " pyomo variables/params to make a pyomo Expression\n", - " :param capacity: The capacity to use to calculate cost\n", - " :param invest_cost: the cost/capacity\n", - " :param loan_annualize: parameter\n", - " :param lifetime_loan_process: lifetime of the loan\n", - " :param P_0: the year to discount the costs back to\n", - " :param P_e: the 'end year' or cutoff year for loan payments\n", - " :param GDR: Global Discount Rate\n", - " :param vintage: the base year of the loan\n", - " :return: fixed number or pyomo expression based on input types\n", - " \"\"\"\n", - " if GDR == 0: # return the non-discounted result\n", - " regular_payment = capacity * invest_cost * loan_annualize\n", - " payments_made = min(lifetime_loan_process, P_e - vintage)\n", - " return regular_payment * payments_made\n", - " x = 1 + GDR # a convenience\n", - " res = (\n", - " capacity\n", - " * (\n", - " invest_cost\n", - " * loan_annualize\n", - " * (\n", - " lifetime_loan_process\n", - " if not GDR\n", - " else (x ** (P_0 - vintage + 1) * (1 - x ** (-lifetime_loan_process)) / GDR)\n", - " )\n", - " )\n", - " * (\n", - " (1 - x ** (-min(lifetime_loan_process, P_e - vintage)))\n", - " / (1 - x ** (-lifetime_loan_process))\n", - " )\n", - " )\n", - " return res" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.218556Z", - "start_time": "2024-04-09T20:34:40.193819Z" - } - }, - "id": "44ece17e11ca078a", - "execution_count": 16, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "### Exemplar tech data\n", - "Consider an anonymous `tech` in a myopic run with periods @ 5 year increments [2020, 2050] and a myopic view of 1 period at a time.\n", - "In the myopic window 2030 -> 2035 a decision is made to build 100K capacity units:\n" - ], - "metadata": { - "collapsed": false - }, - "id": "bf57ce2583a5b515" - }, - { - "cell_type": "code", - "source": [ - "capacity = 100_000 # units\n", - "cost_invest = 1 # $/unit of capacity\n", - "loan_life = 40\n", - "loan_rate = 0.08\n", - "GDR = 0.05\n", - "tech_lifetime = 50\n", - "base_year = 2020 # the \"myopic base year\" to which all prices are discounted\n", - "vintage = 2030 # the vintage of the new 'tech'\n", - "window_end = 2035 # last year in the myopic view" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.221183Z", - "start_time": "2024-04-09T20:34:40.219509Z" - } - }, - "id": "ce1bcf1453d44d6", - "execution_count": 17, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "#### We need LoanAnnualize...\n", - "LoanAnnualize is a model parameter that is computed within the model using the discount rate specific to that process. I have also \"extracted the math\" to a separate function that can is also dual-use (making the parameter, or externally producing a hard number)\n" - ], - "metadata": { - "collapsed": false - }, - "id": "58218ee0e550b94a" - }, - { - "cell_type": "code", - "source": "loan_annualize = loan_annualization_rate(loan_rate=loan_rate, loan_life=loan_life)", - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.223325Z", - "start_time": "2024-04-09T20:34:40.221840Z" - } - }, - "id": "a790bdc43d4fb128", - "execution_count": 18, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "print(f\"Loan annualization rate: {loan_annualize:0.4f}\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.225797Z", - "start_time": "2024-04-09T20:34:40.224302Z" - } - }, - "id": "ee945bc2df5c8f70", - "execution_count": 19, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "cost = loan_cost(\n", - " capacity=capacity,\n", - " invest_cost=cost_invest,\n", - " loan_annualize=loan_annualize,\n", - " lifetime_loan_process=loan_life,\n", - " P_0=base_year,\n", - " P_e=window_end,\n", - " GDR=GDR,\n", - " vintage=vintage\n", - ")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.227870Z", - "start_time": "2024-04-09T20:34:40.226388Z" - } - }, - "id": "675f7cf04200c8f8", - "execution_count": 20, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "print(f\"Loan cost: ${cost:,.2f}\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.229793Z", - "start_time": "2024-04-09T20:34:40.228285Z" - } - }, - "id": "3d6354622519237e", - "execution_count": 21, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "#### And the Undiscounted ..." - ], - "metadata": { - "collapsed": false - }, - "id": "57e9ba26dccbcef7" - }, - { - "cell_type": "code", - "source": [ - "undiscounted_cost = loan_cost(\n", - " capacity=capacity,\n", - " invest_cost=cost_invest,\n", - " loan_annualize=loan_annualize,\n", - " lifetime_loan_process=loan_life,\n", - " P_0=base_year,\n", - " P_e=window_end,\n", - " GDR=0, # <-- override with a zero\n", - " vintage=vintage\n", - ")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.232090Z", - "start_time": "2024-04-09T20:34:40.230405Z" - } - }, - "id": "82b8f08bc0780562", - "execution_count": 22, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "print(f\"Undiscounted Loan cost: ${undiscounted_cost:,.2f}\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.234124Z", - "start_time": "2024-04-09T20:34:40.232706Z" - } - }, - "id": "e99fb0c033558324", - "execution_count": 23, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "### Are these correct?\n", - "The end of the window is eclipsing most future payments.\n", - "If the problem were \"perfect foresight\" then..." - ], - "metadata": { - "collapsed": false - }, - "id": "969780f884c44684" - }, - { - "cell_type": "code", - "source": [ - "window_end = 2050 # overwrite the value of the window end\n", - "\n", - "cost = loan_cost(\n", - " capacity=capacity,\n", - " invest_cost=cost_invest,\n", - " loan_annualize=loan_annualize,\n", - " lifetime_loan_process=loan_life,\n", - " P_0=base_year,\n", - " P_e=window_end,\n", - " GDR=GDR,\n", - " vintage=vintage\n", - ")\n", - "print(f\"Loan cost: ${cost:,.2f}\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.236367Z", - "start_time": "2024-04-09T20:34:40.234543Z" - } - }, - "id": "63eaa2ed2074c123", - "execution_count": 24, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "### Or if the end of the window exposed the entire loan life...\n" - ], - "metadata": { - "collapsed": false - }, - "id": "b1ebba20323f39c4" - }, - { - "cell_type": "code", - "source": [ - "window_end = 2100 # override to expose full loan length\n", - "\n", - "cost = loan_cost(\n", - " capacity=capacity,\n", - " invest_cost=cost_invest,\n", - " loan_annualize=loan_annualize,\n", - " lifetime_loan_process=loan_life,\n", - " P_0=base_year,\n", - " P_e=window_end,\n", - " GDR=GDR,\n", - " vintage=vintage\n", - ")\n", - "print(f\"Loan cost: ${cost:,.2f}\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.239344Z", - "start_time": "2024-04-09T20:34:40.237873Z" - } - }, - "id": "1cee546365df4909", - "execution_count": 25, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "undiscounted_cost = loan_cost(\n", - " capacity=capacity,\n", - " invest_cost=cost_invest,\n", - " loan_annualize=loan_annualize,\n", - " lifetime_loan_process=loan_life,\n", - " P_0=base_year,\n", - " P_e=window_end,\n", - " GDR=0, # <-- set to zero\n", - " vintage=vintage\n", - ")\n", - "print(f\"Undiscounted Loan cost: ${undiscounted_cost:,.2f}\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-09T20:34:40.241538Z", - "start_time": "2024-04-09T20:34:40.239871Z" - } - }, - "id": "7a13599b5a10281", - "execution_count": 26, - "outputs": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/temoa_utopia_analysis_with_pyam.ipynb b/notebooks/temoa_utopia_analysis_with_pyam.ipynb deleted file mode 100644 index c8dd8af56..000000000 --- a/notebooks/temoa_utopia_analysis_with_pyam.ipynb +++ /dev/null @@ -1,439 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Analysis of Temoa Utopia results using pyam\n", - "\n", - "The file `temoa_utopia_pyam.xlsx` used in this notebook is created by running the \"Utopia\" model\n", - "\n", - " $ python temoa_model/ --config=temoa_model/config_sample\n", - "\n", - "Running **Temoa** exports the results (among other formats) in an Excel format compatible\n", - "with the IAMC timeseries template\n", - "used by the IPCC WG3, the Energy Modeling Forum, and numerous EU Horizon 2020 projects\n", - "([read more](https://pyam-iamc.readthedocs.io/en/stable/data.html)).\n", - "\n", - "The **pyam** package provides a suite of tools for analysis and visualization\n", - "of energy systems scenarios based on that format - [read the docs](https://pyam-iamc.readthedocs.io)!" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "IPython.OutputArea.prototype._should_scroll = function(lines) { return false; }" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import pyam" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "pyam - INFO: Running in a notebook, setting up a basic logging config at level INFO\n", - "pyam.core - INFO: Reading file temoa_utopia_test_run_model/test_run_pyam.xlsx\n", - "pyam.core - INFO: Reading meta indicators\n" - ] - } - ], - "source": [ - "df = pyam.IamDataFrame('temoa_utopia_test_run_model/test_run_pyam.xlsx')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualization of CO2 emissions by species and sectors\n", - "\n", - "The first plot shows the aggregate CO2 and NOx emissions over the model horizon." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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lBRq1KhBbGws5zbzVIHCGENqaiopKSm0bNnS6XCUUh4E7eWgzMxM6tWrpwkggIkI9erV07M1pQJY0CYBQBNAENDPSKnAFtRJQCmlQtXB45lM/HoTx0/n+HQ+QXtPQCmlQtGR9CwmL9rBpyt24zKGS1vXY0D7hj6bn54JlENERARdunQ583j55ZdL/N79+/dz8803l3qeI0eOZNOmTaV+X1HK2pLj888/p1OnTnTq1IlLL72UpKQkr8alVDg5fiqH1+Zups+rC/lw2U6u79yEBY/29WkCAD0TKJcqVaqQmJhYpvc2adKEL774otTve//998s0P19o2bIlixcvpk6dOnz33XeMGjWKX375xemwlAoqJzNz+GjZLt77KZn0rFyu69SEsQPa0rp+db/MPySSwPNfb2TT/hNenWb7JjWZcF2HMr03NjaWO+64g4ULF5KTk8OUKVMYN24c27dv5/HHH2f06NHs2rWLIUOGsGHDBjZu3Mi9995LdnY2LpeLL7/8kiZNmnDLLbeQkpJCXl4ezzzzDLfeeit9+/bl9ddfJz4+noSEBP7xj39gjOHaa6/llVdeAaB69eqMHTuWb775hipVqjB79mwaNmzIjBkzeP7554mIiKBWrVosWbLkvNi///57nn76afLy8oiOjubHH3/k6NGjjBgxguTkZKpWrcqUKVPOHP3n69mzJykpfvvVQqWC3unsPD5ZvovJi3dw7FQOg9o35JFBcVzYqKZf4wiJJOCU06dP06VLlzN/jxs3jltvvRWA5s2bs3z5ch5++GGGDx/OsmXLyMzMpEOHDowePfqc6UyePJmxY8cybNgwsrOzycvL43//+x9NmjTh22+/BeD48ePnvGf//v08+eSTrF69mjp16jBo0CBmzZrFjTfeSEZGBj179uTFF1/kiSee4L333mP8+PFMnDiRuXPn0rRpU9LS0s5bnsOHD3PfffexZMkSWrZsydGjRwGYMGECXbt2ZdasWSxYsIC77777vDOgDz74gKuvLulP/yoVvrJy85j6617eXridwyezuCKuPo8OiqNTs9qOxBMSSaCsR+zlVdTloOuvvx6Ajh07kp6eTo0aNahRowZRUVHn7YB79erFiy++SEpKCkOHDqVt27Z07NiRxx57jCeffJIhQ4bQu/e5v2e+cuVK+vbtS/36VpfYYcOGsWTJEm688UYqVarE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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "df.filter(variable='Emissions|*', level=0).plot()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The next plot shows the CO2 emissions by sector." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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bNjOiuZLTk5WTk5OcX5+fsPy07Kysg49+uijhc8++2xbSdq4cWNyp06dvh6gtH///vvLLxOvT58+B7Kzsw/WpL7VoSUDQJ1Lm/jXWimnYMoVtVIOat/BgweTunbtmhV7fu+992644447dkhSp06dDi1ZsmTV7bff3mnEiBFpCxcuXLV///6kHj16dJ8wYcIRg1hNnTo1dcyYMZtGjx69/cCBA1ZaWqrXX3/9jHbt2pXMmzdvjSRt27atQfwyBQUFyZMnT+6wePHilampqaXf/va3M2bMmHHmTTfdtHP//v1JAwcOLJ46der6UaNGdZw6dWrqo48+umHKlClnvf3226u7dOlSsnXr1iPKk6SioqLI2LFj0+bNm7eqa9euhzZt2tRAkiZMmNA+Ozt739/+9rfPZ8+e3fyWW27pUr4FZ+rUqW0uuuiiXTXZb4MHD94zatSozqWlpXrttddaTZ8+fd0TTzxxVvn59uzZk/Tee++1mDRp0vqKyvnWt761b+3atY0k6Uc/+tHmW2+99RvPPPPMvkGDBu0ePXr0trS0tJKa1OdE0ZIBAKh1sdMlsUcsYEjStddeu1OSevbsua9Pnz57W7ZsWda+ffvShg0blpX/gh84cODexx577KwHH3ywXX5+fkqzZs28T58++//xj3+0GD16dIe5c+c2a9269eH4ZebPn990wIABe9q3b1+anJys6667bvu7777bTJKSk5P9+uuv3yVJffv23btu3boUSerXr1/xDTfckPbYY4+1KS0tPWp75s2b1zQnJ2dP165dD0lS27ZtD0vSRx991Pz222/fJklXXnnlnp07d0biQ8+cOXOaz5w5s81TTz31VU32WyQS8ZycnOIXXnih1YEDB5IyMzMPxU8vLCxs2LVr16ycnJyugwcP3nXttdfurqgc939eXHT11VfvXrNmzbLbbrtta15eXuO+fftmFRUV1UkjAyEDAFCnGjVq5JKUlJSklJSUr78Nk5KSVFJSYvHzjho1avubb765pnHjxmVDhgzJmD17dvNevXod/OSTT3J79uy5/8EHH+xw3333HfFLP/4LtrxIJOKxTpWRSESlpaUmSX/605++/OUvf1lUWFiY0rt37+4bN248Iuy4u8zsqPIqWpeZuRT0fRgzZkznWbNmrWnXrt3ho2asxA033LB94sSJZw8fPnxH+WmxPhkrV67M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zUeMmBobu2TVqlUrVq9e3XjmzJlnVlfmo48+mvr3v/+9xeLFi1fm5+evWLBgQV7sRpzjx4/vUFxcnLR27drl69atW37llVfu/O53v3tuWVmZJGnEiBFbZ8+efVx3+5TqIGSYWQMz+9TM3gp7XQCAxNahQ4eeY8eO7dC7d++uPXr06DZ//vwm559/fnqnTp16PProo6mSlJeXl5Kent5dkhYtWtSoZ8+e3bp27ZqVkZGRtWzZsoa7d+9OGjRo0LmZmZlZ6enp3WPDyOfk5GS+9957TSTpueeea5WRkZGVnp7effTo0R1i62/SpMl548aN65CZmZmVnZ3dtbCwMCJJ06dPb5ment49MzMzq1+/fpkV1f31119vkZWV1S0zMzNr4MCBGVIwhsmll156TkZGRlZ2dnbXhQsXNpakyy67bG9qauphKRj3ZOPGjSkVlVne8OHDt7/88sutJGnmzJlnDhs2bGdF8yUnJysnJ6c4Pz+/YXVlPvHEE+2ee+65L1u1alUmSa1btz48bty4bXv27El69dVX2zz77LOFkUhwYuOuu+7alpKSUjZnzpzmkjRkyJDi1NTUo4elraG6aMm4S1KN7tcOADg1xMYuiT1iQUCSOnXqdGjJkiWr+vfvXzxixIi0OXPmfL5w4cJVU6ZMaV++nKlTp6aOGTNm06pVq3KXLl26skuXLofeeOONFu3atSvJy8vLzc/PXzF8+PAjhjsvKChInjx5cod58+atzs3NXfHpp582nTFjxpmStH///qSBAwcW5+Xl5Q4cOLB46tSpqZI0ZcqUs95+++3VeXl5uXPnzl1Tvh5FRUWRsWPHpr3xxhuf5+Xl5c6aNetzSZowYUL77OzsfatXr879xS9+sf6WW27pUsE2tLnooot21WS/DR48eM+HH37YLDaw280331zhIGl79uxJeu+991r06tVrv/TPIeBjjz/+8Y+pUjAg2t69ext07979qEHacnNzG5511lmHYuEjpnfv3vuWLVvWuCb1rU6oIcPMOkq6QtILYa4HAJBYyp8uueOOO74etvzaa6/dKUk9e/bc16dPn70tW7Ysa9++fWnDhg3Ltm7desQQ6wMHDtz72GOPnfXggw+2y8/PT2nWrJn36dNn/z/+8Y8Wo0eP7jB37txm5e+eOX/+/KYDBgzY0759+9Lk5GRdd9112999991mkpScnOzXX3/9Lknq27fv3nXr1qVIUr9+/YpvuOGGtMcee6xNaenRP9znzZvXNCcnZ0/Xrl0PScHQ6ZL00UcfNb/99tu3SdKVV165Z+fOnZFt27Z9vQ1z5sxpPnPmzDZPPfXUVzXZb5FIxHNycopfeOGFVgcOHEgqP75JLEzk5OR0HTx48K5rr712t3TEaZTcVatW5d58881bpMqHqJeksrKyr4elj1fVMscq7JaMJyVNkFRWzXwAgNNEo0aNXApGG01JSfn6Sy4pKUklJSVHfLuNGjVq+5tvvrmmcePGZUOGDMmYPXt28169eh385JNPcnv27Ln/wQcf7HDfffedFb9MVQN/RiIRj3WqjEQiKi0tNUn605/+9OUvf/nLosLCwpTevXt337hx4xFhp7Iv3orWFfviXrhwYeMxY8Z0njVr1pp27drV+DbiN9xww/aJEyeePXz48B3lp8XCxMqVK3Mff/zxourKatWqVVnjxo3LcnNzjzpd071794NFRUUNd+zYcUQWWLp0aZMePXrsLz//8QgtZJjZUEmb3b3KXshmdqeZLTKzRVu2bAmrOgCAk1Bubm5Kt27dDj700EObBw8evHPJkiWNCwoKkps3b142ZsyY7XffffemJUuWNIlf5oILLti7cOHC5hs2bIjETjsMGjSouKr1rFixouHFF1+898knnyxq2bJl6RdffHHEl/JFF120d+HChc1XrVqVIgV9MSRpwIABe1588cXWkvTWW281b9myZWmrVq3K8vPzU6655ppzpk+fvrZXr15HnaqoyuWXX148fvz4DSNGjKjwVMmxuvvuuzeMGjWq8/bt25Mkafv27Um/+c1v2rRo0aLse9/73tbRo0d3irXe/Pa3v2194MCBpGHDhu2pjXWHeQnrv0q60sz+XVIjSS3MbKa73xg/k7s/L+l5KRjqPcT6AMBpqSaXnNa2WJ+M2POLL75417Rp09YfazkzZsxo9dprr7WORCKemppa8sgjjxTNnz+/6QMPPNAxKSlJkUjEp02bti5+mc6dO5dMmjRp/YUXXpjh7nbJJZfsuvHGG3dWtZ577rmnY0FBQUN3t/PPP3/3gAEDjvgl3759+9Knn3664Kqrrjq3rKxMrVu3LlmwYEH+r3/966If/OAHaRkZGVmNGzcue+mll9ZK0kMPPXTWzp07I+PGjessBS0oy5cvr1H/xKSkJMUu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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "df.filter(variable='Emissions|co2|*').plot.bar()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or if you prefer stacked bar charts..." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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Mh9bQIiIiMh0TECIiIjIdK1iILKOHTtqZGSk1WEQEbUqX3/99UlVDb32kkTmaxUJSGRkJLKzs60Og4ioVRGR2kPKE7UYvARDREREpmMCQkRERKZjAkJERESmYwJCREREpmMCQkRERKZjAkJERESmYwJCREREpmMCQkRERKZrFQOREdHNIe7PcVaH4FW7J+22OgSiFoMtIERERGQ6JiBERERkOiYgREREZDomIERERGQ6JiBERERkOq8lICKyUkROiMieWtPTRcQhIntF5AVv1U9EREQtlzdbQN4FcK/nBBEZBOA+APGq2gvAi16sn4iIiFooryUgqroVQGmtyTMALFXVC65lTnirfiIiImq5zO4DEg3g30UkS0S2iMiP6ltQRB4VkWwRyS4pKTExRCIiIvI2sxMQO4BgACkAfg3gQxGRuhZU1bdVNUlVk0JDQ82MkYiIiLzM7ATkOwCr1fAlACeAjibHQERERBYzOwFZA2AwAIhINAA/ACdNjoGIiIgs5rWH0YnIXwEMBNBRRL4D8DsAKwGsdN2aexHAJFVVb8VARERELZPXEhBVfbCeWRO8VScRERG1DhwJlYiIiEzHBISIiIhMxwSEiIiITMcEhIiIiEzHBISIiIhMxwSEiIiITMcEhIiIiEzHBISIiIhMxwSEiIiITMcEhIiIiEzHBISIiIhMxwSEiIiITMcEhIiIiEzHBISIiIhMxwSEiIiITMcEhIiIiEzntQRERFaKyAkR2VPHvCdFREWko7fqJyIiopbLmy0g7wK4t/ZEEYkAcA+AI16sm4iIiFowryUgqroVQGkds14BMBeAeqtuIiIiatlM7QMiIqMAHFXVnY1Y9lERyRaR7JKSEhOiIyIiIrOYloCISFsACwAsaszyqvq2qiapalJoaKh3gyMiIiJTmdkCcgeAbgB2ikgBgC4AvhGRzibGQERERC2A3ayKVHU3gB+437uSkCRVPWlWDERERNQyePM23L8C+AJAjIh8JyKPeKsuIiIial281gKiqg9eY36kt+omIiKilo0joRIREZHpmIAQERGR6ZiAEBERkemYgBAREZHpmIAQERGR6ZiAEBERkemYgBAREZHpmIAQERGR6ZiAEBERkemYgBAREZHpmIAQERGR6ZiAEBERkemYgBAREZHpmIAQERGR6ZiAEBERkem8loCIyEoROSEiezym/W8R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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "df.filter(variable='Emissions|co2|*').plot.bar(stacked=True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Further analysis of emissions\n", - "\n", - "So you want to \"look\" at the actual values of some timeseries?\n", - "\n", - "Here, we first select aggregate CO2 and the emissions by each technology starting with 'IMP'\n", - "(because the other technologies are zero anyway, as we have seen from the plots above)." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
199020002010
modelscenarioregionvariableunit
Temoatest_runutopiaEmissions|co2?5.69633712.15880016.747008
Emissions|co2|IMPDSL1?2.8948052.4549225.453948
Emissions|co2|IMPGSL1?1.4935071.9610392.250000
Emissions|co2|IMPHCO1?1.3080257.7428399.043060
\n", - "
" - ], - "text/plain": [ - " 1990 2000 \\\n", - "model scenario region variable unit \n", - "Temoa test_run utopia Emissions|co2 ? 5.696337 12.158800 \n", - " Emissions|co2|IMPDSL1 ? 2.894805 2.454922 \n", - " Emissions|co2|IMPGSL1 ? 1.493507 1.961039 \n", - " Emissions|co2|IMPHCO1 ? 1.308025 7.742839 \n", - "\n", - " 2010 \n", - "model scenario region variable unit \n", - "Temoa test_run utopia Emissions|co2 ? 16.747008 \n", - " Emissions|co2|IMPDSL1 ? 5.453948 \n", - " Emissions|co2|IMPGSL1 ? 2.250000 \n", - " Emissions|co2|IMPHCO1 ? 9.043060 " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "co2 = df.filter(variable=['Emissions|co2', 'Emissions|co2|IMP*']).timeseries()\n", - "co2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The next cell uses the [pyam.cumulative](https://pyam-iamc.readthedocs.io/en/stable/api/timeseries.html#pyam.cumulative) function\n", - "to compute the emissions on the timeseries data selected above." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "model scenario region variable unit\n", - "Temoa test_run utopia Emissions|co2 ? 245.026399\n", - " Emissions|co2|IMPDSL1 ? 70.467368\n", - " Emissions|co2|IMPGSL1 ? 40.199675\n", - " Emissions|co2|IMPHCO1 ? 134.359356\n", - "dtype: float64" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "co2.apply(pyam.cumulative, first_year=1990, last_year=2010, axis=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And just to prove the usefulness of this function:\n", - "it also works if you select a `first_year` argument that is not a native model year!" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "model scenario region variable unit\n", - "Temoa test_run utopia Emissions|co2 ? 210.082252\n", - " Emissions|co2|IMPDSL1 ? 56.433225\n", - " Emissions|co2|IMPGSL1 ? 32.264610\n", - " Emissions|co2|IMPHCO1 ? 121.384416\n", - "dtype: float64" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "co2.apply(pyam.cumulative, first_year=1995, last_year=2010, axis=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exporting to different file formats\n", - "\n", - "You can use pyam to export your data in different file formats,\n", - "for example as a [frictionless data package](https://frictionlessdata.io)!" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.to_datapackage('utopia.zip')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "\n", - "## Questions?\n", - "\n", - "Take a look at [this tutorial](https://pyam-iamc.readthedocs.io/en/stable/tutorials/pyam_first_steps.html) for *first steps with pyam* - then join our [mailing list](https://groups.io/g/pyam)!\n", - "\n", - "
" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/pyproject.toml b/pyproject.toml index 19e473efb..0e4b35570 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,203 @@ -[tool.ruff] +[project] +name = "temoa" +dynamic = ["version"] +description = "Tools for Energy Model Optimization and Analysis" +readme = "README.md" +requires-python = ">=3.12" +license = "MIT" +authors = [ + { name = "TemoaProject Team", email = "info@temoaproject.org" } +] + +dependencies = [ + "pyomo>=6.8.0", + "matplotlib>=3.9.2", + "pandas>=2.2.2", + "numpy>=2.1.0", + "joblib", + "salib>=1.5.1", + "graphviz>=0.20.3", + "seaborn>=0.13.2", + "tabulate>=0.9.0", + "xlsxwriter>=3.2.0", + "pytest>=8.3.2", + "deprecated>=1.2.14", + "openpyxl>=3.1.5", + "networkx>=3.3", + "highspy>=1.7.2", + "scipy>=1.14.1", + "typer>=0.20.0", + "rich>=14.2.0", + "tomlkit>=0.12.0", + "pint>=0.25.2", + "mpi-sppy>=0.12.1", +] + + +[project.scripts] +temoa = "temoa.cli:app" + +[dependency-groups] +dev = [ + "types-deprecated>=1.2.15.20250304", + "ruff>=0.2.0", + "mypy>=1.0.0", + "pre-commit", + "pytest", + "pytest-cov", + "pandas-stubs>=2.3.2.250926", + "types-networkx>=3.5.0.20251001", +] + +[project.optional-dependencies] +docs = [ + "sphinx>=7.4.7", + "sphinx-book-theme>=1.1.3", + "sphinxcontrib-htmlhelp>=2.1.0", + "sphinxcontrib-serializinghtml>=2.0.0", + "sphinxcontrib-bibtex>=2.6.2", + "myst-parser>=2.0.0", + "sphinxcontrib-mermaid>=1.2.3", +] + +plotting = [ + "matplotlib>=3.9.2", + "seaborn>=0.13.2", +] +solver = [ + "gurobipy>=12.0.3", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +include = [ + "temoa", +] +package-data = { "temoa" = ["db_schema/*.sql", "tutorial_assets/*", "py.typed"] } + + +[tool.hatch.version] +path = "temoa/__about__.py" + +[tool.ruff] line-length = 100 indent-width = 4 +exclude = ["stubs", + "temoa/extensions/" +] [tool.ruff.format] -# Single quote strings quote-style = "single" +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "N", # flake8-naming + "G004", # logging-f-string + "TC", # flake8-type-checking + "UP", # pyupgrade + "C4", # flake8-comprehensions + "Q", # flake8-quotes +] + +ignore = [ + "Q000", # the formatter deals with this + "Q003" # the formatter deals with this +] + +[tool.ruff.lint.pep8-naming] +ignore-names = ["MGA", "SVMGA"] # Allow these names to violate naming conventions + +[tool.ruff.lint.per-file-ignores] +"temoa/extensions/stochastics/legacy_files/**/*.py" = ["E501"] + + +[tool.pytest.ini_options] +testpaths = [ + "tests", +] +filterwarnings = [ + # Treat warnings from temoa and tests as errors + "error::DeprecationWarning:temoa.*", + "error::FutureWarning:temoa.*", + "error::RuntimeWarning:temoa.*", + "error::UserWarning:temoa.*", + "error::DeprecationWarning:tests.*", + "error::FutureWarning:tests.*", + "error::RuntimeWarning:tests.*", + "error::UserWarning:tests.*", + # Ignore SyntaxWarning from mpi-sppy (third-party package). + # These are caused by invalid escape sequences in regex strings within sputils.py. + # Mitigation should be removed once mpi-sppy is upgraded to a version where these are fixed (0.13.1+). + "ignore:invalid escape sequence:SyntaxWarning:mpisppy.utils.sputils", +] + +[tool.mypy] +# Python version targeting +python_version = "3.12" + +# Tell mypy to also look for stubs in this directory +mypy_path = "stubs" + +# Exclude specific directories from type checking will try to add them back gradually +exclude = "(?x)(^stubs/)" + +# Strict typing for our own code +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +strict_equality = true + +# Handle external dependencies gracefully +ignore_missing_imports = false + +# Be explicit about Any usage +disallow_any_generics = true +disallow_any_unimported = true +disallow_any_expr = false # temporarily false to ease transition +disallow_any_decorated = true +disallow_any_explicit = false # temporarily false to ease transition + + +# Enable strict checking for core modules +[[tool.mypy.overrides]] +module = [ + "temoa.core.*", + "temoa.components.*", + "temoa._internal.*", + "temoa.data_io.*", + "temoa.model_checking.*", +] +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true + +# Less strict for type definitions (allow Any for stubs) +[[tool.mypy.overrides]] +module = "temoa.types.*" +disallow_any_explicit = false + +# More lenient settings for test files to accommodate pytest decorators and test-specific patterns +[[tool.mypy.overrides]] +module = "tests.*" +disallow_any_decorated = false + +[project.urls] +Homepage = "https://temoaproject.org" +Documentation = "https://temoaproject.github.io/temoa" +Repository = "https://github.com/TemoaProject/temoa" diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 000000000..8a0497d63 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,232 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml --all-extras -o requirements-dev.txt +accessible-pygments==0.0.5 + # via pydata-sphinx-theme +alabaster==1.0.0 + # via sphinx +babel==2.17.0 + # via + # pydata-sphinx-theme + # sphinx +beautifulsoup4==4.14.3 + # via pydata-sphinx-theme +certifi==2025.7.14 + # via requests +charset-normalizer==3.4.2 + # via requests +click==8.3.0 + # via typer +contourpy==1.3.2 + # via matplotlib +cycler==0.12.1 + # via matplotlib +deprecated==1.2.18 + # via temoa (pyproject.toml) +dill==0.4.0 + # via multiprocess +docutils==0.21.2 + # via + # myst-parser + # pybtex-docutils + # pydata-sphinx-theme + # sphinx + # sphinxcontrib-bibtex +et-xmlfile==2.0.0 + # via openpyxl +flexcache==0.3 + # via pint +flexparser==0.4 + # via pint +fonttools==4.59.0 + # via matplotlib +graphviz==0.21 + # via temoa (pyproject.toml) +gurobipy==13.0.1 + # via temoa (pyproject.toml) +highspy==1.11.0 + # via temoa (pyproject.toml) +idna==3.10 + # via requests +imagesize==1.4.1 + # via sphinx +iniconfig==2.1.0 + # via pytest +jinja2==3.1.6 + # via + # myst-parser + # sphinx +joblib==1.5.1 + # via temoa (pyproject.toml) +kiwisolver==1.4.8 + # via matplotlib +latexcodec==3.0.1 + # via pybtex +markdown-it-py==3.0.0 + # via + # mdit-py-plugins + # myst-parser + # rich +markupsafe==3.0.2 + # via jinja2 +matplotlib==3.9.2 + # via + # temoa (pyproject.toml) + # salib + # seaborn +mdit-py-plugins==0.5.0 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +mpi-sppy==0.12.1 + # via temoa (pyproject.toml) +multiprocess==0.70.18 + # via salib +myst-parser==4.0.1 + # via temoa (pyproject.toml) +networkx==3.5 + # via temoa (pyproject.toml) +numpy==2.3.1 + # via + # temoa (pyproject.toml) + # contourpy + # highspy + # matplotlib + # mpi-sppy + # pandas + # salib + # scipy + # seaborn +openpyxl==3.1.5 + # via temoa (pyproject.toml) +packaging==25.0 + # via + # matplotlib + # pydata-sphinx-theme + # pytest + # sphinx +pandas==2.3.1 + # via + # temoa (pyproject.toml) + # salib + # seaborn +pillow==11.3.0 + # via matplotlib +pint==0.25.2 + # via temoa (pyproject.toml) +platformdirs==4.5.1 + # via pint +pluggy==1.6.0 + # via pytest +ply==3.11 + # via pyomo +pybtex==0.25.1 + # via + # pybtex-docutils + # sphinxcontrib-bibtex +pybtex-docutils==1.0.3 + # via sphinxcontrib-bibtex +pydata-sphinx-theme==0.15.4 + # via sphinx-book-theme +pygments==2.19.2 + # via + # accessible-pygments + # pydata-sphinx-theme + # pytest + # rich + # sphinx +pyomo==6.9.2 + # via + # temoa (pyproject.toml) + # mpi-sppy +pyparsing==3.2.3 + # via matplotlib +pytest==8.4.1 + # via temoa (pyproject.toml) +python-dateutil==2.9.0.post0 + # via + # matplotlib + # pandas +pytz==2025.2 + # via pandas +pyyaml==6.0.2 + # via + # myst-parser + # pybtex + # sphinxcontrib-mermaid +requests==2.32.4 + # via sphinx +rich==14.2.0 + # via + # temoa (pyproject.toml) + # typer +roman-numerals-py==3.1.0 + # via sphinx +salib==1.5.1 + # via temoa (pyproject.toml) +scipy==1.16.0 + # via + # temoa (pyproject.toml) + # mpi-sppy + # salib +seaborn==0.13.2 + # via temoa (pyproject.toml) +shellingham==1.5.4 + # via typer +six==1.17.0 + # via python-dateutil +snowballstemmer==3.0.1 + # via sphinx +soupsieve==2.8.1 + # via beautifulsoup4 +sphinx==8.2.3 + # via + # temoa (pyproject.toml) + # myst-parser + # pydata-sphinx-theme + # sphinx-book-theme + # sphinxcontrib-bibtex + # sphinxcontrib-mermaid +sphinx-book-theme==1.1.4 + # via temoa (pyproject.toml) +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-bibtex==2.6.5 + # via temoa (pyproject.toml) +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via + # temoa (pyproject.toml) + # sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-mermaid==1.2.3 + # via temoa (pyproject.toml) +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via + # temoa (pyproject.toml) + # sphinx +tabulate==0.9.0 + # via temoa (pyproject.toml) +tomlkit==0.13.3 + # via temoa (pyproject.toml) +typer==0.20.0 + # via temoa (pyproject.toml) +typing-extensions==4.15.0 + # via + # beautifulsoup4 + # flexcache + # flexparser + # pint + # pydata-sphinx-theme + # typer +tzdata==2025.2 + # via pandas +urllib3==2.5.0 + # via requests +wrapt==1.17.2 + # via deprecated +xlsxwriter==3.2.5 + # via temoa (pyproject.toml) diff --git a/requirements.in b/requirements.in deleted file mode 100644 index b5e948a6b..000000000 --- a/requirements.in +++ /dev/null @@ -1,44 +0,0 @@ -# Requirements for core model functionality -# python used = 3.12 - -# to use pip to install (in a venv, if desired): -# $: pip install -r requirements.txt - -pyomo -ipython -matplotlib -pandas -numpy -joblib -salib -pydoe -pyutilib - -graphviz -ipykernel -jupyter -jupyter_contrib_nbextensions -seaborn -tabulate -xlsxwriter -plotly -pyam-iamc - -# Below required to update documentation -sphinx -# sphisphinx_rtd_theme <-- not available on PyPi -sphinx-rtd-theme -sphinxcontrib-htmlhelp -sphinxcontrib-serializinghtml -sphinxcontrib-bibtex -# nxcontrib-bibtex <-- not available on pypi - -# new requirements -pytest -Deprecated -openpyxl -networkx -gravis -highspy -scipy -gurobipy diff --git a/requirements.txt b/requirements.txt index bba2cd778..ebf917c83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,636 +1,130 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile -# -alabaster==0.7.16 - # via sphinx -alembic==1.13.2 - # via ixmp4 -annotated-types==0.7.0 - # via pydantic -anyio==4.4.0 - # via - # httpx - # jupyter-server - # starlette -appdirs==1.4.4 - # via pint -appnope==0.1.4 - # via ipykernel -argon2-cffi==23.1.0 - # via jupyter-server -argon2-cffi-bindings==21.2.0 - # via argon2-cffi -arrow==1.3.0 - # via isoduration -asttokens==2.4.1 - # via stack-data -async-lru==2.0.4 - # via jupyterlab -attrs==24.2.0 - # via - # jsonschema - # referencing -babel==2.16.0 - # via - # jupyterlab-server - # sphinx -beautifulsoup4==4.12.3 - # via nbconvert -bleach==6.1.0 - # via nbconvert -certifi==2024.8.30 - # via - # httpcore - # httpx - # requests -cffi==1.17.0 - # via argon2-cffi-bindings -charset-normalizer==3.3.2 - # via requests -click==8.1.7 +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o requirements.txt +click==8.3.0 # via typer -comm==0.2.2 - # via - # ipykernel - # ipywidgets -contourpy==1.3.0 +contourpy==1.3.2 # via matplotlib cycler==0.12.1 # via matplotlib -debugpy==1.8.5 - # via ipykernel -decorator==5.1.1 - # via ipython -defusedxml==0.7.1 - # via nbconvert -deprecated==1.2.14 - # via -r requirements.in -dill==0.3.8 +deprecated==1.2.18 + # via temoa (pyproject.toml) +dill==0.4.0 # via multiprocess -docutils==0.20.1 - # via - # pybtex-docutils - # sphinx - # sphinx-rtd-theme - # sphinxcontrib-bibtex -et-xmlfile==1.1.0 +et-xmlfile==2.0.0 # via openpyxl -executing==2.0.1 - # via stack-data -fastapi==0.112.2 - # via ixmp4 -fastjsonschema==2.20.0 - # via nbformat flexcache==0.3 # via pint -flexparser==0.3.1 +flexparser==0.4 # via pint -fonttools==4.53.1 +fonttools==4.59.0 # via matplotlib -fqdn==1.5.1 - # via jsonschema -graphviz==0.20.3 - # via -r requirements.in -gravis==0.1.0 - # via -r requirements.in -gurobipy==11.0.3 - # via -r requirements.in -h11==0.14.0 - # via httpcore -h2==4.1.0 - # via httpx -highspy==1.7.2 - # via -r requirements.in -hpack==4.0.0 - # via h2 -httpcore==1.0.5 - # via httpx -httpx[http2]==0.27.2 - # via - # ixmp4 - # jupyterlab -hyperframe==6.0.1 - # via h2 -iam-units==2023.9.12 - # via pyam-iamc -idna==3.8 - # via - # anyio - # httpx - # jsonschema - # requests -imagesize==1.4.1 - # via sphinx -iniconfig==2.0.0 +graphviz==0.21 + # via temoa (pyproject.toml) +highspy==1.11.0 + # via temoa (pyproject.toml) +iniconfig==2.1.0 # via pytest -ipykernel==6.29.5 - # via - # -r requirements.in - # jupyter - # jupyter-console - # jupyterlab -ipython==8.26.0 - # via - # -r requirements.in - # ipykernel - # ipywidgets - # jupyter-console -ipython-genutils==0.2.0 - # via jupyter-contrib-nbextensions -ipywidgets==8.1.5 - # via jupyter -isoduration==20.11.0 - # via jsonschema -ixmp4==0.9.2 - # via pyam-iamc -jedi==0.19.1 - # via ipython -jinja2==3.1.4 - # via - # jupyter-server - # jupyterlab - # jupyterlab-server - # nbconvert - # sphinx -joblib==1.4.2 - # via -r requirements.in -json5==0.9.25 - # via jupyterlab-server -jsonpointer==3.0.0 - # via jsonschema -jsonschema[format-nongpl]==4.23.0 - # via - # jupyter-events - # jupyterlab-server - # nbformat -jsonschema-specifications==2023.12.1 - # via jsonschema -jupyter==1.1.0 - # via -r requirements.in -jupyter-client==8.6.2 - # via - # ipykernel - # jupyter-console - # jupyter-server - # nbclient -jupyter-console==6.6.3 - # via jupyter -jupyter-contrib-core==0.4.2 - # via - # jupyter-contrib-nbextensions - # jupyter-nbextensions-configurator -jupyter-contrib-nbextensions==0.7.0 - # via -r requirements.in -jupyter-core==5.7.2 - # via - # ipykernel - # jupyter-client - # jupyter-console - # jupyter-contrib-core - # jupyter-contrib-nbextensions - # jupyter-nbextensions-configurator - # jupyter-server - # jupyterlab - # nbclient - # nbconvert - # nbformat -jupyter-events==0.10.0 - # via jupyter-server -jupyter-highlight-selected-word==0.2.0 - # via jupyter-contrib-nbextensions -jupyter-lsp==2.2.5 - # via jupyterlab -jupyter-nbextensions-configurator==0.6.4 - # via jupyter-contrib-nbextensions -jupyter-server==2.14.2 - # via - # jupyter-lsp - # jupyter-nbextensions-configurator - # jupyterlab - # jupyterlab-server - # notebook - # notebook-shim -jupyter-server-terminals==0.5.3 - # via jupyter-server -jupyterlab==4.2.5 - # via - # jupyter - # notebook -jupyterlab-pygments==0.3.0 - # via nbconvert -jupyterlab-server==2.27.3 - # via - # jupyterlab - # notebook -jupyterlab-widgets==3.0.13 - # via ipywidgets -kiwisolver==1.4.5 +joblib==1.5.1 + # via temoa (pyproject.toml) +kiwisolver==1.4.8 # via matplotlib -latexcodec==3.0.0 - # via pybtex -lxml==5.3.0 - # via jupyter-contrib-nbextensions -mako==1.3.5 - # via alembic -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich -markupsafe==2.1.5 - # via - # jinja2 - # mako - # nbconvert matplotlib==3.9.2 # via - # -r requirements.in - # pyam-iamc + # temoa (pyproject.toml) # salib # seaborn -matplotlib-inline==0.1.7 - # via - # ipykernel - # ipython mdurl==0.1.2 # via markdown-it-py -mistune==3.0.2 - # via nbconvert -multimethod==1.10 - # via pandera -multiprocess==0.70.16 +mpi-sppy==0.12.1 + # via temoa (pyproject.toml) +multiprocess==0.70.18 # via salib -mypy==1.11.2 - # via sqlalchemy -mypy-extensions==1.0.0 +networkx==3.5 + # via temoa (pyproject.toml) +numpy==2.3.1 # via - # mypy - # typing-inspect -nbclient==0.10.0 - # via nbconvert -nbconvert==7.16.4 - # via - # jupyter - # jupyter-contrib-nbextensions - # jupyter-server -nbformat==5.10.4 - # via - # jupyter-server - # nbclient - # nbconvert -nest-asyncio==1.6.0 - # via ipykernel -networkx==3.3 - # via -r requirements.in -nose==1.3.7 - # via pyutilib -notebook==7.2.2 - # via - # jupyter - # jupyter-contrib-core - # jupyter-contrib-nbextensions - # jupyter-nbextensions-configurator -notebook-shim==0.2.4 - # via - # jupyterlab - # notebook -numpy==2.1.0 - # via - # -r requirements.in + # temoa (pyproject.toml) # contourpy # highspy # matplotlib + # mpi-sppy # pandas - # pandera - # pyam-iamc - # pydoe # salib # scipy # seaborn - # wquantiles openpyxl==3.1.5 + # via temoa (pyproject.toml) +packaging==25.0 # via - # -r requirements.in - # ixmp4 - # pyam-iamc -overrides==7.7.0 - # via jupyter-server -packaging==24.1 - # via - # ipykernel - # jupyter-server - # jupyterlab - # jupyterlab-server # matplotlib - # nbconvert - # pandera - # plotly # pytest - # sphinx -pandas==2.2.2 +pandas==2.3.1 # via - # -r requirements.in - # ixmp4 - # pandera - # pyam-iamc + # temoa (pyproject.toml) # salib # seaborn -pandera==0.20.3 - # via ixmp4 -pandocfilters==1.5.1 - # via nbconvert -parso==0.8.4 - # via jedi -pexpect==4.9.0 - # via ipython -pillow==10.4.0 +pillow==11.3.0 # via matplotlib -pint==0.24.3 - # via - # iam-units - # pyam-iamc -platformdirs==4.2.2 - # via jupyter-core -plotly==5.24.0 - # via -r requirements.in -pluggy==1.5.0 +pint==0.25.2 + # via temoa (pyproject.toml) +platformdirs==4.5.1 + # via pint +pluggy==1.6.0 # via pytest ply==3.11 # via pyomo -prometheus-client==0.20.0 - # via jupyter-server -prompt-toolkit==3.0.47 - # via - # ipython - # jupyter-console -psutil==6.0.0 - # via ipykernel -psycopg[binary]==3.2.1 - # via ixmp4 -psycopg-binary==3.2.1 - # via psycopg -ptyprocess==0.7.0 - # via - # pexpect - # terminado -pure-eval==0.2.3 - # via stack-data -pyam-iamc==2.2.4 - # via -r requirements.in -pybtex==0.24.0 - # via - # pybtex-docutils - # sphinxcontrib-bibtex -pybtex-docutils==1.0.3 - # via sphinxcontrib-bibtex -pycparser==2.22 - # via cffi -pydantic==2.8.2 +pygments==2.19.2 # via - # fastapi - # ixmp4 - # pandera - # pydantic-settings -pydantic-core==2.20.1 - # via pydantic -pydantic-settings==2.4.0 - # via ixmp4 -pydoe==0.3.8 - # via -r requirements.in -pygments==2.18.0 - # via - # ipython - # jupyter-console - # nbconvert + # pytest # rich - # sphinx -pyjwt==2.9.0 - # via ixmp4 -pyomo==6.8.0 - # via -r requirements.in -pyparsing==3.1.4 +pyomo==6.9.2 + # via + # temoa (pyproject.toml) + # mpi-sppy +pyparsing==3.2.3 # via matplotlib -pytest==8.3.2 - # via -r requirements.in +pytest==8.4.1 + # via temoa (pyproject.toml) python-dateutil==2.9.0.post0 # via - # arrow - # jupyter-client # matplotlib # pandas -python-dotenv==1.0.1 - # via - # ixmp4 - # pydantic-settings -python-json-logger==2.0.7 - # via jupyter-events -pytz==2024.1 +pytz==2025.2 # via pandas -pyutilib==6.0.0 - # via -r requirements.in -pyyaml==6.0.2 - # via - # jupyter-events - # jupyter-nbextensions-configurator - # pyam-iamc - # pybtex -pyzmq==26.2.0 - # via - # ipykernel - # jupyter-client - # jupyter-console - # jupyter-server -referencing==0.35.1 - # via - # jsonschema - # jsonschema-specifications - # jupyter-events -requests==2.32.3 - # via - # jupyterlab-server - # pyam-iamc - # sphinx -rfc3339-validator==0.1.4 - # via - # jsonschema - # jupyter-events -rfc3986-validator==0.1.1 - # via - # jsonschema - # jupyter-events -rich==13.8.0 +rich==14.2.0 # via - # ixmp4 + # temoa (pyproject.toml) # typer -rpds-py==0.20.0 - # via - # jsonschema - # referencing salib==1.5.1 - # via -r requirements.in -scipy==1.14.1 + # via temoa (pyproject.toml) +scipy==1.16.0 # via - # -r requirements.in - # pyam-iamc - # pydoe + # temoa (pyproject.toml) + # mpi-sppy # salib seaborn==0.13.2 - # via - # -r requirements.in - # pyam-iamc -send2trash==1.8.3 - # via jupyter-server + # via temoa (pyproject.toml) shellingham==1.5.4 # via typer -six==1.16.0 - # via - # asttokens - # bleach - # pybtex - # python-dateutil - # pyutilib - # rfc3339-validator -sniffio==1.3.1 - # via - # anyio - # httpx -snowballstemmer==2.2.0 - # via sphinx -soupsieve==2.6 - # via beautifulsoup4 -sphinx==7.4.7 - # via - # -r requirements.in - # sphinx-rtd-theme - # sphinxcontrib-bibtex - # sphinxcontrib-jquery -sphinx-rtd-theme==2.0.0 - # via -r requirements.in -sphinxcontrib-applehelp==2.0.0 - # via sphinx -sphinxcontrib-bibtex==2.6.2 - # via -r requirements.in -sphinxcontrib-devhelp==2.0.0 - # via sphinx -sphinxcontrib-htmlhelp==2.1.0 - # via - # -r requirements.in - # sphinx -sphinxcontrib-jquery==4.1 - # via sphinx-rtd-theme -sphinxcontrib-jsmath==1.0.1 - # via sphinx -sphinxcontrib-qthelp==2.0.0 - # via sphinx -sphinxcontrib-serializinghtml==2.0.0 - # via - # -r requirements.in - # sphinx -sqlalchemy[mypy]==2.0.32 - # via - # alembic - # ixmp4 - # sqlalchemy - # sqlalchemy-utils -sqlalchemy-utils==0.41.2 - # via ixmp4 -stack-data==0.6.3 - # via ipython -starlette==0.38.2 - # via fastapi +six==1.17.0 + # via python-dateutil tabulate==0.9.0 - # via -r requirements.in -tenacity==9.0.0 - # via plotly -terminado==0.18.1 - # via - # jupyter-server - # jupyter-server-terminals -tinycss2==1.3.0 - # via nbconvert -toml==0.10.2 - # via ixmp4 -tornado==6.4.1 - # via - # ipykernel - # jupyter-client - # jupyter-contrib-core - # jupyter-contrib-nbextensions - # jupyter-nbextensions-configurator - # jupyter-server - # jupyterlab - # notebook - # terminado -traitlets==5.14.3 - # via - # comm - # ipykernel - # ipython - # ipywidgets - # jupyter-client - # jupyter-console - # jupyter-contrib-core - # jupyter-contrib-nbextensions - # jupyter-core - # jupyter-events - # jupyter-nbextensions-configurator - # jupyter-server - # jupyterlab - # matplotlib-inline - # nbclient - # nbconvert - # nbformat -typeguard==4.3.0 - # via pandera -typer==0.12.5 - # via ixmp4 -types-python-dateutil==2.9.0.20240821 - # via arrow -typing-extensions==4.12.2 + # via temoa (pyproject.toml) +tomlkit==0.13.3 + # via temoa (pyproject.toml) +typer==0.20.0 + # via temoa (pyproject.toml) +typing-extensions==4.15.0 # via - # alembic - # fastapi # flexcache # flexparser - # mypy # pint - # psycopg - # pydantic - # pydantic-core - # sqlalchemy - # typeguard # typer - # typing-inspect -typing-inspect==0.9.0 - # via pandera -tzdata==2024.1 +tzdata==2025.2 # via pandas -uri-template==1.3.0 - # via jsonschema -urllib3==2.2.2 - # via requests -wcwidth==0.2.13 - # via prompt-toolkit -webcolors==24.8.0 - # via jsonschema -webencodings==0.5.1 - # via - # bleach - # tinycss2 -websocket-client==1.8.0 - # via jupyter-server -widgetsnbextension==4.0.13 - # via ipywidgets -wquantiles==0.6 - # via pyam-iamc -wrapt==1.16.0 - # via - # deprecated - # pandera -xlsxwriter==3.2.0 - # via - # -r requirements.in - # pyam-iamc - -# The following packages are considered to be unsafe in a requirements file: -# setuptools +wrapt==1.17.2 + # via deprecated +xlsxwriter==3.2.5 + # via temoa (pyproject.toml) diff --git a/scripts/bump_version.py b/scripts/bump_version.py new file mode 100755 index 000000000..bec70c19a --- /dev/null +++ b/scripts/bump_version.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +import argparse +import re +import sys +from pathlib import Path +from typing import Any + + +def parse_version(version_str: str) -> dict[str, Any]: + """ + Parses a PEP 440 version string into its components. + Basic supported format: major.minor.patch[a|b|rcN] + """ + pattern = r'^(\d+)\.(\d+)\.(\d+)(?:([abc]|rc)(\d+))?$' + match = re.match(pattern, version_str) + if not match: + raise ValueError(f"Version '{version_str}' does not match expected format X.Y.Z[a|b|rcN]") + + major, minor, patch, pre_type, pre_num = match.groups() + return { + 'major': int(major), + 'minor': int(minor), + 'patch': int(patch), + 'pre_type': pre_type, + 'pre_num': int(pre_num) if pre_num else None, + } + + +def stringify_version(components: dict[str, Any]) -> str: + base = f'{components["major"]}.{components["minor"]}.{components["patch"]}' + if components['pre_type']: + return f'{base}{components["pre_type"]}{components["pre_num"] or 1}' + return base + + +def bump_version(components: dict[str, Any], part: str) -> dict[str, Any]: + new = components.copy() + + if part == 'major': + new['major'] += 1 + new['minor'] = 0 + new['patch'] = 0 + new['pre_type'] = None + new['pre_num'] = None + elif part == 'minor': + new['minor'] += 1 + new['patch'] = 0 + new['pre_type'] = None + new['pre_num'] = None + elif part == 'patch': + new['patch'] += 1 + new['pre_type'] = None + new['pre_num'] = None + elif part in ['a', 'b', 'rc', 'alpha', 'beta']: + pre_map = {'alpha': 'a', 'beta': 'b', 'rc': 'rc', 'a': 'a', 'b': 'b'} + target_pre = pre_map[part] + pre_ordinal = {'a': 0, 'b': 1, 'rc': 2} + + # Check for precedence to prevent downgrading within the same patch version + if new['pre_type'] and pre_ordinal[target_pre] < pre_ordinal[new['pre_type']]: + new['patch'] += 1 + new['pre_type'] = target_pre + new['pre_num'] = 1 + elif new['pre_type'] == target_pre: + new['pre_num'] = (new['pre_num'] or 0) + 1 + else: + # If moving to higher precedence (e.g., a -> b) or starting from final + if not new['pre_type']: + new['patch'] += 1 + new['pre_type'] = target_pre + new['pre_num'] = 1 + elif part == 'final': + new['pre_type'] = None + new['pre_num'] = None + else: + raise ValueError(f'Unknown part to bump: {part}') + + return new + + +def main() -> None: + parser = argparse.ArgumentParser(description='Bump Temoa version in temoa/__about__.py') + parser.add_argument( + 'part', + choices=['major', 'minor', 'patch', 'alpha', 'beta', 'rc', 'final', 'a', 'b'], + help='The part of the version to bump', + ) + parser.add_argument('--dry-run', action='store_true', help="Don't write to file") + + args = parser.parse_args() + + about_path = Path('temoa/__about__.py') + if not about_path.exists(): + print(f'Error: {about_path} not found.') + sys.exit(1) + + content = about_path.read_text() + version_match = re.search(r"__version__ = ['\"]([^'\"]+)['\"]", content) + if not version_match: + print('Error: Could not find __version__ in temoa/__about__.py') + sys.exit(1) + + old_version_str = version_match.group(1) + try: + components = parse_version(old_version_str) + except ValueError as e: + print(f'Error: {e}') + sys.exit(1) + + new_components = bump_version(components, args.part) + new_version_str = stringify_version(new_components) + + if old_version_str == new_version_str: + print(f'Version is already {new_version_str}. No change made.') + return + + print(f'Bumping version: {old_version_str} -> {new_version_str}') + + if not args.dry_run: + new_content = content.replace( + f"__version__ = '{old_version_str}'", f"__version__ = '{new_version_str}'" + ) + new_content = new_content.replace( + f'__version__ = "{old_version_str}"', f'__version__ = "{new_version_str}"' + ) + + # Also update TEMOA_MAJOR and TEMOA_MINOR if they changed + if new_components['major'] != components['major']: + new_content = re.sub( + r'TEMOA_MAJOR = \d+', f'TEMOA_MAJOR = {new_components["major"]}', new_content + ) + if new_components['minor'] != components['minor']: + new_content = re.sub( + r'TEMOA_MINOR = \d+', f'TEMOA_MINOR = {new_components["minor"]}', new_content + ) + + about_path.write_text(new_content) + print(f'Successfully updated {about_path}') + + print('\nNext steps:') + print(f' git add {about_path}') + print(f' git commit -m "chore: bump version to {new_version_str}"') + print(f' git tag -a v{new_version_str} -m "Release v{new_version_str}"') + print(f' git push origin v{new_version_str}') + else: + print('Dry run: file not modified.') + + +if __name__ == '__main__': + main() diff --git a/stubs/pyomo/__init__.pyi b/stubs/pyomo/__init__.pyi new file mode 100644 index 000000000..34bfef24c --- /dev/null +++ b/stubs/pyomo/__init__.pyi @@ -0,0 +1,4 @@ +from pyomo.common.deprecation import moved_module as moved_module + +from . import common as common +from .version import __version__ as __version__ diff --git a/stubs/pyomo/_archive/__init__.pyi b/stubs/pyomo/_archive/__init__.pyi new file mode 100644 index 000000000..3a03abe59 --- /dev/null +++ b/stubs/pyomo/_archive/__init__.pyi @@ -0,0 +1 @@ +__doc__: str diff --git a/temoa/extensions/stochastics/options/__init__.py b/stubs/pyomo/_archive/chull.pyi similarity index 100% rename from temoa/extensions/stochastics/options/__init__.py rename to stubs/pyomo/_archive/chull.pyi diff --git a/stubs/pyomo/_archive/component_map.pyi b/stubs/pyomo/_archive/component_map.pyi new file mode 100644 index 000000000..9ca2b6f68 --- /dev/null +++ b/stubs/pyomo/_archive/component_map.pyi @@ -0,0 +1 @@ +from pyomo.common.collections import ComponentMap as ComponentMap diff --git a/stubs/pyomo/_archive/component_set.pyi b/stubs/pyomo/_archive/component_set.pyi new file mode 100644 index 000000000..2094a8416 --- /dev/null +++ b/stubs/pyomo/_archive/component_set.pyi @@ -0,0 +1 @@ +from pyomo.common.collections import ComponentSet as ComponentSet diff --git a/stubs/pyomo/_archive/current.pyi b/stubs/pyomo/_archive/current.pyi new file mode 100644 index 000000000..ecc3a5386 --- /dev/null +++ b/stubs/pyomo/_archive/current.pyi @@ -0,0 +1,127 @@ +from pyomo.core.expr import AbsExpression as AbsExpression +from pyomo.core.expr import AndExpression as AndExpression +from pyomo.core.expr import AtLeastExpression as AtLeastExpression +from pyomo.core.expr import AtMostExpression as AtMostExpression +from pyomo.core.expr import BinaryBooleanExpression as BinaryBooleanExpression +from pyomo.core.expr import Boolean_GetAttrExpression as Boolean_GetAttrExpression +from pyomo.core.expr import Boolean_GetItemExpression as Boolean_GetItemExpression +from pyomo.core.expr import BooleanConstant as BooleanConstant +from pyomo.core.expr import BooleanExpressionBase as BooleanExpressionBase +from pyomo.core.expr import BooleanValue as BooleanValue +from pyomo.core.expr import CallExpression as CallExpression +from pyomo.core.expr import DivisionExpression as DivisionExpression +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.core.expr import EquivalenceExpression as EquivalenceExpression +from pyomo.core.expr import ExactlyExpression as ExactlyExpression +from pyomo.core.expr import Expr_if as Expr_if +from pyomo.core.expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr import ExpressionBase as ExpressionBase +from pyomo.core.expr import ExpressionReplacementVisitor as ExpressionReplacementVisitor +from pyomo.core.expr import ExpressionValueVisitor as ExpressionValueVisitor +from pyomo.core.expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr import FixedExpressionError as FixedExpressionError +from pyomo.core.expr import GetAttrExpression as GetAttrExpression +from pyomo.core.expr import GetItemExpression as GetItemExpression +from pyomo.core.expr import ImplicationExpression as ImplicationExpression +from pyomo.core.expr import IndexTemplate as IndexTemplate +from pyomo.core.expr import InequalityExpression as InequalityExpression +from pyomo.core.expr import LinearDecompositionError as LinearDecompositionError +from pyomo.core.expr import LinearExpression as LinearExpression +from pyomo.core.expr import Mode as Mode +from pyomo.core.expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr import NaryBooleanExpression as NaryBooleanExpression +from pyomo.core.expr import NegationExpression as NegationExpression +from pyomo.core.expr import NonConstantExpressionError as NonConstantExpressionError +from pyomo.core.expr import NotExpression as NotExpression +from pyomo.core.expr import NPV_AbsExpression as NPV_AbsExpression +from pyomo.core.expr import NPV_Boolean_GetAttrExpression as NPV_Boolean_GetAttrExpression +from pyomo.core.expr import NPV_Boolean_GetItemExpression as NPV_Boolean_GetItemExpression +from pyomo.core.expr import NPV_DivisionExpression as NPV_DivisionExpression +from pyomo.core.expr import NPV_Expr_ifExpression as NPV_Expr_ifExpression +from pyomo.core.expr import NPV_expression_types as NPV_expression_types +from pyomo.core.expr import NPV_ExternalFunctionExpression as NPV_ExternalFunctionExpression +from pyomo.core.expr import NPV_NegationExpression as NPV_NegationExpression +from pyomo.core.expr import NPV_Numeric_GetAttrExpression as NPV_Numeric_GetAttrExpression +from pyomo.core.expr import NPV_Numeric_GetItemExpression as NPV_Numeric_GetItemExpression +from pyomo.core.expr import NPV_PowExpression as NPV_PowExpression +from pyomo.core.expr import NPV_ProductExpression as NPV_ProductExpression +from pyomo.core.expr import NPV_Structural_GetAttrExpression as NPV_Structural_GetAttrExpression +from pyomo.core.expr import NPV_Structural_GetItemExpression as NPV_Structural_GetItemExpression +from pyomo.core.expr import NPV_SumExpression as NPV_SumExpression +from pyomo.core.expr import NPV_UnaryFunctionExpression as NPV_UnaryFunctionExpression +from pyomo.core.expr import Numeric_GetAttrExpression as Numeric_GetAttrExpression +from pyomo.core.expr import Numeric_GetItemExpression as Numeric_GetItemExpression +from pyomo.core.expr import NumericExpression as NumericExpression +from pyomo.core.expr import NumericValue as NumericValue +from pyomo.core.expr import OrExpression as OrExpression +from pyomo.core.expr import PowExpression as PowExpression +from pyomo.core.expr import ProductExpression as ProductExpression +from pyomo.core.expr import RangedExpression as RangedExpression +from pyomo.core.expr import RelationalExpression as RelationalExpression +from pyomo.core.expr import ReplaceTemplateExpression as ReplaceTemplateExpression +from pyomo.core.expr import SimpleExpressionVisitor as SimpleExpressionVisitor +from pyomo.core.expr import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.expr import Structural_GetAttrExpression as Structural_GetAttrExpression +from pyomo.core.expr import Structural_GetItemExpression as Structural_GetItemExpression +from pyomo.core.expr import SumExpression as SumExpression +from pyomo.core.expr import SumExpressionBase as SumExpressionBase +from pyomo.core.expr import SymbolMap as SymbolMap +from pyomo.core.expr import TemplateExpressionError as TemplateExpressionError +from pyomo.core.expr import TemplateSumExpression as TemplateSumExpression +from pyomo.core.expr import UnaryBooleanExpression as UnaryBooleanExpression +from pyomo.core.expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr import XorExpression as XorExpression +from pyomo.core.expr import acos as acos +from pyomo.core.expr import acosh as acosh +from pyomo.core.expr import as_numeric as as_numeric +from pyomo.core.expr import asin as asin +from pyomo.core.expr import asinh as asinh +from pyomo.core.expr import atan as atan +from pyomo.core.expr import atanh as atanh +from pyomo.core.expr import atleast as atleast +from pyomo.core.expr import atmost as atmost +from pyomo.core.expr import ceil as ceil +from pyomo.core.expr import clone_expression as clone_expression +from pyomo.core.expr import cos as cos +from pyomo.core.expr import cosh as cosh +from pyomo.core.expr import decompose_term as decompose_term +from pyomo.core.expr import equivalent as equivalent +from pyomo.core.expr import evaluate_expression as evaluate_expression +from pyomo.core.expr import exactly as exactly +from pyomo.core.expr import exp as exp +from pyomo.core.expr import expression_to_string as expression_to_string +from pyomo.core.expr import floor as floor +from pyomo.core.expr import identify_components as identify_components +from pyomo.core.expr import identify_mutable_parameters as identify_mutable_parameters +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.expr import implies as implies +from pyomo.core.expr import inequality as inequality +from pyomo.core.expr import land as land +from pyomo.core.expr import linear_expression as linear_expression +from pyomo.core.expr import lnot as lnot +from pyomo.core.expr import log as log +from pyomo.core.expr import log10 as log10 +from pyomo.core.expr import lor as lor +from pyomo.core.expr import native_logical_types as native_logical_types +from pyomo.core.expr import native_numeric_types as native_numeric_types +from pyomo.core.expr import native_types as native_types +from pyomo.core.expr import nonlinear_expression as nonlinear_expression +from pyomo.core.expr import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr import polynomial_degree as polynomial_degree +from pyomo.core.expr import replace_expressions as replace_expressions +from pyomo.core.expr import resolve_template as resolve_template +from pyomo.core.expr import sin as sin +from pyomo.core.expr import sinh as sinh +from pyomo.core.expr import sizeof_expression as sizeof_expression +from pyomo.core.expr import special_boolean_atom_types as special_boolean_atom_types +from pyomo.core.expr import sqrt as sqrt +from pyomo.core.expr import substitute_getitem_with_param as substitute_getitem_with_param +from pyomo.core.expr import substitute_template_expression as substitute_template_expression +from pyomo.core.expr import substitute_template_with_value as substitute_template_with_value +from pyomo.core.expr import tan as tan +from pyomo.core.expr import tanh as tanh +from pyomo.core.expr import templatize_constraint as templatize_constraint +from pyomo.core.expr import templatize_rule as templatize_rule +from pyomo.core.expr import value as value +from pyomo.core.expr import xor as xor +from pyomo.core.expr.expr_common import clone_counter as clone_counter diff --git a/stubs/pyomo/_archive/plugin.pyi b/stubs/pyomo/_archive/plugin.pyi new file mode 100644 index 000000000..e6deb9430 --- /dev/null +++ b/stubs/pyomo/_archive/plugin.pyi @@ -0,0 +1,30 @@ +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.transformation import Transformation as Transformation +from pyomo.core.base.transformation import TransformationData as TransformationData +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.base.transformation import TransformationInfo as TransformationInfo +from pyomo.core.base.transformation import TransformationTimer as TransformationTimer +from pyomo.scripting.interface import DeprecatedInterface as DeprecatedInterface +from pyomo.scripting.interface import ExtensionPoint as ExtensionPoint +from pyomo.scripting.interface import Interface as Interface +from pyomo.scripting.interface import IPyomoPresolveAction as IPyomoPresolveAction +from pyomo.scripting.interface import IPyomoPresolver as IPyomoPresolver +from pyomo.scripting.interface import IPyomoScriptCreateDataPortal as IPyomoScriptCreateDataPortal +from pyomo.scripting.interface import IPyomoScriptCreateModel as IPyomoScriptCreateModel +from pyomo.scripting.interface import IPyomoScriptModifyInstance as IPyomoScriptModifyInstance +from pyomo.scripting.interface import IPyomoScriptPostprocess as IPyomoScriptPostprocess +from pyomo.scripting.interface import IPyomoScriptPreprocess as IPyomoScriptPreprocess +from pyomo.scripting.interface import IPyomoScriptPrintInstance as IPyomoScriptPrintInstance +from pyomo.scripting.interface import IPyomoScriptPrintModel as IPyomoScriptPrintModel +from pyomo.scripting.interface import IPyomoScriptPrintResults as IPyomoScriptPrintResults +from pyomo.scripting.interface import IPyomoScriptSaveInstance as IPyomoScriptSaveInstance +from pyomo.scripting.interface import IPyomoScriptSaveResults as IPyomoScriptSaveResults +from pyomo.scripting.interface import Plugin as Plugin +from pyomo.scripting.interface import implements as implements +from pyomo.scripting.interface import pyomo_callback as pyomo_callback + +class IPyomoExpression(DeprecatedInterface): + def type(self) -> None: ... + def create(self, args) -> None: ... + +class IParamRepresentation(DeprecatedInterface): ... diff --git a/stubs/pyomo/_archive/rangeset.pyi b/stubs/pyomo/_archive/rangeset.pyi new file mode 100644 index 000000000..b13d84222 --- /dev/null +++ b/stubs/pyomo/_archive/rangeset.pyi @@ -0,0 +1 @@ +from pyomo.core.base.set import RangeSet as RangeSet diff --git a/stubs/pyomo/_archive/register_numpy_types.pyi b/stubs/pyomo/_archive/register_numpy_types.pyi new file mode 100644 index 000000000..e519b8933 --- /dev/null +++ b/stubs/pyomo/_archive/register_numpy_types.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import numpy as numpy +from pyomo.common.numeric_types import RegisterBooleanType as RegisterBooleanType +from pyomo.common.numeric_types import RegisterIntegerType as RegisterIntegerType +from pyomo.common.numeric_types import RegisterNumericType as RegisterNumericType +from pyomo.common.numeric_types import native_boolean_types as native_boolean_types +from pyomo.common.numeric_types import native_complex_types as native_complex_types +from pyomo.common.numeric_types import native_integer_types as native_integer_types +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types + +numpy_int_names: Incomplete +numpy_int: Incomplete +numpy_float_names: Incomplete +numpy_float: Incomplete +numpy_bool_names: Incomplete +numpy_bool: Incomplete +numpy_complex_names: Incomplete +numpy_complex: Incomplete diff --git a/stubs/pyomo/_archive/sets.pyi b/stubs/pyomo/_archive/sets.pyi new file mode 100644 index 000000000..29c397ed4 --- /dev/null +++ b/stubs/pyomo/_archive/sets.pyi @@ -0,0 +1,7 @@ +from pyomo.core.base.set import IndexedSet as IndexedSet +from pyomo.core.base.set import Set as Set +from pyomo.core.base.set import SetData as SetData +from pyomo.core.base.set import SetOf as SetOf +from pyomo.core.base.set import process_setarg as process_setarg +from pyomo.core.base.set import set_options as set_options +from pyomo.core.base.set import simple_set_rule as simple_set_rule diff --git a/stubs/pyomo/_archive/template_expr.pyi b/stubs/pyomo/_archive/template_expr.pyi new file mode 100644 index 000000000..1c6801e76 --- /dev/null +++ b/stubs/pyomo/_archive/template_expr.pyi @@ -0,0 +1,2 @@ +from pyomo.core.expr.template_expr import IndexTemplate as IndexTemplate +from pyomo.core.expr.template_expr import TemplateExpressionError as TemplateExpressionError diff --git a/stubs/pyomo/common/__init__.pyi b/stubs/pyomo/common/__init__.pyi new file mode 100644 index 000000000..1eb009460 --- /dev/null +++ b/stubs/pyomo/common/__init__.pyi @@ -0,0 +1,17 @@ +from . import config as config +from . import dependencies as dependencies +from . import envvar as envvar +from . import log as log +from . import shutdown as shutdown +from . import timing as timing +from ._command import get_pyomo_commands as get_pyomo_commands +from ._command import pyomo_command as pyomo_command +from .deprecation import deprecated as deprecated +from .deprecation import moved_module as moved_module +from .errors import DeveloperError as DeveloperError +from .factory import Factory as Factory +from .fileutils import Executable as Executable +from .fileutils import Library as Library +from .fileutils import register_executable as register_executable +from .fileutils import registered_executable as registered_executable +from .fileutils import unregister_executable as unregister_executable diff --git a/stubs/pyomo/common/_command.pyi b/stubs/pyomo/common/_command.pyi new file mode 100644 index 000000000..7b4ad01b5 --- /dev/null +++ b/stubs/pyomo/common/_command.pyi @@ -0,0 +1,7 @@ +from _typeshed import Incomplete + +logger: Incomplete +registry: Incomplete + +def pyomo_command(name=None, doc=None): ... +def get_pyomo_commands(): ... diff --git a/stubs/pyomo/common/_common.pyi b/stubs/pyomo/common/_common.pyi new file mode 100644 index 000000000..95613821b --- /dev/null +++ b/stubs/pyomo/common/_common.pyi @@ -0,0 +1,6 @@ +from _typeshed import Incomplete + +old_help: Incomplete +old_help = help + +def help(thing=None) -> None: ... diff --git a/stubs/pyomo/common/autoslots.pyi b/stubs/pyomo/common/autoslots.pyi new file mode 100644 index 000000000..45a27800d --- /dev/null +++ b/stubs/pyomo/common/autoslots.pyi @@ -0,0 +1,30 @@ +import collections +from typing import NamedTuple + +from _typeshed import Incomplete + +class _autoslot_info(NamedTuple): + has_dict: Incomplete + slots: Incomplete + slot_mappers: Incomplete + field_mappers: Incomplete + +class _DeepcopyDispatcher(collections.defaultdict): + def __missing__(self, key): ... + +def fast_deepcopy(obj, memo): ... + +class AutoSlots(type): + def __init__(cls, name, bases, classdict) -> None: ... + @staticmethod + def collect_autoslots(cls) -> None: ... + @staticmethod + def weakref_mapper(encode, val): ... + @staticmethod + def weakref_sequence_mapper(encode, val): ... + @staticmethod + def encode_as_none(encode, val): ... + class Mixin: + def __init_subclass__(cls, **kwds) -> None: ... + def __deepcopy__(self, memo): ... + def __deepcopy_state__(self, memo, new_object) -> None: ... diff --git a/stubs/pyomo/common/backports.pyi b/stubs/pyomo/common/backports.pyi new file mode 100644 index 000000000..21f55e254 --- /dev/null +++ b/stubs/pyomo/common/backports.pyi @@ -0,0 +1 @@ +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute diff --git a/stubs/pyomo/common/cmake_builder.pyi b/stubs/pyomo/common/cmake_builder.pyi new file mode 100644 index 000000000..5b323868d --- /dev/null +++ b/stubs/pyomo/common/cmake_builder.pyi @@ -0,0 +1,7 @@ +from pyomo.common.fileutils import find_executable as find_executable +from pyomo.common.fileutils import this_file_dir as this_file_dir + +def handleReadonly(function, path, excinfo) -> None: ... +def build_cmake_project( + targets, package_name=None, description=None, user_args=[], parallel=None +) -> None: ... diff --git a/stubs/pyomo/common/collections/__init__.pyi b/stubs/pyomo/common/collections/__init__.pyi new file mode 100644 index 000000000..355d79497 --- /dev/null +++ b/stubs/pyomo/common/collections/__init__.pyi @@ -0,0 +1,13 @@ +from collections import UserDict as UserDict +from collections.abc import Mapping as Mapping +from collections.abc import MutableMapping as MutableMapping +from collections.abc import MutableSet as MutableSet +from collections.abc import Sequence as Sequence +from collections.abc import Set as Set + +from .bunch import Bunch as Bunch +from .component_map import ComponentMap as ComponentMap +from .component_map import DefaultComponentMap as DefaultComponentMap +from .component_set import ComponentSet as ComponentSet +from .orderedset import OrderedDict as OrderedDict +from .orderedset import OrderedSet as OrderedSet diff --git a/stubs/pyomo/common/collections/bunch.pyi b/stubs/pyomo/common/collections/bunch.pyi new file mode 100644 index 000000000..ecd3d90d3 --- /dev/null +++ b/stubs/pyomo/common/collections/bunch.pyi @@ -0,0 +1,10 @@ +class Bunch(dict): + def __init__(self, *args, **kw) -> None: ... + def update(self, d): ... + def set_name(self, name) -> None: ... + def __getitem__(self, name): ... + def __setitem__(self, name, val) -> None: ... + def __delitem__(self, name) -> None: ... + def __getattr__(self, name): ... + def __setattr__(self, name, val) -> None: ... + def __delattr__(self, name) -> None: ... diff --git a/stubs/pyomo/common/collections/component_map.pyi b/stubs/pyomo/common/collections/component_map.pyi new file mode 100644 index 000000000..0f82b50d7 --- /dev/null +++ b/stubs/pyomo/common/collections/component_map.pyi @@ -0,0 +1,31 @@ +import collections + +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots + +class _Hasher(collections.defaultdict): + def __init__(self, *args, **kwargs) -> None: ... + def hashable(self, obj, hashable=None): ... + +class ComponentMap(AutoSlots.Mixin, collections.abc.MutableMapping): + __autoslot_mappers__: Incomplete + hasher: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def __getitem__(self, obj): ... + def __setitem__(self, obj, val) -> None: ... + def __delitem__(self, obj) -> None: ... + def __iter__(self): ... + def __len__(self) -> int: ... + def update(self, *args, **kwargs): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __contains__(self, obj) -> bool: ... + def clear(self) -> None: ... + def get(self, key, default=None): ... + def setdefault(self, key, default=None): ... + +class DefaultComponentMap(ComponentMap): + default_factory: Incomplete + def __init__(self, default_factory=None, *args, **kwargs) -> None: ... + def __missing__(self, key): ... + def __getitem__(self, obj): ... diff --git a/stubs/pyomo/common/collections/component_set.pyi b/stubs/pyomo/common/collections/component_set.pyi new file mode 100644 index 000000000..4ff0b9ea1 --- /dev/null +++ b/stubs/pyomo/common/collections/component_set.pyi @@ -0,0 +1,19 @@ +from collections.abc import MutableSet as collections_MutableSet + +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots + +class ComponentSet(AutoSlots.Mixin, collections_MutableSet): + __autoslot_mappers__: Incomplete + hasher: Incomplete + def __init__(self, iterable=None) -> None: ... + def update(self, iterable) -> None: ... + def __contains__(self, val) -> bool: ... + def __iter__(self): ... + def __len__(self) -> int: ... + def add(self, val) -> None: ... + def discard(self, val) -> None: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def clear(self) -> None: ... + def remove(self, val) -> None: ... diff --git a/stubs/pyomo/common/collections/orderedset.pyi b/stubs/pyomo/common/collections/orderedset.pyi new file mode 100644 index 000000000..abed5a8e0 --- /dev/null +++ b/stubs/pyomo/common/collections/orderedset.pyi @@ -0,0 +1,18 @@ +from collections import OrderedDict as OrderedDict +from collections.abc import MutableSet + +from pyomo.common.autoslots import AutoSlots as AutoSlots + +class OrderedSet(AutoSlots.Mixin, MutableSet): + def __init__(self, iterable=None) -> None: ... + def update(self, iterable) -> None: ... + def __contains__(self, val) -> bool: ... + def __iter__(self): ... + def __len__(self) -> int: ... + def add(self, val) -> None: ... + def discard(self, val) -> None: ... + def clear(self) -> None: ... + def remove(self, val) -> None: ... + def intersection(self, other): ... + def union(self, other): ... + def __reversed__(self): ... diff --git a/stubs/pyomo/common/config.pyi b/stubs/pyomo/common/config.pyi new file mode 100644 index 000000000..e0fff8278 --- /dev/null +++ b/stubs/pyomo/common/config.pyi @@ -0,0 +1,238 @@ +import enum +import types +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import Mapping as Mapping +from pyomo.common.collections import Sequence as Sequence +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.fileutils import import_file as import_file +from pyomo.common.flags import NOTSET as NOTSET +from pyomo.common.flags import building_documentation as building_documentation +from pyomo.common.formatting import wrap_reStructuredText as wrap_reStructuredText + +logger: Incomplete +USER_OPTION: int +ADVANCED_OPTION: int +DEVELOPER_OPTION: int + +def Bool(val): ... +def Integer(val): ... +def PositiveInt(val): ... +def NegativeInt(val): ... +def NonPositiveInt(val): ... +def NonNegativeInt(val): ... +def PositiveFloat(val): ... +def NegativeFloat(val): ... +def NonPositiveFloat(val): ... +def NonNegativeFloat(val): ... + +class In: + def __new__(cls, domain=None, cast=None): ... + def __init__(self, domain, cast=None) -> None: ... + def __call__(self, value): ... + def domain_name(self): ... + +class InEnum: + def __init__(self, domain) -> None: ... + def __call__(self, value): ... + def domain_name(self): ... + +class IsInstance: + baseClasses: Incomplete + document_full_base_names: Incomplete + def __init__(self, *bases, document_full_base_names: bool = False) -> None: ... + def __call__(self, obj): ... + def domain_name(self): ... + +class ListOf: + itemtype: Incomplete + domain: Incomplete + string_lexer: Incomplete + def __init__(self, itemtype, domain=None, string_lexer=...) -> None: ... + def __call__(self, value): ... + def domain_name(self): ... + +class Module: + basePath: Incomplete + expandPath: Incomplete + def __init__(self, basePath=None, expandPath=None) -> None: ... + def __call__(self, module_id): ... + +class Path: + BasePath: Incomplete + SuppressPathExpansion: bool + basePath: Incomplete + expandPath: Incomplete + def __init__(self, basePath=None, expandPath=None) -> None: ... + def __call__(self, path): ... + def domain_name(self): ... + +class PathList(Path): + def __call__(self, data): ... + +class DynamicImplicitDomain: + callback: Incomplete + def __init__(self, callback) -> None: ... + def __call__(self, key, value): ... + +class ConfigEnum(enum.Enum): + def __new__(cls, value, *args): ... + @classmethod + def from_enum_or_string(cls, arg): ... + +__doc__: str + +class _UnpickleableDomain: + def __init__(self, obj) -> None: ... + def __call__(self, arg): ... + +class ConfigFormatter: + def generate(self, config, indent_spacing: int = 2, width: int = 78, visibility=None): ... + +class String_ConfigFormatter(ConfigFormatter): + def __init__(self, block_start, block_end, item_start, item_body, item_end) -> None: ... + +class LaTeX_ConfigFormatter(String_ConfigFormatter): + def __init__(self) -> None: ... + +class numpydoc_ConfigFormatter(ConfigFormatter): ... + +def add_docstring_list(docstring, configdict, indent_by: int = 4): ... + +class document_kwargs_from_configdict: + config: Incomplete + section: Incomplete + indent_spacing: Incomplete + width: Incomplete + visibility: Incomplete + doc: Incomplete + def __init__( + self, + config, + section: str = 'Keyword Arguments', + indent_spacing: int = 4, + width: int = 78, + visibility=None, + doc=None, + ) -> None: ... + def __call__(self, fcn): ... + +class UninitializedMixin: ... + +class ConfigBase: + class NoArgument: ... + __class__: Incomplete + def __init__( + self, default=None, domain=None, description=None, doc=None, visibility: int = 0 + ) -> None: ... + def __call__( + self, + value=..., + default=..., + domain=..., + description=..., + doc=..., + visibility=..., + implicit=..., + implicit_domain=..., + preserve_implicit: bool = False, + ): ... + def name(self, fully_qualified: bool = False): ... + def domain_name(self): ... + def set_default_value(self, default) -> None: ... + def set_domain(self, domain) -> None: ... + def reset(self) -> None: ... + def declare_as_argument(self, *args, **kwds): ... + def initialize_argparse(self, parser): ... + def import_argparse(self, parsed_args): ... + def display( + self, content_filter=None, indent_spacing: int = 2, ostream=None, visibility=None + ) -> None: ... + def generate_yaml_template( + self, indent_spacing: int = 2, width: int = 78, visibility: int = 0 + ): ... + def generate_documentation( + self, + block_start=None, + block_end=None, + item_start=None, + item_body=None, + item_end=None, + indent_spacing: int = 2, + width: int = 78, + visibility=None, + format: str = 'latex', + ): ... + def user_values(self) -> Generator[Incomplete]: ... + def unused_user_values(self) -> Generator[Incomplete]: ... + +class ConfigValue(ConfigBase): + def value(self, accessValue: bool = True): ... + def set_value(self, value) -> None: ... + +class ImmutableConfigValue(ConfigValue): + def __new__(self, *args, **kwds): ... + +class MarkImmutable: + def __init__(self, *args) -> None: ... + def lock(self) -> None: ... + def release_lock(self) -> None: ... + def __enter__(self): ... + def __exit__( + self, t: type[BaseException] | None, v: BaseException | None, tb: types.TracebackType | None + ) -> None: ... + +class ConfigList(ConfigBase, Sequence): + def __init__( + self, default=None, domain=None, description=None, doc=None, visibility: int = 0 + ) -> None: ... + def __getitem__(self, key): ... + def get(self, key, default=...): ... + def __setitem__(self, key, val) -> None: ... + def __len__(self) -> int: ... + def __iter__(self): ... + def value(self, accessValue: bool = True): ... + def set_value(self, value) -> None: ... + def append(self, value=...) -> None: ... + def add(self, value=...): ... + +class ConfigDict(ConfigBase, Mapping): + content_filters: Incomplete + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + def domain_name(self): ... + def __dir__(self): ... + def __getitem__(self, key): ... + def get(self, key, default=...): ... + def setdefault(self, key, default=...): ... + def __setitem__(self, key, val) -> None: ... + def __delitem__(self, key) -> None: ... + def __contains__(self, key) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self): ... + def __getattr__(self, attr): ... + def __setattr__(self, name, value) -> None: ... + def __delattr__(self, name) -> None: ... + def keys(self): ... + def values(self): ... + def items(self) -> Generator[Incomplete]: ... + def iterkeys(self): ... + def itervalues(self): ... + def iteritems(self): ... + def declare(self, name, config): ... + def declare_from(self, other, skip=None) -> None: ... + def add(self, name, config): ... + def value(self, accessValue: bool = True): ... + def set_value(self, value, skip_implicit: bool = False): ... + def reset(self) -> None: ... + +ConfigBlock = ConfigDict diff --git a/stubs/pyomo/common/dependencies.pyi b/stubs/pyomo/common/dependencies.pyi new file mode 100644 index 000000000..cfc7be4a9 --- /dev/null +++ b/stubs/pyomo/common/dependencies.pyi @@ -0,0 +1,128 @@ +import types +from types import ModuleType + +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import DeferredImportError as DeferredImportError +from pyomo.common.flags import building_documentation as building_documentation +from pyomo.common.flags import in_testing_environment as in_testing_environment +from pyomo.common.flags import serializing as serializing + +SUPPRESS_DEPENDENCY_WARNINGS: bool + +class ModuleUnavailable: + def __init__(self, name, message, version_error, import_error, package) -> None: ... + def __getattr__(self, attr) -> None: ... + def mro(self): ... + def log_import_warning(self, logger: str = 'pyomo', msg=None) -> None: ... + def generate_import_warning(self, logger: str = 'pyomo.common') -> None: ... + +class DeferredImportModule: + def __init__(self, indicator, deferred_submodules, submodule_name) -> None: ... + def __getattr__(self, attr): ... + def mro(self): ... + +def UnavailableClass(unavailable_module): ... + +class _DeferredImportIndicatorBase: + def __and__(self, other): ... + def __or__(self, other): ... + def __rand__(self, other): ... + def __ror__(self, other): ... + +class DeferredImportIndicator(_DeferredImportIndicatorBase): + def __init__( + self, + name, + error_message, + catch_exceptions, + minimum_version, + original_globals, + callback, + importer, + deferred_submodules, + ) -> None: ... + def __bool__(self) -> bool: ... + def resolve(self) -> None: ... + def replace_self_in_globals(self, _globals) -> None: ... + +class _DeferredAnd(_DeferredImportIndicatorBase): + def __init__(self, a, b) -> None: ... + def __bool__(self) -> bool: ... + +class _DeferredOr(_DeferredImportIndicatorBase): + def __init__(self, a, b) -> None: ... + def __bool__(self) -> bool: ... + +def check_min_version(module, min_version): ... + +class DeferredImportCallbackLoader: + def __init__(self, loader, deferred_indicators: list[DeferredImportIndicator]) -> None: ... + def module_repr(self, module: ModuleType) -> str: ... + def create_module(self, spec) -> ModuleType: ... + def exec_module(self, module: ModuleType) -> None: ... + def load_module(self, fullname) -> ModuleType: ... + +class DeferredImportCallbackFinder: + def find_spec(self, fullname, path, target=None): ... + def invalidate_caches(self) -> None: ... + +def attempt_import( + name, + error_message=None, + only_catch_importerror=None, + minimum_version=None, + alt_names=None, + callback=None, + importer=None, + defer_check=None, + defer_import=None, + deferred_submodules=None, + catch_exceptions=None, +): ... +def declare_deferred_modules_as_importable(globals_dict): ... + +class declare_modules_as_importable: + globals_dict: Incomplete + init_dict: Incomplete + init_modules: Incomplete + def __init__(self, globals_dict) -> None: ... + def __enter__(self) -> None: ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +yaml_load_args: Incomplete +ctypes: Incomplete +_: Incomplete +random: Incomplete +dill: Incomplete +dill_available: Incomplete +mpi4py: Incomplete +mpi4py_available: Incomplete +networkx: Incomplete +networkx_available: Incomplete +numpy: Incomplete +numpy_available: Incomplete +pandas: Incomplete +pandas_available: Incomplete +pathlib: Incomplete +pathlib_available: Incomplete +pint: Incomplete +pint_available: Incomplete +plotly: Incomplete +plotly_available: Incomplete +pympler: Incomplete +pympler_available: Incomplete +pyutilib: Incomplete +pyutilib_available: Incomplete +scipy: Incomplete +scipy_available: Incomplete +yaml: Incomplete +yaml_available: Incomplete +matplotlib: Incomplete +matplotlib_available: Incomplete diff --git a/stubs/pyomo/common/deprecation.pyi b/stubs/pyomo/common/deprecation.pyi new file mode 100644 index 000000000..86bcdb14a --- /dev/null +++ b/stubs/pyomo/common/deprecation.pyi @@ -0,0 +1,45 @@ +import types +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.flags import NOTSET as NOTSET +from pyomo.common.flags import building_documentation as building_documentation +from pyomo.common.flags import in_testing_environment as in_testing_environment + +def default_deprecation_msg(obj, user_msg, version, remove_in): ... +def deprecation_warning( + msg, logger=None, version=None, remove_in=None, calling_frame=None +) -> None: ... +def deprecated(msg=None, logger=None, version=None, remove_in=None): ... +def relocated_module(new_name, msg=None, logger=None, version=None, remove_in=None) -> None: ... +def relocated_module_attribute( + local, target, version, remove_in=None, msg=None, f_globals=None +): ... + +class RenamedClass(type): + def __new__(cls, name, bases, classdict, *args, **kwargs): ... + def __instancecheck__(cls, instance): ... + def __subclasscheck__(cls, subclass): ... + +class MovedModuleLoader: + def __init__(self, info) -> None: ... + def create_module(self, spec) -> types.ModuleType: ... + def exec_module(self, module: types.ModuleType) -> None: ... + +class MovedModuleFinder: + mapping: Incomplete + def find_spec(self, fullname, path, target=None): ... + def invalidate_caches(self) -> None: ... + +class MovedModuleInfo(NamedTuple): + old_name: str + new_name: str + msg: str + logger: str + version: str + remove_in: str + +def moved_module( + old_name, new_name, msg=..., logger=None, version=None, remove_in=None +) -> None: ... diff --git a/stubs/pyomo/common/download.pyi b/stubs/pyomo/common/download.pyi new file mode 100644 index 000000000..c90097c36 --- /dev/null +++ b/stubs/pyomo/common/download.pyi @@ -0,0 +1,41 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import + +from . import envvar as envvar +from .deprecation import deprecated as deprecated +from .errors import DeveloperError as DeveloperError + +request: Incomplete +urllib_error: Incomplete +ssl: Incomplete +zipfile: Incomplete +tarfile: Incomplete +gzip: Incomplete +distro: Incomplete +distro_available: Incomplete +logger: Incomplete +DownloadFactory: Incomplete + +class FileDownloader: + target: Incomplete + insecure: Incomplete + cacert: Incomplete + def __init__(self, insecure: bool = False, cacert=None) -> None: ... + @classmethod + def get_sysinfo(cls): ... + @classmethod + def get_os_version(cls, normalize: bool = True): ... + def get_url(self, urlmap): ... + def get_platform_url(self, urlmap): ... + def create_parser(self, parser=None): ... + def parse_args(self, argv) -> None: ... + def set_destination_filename(self, default) -> None: ... + def destination(self): ... + def retrieve_url(self, url): ... + def get_file(self, url, binary) -> None: ... + def get_binary_file(self, url): ... + def get_text_file(self, url): ... + def get_binary_file_from_zip_archive(self, url, srcname) -> None: ... + def get_zip_archive(self, url, dirOffset: int = 0) -> None: ... + def get_tar_archive(self, url, dirOffset: int = 0): ... + def get_gzipped_binary_file(self, url) -> None: ... diff --git a/stubs/pyomo/common/enums.pyi b/stubs/pyomo/common/enums.pyi new file mode 100644 index 000000000..02f42777e --- /dev/null +++ b/stubs/pyomo/common/enums.pyi @@ -0,0 +1,28 @@ +import enum + +from _typeshed import Incomplete + +Enum = enum.Enum + +class IntEnum(enum.IntEnum): + __doc__ = ... + def to_bytes(self, /, length: int = 1, byteorder: str = 'big', *, signed: bool = False): ... + @classmethod + def from_bytes(cls, bytes, byteorder: str = 'big', *, signed: bool = False): ... + +class ExtendedEnumType(_EnumType): + def __getattr__(cls, attr): ... + def __iter__(cls): ... + def __contains__(cls, member) -> bool: ... + def __dir__(self): ... + def __instancecheck__(cls, instance): ... + def __new__(metacls, cls, bases, classdict, **kwds): ... + +class NamedIntEnum(IntEnum): ... + +class ObjectiveSense(NamedIntEnum): + minimize = 1 + maximize = -1 + +minimize: Incomplete +maximize: Incomplete diff --git a/stubs/pyomo/common/env.pyi b/stubs/pyomo/common/env.pyi new file mode 100644 index 000000000..e7fdc1608 --- /dev/null +++ b/stubs/pyomo/common/env.pyi @@ -0,0 +1,58 @@ +import types + +from _typeshed import Incomplete + +from .dependencies import ctypes as ctypes + +class _RestorableEnvironInterface: + dll: Incomplete + def __init__(self, dll) -> None: ... + def restore(self) -> None: ... + def __getitem__(self, key): ... + def __setitem__(self, key, val) -> None: ... + def __delitem__(self, key) -> None: ... + +class _OSEnviron: + def available(self): ... + def get_env_dict(self): ... + def getenv(self, key): ... + def wgetenv(self, key): ... + def putenv_s(self, key, val) -> None: ... + def wputenv_s(self, key, val) -> None: ... + +class _MsvcrtDLL: + dll: Incomplete + def __init__(self, name) -> None: ... + putenv_s: Incomplete + wputenv_s: Incomplete + getenv: Incomplete + wgetenv: Incomplete + def available(self): ... + def get_env_dict(self): ... + +class _Win32DLL: + dll: Incomplete + def __init__(self, name) -> None: ... + putenv_s: Incomplete + wputenv_s: Incomplete + def available(self): ... + def getenv(self, key): ... + def wgetenv(self, key): ... + def get_env_dict(self): ... + +class CtypesEnviron: + DLLs: Incomplete + interfaces: Incomplete + def __init__(self, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + exc_traceback: types.TracebackType | None, + ) -> None: ... + def restore(self) -> None: ... + def __getitem__(self, key): ... + def __contains__(self, key) -> bool: ... + def __setitem__(self, key, val) -> None: ... + def __delitem__(self, key) -> None: ... diff --git a/stubs/pyomo/common/envvar.pyi b/stubs/pyomo/common/envvar.pyi new file mode 100644 index 000000000..c9f7bfc5f --- /dev/null +++ b/stubs/pyomo/common/envvar.pyi @@ -0,0 +1,3 @@ +from _typeshed import Incomplete + +PYOMO_CONFIG_DIR: Incomplete diff --git a/stubs/pyomo/common/errors.pyi b/stubs/pyomo/common/errors.pyi new file mode 100644 index 000000000..fde7c3021 --- /dev/null +++ b/stubs/pyomo/common/errors.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete + +def format_exception(msg, prolog=None, epilog=None, exception=None, width: int = 76): ... + +class ApplicationError(Exception): ... +class PyomoException(Exception): ... +class DeferredImportError(ImportError): ... +class DeveloperError(PyomoException, NotImplementedError): ... +class InfeasibleConstraintException(PyomoException): ... +class IterationLimitError(PyomoException, RuntimeError): ... +class IntervalException(PyomoException, ValueError): ... +class InvalidValueError(PyomoException, ValueError): ... +class MouseTrap(PyomoException, NotImplementedError): ... +class NondifferentiableError(PyomoException, ValueError): ... +class TempfileContextError(PyomoException, IndexError): ... + +class TemplateExpressionError(ValueError): + template: Incomplete + def __init__(self, template, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/common/extensions.pyi b/stubs/pyomo/common/extensions.pyi new file mode 100644 index 000000000..a89dd55eb --- /dev/null +++ b/stubs/pyomo/common/extensions.pyi @@ -0,0 +1,3 @@ +from _typeshed import Incomplete + +ExtensionBuilderFactory: Incomplete diff --git a/stubs/pyomo/common/factory.pyi b/stubs/pyomo/common/factory.pyi new file mode 100644 index 000000000..8210142cc --- /dev/null +++ b/stubs/pyomo/common/factory.pyi @@ -0,0 +1,9 @@ +class Factory: + def __init__(self, description=None) -> None: ... + def __call__(self, name, **kwds): ... + def __iter__(self): ... + def __contains__(self, name) -> bool: ... + def get_class(self, name): ... + def doc(self, name): ... + def unregister(self, name) -> None: ... + def register(self, name, doc=None): ... diff --git a/stubs/pyomo/common/fileutils.pyi b/stubs/pyomo/common/fileutils.pyi new file mode 100644 index 000000000..b959c4679 --- /dev/null +++ b/stubs/pyomo/common/fileutils.pyi @@ -0,0 +1,65 @@ +from _typeshed import Incomplete + +from . import envvar as envvar +from .dependencies import ctypes as ctypes +from .deprecation import deprecated as deprecated +from .deprecation import relocated_module_attribute as relocated_module_attribute + +def this_file(stack_offset: int = 1): ... +def this_file_dir(stack_offset: int = 1): ... + +PYOMO_ROOT_DIR: Incomplete + +def find_path( + name, + validate, + cwd: bool = True, + mode=..., + ext=None, + pathlist=[], + allow_pathlist_deep_references: bool = True, +): ... +def find_file( + filename, + cwd: bool = True, + mode=..., + ext=None, + pathlist=[], + allow_pathlist_deep_references: bool = True, +): ... +def find_dir( + dirname, cwd: bool = True, mode=..., pathlist=[], allow_pathlist_deep_references: bool = True +): ... +def find_library(libname, cwd: bool = True, include_PATH: bool = True, pathlist=None): ... +def find_executable(exename, cwd: bool = True, include_PATH: bool = True, pathlist=None): ... +def import_file(path, clear_cache: bool = False, infer_package: bool = True, module_name=None): ... + +class PathData: + def __init__(self, manager, name) -> None: ... + def path(self): ... + def set_path(self, value) -> None: ... + def get_path(self): ... + def disable(self) -> None: ... + def available(self): ... + def rehash(self) -> None: ... + def __nonzero__(self): ... + __bool__ = __nonzero__ + +class ExecutableData(PathData): + @property + def executable(self): ... + @executable.setter + def executable(self, value) -> None: ... + +class PathManager: + pathlist: Incomplete + def __init__(self, finder, dataClass) -> None: ... + def __call__(self, path): ... + def rehash(self) -> None: ... + +Executable: Incomplete +Library: Incomplete + +def register_executable(name, validate=None): ... +def registered_executable(name): ... +def unregister_executable(name) -> None: ... diff --git a/stubs/pyomo/common/flags.pyi b/stubs/pyomo/common/flags.pyi new file mode 100644 index 000000000..1575dd598 --- /dev/null +++ b/stubs/pyomo/common/flags.pyi @@ -0,0 +1,8 @@ +class FlagType(type): + def __new__(mcs, name, bases, dct): ... + +class NOTSET(metaclass=FlagType): ... + +def in_testing_environment(state=...): ... +def building_documentation(state=...): ... +def serializing(): ... diff --git a/stubs/pyomo/common/formatting.pyi b/stubs/pyomo/common/formatting.pyi new file mode 100644 index 000000000..b1e2bf17e --- /dev/null +++ b/stubs/pyomo/common/formatting.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.common.sorting import sorted_robust as sorted_robust + +def tostr(value, quote_str: bool = False): ... +def tabular_writer(ostream, prefix, data, header, row_generator) -> None: ... + +class StreamIndenter: + os: Incomplete + indent: Incomplete + stripped_indent: Incomplete + newline: bool + def __init__(self, ostream, indent=...) -> None: ... + def __getattr__(self, name): ... + def write(self, data) -> None: ... + def writelines(self, sequence) -> None: ... + +def wrap_reStructuredText(docstr, wrapper): ... diff --git a/stubs/pyomo/common/gc_manager.pyi b/stubs/pyomo/common/gc_manager.pyi new file mode 100644 index 000000000..8a32ecc86 --- /dev/null +++ b/stubs/pyomo/common/gc_manager.pyi @@ -0,0 +1,22 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.multithread import MultiThreadWrapper as MultiThreadWrapper + +class __PauseGCCompanion: + def __init__(self) -> None: ... + +PauseGCCompanion: __PauseGCCompanion + +class PauseGC: + stack_pointer: Incomplete + reenable_gc: Incomplete + def __init__(self) -> None: ... + def __enter__(self): ... + def __exit__( + self, + type: type[BaseException] | None, + value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def close(self) -> None: ... diff --git a/stubs/pyomo/common/gsl.pyi b/stubs/pyomo/common/gsl.pyi new file mode 100644 index 000000000..dd5490eba --- /dev/null +++ b/stubs/pyomo/common/gsl.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete +from pyomo.common import Library as Library +from pyomo.common.deprecation import deprecated as deprecated + +logger: Incomplete + +def get_gsl(downloader) -> None: ... +def find_GSL(): ... diff --git a/stubs/pyomo/common/log.pyi b/stubs/pyomo/common/log.pyi new file mode 100644 index 000000000..416fa4e94 --- /dev/null +++ b/stubs/pyomo/common/log.pyi @@ -0,0 +1,96 @@ +import io +import logging +import types +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.fileutils import PYOMO_ROOT_DIR as PYOMO_ROOT_DIR +from pyomo.common.flags import building_documentation as building_documentation +from pyomo.common.flags import in_testing_environment as in_testing_environment +from pyomo.common.formatting import wrap_reStructuredText as wrap_reStructuredText +from pyomo.version.info import releaselevel as releaselevel + +def RTD(_id): ... +def is_debug_set(logger): ... + +class WrappingFormatter(logging.Formatter): + basepath: Incomplete + def __init__(self, **kwds) -> None: ... + def format(self, record): ... + +class LegacyPyomoFormatter(logging.Formatter): + verbosity: Incomplete + standard_formatter: Incomplete + verbose_formatter: Incomplete + def __init__(self, **kwds) -> None: ... + def format(self, record): ... + +class StdoutHandler(logging.StreamHandler): + stream: Incomplete + def flush(self) -> None: ... + def emit(self, record) -> None: ... + +class Preformatted: + msg: Incomplete + def __init__(self, msg) -> None: ... + +class _GlobalLogFilter: + logger: Incomplete + def __init__(self) -> None: ... + def filter(self, record): ... + +pyomo_logger: Incomplete +pyomo_handler: Incomplete +pyomo_formatter: Incomplete + +class LogHandler(logging.StreamHandler): + def __init__(self, base: str = '', stream=None, level=..., verbosity=None) -> None: ... + +class LoggingIntercept: + handler: Incomplete + output: Incomplete + def __init__( + self, output=None, module=None, level=..., formatter=None, logger=None + ) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + @property + def module(self): ... + +class LogStream(io.TextIOBase): + def __init__(self, level, logger) -> None: ... + def write(self, s: str) -> int: ... + def flush(self) -> None: ... + def redirect_streams(self, redirects) -> Generator[Incomplete]: ... + +class _StreamRedirector: + handler: Incomplete + fd: Incomplete + orig_stream: Incomplete + def __init__(self, handler, fd) -> None: ... + def __enter__(self) -> None: ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + +class _LastResortRedirector: + fd: Incomplete + orig_stream: Incomplete + def __init__(self, fd) -> None: ... + orig: Incomplete + def __enter__(self) -> None: ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... diff --git a/stubs/pyomo/common/modeling.pyi b/stubs/pyomo/common/modeling.pyi new file mode 100644 index 000000000..70c94e066 --- /dev/null +++ b/stubs/pyomo/common/modeling.pyi @@ -0,0 +1,8 @@ +from .dependencies import random as random +from .flags import NOTSET as NOTSET +from .flags import FlagType as FlagType + +NoArgumentGiven = NOTSET + +def randint(a, b): ... +def unique_component_name(instance, name): ... diff --git a/stubs/pyomo/common/multithread.pyi b/stubs/pyomo/common/multithread.pyi new file mode 100644 index 000000000..2208f79cd --- /dev/null +++ b/stubs/pyomo/common/multithread.pyi @@ -0,0 +1,22 @@ +import types + +class MultiThreadWrapper: + def __init__(self, base) -> None: ... + def __getattr__(self, attr): ... + def __setattr__(self, attr, value) -> None: ... + def __delattr__(self, attr) -> None: ... + def __enter__(self): ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ): ... + def __dir__(self): ... + def __new__(cls, wrapped): ... + +class MultiThreadWrapperWithMain(MultiThreadWrapper): + def __init__(self, base) -> None: ... + def __getattr__(self, attr): ... + def __setattr__(self, attr, value) -> None: ... + def __dir__(self): ... diff --git a/stubs/pyomo/common/numeric_types.pyi b/stubs/pyomo/common/numeric_types.pyi new file mode 100644 index 000000000..d17a82fe2 --- /dev/null +++ b/stubs/pyomo/common/numeric_types.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.errors import TemplateExpressionError as TemplateExpressionError + +logger: Incomplete +nonpyomo_leaf_types: Incomplete +native_numeric_types: Incomplete +native_integer_types: Incomplete +native_logical_types: Incomplete +native_complex_types: Incomplete +native_types: Incomplete + +def RegisterNumericType(new_type: type): ... +def RegisterIntegerType(new_type: type): ... +def RegisterBooleanType(new_type: type): ... +def RegisterComplexType(new_type: type): ... +def RegisterLogicalType(new_type: type): ... +def check_if_native_type(obj): ... +def check_if_logical_type(obj): ... +def check_if_numeric_type(obj): ... +def value(obj, exception: bool = True): ... diff --git a/stubs/pyomo/common/plugin_base.pyi b/stubs/pyomo/common/plugin_base.pyi new file mode 100644 index 000000000..71ba1225f --- /dev/null +++ b/stubs/pyomo/common/plugin_base.pyi @@ -0,0 +1,66 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import PyomoException as PyomoException + +class PluginGlobals: + @staticmethod + def add_env(name) -> None: ... + @staticmethod + def pop_env() -> None: ... + @staticmethod + def clear() -> None: ... + +class PluginError(PyomoException): ... + +def alias(name, doc=None, subclass=None) -> None: ... +def implements(interface, inherit=None, namespace=None, service: bool = False) -> None: ... + +class InterfaceMeta(type): + def __new__(cls, name, bases, classdict, *args, **kwargs): ... + +class Interface(metaclass=InterfaceMeta): ... + +class _deprecated_plugin_dict(dict): + def __init__(self, name, classdict) -> None: ... + def __setitem__(self, key, val) -> None: ... + def items(self): ... + +class DeprecatedInterfaceMeta(InterfaceMeta): + def __new__(cls, name, bases, classdict, *args, **kwargs): ... + +class DeprecatedInterface(Interface, metaclass=DeprecatedInterfaceMeta): ... + +class PluginMeta(type): + def __new__(cls, name, bases, classdict, *args, **kwargs): ... + +class Plugin(metaclass=PluginMeta): + def __new__(cls): ... + def activate(self) -> None: ... + enable = activate + def deactivate(self) -> None: ... + disable = deactivate + def enabled(self): ... + +class SingletonPlugin(Plugin): + __singleton__: bool + +class ExtensionPoint: + def __init__(self, interface) -> None: ... + def __iter__(self, key=None, all: bool = False): ... + def __len__(self) -> int: ... + def extensions(self, all: bool = False, key=None): ... + def __call__(self, key=None, all: bool = False): ... + def service(self, key=None, all: bool = False): ... + +class PluginFactory: + interface: Incomplete + def __init__(self, interface) -> None: ... + def __call__(self, name, *args, **kwds): ... + def services(self): ... + def get_class(self, name): ... + def doc(self, name): ... + def deactivate(self, name) -> None: ... + def activate(self, name) -> None: ... + +CreatePluginFactory = PluginFactory diff --git a/stubs/pyomo/common/plugins.pyi b/stubs/pyomo/common/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/common/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/common/pyomo_typing.pyi b/stubs/pyomo/common/pyomo_typing.pyi new file mode 100644 index 000000000..ce0957a02 --- /dev/null +++ b/stubs/pyomo/common/pyomo_typing.pyi @@ -0,0 +1,4 @@ +import typing + +def overload(func: typing.Callable): ... +def get_overloads_for(func: typing.Callable): ... diff --git a/stubs/pyomo/common/shutdown.pyi b/stubs/pyomo/common/shutdown.pyi new file mode 100644 index 000000000..285b1a905 --- /dev/null +++ b/stubs/pyomo/common/shutdown.pyi @@ -0,0 +1 @@ +def python_is_shutting_down(): ... diff --git a/stubs/pyomo/common/sorting.pyi b/stubs/pyomo/common/sorting.pyi new file mode 100644 index 000000000..180827d48 --- /dev/null +++ b/stubs/pyomo/common/sorting.pyi @@ -0,0 +1,5 @@ +class _robust_sort_keyfcn: + def __init__(self, key=None) -> None: ... + def __call__(self, val): ... + +def sorted_robust(iterable, key=None, reverse: bool = False): ... diff --git a/stubs/pyomo/common/tee.pyi b/stubs/pyomo/common/tee.pyi new file mode 100644 index 000000000..a9f847fe9 --- /dev/null +++ b/stubs/pyomo/common/tee.pyi @@ -0,0 +1,89 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.log import LoggingIntercept as LoggingIntercept +from pyomo.common.log import LogStream as LogStream + +logger: Incomplete + +class _SignalFlush: + def __init__(self, ostream, handle) -> None: ... + def flush(self) -> None: ... + def __getattr__(self, attr): ... + def __setattr__(self, attr, val): ... + +class _AutoFlush(_SignalFlush): + def write(self, data) -> None: ... + def writelines(self, data) -> None: ... + +class redirect_fd: + fd: Incomplete + std: Incomplete + target: Incomplete + target_file: Incomplete + synchronize: Incomplete + original_file: Incomplete + original_fd: Incomplete + def __init__(self, fd: int = 1, output=None, synchronize: bool = True) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + +class capture_output: + output: Incomplete + old: Incomplete + tee: Incomplete + capture_fd: Incomplete + context_stack: Incomplete + def __init__(self, output=None, capture_fd: bool = False) -> None: ... + output_stream: Incomplete + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + def __del__(self) -> None: ... + def setup(self): ... + def reset(self): ... + +class _StreamHandle: + buffering: Incomplete + newlines: Incomplete + flush: bool + write_file: Incomplete + decoder_buffer: bytes + encoding: Incomplete + output_buffer: str + def __init__(self, mode, buffering, encoding, newline) -> None: ... + def fileno(self): ... + write_pipe: Incomplete + def close(self) -> None: ... + def finalize(self, ostreams) -> None: ... + def decodeIncomingBuffer(self) -> None: ... + def writeOutputBuffer(self, ostreams, flush) -> None: ... + +class TeeStream: + ostreams: Incomplete + encoding: Incomplete + buffering: Incomplete + def __init__(self, *ostreams, encoding=None, buffering: int = -1) -> None: ... + @property + def STDOUT(self): ... + @property + def STDERR(self): ... + def open(self, mode: str = 'w', buffering: int = -1, encoding=None, newline=None): ... + def close(self, in_exception: bool = False) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + def __del__(self) -> None: ... diff --git a/stubs/pyomo/common/tempfiles.pyi b/stubs/pyomo/common/tempfiles.pyi new file mode 100644 index 000000000..954b22d17 --- /dev/null +++ b/stubs/pyomo/common/tempfiles.pyi @@ -0,0 +1,65 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import pyutilib_available as pyutilib_available +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import TempfileContextError as TempfileContextError +from pyomo.common.multithread import MultiThreadWrapperWithMain as MultiThreadWrapperWithMain + +deletion_errors_are_fatal: bool +logger: Incomplete +pyutilib_tempfiles: Incomplete +_: Incomplete + +class TempfileManagerClass: + tempdir: Incomplete + def __init__(self) -> None: ... + def __del__(self) -> None: ... + def shutdown(self, remove: bool = True) -> None: ... + def context(self): ... + def create_tempfile(self, suffix=None, prefix=None, text: bool = False, dir=None): ... + def create_tempdir(self, suffix=None, prefix=None, dir=None): ... + def add_tempfile(self, filename, exists: bool = True): ... + def clear_tempfiles(self, remove: bool = True) -> None: ... + def sequential_files(self, ctr: int = 0) -> None: ... + def unique_files(self) -> None: ... + def new_context(self): ... + def push(self): ... + def pop(self, remove: bool = True): ... + def __enter__(self): ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: ... + +class TempfileContext: + manager: Incomplete + tempfiles: Incomplete + tempdir: Incomplete + os: Incomplete + shutil: Incomplete + def __init__(self, manager) -> None: ... + def __del__(self) -> None: ... + def __enter__(self): ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: ... + def mkstemp(self, suffix=None, prefix=None, dir=None, text: bool = False): ... + def mkdtemp(self, suffix=None, prefix=None, dir=None): ... + def gettempdir(self): ... + def gettempdirb(self): ... + def gettempprefix(self): ... + def gettempprefixb(self): ... + def create_tempfile(self, suffix=None, prefix=None, text: bool = False, dir=None): ... + def create_tempdir(self, suffix=None, prefix=None, dir=None): ... + def add_tempfile(self, filename, exists: bool = True) -> None: ... + def release(self, remove: bool = True) -> None: ... + +TempfileManager: TempfileManagerClass diff --git a/stubs/pyomo/common/timing.pyi b/stubs/pyomo/common/timing.pyi new file mode 100644 index 000000000..a856d94d5 --- /dev/null +++ b/stubs/pyomo/common/timing.pyi @@ -0,0 +1,97 @@ +import time +import types + +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecation_warning as deprecation_warning + +class report_timing: + def __init__(self, stream: bool = True, level=...) -> None: ... + def reset(self) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + +class GeneralTimer: + fmt: Incomplete + data: Incomplete + def __init__(self, fmt, data) -> None: ... + def report(self) -> None: ... + @property + def obj(self): ... + @property + def timer(self): ... + +class ConstructionTimer: + msg: str + in_progress: str + obj: Incomplete + timer: Incomplete + def __init__(self, obj) -> None: ... + def report(self) -> None: ... + @property + def name(self): ... + +class TransformationTimer: + msg: str + in_progress: str + obj: Incomplete + mode: str + timer: Incomplete + def __init__(self, obj, mode=None) -> None: ... + def report(self) -> None: ... + @property + def name(self): ... + +default_timer = time.perf_counter + +class TicTocTimer: + ostream: Incomplete + logger: Incomplete + level: Incomplete + def __init__(self, ostream=..., logger=None) -> None: ... + def tic(self, msg=..., *args, ostream=..., logger=..., level=...) -> None: ... + def toc(self, msg=..., *args, delta: bool = True, ostream=..., logger=..., level=...): ... + def stop(self): ... + def start(self) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + +tic: Incomplete +toc: Incomplete + +class _HierarchicalHelper: + tic_toc: Incomplete + timers: Incomplete + total_time: int + n_calls: int + def __init__(self) -> None: ... + def start(self) -> None: ... + def stop(self) -> None: ... + def to_str(self, indent, stage_identifier_lengths): ... + def get_timers(self, res, prefix) -> None: ... + def flatten(self) -> None: ... + def clear_except(self, *args) -> None: ... + +class HierarchicalTimer: + stack: Incomplete + timers: Incomplete + def __init__(self) -> None: ... + def start(self, identifier) -> None: ... + def stop(self, identifier) -> None: ... + def reset(self) -> None: ... + def get_total_time(self, identifier): ... + def get_num_calls(self, identifier): ... + def get_relative_percent_time(self, identifier): ... + def get_total_percent_time(self, identifier): ... + def get_timers(self): ... + def flatten(self) -> None: ... + def clear_except(self, *args) -> None: ... diff --git a/stubs/pyomo/common/unittest.pyi b/stubs/pyomo/common/unittest.pyi new file mode 100644 index 000000000..9d765cc70 --- /dev/null +++ b/stubs/pyomo/common/unittest.pyi @@ -0,0 +1,86 @@ +import enum +import types +import unittest as _unittest +from io import StringIO as StringIO +from unittest import * +from unittest import mock as mock + +from _typeshed import Incomplete +from pyomo.common.collections import Mapping as Mapping +from pyomo.common.collections import Sequence as Sequence +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import check_min_version as check_min_version +from pyomo.common.errors import InvalidValueError as InvalidValueError +from pyomo.common.fileutils import import_file as import_file +from pyomo.common.log import LoggingIntercept as LoggingIntercept +from pyomo.common.log import pyomo_formatter as pyomo_formatter +from pyomo.common.tee import capture_output as capture_output + +pytest: Incomplete +pytest_available: Incomplete + +def assertStructuredAlmostEqual( + first, + second, + places=None, + msg=None, + delta=None, + reltol=None, + abstol=None, + allow_second_superset: bool = False, + item_callback=..., + exception=..., + formatter=..., +) -> None: ... + +class _RunnerResult(enum.Enum): + exception = 0 + call = 1 + unittest = 2 + +def timeout(seconds, require_fork: bool = False, timeout_raises=...): ... + +class _AssertRaisesContext_NormalizeWhitespace(_unittest.case._AssertRaisesContext): + expected_regex: Incomplete + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: types.TracebackType | None, + ): ... + +class TestCase(_unittest.TestCase): + maxDiff: Incomplete + def assertStructuredAlmostEqual( + self, + first, + second, + places=None, + msg=None, + delta=None, + reltol=None, + abstol=None, + allow_second_superset: bool = False, + item_callback=..., + ) -> None: ... + def assertRaisesRegex(self, expected_exception, expected_regex, *args, **kwargs): ... + def assertExpressionsEqual(self, a, b, include_named_exprs: bool = True, places=None): ... + def assertExpressionsStructurallyEqual( + self, a, b, include_named_exprs: bool = True, places=None + ): ... + +class BaselineTestDriver: + @staticmethod + def custom_name_func(test_func, test_num, test_params): ... + def __init__(self, test) -> None: ... + def initialize_dependencies(self) -> None: ... + @classmethod + def gather_tests(cls, test_dirs): ... + def check_skip(self, name): ... + def filter_fcn(self, line): ... + def filter_file_contents(self, lines, abstol=None): ... + def compare_baseline( + self, test_output, baseline, abstol: float = 1e-06, reltol: float = 1e-08 + ): ... + def python_test_driver(self, tname, test_file, base_file) -> None: ... + def shell_test_driver(self, tname, test_file, base_file) -> None: ... diff --git a/stubs/pyomo/contrib/__init__.pyi b/stubs/pyomo/contrib/__init__.pyi new file mode 100644 index 000000000..460fc3dc3 --- /dev/null +++ b/stubs/pyomo/contrib/__init__.pyi @@ -0,0 +1 @@ +from pyomo.common.deprecation import moved_module as moved_module diff --git a/stubs/pyomo/contrib/alternative_solutions/__init__.pyi b/stubs/pyomo/contrib/alternative_solutions/__init__.pyi new file mode 100644 index 000000000..fa3439d27 --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/__init__.pyi @@ -0,0 +1,15 @@ +from pyomo.contrib.alternative_solutions.aos_utils import logcontext as logcontext +from pyomo.contrib.alternative_solutions.balas import ( + enumerate_binary_solutions as enumerate_binary_solutions, +) +from pyomo.contrib.alternative_solutions.lp_enum import ( + enumerate_linear_solutions as enumerate_linear_solutions, +) +from pyomo.contrib.alternative_solutions.obbt import obbt_analysis as obbt_analysis +from pyomo.contrib.alternative_solutions.obbt import ( + obbt_analysis_bounds_and_solutions as obbt_analysis_bounds_and_solutions, +) +from pyomo.contrib.alternative_solutions.solnpool import ( + gurobi_generate_solutions as gurobi_generate_solutions, +) +from pyomo.contrib.alternative_solutions.solution import Solution as Solution diff --git a/stubs/pyomo/contrib/alternative_solutions/aos_utils.pyi b/stubs/pyomo/contrib/alternative_solutions/aos_utils.pyi new file mode 100644 index 000000000..6876212cc --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/aos_utils.pyi @@ -0,0 +1,24 @@ +from collections.abc import Generator +from contextlib import contextmanager + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.modeling import unique_component_name as unique_component_name + +logger: Incomplete + +@contextmanager +def logcontext(level) -> Generator[None]: ... +def get_active_objective(model): ... + +rng: Incomplete + +def get_model_variables( + model, + components=None, + include_continuous: bool = True, + include_binary: bool = True, + include_integer: bool = True, + include_fixed: bool = False, +): ... diff --git a/stubs/pyomo/contrib/alternative_solutions/balas.pyi b/stubs/pyomo/contrib/alternative_solutions/balas.pyi new file mode 100644 index 000000000..c3c722540 --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/balas.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.contrib.alternative_solutions import Solution as Solution + +logger: Incomplete + +def enumerate_binary_solutions( + model, + *, + num_solutions: int = 10, + variables=None, + rel_opt_gap=None, + abs_opt_gap=None, + search_mode: str = 'optimal', + solver: str = 'gurobi', + solver_options={}, + tee: bool = False, + seed=None, +): ... diff --git a/stubs/pyomo/contrib/alternative_solutions/lp_enum.pyi b/stubs/pyomo/contrib/alternative_solutions/lp_enum.pyi new file mode 100644 index 000000000..7e82e6f6e --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/lp_enum.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.contrib import appsi as appsi +from pyomo.contrib.alternative_solutions import aos_utils as aos_utils +from pyomo.contrib.alternative_solutions import shifted_lp as shifted_lp +from pyomo.contrib.alternative_solutions import solnpool as solnpool +from pyomo.contrib.alternative_solutions import solution as solution + +logger: Incomplete + +def enumerate_linear_solutions( + model, + *, + num_solutions: int = 10, + rel_opt_gap=None, + abs_opt_gap=None, + zero_threshold: float = 1e-05, + search_mode: str = 'optimal', + solver: str = 'gurobi', + solver_options={}, + tee: bool = False, + seed=None, +): ... diff --git a/stubs/pyomo/contrib/alternative_solutions/lp_enum_solnpool.pyi b/stubs/pyomo/contrib/alternative_solutions/lp_enum_solnpool.pyi new file mode 100644 index 000000000..0ba7bd178 --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/lp_enum_solnpool.pyi @@ -0,0 +1,42 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.contrib import appsi as appsi +from pyomo.contrib.alternative_solutions import aos_utils as aos_utils +from pyomo.contrib.alternative_solutions import shifted_lp as shifted_lp +from pyomo.contrib.alternative_solutions import solution as solution + +logger: Incomplete +gurobipy: Incomplete +gurobi_available: Incomplete + +class NoGoodCutGenerator: + model: Incomplete + zero_threshold: Incomplete + variable_groups: Incomplete + variables: Incomplete + orig_model: Incomplete + all_variables: Incomplete + orig_objective: Incomplete + solutions: Incomplete + num_solutions: Incomplete + def __init__( + self, + model, + variable_groups, + zero_threshold, + orig_model, + all_variables, + orig_objective, + num_solutions, + ) -> None: ... + def cut_generator_callback(self, cb_m, cb_opt, cb_where) -> None: ... + +def enumerate_linear_solutions_soln_pool( + model, + num_solutions: int = 10, + rel_opt_gap=None, + abs_opt_gap=None, + zero_threshold: float = 1e-05, + solver_options={}, + tee: bool = False, +): ... diff --git a/stubs/pyomo/contrib/alternative_solutions/obbt.pyi b/stubs/pyomo/contrib/alternative_solutions/obbt.pyi new file mode 100644 index 000000000..65b26d28b --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/obbt.pyi @@ -0,0 +1,31 @@ +from _typeshed import Incomplete +from pyomo.contrib import appsi as appsi +from pyomo.contrib.alternative_solutions import Solution as Solution +from pyomo.contrib.alternative_solutions import aos_utils as aos_utils + +logger: Incomplete + +def obbt_analysis( + model, + *, + variables=None, + rel_opt_gap=None, + abs_opt_gap=None, + refine_discrete_bounds: bool = False, + warmstart: bool = True, + solver: str = 'gurobi', + solver_options={}, + tee: bool = False, +): ... +def obbt_analysis_bounds_and_solutions( + model, + *, + variables=None, + rel_opt_gap=None, + abs_opt_gap=None, + refine_discrete_bounds: bool = False, + warmstart: bool = True, + solver: str = 'gurobi', + solver_options={}, + tee: bool = False, +): ... diff --git a/stubs/pyomo/contrib/alternative_solutions/shifted_lp.pyi b/stubs/pyomo/contrib/alternative_solutions/shifted_lp.pyi new file mode 100644 index 000000000..ecebe649a --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/shifted_lp.pyi @@ -0,0 +1,8 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.contrib.alternative_solutions import aos_utils as aos_utils +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.gdp.util import ( + clone_without_expression_components as clone_without_expression_components, +) + +def get_shifted_linear_model(model, block=None): ... diff --git a/stubs/pyomo/contrib/alternative_solutions/solnpool.pyi b/stubs/pyomo/contrib/alternative_solutions/solnpool.pyi new file mode 100644 index 000000000..c9abb3556 --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/solnpool.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.contrib import appsi as appsi +from pyomo.contrib.alternative_solutions import Solution as Solution + +logger: Incomplete + +def gurobi_generate_solutions( + model, + *, + num_solutions: int = 10, + rel_opt_gap=None, + abs_opt_gap=None, + solver_options={}, + tee: bool = False, +): ... diff --git a/stubs/pyomo/contrib/alternative_solutions/solution.pyi b/stubs/pyomo/contrib/alternative_solutions/solution.pyi new file mode 100644 index 000000000..0fe2241c0 --- /dev/null +++ b/stubs/pyomo/contrib/alternative_solutions/solution.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.contrib.alternative_solutions import aos_utils as aos_utils + +class Solution: + variables: Incomplete + fixed_vars: Incomplete + objective: Incomplete + def __init__( + self, model, variable_list, include_fixed: bool = True, objective=None + ) -> None: ... + @property + def objective_value(self): ... + def pprint( + self, round_discrete: bool = True, sort_keys: bool = True, indent: int = 4 + ) -> None: ... + def to_string(self, round_discrete: bool = True, sort_keys: bool = True, indent: int = 4): ... + def to_dict(self, round_discrete: bool = True): ... + __repn__: Incomplete + def get_variable_name_values(self, include_fixed: bool = True, round_discrete: bool = True): ... + def get_fixed_variable_names(self): ... diff --git a/temoa/temoa_model/__init__.py b/stubs/pyomo/contrib/ampl_function_demo/__init__.pyi similarity index 100% rename from temoa/temoa_model/__init__.py rename to stubs/pyomo/contrib/ampl_function_demo/__init__.pyi diff --git a/stubs/pyomo/contrib/ampl_function_demo/build.pyi b/stubs/pyomo/contrib/ampl_function_demo/build.pyi new file mode 100644 index 000000000..29a3b23d7 --- /dev/null +++ b/stubs/pyomo/contrib/ampl_function_demo/build.pyi @@ -0,0 +1,6 @@ +from pyomo.common.cmake_builder import build_cmake_project as build_cmake_project + +def build_ampl_function_demo(user_args=[], parallel=None): ... + +class AMPLFunctionDemoBuilder: + def __call__(self, parallel): ... diff --git a/stubs/pyomo/contrib/ampl_function_demo/plugins.pyi b/stubs/pyomo/contrib/ampl_function_demo/plugins.pyi new file mode 100644 index 000000000..4e29bceb5 --- /dev/null +++ b/stubs/pyomo/contrib/ampl_function_demo/plugins.pyi @@ -0,0 +1,6 @@ +from pyomo.common.extensions import ExtensionBuilderFactory as ExtensionBuilderFactory +from pyomo.contrib.ampl_function_demo.build import ( + AMPLFunctionDemoBuilder as AMPLFunctionDemoBuilder, +) + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/appsi/__init__.pyi b/stubs/pyomo/contrib/appsi/__init__.pyi new file mode 100644 index 000000000..b4ad4cc20 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/__init__.pyi @@ -0,0 +1,4 @@ +from . import base as base +from . import fbbt as fbbt +from . import solvers as solvers +from . import writers as writers diff --git a/stubs/pyomo/contrib/appsi/base.pyi b/stubs/pyomo/contrib/appsi/base.pyi new file mode 100644 index 000000000..fb74a440a --- /dev/null +++ b/stubs/pyomo/contrib/appsi/base.pyi @@ -0,0 +1,290 @@ +import abc +import enum +import types +from typing import Mapping, MutableMapping, NoReturn, Sequence + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.enums import IntEnum as IntEnum +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.factory import Factory as Factory +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import Param as Param +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraint as SOSConstraint +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import Var as Var +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numvalue import NumericConstant as NumericConstant +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +from .cmodel import cmodel as cmodel +from .cmodel import cmodel_available as cmodel_available +from .utils.collect_vars_and_named_exprs import ( + collect_vars_and_named_exprs as collect_vars_and_named_exprs, +) +from .utils.get_objective import get_objective as get_objective + +class TerminationCondition(enum.Enum): + unknown = 0 + maxTimeLimit = 1 + maxIterations = 2 + objectiveLimit = 3 + minStepLength = 4 + optimal = 5 + unbounded = 8 + infeasible = 9 + infeasibleOrUnbounded = 10 + error = 11 + interrupted = 12 + licensingProblems = 13 + +class SolverConfig(ConfigDict): + time_limit: float | None + warmstart: bool + stream_solver: bool + load_solution: bool + symbolic_solver_labels: bool + report_timing: bool + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class MIPSolverConfig(SolverConfig): + mip_gap: float | None + relax_integrality: bool + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class SolutionLoaderBase(abc.ABC, metaclass=abc.ABCMeta): + def load_vars(self, vars_to_load: Sequence[VarData] | None = None) -> NoReturn: ... + @abc.abstractmethod + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_slacks( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + +class SolutionLoader(SolutionLoaderBase): + def __init__( + self, + primals: MutableMapping | None, + duals: MutableMapping | None, + slacks: MutableMapping | None, + reduced_costs: MutableMapping | None, + ) -> None: ... + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_slacks( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + +class Results: + solution_loader: SolutionLoaderBase + termination_condition: TerminationCondition + best_feasible_objective: float | None + best_objective_bound: float | None + def __init__(self) -> None: ... + +class UpdateConfig(ConfigDict): + check_for_new_or_removed_constraints: bool + check_for_new_or_removed_vars: bool + check_for_new_or_removed_params: bool + check_for_new_objective: bool + update_constraints: bool + update_vars: bool + update_params: bool + update_named_expressions: bool + update_objective: bool + treat_fixed_vars_as_params: bool + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class Solver(abc.ABC, metaclass=abc.ABCMeta): + class Availability(IntEnum): + NotFound = 0 + BadVersion = -1 + BadLicense = -2 + FullLicense = 1 + LimitedLicense = 2 + NeedsCompiledExtension = -3 + def __bool__(self) -> bool: ... + def __format__(self, format_spec) -> str: ... + + @abc.abstractmethod + def solve(self, model: BlockData, timer: HierarchicalTimer = None) -> Results: ... + @abc.abstractmethod + def available(self): ... + @abc.abstractmethod + def version(self) -> tuple: ... + @property + @abc.abstractmethod + def config(self): ... + @property + @abc.abstractmethod + def symbol_map(self): ... + def is_persistent(self): ... + +class PersistentSolver(Solver, metaclass=abc.ABCMeta): + def is_persistent(self): ... + def load_vars(self, vars_to_load: Sequence[VarData] | None = None) -> NoReturn: ... + @abc.abstractmethod + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_slacks( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + @property + @abc.abstractmethod + def update_config(self) -> UpdateConfig: ... + @abc.abstractmethod + def set_instance(self, model): ... + @abc.abstractmethod + def add_variables(self, variables: list[VarData]): ... + @abc.abstractmethod + def add_params(self, params: list[ParamData]): ... + @abc.abstractmethod + def add_constraints(self, cons: list[ConstraintData]): ... + @abc.abstractmethod + def add_block(self, block: BlockData): ... + @abc.abstractmethod + def remove_variables(self, variables: list[VarData]): ... + @abc.abstractmethod + def remove_params(self, params: list[ParamData]): ... + @abc.abstractmethod + def remove_constraints(self, cons: list[ConstraintData]): ... + @abc.abstractmethod + def remove_block(self, block: BlockData): ... + @abc.abstractmethod + def set_objective(self, obj: ObjectiveData): ... + @abc.abstractmethod + def update_variables(self, variables: list[VarData]): ... + @abc.abstractmethod + def update_params(self): ... + +class PersistentSolutionLoader(SolutionLoaderBase): + def __init__(self, solver: PersistentSolver) -> None: ... + def get_primals(self, vars_to_load=None): ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_slacks( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def invalidate(self) -> None: ... + +class PersistentBase(abc.ABC, metaclass=abc.ABCMeta): + use_extensions: bool + def __init__(self, only_child_vars: bool = False) -> None: ... + @property + def update_config(self): ... + @update_config.setter + def update_config(self, val: UpdateConfig): ... + def set_instance(self, model) -> None: ... + def add_variables(self, variables: list[VarData]): ... + def add_params(self, params: list[ParamData]): ... + def add_constraints(self, cons: list[ConstraintData]): ... + def add_sos_constraints(self, cons: list[SOSConstraintData]): ... + def set_objective(self, obj: ObjectiveData): ... + def add_block(self, block) -> None: ... + def remove_constraints(self, cons: list[ConstraintData]): ... + def remove_sos_constraints(self, cons: list[SOSConstraintData]): ... + def remove_variables(self, variables: list[VarData]): ... + def remove_params(self, params: list[ParamData]): ... + def remove_block(self, block) -> None: ... + def update_variables(self, variables: list[VarData]): ... + @abc.abstractmethod + def update_params(self): ... + def update(self, timer: HierarchicalTimer = None): ... + +legacy_termination_condition_map: Incomplete +legacy_solver_status_map: Incomplete +legacy_solution_status_map: Incomplete + +class LegacySolverInterface: + config: Incomplete + def solve( + self, + model: BlockData, + tee: bool = False, + load_solutions: bool = True, + logfile: str | None = None, + solnfile: str | None = None, + timelimit: float | None = None, + report_timing: bool = False, + solver_io: str | None = None, + suffixes: Sequence | None = None, + options: dict | None = None, + keepfiles: bool = False, + symbolic_solver_labels: bool = False, + warmstart: bool = False, + ): ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self) -> bool: ... + @property + def options(self): ... + @options.setter + def options(self, val) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +class SolverFactoryClass(Factory): + def register(self, name, doc=None): ... + +SolverFactory: Incomplete diff --git a/stubs/pyomo/contrib/appsi/build.pyi b/stubs/pyomo/contrib/appsi/build.pyi new file mode 100644 index 000000000..b80861cc2 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/build.pyi @@ -0,0 +1,5 @@ +def get_appsi_extension(in_setup: bool = False, appsi_root=None): ... +def build_appsi(args=[]) -> None: ... + +class AppsiBuilder: + def __call__(self, parallel): ... diff --git a/stubs/pyomo/contrib/appsi/cmodel/__init__.pyi b/stubs/pyomo/contrib/appsi/cmodel/__init__.pyi new file mode 100644 index 000000000..8474f39f5 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/cmodel/__init__.pyi @@ -0,0 +1,4 @@ +from _typeshed import Incomplete + +cmodel: Incomplete +cmodel_available: Incomplete diff --git a/stubs/pyomo/contrib/appsi/cmodel/appsi_cmodel.pyi b/stubs/pyomo/contrib/appsi/cmodel/appsi_cmodel.pyi new file mode 100644 index 000000000..914a6fecc --- /dev/null +++ b/stubs/pyomo/contrib/appsi/cmodel/appsi_cmodel.pyi @@ -0,0 +1,258 @@ +from typing import ClassVar, overload + +import pyomo.common.errors + +division: ExprType +external_func: ExprType +inf: float +linear: ExprType +named_expr: ExprType +negation: ExprType +numeric_constant: ExprType +param: ExprType +power: ExprType +product: ExprType +py_float: ExprType +sum: ExprType +unary_func: ExprType +var: ExprType + +class Constant(ExpressionBase): + value: float + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: float) -> None: ... + +class Constraint: + active: bool + lb: ExpressionBase + name: str + ub: ExpressionBase + def __init__(self) -> None: ... + +class ExprType: + __members__: ClassVar[dict] = ... # read-only + __entries: ClassVar[dict] = ... + division: ClassVar[ExprType] = ... + external_func: ClassVar[ExprType] = ... + linear: ClassVar[ExprType] = ... + named_expr: ClassVar[ExprType] = ... + negation: ClassVar[ExprType] = ... + numeric_constant: ClassVar[ExprType] = ... + param: ClassVar[ExprType] = ... + power: ClassVar[ExprType] = ... + product: ClassVar[ExprType] = ... + py_float: ClassVar[ExprType] = ... + sum: ClassVar[ExprType] = ... + unary_func: ClassVar[ExprType] = ... + var: ClassVar[ExprType] = ... + def __init__(self, value: int) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> int: ... + def __int__(self) -> int: ... + def __ne__(self, other: object) -> bool: ... + @property + def name(self) -> str: ... + @property + def value(self) -> int: ... + +class Expression(ExpressionBase): + def __init__(self, arg0: int) -> None: ... + def get_operators(self) -> list[Operator]: ... + +class ExpressionBase(Node): + def __init__(self, *args, **kwargs) -> None: ... + def evaluate(self) -> float: ... + +class FBBTConstraint(Constraint): + body: ExpressionBase + def __init__( + self, arg0: ExpressionBase, arg1: ExpressionBase, arg2: ExpressionBase + ) -> None: ... + def perform_fbbt( + self, arg0: float, arg1: float, arg2: float, arg3: set[Var], arg4: bool + ) -> None: ... + +class FBBTModel(Model): + def __init__(self) -> None: ... + def perform_fbbt(self, arg0: float, arg1: float, arg2: float, arg3: int, arg4: bool) -> int: ... + def perform_fbbt_with_seed( + self, arg0: Var, arg1: float, arg2: float, arg3: float, arg4: int, arg5: bool + ) -> int: ... + +class FBBTObjective(Objective): + expr: ExpressionBase + def __init__(self, arg0: ExpressionBase) -> None: ... + +class InfeasibleConstraintException(pyomo.common.errors.InfeasibleConstraintException): ... +class IntervalException(pyomo.common.errors.IntervalException): ... + +class LPBase: + def __init__(self, *args, **kwargs) -> None: ... + +class LPConstraint(LPBase, Constraint): + def __init__(self) -> None: ... + +class LPObjective(LPBase, Objective): + def __init__(self) -> None: ... + +class LPWriter(Model): + def __init__(self) -> None: ... + def get_solve_cons(self) -> list[LPConstraint]: ... + def write(self, arg0: str) -> None: ... + +class Model: + constraints: set[Constraint] + objective: Objective + def __init__(self) -> None: ... + def add_constraint(self, arg0: Constraint) -> None: ... + def remove_constraint(self, arg0: Constraint) -> None: ... + +class NLBase: + def __init__(self, *args, **kwargs) -> None: ... + +class NLConstraint(NLBase, Constraint): + def __init__( + self, + arg0: ExpressionBase, + arg1: list[ExpressionBase], + arg2: list[Var], + arg3: ExpressionBase, + ) -> None: ... + +class NLObjective(NLBase, Objective): + def __init__( + self, + arg0: ExpressionBase, + arg1: list[ExpressionBase], + arg2: list[Var], + arg3: ExpressionBase, + ) -> None: ... + +class NLWriter(Model): + def __init__(self) -> None: ... + def get_solve_cons(self) -> list[NLConstraint]: ... + def get_solve_vars(self) -> list[Var]: ... + def write(self, arg0: str) -> None: ... + +class Node: + def __init__(self, *args, **kwargs) -> None: ... + def is_constant_type(self) -> bool: ... + def is_expression_type(self) -> bool: ... + def is_leaf(self) -> bool: ... + def is_operator_type(self) -> bool: ... + def is_param_type(self) -> bool: ... + def is_variable_type(self) -> bool: ... + +class Objective: + name: str + sense: int + def __init__(self) -> None: ... + +class Operator(Node): + def __init__(self, *args, **kwargs) -> None: ... + +class Param(ExpressionBase): + name: str + value: float + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: float) -> None: ... + @overload + def __init__(self, arg0: str, arg1: float) -> None: ... + @overload + def __init__(self, arg0: str) -> None: ... + +class PyomoExprTypes: + def __init__(self) -> None: ... + +class Var(ExpressionBase): + domain: Domain + fixed: bool + lb: ExpressionBase + name: str + ub: ExpressionBase + value: float + @overload + def __init__(self) -> None: ... + @overload + def __init__(self, arg0: float) -> None: ... + @overload + def __init__(self, arg0: str, arg1: float) -> None: ... + @overload + def __init__(self, arg0: str) -> None: ... + def get_domain(self) -> Domain: ... + def get_lb(self) -> float: ... + def get_ub(self) -> float: ... + +def appsi_expr_from_pyomo_expr( + arg0: object, arg1: object, arg2: object, arg3: PyomoExprTypes +) -> ExpressionBase: ... +def appsi_exprs_from_pyomo_exprs(arg0: list, arg1: dict, arg2: dict) -> list[ExpressionBase]: ... +def create_constants(arg0: int) -> list[Constant]: ... +def create_params(arg0: int) -> list[Param]: ... +def create_vars(arg0: int) -> list[Var]: ... +def prep_for_repn(arg0: object, arg1: PyomoExprTypes) -> tuple: ... +def process_fbbt_constraints( + arg0: FBBTModel, + arg1: PyomoExprTypes, + arg2: list, + arg3: dict, + arg4: dict, + arg5: dict, + arg6: dict, + arg7: dict, +) -> None: ... +def process_lp_constraints(arg0: list, arg1: object) -> None: ... +def process_lp_objective( + arg0: PyomoExprTypes, arg1: object, arg2: dict, arg3: dict +) -> LPObjective: ... +def process_nl_constraints( + arg0: NLWriter, + arg1: PyomoExprTypes, + arg2: list, + arg3: dict, + arg4: dict, + arg5: dict, + arg6: dict, + arg7: dict, +) -> None: ... +def process_pyomo_vars( + arg0: PyomoExprTypes, + arg1: list, + arg2: dict, + arg3: dict, + arg4: dict, + arg5: dict, + arg6: bool, + arg7: object, + arg8: object, + arg9: bool, +) -> None: ... +def py_interval_abs(arg0: float, arg1: float) -> tuple[float, float]: ... +def py_interval_acos( + arg0: float, arg1: float, arg2: float, arg3: float, arg4: float +) -> tuple[float, float]: ... +def py_interval_add(arg0: float, arg1: float, arg2: float, arg3: float) -> tuple[float, float]: ... +def py_interval_asin( + arg0: float, arg1: float, arg2: float, arg3: float, arg4: float +) -> tuple[float, float]: ... +def py_interval_atan(arg0: float, arg1: float, arg2: float, arg3: float) -> tuple[float, float]: ... +def py_interval_cos(arg0: float, arg1: float) -> tuple[float, float]: ... +def py_interval_div( + arg0: float, arg1: float, arg2: float, arg3: float, arg4: float +) -> tuple[float, float]: ... +def py_interval_exp(arg0: float, arg1: float) -> tuple[float, float]: ... +def py_interval_inv(arg0: float, arg1: float, arg2: float) -> tuple[float, float]: ... +def py_interval_log(arg0: float, arg1: float) -> tuple[float, float]: ... +def py_interval_log10(arg0: float, arg1: float) -> tuple[float, float]: ... +def py_interval_mul(arg0: float, arg1: float, arg2: float, arg3: float) -> tuple[float, float]: ... +def py_interval_power( + arg0: float, arg1: float, arg2: float, arg3: float, arg4: float +) -> tuple[float, float]: ... +def py_interval_sin(arg0: float, arg1: float) -> tuple[float, float]: ... +def py_interval_sub(arg0: float, arg1: float, arg2: float, arg3: float) -> tuple[float, float]: ... +def py_interval_tan(arg0: float, arg1: float) -> tuple[float, float]: ... diff --git a/stubs/pyomo/contrib/appsi/examples/__init__.pyi b/stubs/pyomo/contrib/appsi/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/appsi/examples/getting_started.pyi b/stubs/pyomo/contrib/appsi/examples/getting_started.pyi new file mode 100644 index 000000000..273861b0d --- /dev/null +++ b/stubs/pyomo/contrib/appsi/examples/getting_started.pyi @@ -0,0 +1,4 @@ +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib import appsi as appsi + +def main(plot: bool = True, n_points: int = 200) -> None: ... diff --git a/stubs/pyomo/contrib/appsi/fbbt.pyi b/stubs/pyomo/contrib/appsi/fbbt.pyi new file mode 100644 index 000000000..491c65bb4 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/fbbt.pyi @@ -0,0 +1,48 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.contrib.appsi.base import PersistentBase as PersistentBase +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.objective import maximize as maximize +from pyomo.core.base.objective import minimize as minimize +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import VarData as VarData + +from .cmodel import cmodel as cmodel +from .cmodel import cmodel_available as cmodel_available + +class IntervalConfig(ConfigDict): + feasibility_tol: float + integer_tol: float + improvement_tol: float + max_iter: int + deactivate_satisfied_constraints: bool + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class IntervalTightener(PersistentBase): + def __init__(self) -> None: ... + @property + def config(self): ... + @config.setter + def config(self, val: IntervalConfig): ... + update_config: Incomplete + def set_instance(self, model, symbolic_solver_labels: bool | None = None): ... + def update_params(self) -> None: ... + def set_objective(self, obj: ObjectiveData): ... + def perform_fbbt(self, model: BlockData, symbolic_solver_labels: bool | None = None): ... + def perform_fbbt_with_seed(self, model: BlockData, seed_var: VarData): ... diff --git a/stubs/pyomo/contrib/appsi/plugins.pyi b/stubs/pyomo/contrib/appsi/plugins.pyi new file mode 100644 index 000000000..2e79db83a --- /dev/null +++ b/stubs/pyomo/contrib/appsi/plugins.pyi @@ -0,0 +1,12 @@ +from pyomo.common.extensions import ExtensionBuilderFactory as ExtensionBuilderFactory + +from .base import SolverFactory as SolverFactory +from .build import AppsiBuilder as AppsiBuilder +from .solvers import Cbc as Cbc +from .solvers import Cplex as Cplex +from .solvers import Gurobi as Gurobi +from .solvers import Highs as Highs +from .solvers import Ipopt as Ipopt +from .solvers import MAiNGO as MAiNGO + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/appsi/solvers/__init__.pyi b/stubs/pyomo/contrib/appsi/solvers/__init__.pyi new file mode 100644 index 000000000..325f3d11e --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/__init__.pyi @@ -0,0 +1,9 @@ +from .cbc import Cbc as Cbc +from .cplex import Cplex as Cplex +from .gurobi import Gurobi as Gurobi +from .gurobi import GurobiResults as GurobiResults +from .highs import Highs as Highs +from .ipopt import Ipopt as Ipopt +from .maingo import MAiNGO as MAiNGO +from .wntr import Wntr as Wntr +from .wntr import WntrResults as WntrResults diff --git a/stubs/pyomo/contrib/appsi/solvers/cbc.pyi b/stubs/pyomo/contrib/appsi/solvers/cbc.pyi new file mode 100644 index 000000000..344239bab --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/cbc.pyi @@ -0,0 +1,86 @@ +from typing import Mapping, Sequence + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.fileutils import Executable as Executable +from pyomo.common.log import LogStream as LogStream +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import PersistentSolutionLoader as PersistentSolutionLoader +from pyomo.contrib.appsi.base import PersistentSolver as PersistentSolver +from pyomo.contrib.appsi.base import Results as Results +from pyomo.contrib.appsi.base import SolverConfig as SolverConfig +from pyomo.contrib.appsi.base import TerminationCondition as TerminationCondition +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.contrib.appsi.writers import LPWriter as LPWriter +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +logger: Incomplete + +class CbcConfig(SolverConfig): + executable: Incomplete + filename: Incomplete + keepfiles: bool + solver_output_logger: Incomplete + log_level: Incomplete + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class Cbc(PersistentSolver): + def __init__(self, only_child_vars: bool = False) -> None: ... + def available(self): ... + def version(self): ... + def lp_filename(self): ... + def log_filename(self): ... + def soln_filename(self): ... + @property + def config(self): ... + @config.setter + def config(self, val) -> None: ... + @property + def cbc_options(self): ... + @cbc_options.setter + def cbc_options(self, val: dict): ... + @property + def update_config(self): ... + @property + def writer(self): ... + @property + def symbol_map(self): ... + def set_instance(self, model) -> None: ... + def add_variables(self, variables: list[VarData]): ... + def add_params(self, params: list[ParamData]): ... + def add_constraints(self, cons: list[ConstraintData]): ... + def add_block(self, block: BlockData): ... + def remove_variables(self, variables: list[VarData]): ... + def remove_params(self, params: list[ParamData]): ... + def remove_constraints(self, cons: list[ConstraintData]): ... + def remove_block(self, block: BlockData): ... + def set_objective(self, obj: ObjectiveData): ... + def update_variables(self, variables: list[VarData]): ... + def update_params(self) -> None: ... + def solve(self, model, timer: HierarchicalTimer = None): ... + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals(self, cons_to_load=None): ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... diff --git a/stubs/pyomo/contrib/appsi/solvers/cplex.pyi b/stubs/pyomo/contrib/appsi/solvers/cplex.pyi new file mode 100644 index 000000000..81b8eb9ff --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/cplex.pyi @@ -0,0 +1,87 @@ +from typing import Mapping, Sequence + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.log import LogStream as LogStream +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import MIPSolverConfig as MIPSolverConfig +from pyomo.contrib.appsi.base import PersistentSolutionLoader as PersistentSolutionLoader +from pyomo.contrib.appsi.base import PersistentSolver as PersistentSolver +from pyomo.contrib.appsi.base import Results as Results +from pyomo.contrib.appsi.base import TerminationCondition as TerminationCondition +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.contrib.appsi.writers import LPWriter as LPWriter +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +logger: Incomplete + +class CplexConfig(MIPSolverConfig): + filename: Incomplete + keepfiles: bool + solver_output_logger: Incomplete + log_level: Incomplete + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class CplexResults(Results): + wallclock_time: Incomplete + solution_loader: Incomplete + def __init__(self, solver) -> None: ... + +class Cplex(PersistentSolver): + def __init__(self, only_child_vars: bool = False) -> None: ... + @property + def writer(self): ... + @property + def symbol_map(self): ... + def available(self): ... + def version(self): ... + def lp_filename(self): ... + def log_filename(self): ... + @property + def config(self): ... + @config.setter + def config(self, val) -> None: ... + @property + def cplex_options(self): ... + @cplex_options.setter + def cplex_options(self, val: dict): ... + @property + def update_config(self): ... + def set_instance(self, model) -> None: ... + def add_variables(self, variables: list[VarData]): ... + def add_params(self, params: list[ParamData]): ... + def add_constraints(self, cons: list[ConstraintData]): ... + def add_block(self, block: BlockData): ... + def remove_variables(self, variables: list[VarData]): ... + def remove_params(self, params: list[ParamData]): ... + def remove_constraints(self, cons: list[ConstraintData]): ... + def remove_block(self, block: BlockData): ... + def set_objective(self, obj: ObjectiveData): ... + def update_variables(self, variables: list[VarData]): ... + def update_params(self) -> None: ... + def solve(self, model, timer: HierarchicalTimer = None): ... + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... diff --git a/stubs/pyomo/contrib/appsi/solvers/gurobi.pyi b/stubs/pyomo/contrib/appsi/solvers/gurobi.pyi new file mode 100644 index 000000000..c0a8a9558 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/gurobi.pyi @@ -0,0 +1,177 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.collections import OrderedSet as OrderedSet +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.log import LogStream as LogStream +from pyomo.common.shutdown import python_is_shutting_down as python_is_shutting_down +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import MIPSolverConfig as MIPSolverConfig +from pyomo.contrib.appsi.base import PersistentBase as PersistentBase +from pyomo.contrib.appsi.base import PersistentSolutionLoader as PersistentSolutionLoader +from pyomo.contrib.appsi.base import PersistentSolver as PersistentSolver +from pyomo.contrib.appsi.base import Results as Results +from pyomo.contrib.appsi.base import TerminationCondition as TerminationCondition +from pyomo.contrib.appsi.cmodel import cmodel as cmodel +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import Var as Var +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numeric_expr import NPV_MaxExpression as NPV_MaxExpression +from pyomo.core.expr.numeric_expr import NPV_MinExpression as NPV_MinExpression +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete +gurobipy: Incomplete +gurobipy_available: Incomplete + +class DegreeError(PyomoException): ... + +class GurobiConfig(MIPSolverConfig): + logfile: str + solver_output_logger: Incomplete + log_level: Incomplete + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class GurobiSolutionLoader(PersistentSolutionLoader): + def load_vars(self, vars_to_load=None, solution_number: int = 0) -> None: ... + def get_primals(self, vars_to_load=None, solution_number: int = 0): ... + +class GurobiResults(Results): + wallclock_time: Incomplete + solution_loader: Incomplete + def __init__(self, solver) -> None: ... + +class _MutableLowerBound: + var: Incomplete + expr: Incomplete + def __init__(self, expr) -> None: ... + def update(self) -> None: ... + +class _MutableUpperBound: + var: Incomplete + expr: Incomplete + def __init__(self, expr) -> None: ... + def update(self) -> None: ... + +class _MutableLinearCoefficient: + expr: Incomplete + var: Incomplete + con: Incomplete + gurobi_model: Incomplete + def __init__(self) -> None: ... + def update(self) -> None: ... + +class _MutableRangeConstant: + lhs_expr: Incomplete + rhs_expr: Incomplete + con: Incomplete + slack_name: Incomplete + gurobi_model: Incomplete + def __init__(self) -> None: ... + def update(self) -> None: ... + +class _MutableConstant: + expr: Incomplete + con: Incomplete + def __init__(self) -> None: ... + def update(self) -> None: ... + +class _MutableQuadraticConstraint: + con: Incomplete + gurobi_model: Incomplete + constant: Incomplete + last_constant_value: Incomplete + linear_coefs: Incomplete + last_linear_coef_values: Incomplete + quadratic_coefs: Incomplete + last_quadratic_coef_values: Incomplete + def __init__( + self, gurobi_model, gurobi_con, constant, linear_coefs, quadratic_coefs + ) -> None: ... + def get_updated_expression(self): ... + def get_updated_rhs(self): ... + +class _MutableObjective: + gurobi_model: Incomplete + constant: Incomplete + linear_coefs: Incomplete + quadratic_coefs: Incomplete + last_quadratic_coef_values: Incomplete + def __init__(self, gurobi_model, constant, linear_coefs, quadratic_coefs) -> None: ... + def get_updated_expression(self): ... + +class _MutableQuadraticCoefficient: + expr: Incomplete + var1: Incomplete + var2: Incomplete + def __init__(self) -> None: ... + +class Gurobi(PersistentBase, PersistentSolver): + def __init__(self, only_child_vars: bool = False) -> None: ... + def available(self): ... + def release_license(self) -> None: ... + def __del__(self) -> None: ... + def version(self): ... + @property + def config(self) -> GurobiConfig: ... + @config.setter + def config(self, val: GurobiConfig): ... + @property + def gurobi_options(self): ... + @gurobi_options.setter + def gurobi_options(self, val: dict): ... + @property + def symbol_map(self): ... + def solve(self, model, timer: HierarchicalTimer = None) -> Results: ... + def set_instance(self, model) -> None: ... + def update_params(self) -> None: ... + def load_vars(self, vars_to_load=None, solution_number: int = 0) -> None: ... + def get_primals(self, vars_to_load=None, solution_number: int = 0): ... + def get_reduced_costs(self, vars_to_load=None): ... + def get_duals(self, cons_to_load=None): ... + def get_slacks(self, cons_to_load=None): ... + def update(self, timer: HierarchicalTimer = None): ... + def get_model_attr(self, attr): ... + def write(self, filename) -> None: ... + def set_linear_constraint_attr(self, con, attr, val) -> None: ... + def set_var_attr(self, var, attr, val) -> None: ... + def get_var_attr(self, var, attr): ... + def get_linear_constraint_attr(self, con, attr): ... + def get_sos_attr(self, con, attr): ... + def get_quadratic_constraint_attr(self, con, attr): ... + def set_gurobi_param(self, param, val) -> None: ... + def get_gurobi_param_info(self, param): ... + def set_callback(self, func=None) -> None: ... + def cbCut(self, con) -> None: ... + def cbGet(self, what): ... + def cbGetNodeRel(self, vars) -> None: ... + def cbGetSolution(self, vars) -> None: ... + def cbLazy(self, con) -> None: ... + def cbSetSolution(self, vars, solution) -> None: ... + def cbUseSolution(self): ... + def reset(self) -> None: ... diff --git a/stubs/pyomo/contrib/appsi/solvers/highs.pyi b/stubs/pyomo/contrib/appsi/solvers/highs.pyi new file mode 100644 index 000000000..cb7c07710 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/highs.pyi @@ -0,0 +1,121 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.log import LogStream as LogStream +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import MIPSolverConfig as MIPSolverConfig +from pyomo.contrib.appsi.base import PersistentBase as PersistentBase +from pyomo.contrib.appsi.base import PersistentSolutionLoader as PersistentSolutionLoader +from pyomo.contrib.appsi.base import PersistentSolver as PersistentSolver +from pyomo.contrib.appsi.base import Results as Results +from pyomo.contrib.appsi.base import TerminationCondition as TerminationCondition +from pyomo.contrib.appsi.cmodel import cmodel as cmodel +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numeric_expr import NPV_MaxExpression as NPV_MaxExpression +from pyomo.core.expr.numeric_expr import NPV_MinExpression as NPV_MinExpression +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete +highspy: Incomplete +highspy_available: Incomplete + +class DegreeError(PyomoException): ... + +class HighsConfig(MIPSolverConfig): + logfile: str + solver_output_logger: Incomplete + log_level: Incomplete + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class HighsResults(Results): + wallclock_time: Incomplete + solution_loader: Incomplete + def __init__(self, solver) -> None: ... + +class _MutableVarBounds: + pyomo_var_id: Incomplete + lower_expr: Incomplete + upper_expr: Incomplete + var_map: Incomplete + highs: Incomplete + def __init__(self, lower_expr, upper_expr, pyomo_var_id, var_map, highs) -> None: ... + def update(self) -> None: ... + +class _MutableLinearCoefficient: + expr: Incomplete + highs: Incomplete + pyomo_var_id: Incomplete + pyomo_con: Incomplete + con_map: Incomplete + var_map: Incomplete + def __init__(self, pyomo_con, pyomo_var_id, con_map, var_map, expr, highs) -> None: ... + def update(self) -> None: ... + +class _MutableObjectiveCoefficient: + expr: Incomplete + highs: Incomplete + pyomo_var_id: Incomplete + var_map: Incomplete + def __init__(self, pyomo_var_id, var_map, expr, highs) -> None: ... + def update(self) -> None: ... + +class _MutableObjectiveOffset: + expr: Incomplete + highs: Incomplete + def __init__(self, expr, highs) -> None: ... + def update(self) -> None: ... + +class _MutableConstraintBounds: + lower_expr: Incomplete + upper_expr: Incomplete + con: Incomplete + con_map: Incomplete + highs: Incomplete + def __init__(self, lower_expr, upper_expr, pyomo_con, con_map, highs) -> None: ... + def update(self) -> None: ... + +class Highs(PersistentBase, PersistentSolver): + def __init__(self, only_child_vars: bool = False) -> None: ... + def available(self): ... + def version(self): ... + @property + def config(self) -> HighsConfig: ... + @config.setter + def config(self, val: HighsConfig): ... + @property + def highs_options(self): ... + @highs_options.setter + def highs_options(self, val: dict): ... + @property + def symbol_map(self): ... + def warm_start_capable(self): ... + def solve(self, model, timer: HierarchicalTimer = None) -> Results: ... + def set_instance(self, model) -> None: ... + def update_params(self) -> None: ... + def load_vars(self, vars_to_load=None) -> None: ... + def get_primals(self, vars_to_load=None, solution_number: int = 0): ... + def get_reduced_costs(self, vars_to_load=None): ... + def get_duals(self, cons_to_load=None): ... + def get_slacks(self, cons_to_load=None): ... diff --git a/stubs/pyomo/contrib/appsi/solvers/ipopt.pyi b/stubs/pyomo/contrib/appsi/solvers/ipopt.pyi new file mode 100644 index 000000000..06dc3f480 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/ipopt.pyi @@ -0,0 +1,90 @@ +from typing import Mapping, Sequence + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.fileutils import Executable as Executable +from pyomo.common.log import LogStream as LogStream +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import PersistentSolutionLoader as PersistentSolutionLoader +from pyomo.contrib.appsi.base import PersistentSolver as PersistentSolver +from pyomo.contrib.appsi.base import Results as Results +from pyomo.contrib.appsi.base import SolverConfig as SolverConfig +from pyomo.contrib.appsi.base import TerminationCondition as TerminationCondition +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.contrib.appsi.writers import NLWriter as NLWriter +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import replace_expressions as replace_expressions +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +logger: Incomplete + +class IpoptConfig(SolverConfig): + executable: Incomplete + filename: Incomplete + keepfiles: bool + solver_output_logger: Incomplete + log_level: Incomplete + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +ipopt_command_line_options: Incomplete + +class Ipopt(PersistentSolver): + def __init__(self, only_child_vars: bool = False) -> None: ... + def available(self): ... + def version(self): ... + def nl_filename(self): ... + def sol_filename(self): ... + def options_filename(self): ... + @property + def config(self): ... + @config.setter + def config(self, val) -> None: ... + @property + def ipopt_options(self): ... + @ipopt_options.setter + def ipopt_options(self, val: dict): ... + @property + def update_config(self): ... + @property + def writer(self): ... + @property + def symbol_map(self): ... + def set_instance(self, model) -> None: ... + def add_variables(self, variables: list[VarData]): ... + def add_params(self, params: list[ParamData]): ... + def add_constraints(self, cons: list[ConstraintData]): ... + def add_block(self, block: BlockData): ... + def remove_variables(self, variables: list[VarData]): ... + def remove_params(self, params: list[ParamData]): ... + def remove_constraints(self, cons: list[ConstraintData]): ... + def remove_block(self, block: BlockData): ... + def set_objective(self, obj: ObjectiveData): ... + def update_variables(self, variables: list[VarData]): ... + def update_params(self) -> None: ... + def solve(self, model, timer: HierarchicalTimer = None): ... + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals(self, cons_to_load: Sequence[ConstraintData] | None = None): ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def has_linear_solver(self, linear_solver): ... diff --git a/stubs/pyomo/contrib/appsi/solvers/maingo.pyi b/stubs/pyomo/contrib/appsi/solvers/maingo.pyi new file mode 100644 index 000000000..69a604576 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/maingo.pyi @@ -0,0 +1,96 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.log import LogStream as LogStream +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import MIPSolverConfig as MIPSolverConfig +from pyomo.contrib.appsi.base import PersistentBase as PersistentBase +from pyomo.contrib.appsi.base import PersistentSolutionLoader as PersistentSolutionLoader +from pyomo.contrib.appsi.base import PersistentSolver as PersistentSolver +from pyomo.contrib.appsi.base import Results as Results +from pyomo.contrib.appsi.base import TerminationCondition as TerminationCondition +from pyomo.contrib.appsi.cmodel import cmodel as cmodel +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn.util import valid_expr_ctypes_minlp as valid_expr_ctypes_minlp + +logger: Incomplete + +class MaingoVar(NamedTuple): + type: Incomplete + name: Incomplete + lb: Incomplete + ub: Incomplete + init: Incomplete + +maingopy: Incomplete +maingopy_available: Incomplete +maingo_solvermodel: Incomplete + +class MAiNGOConfig(MIPSolverConfig): + tolerances: ConfigDict + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class MAiNGOSolutionLoader(PersistentSolutionLoader): + def load_vars(self, vars_to_load=None) -> None: ... + def get_primals(self, vars_to_load=None): ... + +class MAiNGOResults(Results): + wallclock_time: Incomplete + cpu_time: Incomplete + globally_optimal: Incomplete + solution_loader: Incomplete + def __init__(self, solver) -> None: ... + +class MAiNGO(PersistentBase, PersistentSolver): + def __init__(self, only_child_vars: bool = False) -> None: ... + def available(self): ... + def version(self): ... + @property + def config(self) -> MAiNGOConfig: ... + @config.setter + def config(self, val: MAiNGOConfig): ... + @property + def maingo_options(self): ... + @maingo_options.setter + def maingo_options(self, val: dict): ... + @property + def symbol_map(self): ... + def solve(self, model, timer: HierarchicalTimer = None): ... + def set_instance(self, model) -> None: ... + def update_params(self) -> None: ... + def load_vars(self, vars_to_load=None) -> None: ... + def get_primals(self, vars_to_load=None): ... + def get_reduced_costs(self, vars_to_load=None) -> None: ... + def get_duals(self, cons_to_load=None) -> None: ... + def update(self, timer: HierarchicalTimer = None): ... diff --git a/stubs/pyomo/contrib/appsi/solvers/maingo_solvermodel.pyi b/stubs/pyomo/contrib/appsi/solvers/maingo_solvermodel.pyi new file mode 100644 index 000000000..c313906fe --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/maingo_solvermodel.pyi @@ -0,0 +1,42 @@ +import pyomo.core.expr as EXPR +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.repn.util import valid_expr_ctypes_minlp as valid_expr_ctypes_minlp + +maingopy: Incomplete +maingopy_available: Incomplete +LEFT_TO_RIGHT: Incomplete +RIGHT_TO_LEFT: Incomplete + +class ToMAiNGOVisitor(EXPR.ExpressionValueVisitor): + variables: Incomplete + idmap: Incomplete + def __init__(self, variables, idmap) -> None: ... + @classmethod + def maingo_log10(cls, x): ... + @classmethod + def maingo_asinh(cls, x): ... + @classmethod + def maingo_acosh(cls, x): ... + @classmethod + def maingo_atanh(cls, x): ... + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +class SolverModel: + def __init__(self, var_list, objective, con_list, idmap, logger) -> None: ... + def build_maingo_objective(self, obj, visitor): ... + def build_maingo_constraints(self, cons, visitor): ... + def get_variables(self): ... + def get_initial_point(self): ... + def evaluate(self, maingo_vars): ... diff --git a/stubs/pyomo/contrib/appsi/solvers/wntr.pyi b/stubs/pyomo/contrib/appsi/solvers/wntr.pyi new file mode 100644 index 000000000..1345139b1 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/solvers/wntr.pyi @@ -0,0 +1,85 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import PersistentBase as PersistentBase +from pyomo.contrib.appsi.base import PersistentSolutionLoader as PersistentSolutionLoader +from pyomo.contrib.appsi.base import PersistentSolver as PersistentSolver +from pyomo.contrib.appsi.base import Results as Results +from pyomo.contrib.appsi.base import SolverConfig as SolverConfig +from pyomo.contrib.appsi.base import TerminationCondition as TerminationCondition +from pyomo.contrib.appsi.cmodel import cmodel as cmodel +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numeric_expr import AbsExpression as AbsExpression +from pyomo.core.expr.numeric_expr import DivisionExpression as DivisionExpression +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression +from pyomo.core.expr.numeric_expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr.numeric_expr import NegationExpression as NegationExpression +from pyomo.core.expr.numeric_expr import NPV_AbsExpression as NPV_AbsExpression +from pyomo.core.expr.numeric_expr import NPV_DivisionExpression as NPV_DivisionExpression +from pyomo.core.expr.numeric_expr import NPV_NegationExpression as NPV_NegationExpression +from pyomo.core.expr.numeric_expr import NPV_PowExpression as NPV_PowExpression +from pyomo.core.expr.numeric_expr import NPV_ProductExpression as NPV_ProductExpression +from pyomo.core.expr.numeric_expr import NPV_SumExpression as NPV_SumExpression +from pyomo.core.expr.numeric_expr import NPV_UnaryFunctionExpression as NPV_UnaryFunctionExpression +from pyomo.core.expr.numeric_expr import PowExpression as PowExpression +from pyomo.core.expr.numeric_expr import ProductExpression as ProductExpression +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression +from pyomo.core.expr.numeric_expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.visitor import ExpressionValueVisitor as ExpressionValueVisitor +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +wntr: Incomplete +wntr_available: Incomplete +logger: Incomplete + +class WntrConfig(SolverConfig): + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class WntrResults(Results): + wallclock_time: Incomplete + solution_loader: Incomplete + def __init__(self, solver) -> None: ... + +class Wntr(PersistentBase, PersistentSolver): + def __init__(self, only_child_vars: bool = True) -> None: ... + def available(self): ... + def version(self): ... + @property + def config(self) -> WntrConfig: ... + @config.setter + def config(self, val: WntrConfig): ... + @property + def wntr_options(self): ... + @wntr_options.setter + def wntr_options(self, val: dict): ... + @property + def symbol_map(self): ... + def solve(self, model: BlockData, timer: HierarchicalTimer = None) -> Results: ... + def set_instance(self, model) -> None: ... + def update_params(self) -> None: ... + def load_vars(self, vars_to_load=None) -> None: ... + def get_primals(self, vars_to_load=None): ... + +class PyomoToWntrVisitor(ExpressionValueVisitor): + var_map: Incomplete + param_map: Incomplete + def __init__(self, var_map, param_map) -> None: ... + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... diff --git a/stubs/pyomo/contrib/appsi/utils/__init__.pyi b/stubs/pyomo/contrib/appsi/utils/__init__.pyi new file mode 100644 index 000000000..6e4add2f5 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/utils/__init__.pyi @@ -0,0 +1,4 @@ +from .collect_vars_and_named_exprs import ( + collect_vars_and_named_exprs as collect_vars_and_named_exprs, +) +from .get_objective import get_objective as get_objective diff --git a/stubs/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.pyi b/stubs/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.pyi new file mode 100644 index 000000000..e425994df --- /dev/null +++ b/stubs/pyomo/contrib/appsi/utils/collect_vars_and_named_exprs.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete +from pyomo.core.expr.visitor import ExpressionValueVisitor as ExpressionValueVisitor +from pyomo.core.expr.visitor import nonpyomo_leaf_types as nonpyomo_leaf_types + +class _VarAndNamedExprCollector(ExpressionValueVisitor): + named_expressions: Incomplete + variables: Incomplete + fixed_vars: Incomplete + def __init__(self) -> None: ... + def visit(self, node, values) -> None: ... + def visiting_potential_leaf(self, node): ... + +def collect_vars_and_named_exprs(expr): ... diff --git a/stubs/pyomo/contrib/appsi/utils/get_objective.pyi b/stubs/pyomo/contrib/appsi/utils/get_objective.pyi new file mode 100644 index 000000000..9ab99f407 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/utils/get_objective.pyi @@ -0,0 +1,3 @@ +from pyomo.core.base.objective import Objective as Objective + +def get_objective(block): ... diff --git a/stubs/pyomo/contrib/appsi/writers/__init__.pyi b/stubs/pyomo/contrib/appsi/writers/__init__.pyi new file mode 100644 index 000000000..d3dd86111 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/writers/__init__.pyi @@ -0,0 +1,2 @@ +from .lp_writer import LPWriter as LPWriter +from .nl_writer import NLWriter as NLWriter diff --git a/stubs/pyomo/contrib/appsi/writers/config.pyi b/stubs/pyomo/contrib/appsi/writers/config.pyi new file mode 100644 index 000000000..6e7af44a9 --- /dev/null +++ b/stubs/pyomo/contrib/appsi/writers/config.pyi @@ -0,0 +1,3 @@ +class WriterConfig: + symbolic_solver_labels: bool + def __init__(self) -> None: ... diff --git a/stubs/pyomo/contrib/appsi/writers/lp_writer.pyi b/stubs/pyomo/contrib/appsi/writers/lp_writer.pyi new file mode 100644 index 000000000..4156bd61b --- /dev/null +++ b/stubs/pyomo/contrib/appsi/writers/lp_writer.pyi @@ -0,0 +1,36 @@ +from _typeshed import Incomplete +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import PersistentBase as PersistentBase +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +from ..cmodel import cmodel as cmodel +from ..cmodel import cmodel_available as cmodel_available +from .config import WriterConfig as WriterConfig + +class LPWriter(PersistentBase): + def __init__(self, only_child_vars: bool = False) -> None: ... + @property + def config(self): ... + @config.setter + def config(self, val: WriterConfig): ... + update_config: Incomplete + def set_instance(self, model) -> None: ... + def update_params(self) -> None: ... + def write(self, model: BlockData, filename: str, timer: HierarchicalTimer = None): ... + def get_vars(self): ... + def get_ordered_cons(self): ... + def get_active_objective(self): ... + @property + def symbol_map(self): ... diff --git a/stubs/pyomo/contrib/appsi/writers/nl_writer.pyi b/stubs/pyomo/contrib/appsi/writers/nl_writer.pyi new file mode 100644 index 000000000..ef26c73aa --- /dev/null +++ b/stubs/pyomo/contrib/appsi/writers/nl_writer.pyi @@ -0,0 +1,38 @@ +from _typeshed import Incomplete +from pyomo.common.collections import OrderedSet as OrderedSet +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.appsi.base import PersistentBase as PersistentBase +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env as set_pyomo_amplfunc_env +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +from ..cmodel import cmodel as cmodel +from ..cmodel import cmodel_available as cmodel_available +from .config import WriterConfig as WriterConfig + +class NLWriter(PersistentBase): + def __init__(self, only_child_vars: bool = False) -> None: ... + @property + def config(self): ... + @config.setter + def config(self, val: WriterConfig): ... + @property + def symbol_map(self): ... + update_config: Incomplete + def set_instance(self, model) -> None: ... + def update_params(self) -> None: ... + def write(self, model: BlockData, filename: str, timer: HierarchicalTimer = None): ... + def update(self, timer: HierarchicalTimer = None): ... + def get_ordered_vars(self): ... + def get_ordered_cons(self): ... + def get_active_objective(self): ... diff --git a/stubs/pyomo/contrib/benders/__init__.pyi b/stubs/pyomo/contrib/benders/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/benders/benders_cuts.pyi b/stubs/pyomo/contrib/benders/benders_cuts.pyi new file mode 100644 index 000000000..bb676132d --- /dev/null +++ b/stubs/pyomo/contrib/benders/benders_cuts.pyi @@ -0,0 +1,40 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import mpi4py as mpi4py +from pyomo.common.dependencies import mpi4py_available as mpi4py_available +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.block import declare_custom_block as declare_custom_block +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver as PersistentSolver + +MPI: Incomplete +logger: Incomplete +__doc__: str +solver_dual_sign_convention: Incomplete + +class BendersCutGeneratorData(BlockData): + num_subproblems_by_rank: int + subproblems: Incomplete + complicating_vars_maps: Incomplete + root_vars: Incomplete + root_vars_indices: Incomplete + root_etas: Incomplete + cuts: Incomplete + subproblem_solvers: Incomplete + tol: Incomplete + all_root_etas: Incomplete + def __init__(self, component) -> None: ... + def global_num_subproblems(self): ... + def local_num_subproblems(self): ... + comm: Incomplete + def set_input(self, root_vars, tol: float = 1e-06, comm=None) -> None: ... + def add_subproblem( + self, + subproblem_fn, + subproblem_fn_kwargs, + root_eta, + subproblem_solver: str = 'gurobi_persistent', + relax_subproblem_cons: bool = False, + ) -> None: ... + def generate_cut(self): ... diff --git a/stubs/pyomo/contrib/benders/examples/__init__.pyi b/stubs/pyomo/contrib/benders/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/benders/examples/farmer.pyi b/stubs/pyomo/contrib/benders/examples/farmer.pyi new file mode 100644 index 000000000..d70db5499 --- /dev/null +++ b/stubs/pyomo/contrib/benders/examples/farmer.pyi @@ -0,0 +1,21 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import mpi4py as mpi4py +from pyomo.contrib.benders.benders_cuts import BendersCutGenerator as BendersCutGenerator + +class Farmer: + crops: Incomplete + total_acreage: int + PriceQuota: Incomplete + SubQuotaSellingPrice: Incomplete + SuperQuotaSellingPrice: Incomplete + CattleFeedRequirement: Incomplete + PurchasePrice: Incomplete + PlantingCostPerAcre: Incomplete + scenarios: Incomplete + crop_yield: Incomplete + scenario_probabilities: Incomplete + def __init__(self) -> None: ... + +def create_root(farmer): ... +def create_subproblem(root, farmer, scenario): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/benders/examples/grothey_ex.pyi b/stubs/pyomo/contrib/benders/examples/grothey_ex.pyi new file mode 100644 index 000000000..7dfc88ef3 --- /dev/null +++ b/stubs/pyomo/contrib/benders/examples/grothey_ex.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.benders.benders_cuts import BendersCutGenerator as BendersCutGenerator + +def create_root(): ... +def create_subproblem(root): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/community_detection/__init__.pyi b/stubs/pyomo/contrib/community_detection/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/community_detection/community_graph.pyi b/stubs/pyomo/contrib/community_detection/community_graph.pyi new file mode 100644 index 000000000..026a1a832 --- /dev/null +++ b/stubs/pyomo/contrib/community_detection/community_graph.pyi @@ -0,0 +1,14 @@ +from pyomo.core import ComponentMap as ComponentMap +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Var as Var +from pyomo.core.expr import identify_variables as identify_variables + +def generate_model_graph( + model, + type_of_graph, + with_objective: bool = True, + weighted_graph: bool = True, + use_only_active_components: bool = True, +): ... diff --git a/stubs/pyomo/contrib/community_detection/detection.pyi b/stubs/pyomo/contrib/community_detection/detection.pyi new file mode 100644 index 000000000..0de67aee7 --- /dev/null +++ b/stubs/pyomo/contrib/community_detection/detection.pyi @@ -0,0 +1,64 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.contrib.community_detection.community_graph import ( + generate_model_graph as generate_model_graph, +) +from pyomo.core import Block as Block +from pyomo.core import ComponentMap as ComponentMap +from pyomo.core import ConcreteModel as ConcreteModel +from pyomo.core import Constraint as Constraint +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.core.expr.visitor import replace_expressions as replace_expressions + +logger: Incomplete +community_louvain: Incomplete +community_louvain_available: Incomplete + +def detect_communities( + model, + type_of_community_map: str = 'constraint', + with_objective: bool = True, + weighted_graph: bool = True, + random_seed=None, + use_only_active_components: bool = True, +): ... + +class CommunityMap: + community_map: Incomplete + type_of_community_map: Incomplete + with_objective: Incomplete + weighted_graph: Incomplete + random_seed: Incomplete + use_only_active_components: Incomplete + model: Incomplete + graph: Incomplete + graph_node_mapping: Incomplete + constraint_variable_map: Incomplete + graph_partition: Incomplete + def __init__( + self, + community_map, + type_of_community_map, + with_objective, + weighted_graph, + random_seed, + use_only_active_components, + model, + graph, + graph_node_mapping, + constraint_variable_map, + graph_partition, + ) -> None: ... + def __eq__(self, other): ... + def __iter__(self): ... + def __getitem__(self, item): ... + def __len__(self) -> int: ... + def keys(self): ... + def values(self): ... + def items(self): ... + def visualize_model_graph(self, type_of_graph: str = 'constraint', filename=None, pos=None): ... + def generate_structured_model(self): ... diff --git a/stubs/pyomo/contrib/community_detection/event_log.pyi b/stubs/pyomo/contrib/community_detection/event_log.pyi new file mode 100644 index 000000000..299cc45ab --- /dev/null +++ b/stubs/pyomo/contrib/community_detection/event_log.pyi @@ -0,0 +1,6 @@ +from _typeshed import Incomplete +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var + +logger: Incomplete diff --git a/stubs/pyomo/contrib/community_detection/plugins.pyi b/stubs/pyomo/contrib/community_detection/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/community_detection/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/cp/__init__.pyi b/stubs/pyomo/contrib/cp/__init__.pyi new file mode 100644 index 000000000..e98e2b538 --- /dev/null +++ b/stubs/pyomo/contrib/cp/__init__.pyi @@ -0,0 +1,25 @@ +from pyomo.contrib.cp.interval_var import IntervalVar as IntervalVar +from pyomo.contrib.cp.interval_var import IntervalVarEndTime as IntervalVarEndTime +from pyomo.contrib.cp.interval_var import IntervalVarLength as IntervalVarLength +from pyomo.contrib.cp.interval_var import IntervalVarPresence as IntervalVarPresence +from pyomo.contrib.cp.interval_var import IntervalVarStartTime as IntervalVarStartTime +from pyomo.contrib.cp.repn.docplex_writer import CPOptimizerSolver as CPOptimizerSolver +from pyomo.contrib.cp.repn.docplex_writer import DocplexWriter as DocplexWriter +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import alternative as alternative +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import spans as spans +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import synchronize as synchronize +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + before_in_sequence as before_in_sequence, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + first_in_sequence as first_in_sequence, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + last_in_sequence as last_in_sequence, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import no_overlap as no_overlap +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import predecessor_to as predecessor_to +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import AlwaysIn as AlwaysIn +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import Pulse as Pulse +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import Step as Step +from pyomo.contrib.cp.sequence_var import SequenceVar as SequenceVar diff --git a/stubs/pyomo/contrib/cp/interval_var.pyi b/stubs/pyomo/contrib/cp/interval_var.pyi new file mode 100644 index 000000000..cc9abbf7d --- /dev/null +++ b/stubs/pyomo/contrib/cp/interval_var.pyi @@ -0,0 +1,63 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.pyomo_typing import overload as overload +from pyomo.contrib.cp.scheduling_expr.precedence_expressions import AtExpression as AtExpression +from pyomo.contrib.cp.scheduling_expr.precedence_expressions import ( + BeforeExpression as BeforeExpression, +) +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import SpanExpression as SpanExpression +from pyomo.core import Integers as Integers +from pyomo.core import value as value +from pyomo.core.base import Any as Any +from pyomo.core.base import ScalarBooleanVar as ScalarBooleanVar +from pyomo.core.base import ScalarVar as ScalarVar +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.initializer import BoundInitializer as BoundInitializer +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.expr import GetItemExpression as GetItemExpression + +class IntervalVarTimePoint(ScalarVar): + def get_associated_interval_var(self): ... + def before(self, time, delay: int = 0): ... + def after(self, time, delay: int = 0): ... + def at(self, time, delay: int = 0): ... + +class IntervalVarStartTime(IntervalVarTimePoint): + def __init__(self, *args, **kwd) -> None: ... + +class IntervalVarEndTime(IntervalVarTimePoint): + def __init__(self, *args, **kwd) -> None: ... + +class IntervalVarLength(ScalarVar): + def __init__(self, *args, **kwd) -> None: ... + def get_associated_interval_var(self): ... + +class IntervalVarPresence(ScalarBooleanVar): + def __init__(self, *args, **kwd) -> None: ... + def get_associated_interval_var(self): ... + +class IntervalVarData(BlockData): + is_present: Incomplete + start_time: Incomplete + end_time: Incomplete + length: Incomplete + def __init__(self, component=None) -> None: ... + @property + def optional(self): ... + @optional.setter + def optional(self, val) -> None: ... + def spans(self, *args): ... + +class IntervalVar(Block): + def __new__(cls, *args, **kwds): ... + +class ScalarIntervalVar(IntervalVarData, IntervalVar): + def __init__(self, *args, **kwds) -> None: ... + +class IndexedIntervalVar(IntervalVar): + def __getitem__(self, args): ... diff --git a/stubs/pyomo/contrib/cp/plugins.pyi b/stubs/pyomo/contrib/cp/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/cp/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/cp/repn/__init__.pyi b/stubs/pyomo/contrib/cp/repn/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/cp/repn/docplex_writer.pyi b/stubs/pyomo/contrib/cp/repn/docplex_writer.pyi new file mode 100644 index 000000000..0aeb358b5 --- /dev/null +++ b/stubs/pyomo/contrib/cp/repn/docplex_writer.pyi @@ -0,0 +1,151 @@ +import types + +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.fileutils import Executable as Executable +from pyomo.contrib.cp import IntervalVar as IntervalVar +from pyomo.contrib.cp.interval_var import IndexedIntervalVar as IndexedIntervalVar +from pyomo.contrib.cp.interval_var import IntervalVarData as IntervalVarData +from pyomo.contrib.cp.interval_var import IntervalVarEndTime as IntervalVarEndTime +from pyomo.contrib.cp.interval_var import IntervalVarLength as IntervalVarLength +from pyomo.contrib.cp.interval_var import IntervalVarPresence as IntervalVarPresence +from pyomo.contrib.cp.interval_var import IntervalVarStartTime as IntervalVarStartTime +from pyomo.contrib.cp.interval_var import ScalarIntervalVar as ScalarIntervalVar +from pyomo.contrib.cp.scheduling_expr.precedence_expressions import AtExpression as AtExpression +from pyomo.contrib.cp.scheduling_expr.precedence_expressions import ( + BeforeExpression as BeforeExpression, +) +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( + AlternativeExpression as AlternativeExpression, +) +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import SpanExpression as SpanExpression +from pyomo.contrib.cp.scheduling_expr.scheduling_logic import ( + SynchronizeExpression as SynchronizeExpression, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + BeforeInSequenceExpression as BeforeInSequenceExpression, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + FirstInSequenceExpression as FirstInSequenceExpression, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + LastInSequenceExpression as LastInSequenceExpression, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + NoOverlapExpression as NoOverlapExpression, +) +from pyomo.contrib.cp.scheduling_expr.sequence_expressions import ( + PredecessorToExpression as PredecessorToExpression, +) +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import AlwaysIn as AlwaysIn +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( + CumulativeFunction as CumulativeFunction, +) +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import ( + NegatedStepFunction as NegatedStepFunction, +) +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import Pulse as Pulse +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import StepAt as StepAt +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import StepAtEnd as StepAtEnd +from pyomo.contrib.cp.scheduling_expr.step_function_expressions import StepAtStart as StepAtStart +from pyomo.contrib.cp.sequence_var import ScalarSequenceVar as ScalarSequenceVar +from pyomo.contrib.cp.sequence_var import SequenceVar as SequenceVar +from pyomo.contrib.cp.sequence_var import SequenceVarData as SequenceVarData +from pyomo.core.base import Block as Block +from pyomo.core.base import BooleanVar as BooleanVar +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import LogicalConstraint as LogicalConstraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import RangeSet as RangeSet +from pyomo.core.base import Set as Set +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import Var as Var +from pyomo.core.base import maximize as maximize +from pyomo.core.base import minimize as minimize +from pyomo.core.base import value as value +from pyomo.core.base.boolean_var import BooleanVarData as BooleanVarData +from pyomo.core.base.boolean_var import IndexedBooleanVar as IndexedBooleanVar +from pyomo.core.base.boolean_var import ScalarBooleanVar as ScalarBooleanVar +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.param import IndexedParam as IndexedParam +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.param import ScalarParam as ScalarParam +from pyomo.core.base.set import SetProduct as SetProduct +from pyomo.core.base.var import IndexedVar as IndexedVar +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.network import Port as Port +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverResults as SolverResults +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.repn.util import ExitNodeDispatcher as ExitNodeDispatcher + +cp: Incomplete +docplex_available: Incomplete +cp_solver: Incomplete +logger: Incomplete + +class _GENERAL: ... +class _START_TIME: ... +class _END_TIME: ... +class _DEFERRED_ELEMENT_CONSTRAINT: ... +class _ELEMENT_CONSTRAINT: ... +class _DEFERRED_BEFORE: ... +class _DEFERRED_AFTER: ... +class _DEFERRED_AT: ... +class _BEFORE: ... +class _AT: ... +class _IMPLIES: ... +class _LAND: ... +class _LOR: ... +class _XOR: ... +class _EQUIVALENT_TO: ... + +step_func_expression_types: Incomplete + +class LogicalToDoCplex(StreamBasedExpressionVisitor): + exit_node_dispatcher: Incomplete + cpx: Incomplete + symbolic_solver_labels: Incomplete + var_map: Incomplete + pyomo_to_docplex: Incomplete + def __init__(self, cpx_model, symbolic_solver_labels: bool = False) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, data): ... + finalizeResult: Incomplete + +def collect_valid_components(model, active: bool = True, sort=None, valid=..., targets=...): ... + +class DocplexWriter: + CONFIG: Incomplete + config: Incomplete + def __init__(self) -> None: ... + def write(self, model, **options): ... + +class CPOptimizerSolver: + CONFIG: Incomplete + config: Incomplete + def __init__(self, **kwds) -> None: ... + @property + def options(self): ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def solve(self, model, **kwds): ... diff --git a/stubs/pyomo/contrib/cp/scheduling_expr/__init__.pyi b/stubs/pyomo/contrib/cp/scheduling_expr/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/cp/scheduling_expr/precedence_expressions.pyi b/stubs/pyomo/contrib/cp/scheduling_expr/precedence_expressions.pyi new file mode 100644 index 000000000..88f38b956 --- /dev/null +++ b/stubs/pyomo/contrib/cp/scheduling_expr/precedence_expressions.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.core.expr.logical_expr import BooleanExpression as BooleanExpression + +class PrecedenceExpression(BooleanExpression): + PRECEDENCE: Incomplete + def nargs(self): ... + @property + def delay(self): ... + +class BeforeExpression(PrecedenceExpression): ... +class AtExpression(PrecedenceExpression): ... diff --git a/stubs/pyomo/contrib/cp/scheduling_expr/scheduling_logic.pyi b/stubs/pyomo/contrib/cp/scheduling_expr/scheduling_logic.pyi new file mode 100644 index 000000000..c626350c2 --- /dev/null +++ b/stubs/pyomo/contrib/cp/scheduling_expr/scheduling_logic.pyi @@ -0,0 +1,9 @@ +from pyomo.core.expr.logical_expr import NaryBooleanExpression as NaryBooleanExpression + +class SpanExpression(NaryBooleanExpression): ... +class AlternativeExpression(NaryBooleanExpression): ... +class SynchronizeExpression(NaryBooleanExpression): ... + +def spans(*args): ... +def alternative(*args): ... +def synchronize(*args): ... diff --git a/stubs/pyomo/contrib/cp/scheduling_expr/sequence_expressions.pyi b/stubs/pyomo/contrib/cp/scheduling_expr/sequence_expressions.pyi new file mode 100644 index 000000000..141dde7cc --- /dev/null +++ b/stubs/pyomo/contrib/cp/scheduling_expr/sequence_expressions.pyi @@ -0,0 +1,22 @@ +from pyomo.core.expr.logical_expr import BooleanExpression as BooleanExpression + +class NoOverlapExpression(BooleanExpression): + def nargs(self): ... + +class FirstInSequenceExpression(BooleanExpression): + def nargs(self): ... + +class LastInSequenceExpression(BooleanExpression): + def nargs(self): ... + +class BeforeInSequenceExpression(BooleanExpression): + def nargs(self): ... + +class PredecessorToExpression(BooleanExpression): + def nargs(self): ... + +def no_overlap(sequence_var): ... +def first_in_sequence(interval_var, sequence_var): ... +def last_in_sequence(interval_var, sequence_var): ... +def before_in_sequence(before_var, after_var, sequence_var): ... +def predecessor_to(before_var, after_var, sequence_var): ... diff --git a/stubs/pyomo/contrib/cp/scheduling_expr/step_function_expressions.pyi b/stubs/pyomo/contrib/cp/scheduling_expr/step_function_expressions.pyi new file mode 100644 index 000000000..c00e8d2c5 --- /dev/null +++ b/stubs/pyomo/contrib/cp/scheduling_expr/step_function_expressions.pyi @@ -0,0 +1,48 @@ +from _typeshed import Incomplete +from pyomo.contrib.cp.interval_var import IntervalVar as IntervalVar +from pyomo.contrib.cp.interval_var import IntervalVarData as IntervalVarData +from pyomo.contrib.cp.interval_var import IntervalVarEndTime as IntervalVarEndTime +from pyomo.contrib.cp.interval_var import IntervalVarStartTime as IntervalVarStartTime +from pyomo.core.expr.base import ExpressionBase as ExpressionBase +from pyomo.core.expr.logical_expr import BooleanExpression as BooleanExpression +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression + +class StepFunction(ExpressionBase): + PRECEDENCE: Incomplete + def __add__(self, other): ... + def __radd__(self, other): ... + def __iadd__(self, other): ... + def __sub__(self, other): ... + def __rsub__(self, other): ... + def __isub__(self, other): ... + def within(self, bounds, times): ... + @property + def args(self): ... + +class Pulse(StepFunction): + def __init__(self, args=None, interval_var=None, height=None) -> None: ... + def nargs(self): ... + +class Step(StepFunction): + def __new__(cls, time, height): ... + +class StepBase(StepFunction): + def __init__(self, args) -> None: ... + def nargs(self): ... + +class StepAt(StepBase): ... +class StepAtStart(StepBase): ... +class StepAtEnd(StepBase): ... + +class CumulativeFunction(StepFunction): + PRECEDENCE: Incomplete + def __init__(self, args, nargs=None) -> None: ... + def nargs(self): ... + +class NegatedStepFunction(StepFunction): + def __init__(self, args) -> None: ... + def nargs(self): ... + +class AlwaysIn(BooleanExpression): + def __init__(self, args=None, cumul_func=None, bounds=None, times=None) -> None: ... + def nargs(self): ... diff --git a/stubs/pyomo/contrib/cp/sequence_var.pyi b/stubs/pyomo/contrib/cp/sequence_var.pyi new file mode 100644 index 000000000..a99bae4d2 --- /dev/null +++ b/stubs/pyomo/contrib/cp/sequence_var.pyi @@ -0,0 +1,26 @@ +from _typeshed import Incomplete +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.contrib.cp import IntervalVar as IntervalVar +from pyomo.core import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.initializer import Initializer as Initializer + +logger: Incomplete + +class SequenceVarData(ActiveComponentData): + interval_vars: Incomplete + def __init__(self, component=None) -> None: ... + def set_value(self, expr) -> None: ... + +class SequenceVar(ActiveIndexedComponent): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + +class ScalarSequenceVar(SequenceVarData, SequenceVar): + def __init__(self, *args, **kwds) -> None: ... + +class IndexedSequenceVar(SequenceVar): ... diff --git a/stubs/pyomo/contrib/cp/transform/__init__.pyi b/stubs/pyomo/contrib/cp/transform/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_program.pyi b/stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_program.pyi new file mode 100644 index 000000000..4480e8c19 --- /dev/null +++ b/stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_program.pyi @@ -0,0 +1,23 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.cp.transform.logical_to_disjunctive_walker import ( + LogicalToDisjunctiveVisitor as LogicalToDisjunctiveVisitor, +) +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import LogicalConstraint as LogicalConstraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Transformation as Transformation +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import VarList as VarList +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction + +class LogicalToDisjunctive(Transformation): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.pyi b/stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.pyi new file mode 100644 index 000000000..35181fe70 --- /dev/null +++ b/stubs/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.pyi @@ -0,0 +1,33 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.errors import MouseTrap as MouseTrap +from pyomo.core.base import Binary as Binary +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import ConstraintList as ConstraintList +from pyomo.core.base import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core.base import VarList as VarList +from pyomo.core.base import value as value +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.param import ScalarParam as ScalarParam +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.expr_common import ExpressionType as ExpressionType +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.gdp.disjunct import AutoLinkedBooleanVar as AutoLinkedBooleanVar +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.gdp.disjunct import Disjunction as Disjunction + +class LogicalToDisjunctiveVisitor(StreamBasedExpressionVisitor): + z_vars: Incomplete + constraints: Incomplete + disjuncts: Incomplete + disjunctions: Incomplete + expansions: Incomplete + boolean_to_binary_map: Incomplete + def __init__(self) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, data): ... + def finalizeResult(self, result): ... diff --git a/stubs/pyomo/contrib/cspline_external/__init__.pyi b/stubs/pyomo/contrib/cspline_external/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/cspline_external/build.pyi b/stubs/pyomo/contrib/cspline_external/build.pyi new file mode 100644 index 000000000..d9a172e03 --- /dev/null +++ b/stubs/pyomo/contrib/cspline_external/build.pyi @@ -0,0 +1,6 @@ +from pyomo.common.cmake_builder import build_cmake_project as build_cmake_project + +def build_cspline_external(user_args=[], parallel=None): ... + +class ASLCsplineExternalBuilder: + def __call__(self, parallel): ... diff --git a/stubs/pyomo/contrib/cspline_external/cspline_parameters.pyi b/stubs/pyomo/contrib/cspline_external/cspline_parameters.pyi new file mode 100644 index 000000000..cafbf0717 --- /dev/null +++ b/stubs/pyomo/contrib/cspline_external/cspline_parameters.pyi @@ -0,0 +1,30 @@ +from _typeshed import Incomplete + +class CsplineParameters: + knots: Incomplete + a1: Incomplete + a2: Incomplete + a3: Incomplete + a4: Incomplete + def __init__(self, model=None, fptr=None) -> None: ... + @property + def n_knots(self): ... + @property + def n_segments(self): ... + @property + def valid(self): ... + def get_parameters_from_model(self, m) -> None: ... + def get_parameters_from_file(self, fptr) -> None: ... + def write_parameters(self, fptr) -> None: ... + def segment(self, x): ... + def f(self, x): ... + +def cubic_parameters_model( + x_data, + y_data, + x_knots=None, + end_point_constraint: bool = True, + objective_form: bool = False, + name: str = 'cubic spline parameters model', +): ... +def add_endpoint_second_derivative_constraints(m): ... diff --git a/stubs/pyomo/contrib/cspline_external/plugins.pyi b/stubs/pyomo/contrib/cspline_external/plugins.pyi new file mode 100644 index 000000000..fdff5a59e --- /dev/null +++ b/stubs/pyomo/contrib/cspline_external/plugins.pyi @@ -0,0 +1,6 @@ +from pyomo.common.extensions import ExtensionBuilderFactory as ExtensionBuilderFactory +from pyomo.contrib.cspline_external.build import ( + ASLCsplineExternalBuilder as ASLCsplineExternalBuilder, +) + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/doe/__init__.pyi b/stubs/pyomo/contrib/doe/__init__.pyi new file mode 100644 index 000000000..dcf0ad3c5 --- /dev/null +++ b/stubs/pyomo/contrib/doe/__init__.pyi @@ -0,0 +1,17 @@ +from pyomo.common.deprecation import deprecated as deprecated + +from .doe import DesignOfExperiments as DesignOfExperiments +from .doe import FiniteDifferenceStep as FiniteDifferenceStep +from .doe import ObjectiveLib as ObjectiveLib +from .utils import rescale_FIM as rescale_FIM + +deprecation_message: str + +class MeasurementVariables: + def __init__(self, *args) -> None: ... + +class DesignVariables: + def __init__(self, *args) -> None: ... + +class ModelOptionLib: + def __init__(self, *args) -> None: ... diff --git a/stubs/pyomo/contrib/doe/doe.pyi b/stubs/pyomo/contrib/doe/doe.pyi new file mode 100644 index 000000000..717b66108 --- /dev/null +++ b/stubs/pyomo/contrib/doe/doe.pyi @@ -0,0 +1,103 @@ +from enum import Enum + +from _typeshed import Incomplete +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.dependencies import pathlib as pathlib +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.timing import TicTocTimer as TicTocTimer +from pyomo.contrib.sensitivity_toolbox.sens import get_dsdp as get_dsdp +from pyomo.opt import SolverStatus as SolverStatus + +class ObjectiveLib(Enum): + determinant = 'determinant' + trace = 'trace' + minimum_eigenvalue = 'minimum_eigenvalue' + zero = 'zero' + +class FiniteDifferenceStep(Enum): + forward = 'forward' + central = 'central' + backward = 'backward' + +class DesignOfExperiments: + experiment: Incomplete + fd_formula: Incomplete + step: Incomplete + objective_option: Incomplete + scale_constant_value: Incomplete + scale_nominal_param_value: Incomplete + prior_FIM: Incomplete + jac_initial: Incomplete + fim_initial: Incomplete + L_diagonal_lower_bound: Incomplete + solver: Incomplete + tee: Incomplete + get_labeled_model_args: Incomplete + logger: Incomplete + Cholesky_option: Incomplete + only_compute_fim_lower: Incomplete + model: Incomplete + results: Incomplete + def __init__( + self, + experiment=None, + fd_formula: str = 'central', + step: float = 0.001, + objective_option: str = 'determinant', + scale_constant_value: float = 1.0, + scale_nominal_param_value: bool = False, + prior_FIM=None, + jac_initial=None, + fim_initial=None, + L_diagonal_lower_bound: float = 1e-07, + solver=None, + tee: bool = False, + get_labeled_model_args=None, + logger_level=..., + _Cholesky_option: bool = True, + _only_compute_fim_lower: bool = True, + ) -> None: ... + def run_doe(self, model=None, results_file=None) -> None: ... + def run_multi_doe_sequential(self, N_exp: int = 1) -> None: ... + def run_multi_doe_simultaneous(self, N_exp: int = 1) -> None: ... + compute_FIM_model: Incomplete + n_parameters: Incomplete + n_measurement_error: Incomplete + n_experiment_inputs: Incomplete + n_experiment_outputs: Incomplete + def compute_FIM(self, model=None, method: str = 'sequential'): ... + def create_doe_model(self, model=None): ... + def create_objective_function(self, model=None): ... + def check_model_labels(self, model=None) -> None: ... + def check_model_FIM(self, model=None, FIM=None) -> None: ... + def check_model_jac(self, jac=None) -> None: ... + def update_FIM_prior(self, model=None, FIM=None) -> None: ... + def update_unknown_parameter_values(self, model=None, param_vals=None) -> None: ... + factorial_model: Incomplete + fim_factorial_results: Incomplete + def compute_FIM_full_factorial( + self, model=None, design_ranges=None, method: str = 'sequential' + ): ... + figure_result_data: Incomplete + figure_sens_des_vars: Incomplete + figure_fixed_des_vars: Incomplete + def draw_factorial_figure( + self, + results=None, + sensitivity_design_variables=None, + fixed_design_variables=None, + full_design_variable_names=None, + title_text: str = '', + xlabel_text: str = '', + ylabel_text: str = '', + figure_file_name=None, + font_axes: int = 16, + font_tick: int = 14, + log_scale: bool = True, + ) -> None: ... + def get_FIM(self, model=None): ... + def get_sensitivity_matrix(self, model=None): ... + def get_experiment_input_values(self, model=None): ... + def get_unknown_parameter_values(self, model=None): ... + def get_experiment_output_values(self, model=None): ... + def get_measurement_error_values(self, model=None): ... diff --git a/stubs/pyomo/contrib/doe/examples/__init__.pyi b/stubs/pyomo/contrib/doe/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/doe/examples/reactor_compute_factorial_FIM.pyi b/stubs/pyomo/contrib/doe/examples/reactor_compute_factorial_FIM.pyi new file mode 100644 index 000000000..1f78ac636 --- /dev/null +++ b/stubs/pyomo/contrib/doe/examples/reactor_compute_factorial_FIM.pyi @@ -0,0 +1,5 @@ +from pyomo.common.dependencies import pathlib as pathlib +from pyomo.contrib.doe import DesignOfExperiments as DesignOfExperiments +from pyomo.contrib.doe.examples.reactor_experiment import ReactorExperiment as ReactorExperiment + +def run_reactor_doe() -> None: ... diff --git a/stubs/pyomo/contrib/doe/examples/reactor_example.pyi b/stubs/pyomo/contrib/doe/examples/reactor_example.pyi new file mode 100644 index 000000000..1f78ac636 --- /dev/null +++ b/stubs/pyomo/contrib/doe/examples/reactor_example.pyi @@ -0,0 +1,5 @@ +from pyomo.common.dependencies import pathlib as pathlib +from pyomo.contrib.doe import DesignOfExperiments as DesignOfExperiments +from pyomo.contrib.doe.examples.reactor_experiment import ReactorExperiment as ReactorExperiment + +def run_reactor_doe() -> None: ... diff --git a/stubs/pyomo/contrib/doe/examples/reactor_experiment.pyi b/stubs/pyomo/contrib/doe/examples/reactor_experiment.pyi new file mode 100644 index 000000000..2e7bba235 --- /dev/null +++ b/stubs/pyomo/contrib/doe/examples/reactor_experiment.pyi @@ -0,0 +1,16 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.experiment import Experiment as Experiment +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.dae import Simulator as Simulator + +class ReactorExperiment(Experiment): + data: Incomplete + nfe: Incomplete + ncp: Incomplete + model: Incomplete + def __init__(self, data, nfe, ncp) -> None: ... + def get_labeled_model(self): ... + def create_model(self): ... + def finalize_model(self): ... + def label_experiment(self) -> None: ... diff --git a/stubs/pyomo/contrib/doe/utils.pyi b/stubs/pyomo/contrib/doe/utils.pyi new file mode 100644 index 000000000..1337e67f3 --- /dev/null +++ b/stubs/pyomo/contrib/doe/utils.pyi @@ -0,0 +1,5 @@ +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.var import VarData as VarData + +def rescale_FIM(FIM, param_vals): ... diff --git a/stubs/pyomo/contrib/example/__init__.pyi b/stubs/pyomo/contrib/example/__init__.pyi new file mode 100644 index 000000000..3e4a1ae19 --- /dev/null +++ b/stubs/pyomo/contrib/example/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.contrib.example import bar as bar +from pyomo.contrib.example.foo import * +from pyomo.contrib.example.plugins import load as load diff --git a/stubs/pyomo/contrib/example/bar.pyi b/stubs/pyomo/contrib/example/bar.pyi new file mode 100644 index 000000000..6ad95b422 --- /dev/null +++ b/stubs/pyomo/contrib/example/bar.pyi @@ -0,0 +1 @@ +b: str diff --git a/stubs/pyomo/contrib/example/foo.pyi b/stubs/pyomo/contrib/example/foo.pyi new file mode 100644 index 000000000..a13145cf8 --- /dev/null +++ b/stubs/pyomo/contrib/example/foo.pyi @@ -0,0 +1 @@ +a: int diff --git a/stubs/pyomo/contrib/example/plugins/__init__.pyi b/stubs/pyomo/contrib/example/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/example/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/example/plugins/ex_plugin.pyi b/stubs/pyomo/contrib/example/plugins/ex_plugin.pyi new file mode 100644 index 000000000..9e8bafbdd --- /dev/null +++ b/stubs/pyomo/contrib/example/plugins/ex_plugin.pyi @@ -0,0 +1,6 @@ +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory + +class Xfrm_PyomoTransformation(Transformation): + def __init__(self) -> None: ... + def create_using(self, instance, **kwds): ... diff --git a/stubs/pyomo/contrib/fbbt/__init__.pyi b/stubs/pyomo/contrib/fbbt/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/fbbt/expression_bounds_walker.pyi b/stubs/pyomo/contrib/fbbt/expression_bounds_walker.pyi new file mode 100644 index 000000000..753d126cc --- /dev/null +++ b/stubs/pyomo/contrib/fbbt/expression_bounds_walker.pyi @@ -0,0 +1,68 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.contrib.fbbt.interval import BoolFlag as BoolFlag +from pyomo.contrib.fbbt.interval import acos as acos +from pyomo.contrib.fbbt.interval import add as add +from pyomo.contrib.fbbt.interval import asin as asin +from pyomo.contrib.fbbt.interval import atan as atan +from pyomo.contrib.fbbt.interval import cos as cos +from pyomo.contrib.fbbt.interval import div as div +from pyomo.contrib.fbbt.interval import eq as eq +from pyomo.contrib.fbbt.interval import exp as exp +from pyomo.contrib.fbbt.interval import if_ as if_ +from pyomo.contrib.fbbt.interval import ineq as ineq +from pyomo.contrib.fbbt.interval import interval_abs as interval_abs +from pyomo.contrib.fbbt.interval import log as log +from pyomo.contrib.fbbt.interval import log10 as log10 +from pyomo.contrib.fbbt.interval import mul as mul +from pyomo.contrib.fbbt.interval import power as power +from pyomo.contrib.fbbt.interval import ranged as ranged +from pyomo.contrib.fbbt.interval import sin as sin +from pyomo.contrib.fbbt.interval import sub as sub +from pyomo.contrib.fbbt.interval import tan as tan +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.expr.logical_expr import BooleanExpression as BooleanExpression +from pyomo.core.expr.numeric_expr import AbsExpression as AbsExpression +from pyomo.core.expr.numeric_expr import DivisionExpression as DivisionExpression +from pyomo.core.expr.numeric_expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr.numeric_expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression +from pyomo.core.expr.numeric_expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr.numeric_expr import NegationExpression as NegationExpression +from pyomo.core.expr.numeric_expr import NumericExpression as NumericExpression +from pyomo.core.expr.numeric_expr import PowExpression as PowExpression +from pyomo.core.expr.numeric_expr import ProductExpression as ProductExpression +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression +from pyomo.core.expr.numeric_expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.relational_expr import EqualityExpression as EqualityExpression +from pyomo.core.expr.relational_expr import InequalityExpression as InequalityExpression +from pyomo.core.expr.relational_expr import RangedExpression as RangedExpression +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.repn.util import BeforeChildDispatcher as BeforeChildDispatcher +from pyomo.repn.util import ExitNodeDispatcher as ExitNodeDispatcher + +inf: Incomplete +logger: Incomplete + +class ExpressionBoundsBeforeChildDispatcher(BeforeChildDispatcher): + def __init__(self) -> None: ... + +class ExpressionBoundsExitNodeDispatcher(ExitNodeDispatcher): + def unexpected_expression_type(self, visitor, node, *args): ... + +class ExpressionBoundsVisitor(StreamBasedExpressionVisitor): + leaf_bounds: Incomplete + feasibility_tol: Incomplete + use_fixed_var_values_as_bounds: Incomplete + def __init__( + self, + leaf_bounds=None, + feasibility_tol: float = 1e-08, + use_fixed_var_values_as_bounds: bool = False, + ) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, data): ... diff --git a/stubs/pyomo/contrib/fbbt/fbbt.pyi b/stubs/pyomo/contrib/fbbt/fbbt.pyi new file mode 100644 index 000000000..ed980c59c --- /dev/null +++ b/stubs/pyomo/contrib/fbbt/fbbt.pyi @@ -0,0 +1,76 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.numeric_types import native_types as native_types +from pyomo.contrib.fbbt.expression_bounds_walker import ( + ExpressionBoundsVisitor as ExpressionBoundsVisitor, +) +from pyomo.core.base.block import Block as Block +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.objective import ScalarObjective as ScalarObjective +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import ExpressionValueVisitor as ExpressionValueVisitor +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.gdp import Disjunct as Disjunct + +logger: Incomplete +__doc__: str + +class FBBTException(PyomoException): ... + +class _FBBTVisitorLeafToRoot(StreamBasedExpressionVisitor): + bnds_dict: Incomplete + integer_tol: Incomplete + feasibility_tol: Incomplete + ignore_fixed: Incomplete + def __init__( + self, + bnds_dict, + integer_tol: float = 0.0001, + feasibility_tol: float = 1e-08, + ignore_fixed: bool = False, + ) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, data) -> None: ... + +class _FBBTVisitorRootToLeaf(ExpressionValueVisitor): + bnds_dict: Incomplete + integer_tol: Incomplete + feasibility_tol: Incomplete + def __init__( + self, bnds_dict, integer_tol: float = 0.0001, feasibility_tol: float = 1e-08 + ) -> None: ... + def visit(self, node, values) -> None: ... + def visiting_potential_leaf(self, node): ... + +def fbbt( + comp, + deactivate_satisfied_constraints: bool = False, + integer_tol: float = 1e-05, + feasibility_tol: float = 1e-08, + max_iter: int = 10, + improvement_tol: float = 0.0001, + descend_into: bool = True, +): ... +def compute_bounds_on_expr(expr, ignore_fixed: bool = False): ... + +class BoundsManager: + def __init__(self, comp) -> None: ... + def save_bounds(self) -> None: ... + def pop_bounds(self, ndx: int = -1) -> None: ... + def load_bounds(self, bnds, save_current_bounds: bool = True) -> None: ... diff --git a/stubs/pyomo/contrib/fbbt/interval.pyi b/stubs/pyomo/contrib/fbbt/interval.pyi new file mode 100644 index 000000000..8d74c3d5f --- /dev/null +++ b/stubs/pyomo/contrib/fbbt/interval.pyi @@ -0,0 +1,46 @@ +from _typeshed import Incomplete +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.common.errors import IntervalException as IntervalException + +logger: Incomplete +inf: Incomplete + +class _bool_flag: + def __init__(self, val) -> None: ... + def __bool__(self) -> bool: ... + __float__: Incomplete + __int__: Incomplete + __abs__: Incomplete + __neg__: Incomplete + __add__: Incomplete + __sub__: Incomplete + __mul__: Incomplete + __div__: Incomplete + __pow__: Incomplete + __radd__: Incomplete + __rsub__: Incomplete + __rmul__: Incomplete + __rdiv__: Incomplete + __rpow__: Incomplete + +def BoolFlag(val): ... +def ineq(xl, xu, yl, yu, feasibility_tol): ... +def eq(xl, xu, yl, yu, feasibility_tol): ... +def ranged(xl, xu, yl, yu, zl, zu, feasibility_tol): ... +def if_(il, iu, tl, tu, fl, fu): ... +def add(xl, xu, yl, yu): ... +def sub(xl, xu, yl, yu): ... +def mul(xl, xu, yl, yu): ... +def inv(xl, xu, feasibility_tol): ... +def div(xl, xu, yl, yu, feasibility_tol): ... +def power(xl, xu, yl, yu, feasibility_tol): ... +def interval_abs(xl, xu): ... +def exp(xl, xu): ... +def log(xl, xu): ... +def log10(xl, xu): ... +def sin(xl, xu): ... +def cos(xl, xu): ... +def tan(xl, xu): ... +def asin(xl, xu, yl, yu, feasibility_tol): ... +def acos(xl, xu, yl, yu, feasibility_tol): ... +def atan(xl, xu, yl, yu): ... diff --git a/stubs/pyomo/contrib/fme/__init__.pyi b/stubs/pyomo/contrib/fme/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/fme/fourier_motzkin_elimination.pyi b/stubs/pyomo/contrib/fme/fourier_motzkin_elimination.pyi new file mode 100644 index 000000000..b1c1ecd66 --- /dev/null +++ b/stubs/pyomo/contrib/fme/fourier_motzkin_elimination.pyi @@ -0,0 +1,37 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import Expression as Expression +from pyomo.core import Objective as Objective +from pyomo.core import Param as Param +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import VarData as VarData +from pyomo.core.plugins.transform.hierarchy import Transformation as Transformation +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn +from pyomo.util.config_domains import ComponentDataSet as ComponentDataSet + +logger: Incomplete + +def gcd(a, b): ... +def lcm(ints): ... + +class Fourier_Motzkin_Elimination_Transformation(Transformation): + CONFIG: Incomplete + def __init__(self) -> None: ... + def post_process_fme_constraints( + self, m, solver_factory, projected_constraints=None, tolerance: int = 0 + ) -> None: ... diff --git a/stubs/pyomo/contrib/fme/plugins.pyi b/stubs/pyomo/contrib/fme/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/fme/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/gdp_bounds/__init__.pyi b/stubs/pyomo/contrib/gdp_bounds/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/gdp_bounds/compute_bounds.pyi b/stubs/pyomo/contrib/gdp_bounds/compute_bounds.pyi new file mode 100644 index 000000000..ae292e6c1 --- /dev/null +++ b/stubs/pyomo/contrib/gdp_bounds/compute_bounds.pyi @@ -0,0 +1,28 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.contrib.fbbt.fbbt import BoundsManager as BoundsManager +from pyomo.contrib.fbbt.fbbt import fbbt as fbbt +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import TraversalStrategy as TraversalStrategy +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.plugins.transform.hierarchy import Transformation as Transformation +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.opt import SolverFactory as SolverFactory + +linear_degrees: Incomplete +inf: Incomplete + +def disjunctive_obbt(model, solver) -> None: ... +def obbt_disjunct(orig_model, idx, solver): ... +def solve_bounding_problem(model, solver): ... +def disjunctive_fbbt(model) -> None: ... +def fbbt_disjunct(disj, parent_bounds) -> None: ... + +class ComputeDisjunctiveVarBounds(Transformation): ... diff --git a/stubs/pyomo/contrib/gdp_bounds/info.pyi b/stubs/pyomo/contrib/gdp_bounds/info.pyi new file mode 100644 index 000000000..d30f008b2 --- /dev/null +++ b/stubs/pyomo/contrib/gdp_bounds/info.pyi @@ -0,0 +1,10 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.core import value as value + +inf: Incomplete + +def disjunctive_bounds(scope): ... +def disjunctive_bound(var, scope): ... +def disjunctive_lb(var, scope): ... +def disjunctive_ub(var, scope): ... diff --git a/stubs/pyomo/contrib/gdp_bounds/plugins.pyi b/stubs/pyomo/contrib/gdp_bounds/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/gdp_bounds/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/gdpopt/GDPopt.pyi b/stubs/pyomo/contrib/gdpopt/GDPopt.pyi new file mode 100644 index 000000000..fddf6253b --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/GDPopt.pyi @@ -0,0 +1,21 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.contrib.gdpopt import __version__ as __version__ +from pyomo.opt.base import SolverFactory as SolverFactory + +class GDPoptSolver: + CONFIG: Incomplete + def solve(self, model, **kwds): ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def version(self): ... diff --git a/stubs/pyomo/contrib/gdpopt/__init__.pyi b/stubs/pyomo/contrib/gdpopt/__init__.pyi new file mode 100644 index 000000000..f27f712b6 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/__init__.pyi @@ -0,0 +1,3 @@ +from _typeshed import Incomplete + +__version__: Incomplete diff --git a/stubs/pyomo/contrib/gdpopt/algorithm_base_class.pyi b/stubs/pyomo/contrib/gdpopt/algorithm_base_class.pyi new file mode 100644 index 000000000..9f4b92574 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/algorithm_base_class.pyi @@ -0,0 +1,62 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.gdpopt import __version__ as __version__ +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_algebraic_variable_list as add_algebraic_variable_list, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_boolean_variable_lists as add_boolean_variable_lists, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import add_disjunct_list as add_disjunct_list +from pyomo.contrib.gdpopt.create_oa_subproblems import add_util_block as add_util_block +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import lower_logger_level_to as lower_logger_level_to +from pyomo.contrib.gdpopt.util import solve_continuous_problem as solve_continuous_problem +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.core.base import Objective as Objective +from pyomo.core.base import maximize as maximize +from pyomo.core.base import minimize as minimize +from pyomo.core.base import value as value +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.opt import SolverResults as SolverResults +from pyomo.util.model_size import build_model_size_report as build_model_size_report + +class _GDPoptAlgorithm: + CONFIG: Incomplete + config: Incomplete + LB: Incomplete + UB: Incomplete + timing: Incomplete + initialization_iteration: int + iteration: int + incumbent_boolean_soln: Incomplete + incumbent_continuous_soln: Incomplete + original_obj: Incomplete + original_util_block: Incomplete + log_formatter: str + def __init__(self, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def version(self): ... + def solve(self, model, **kwds): ... + @property + def objective_sense(self): ... + def relative_gap(self): ... + def primal_bound(self): ... + def update_incumbent(self, util_block) -> None: ... + def bounds_converged(self, config): ... + def reached_iteration_limit(self, config): ... + def reached_time_limit(self, config): ... + def any_termination_criterion_met(self, config): ... diff --git a/stubs/pyomo/contrib/gdpopt/branch_and_bound.pyi b/stubs/pyomo/contrib/gdpopt/branch_and_bound.pyi new file mode 100644 index 000000000..c28d7f304 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/branch_and_bound.pyi @@ -0,0 +1,47 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.contrib.fbbt.fbbt import fbbt as fbbt +from pyomo.contrib.gdpopt.algorithm_base_class import _GDPoptAlgorithm +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_algebraic_variable_list as add_algebraic_variable_list, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_boolean_variable_lists as add_boolean_variable_lists, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import add_disjunct_list as add_disjunct_list +from pyomo.contrib.gdpopt.create_oa_subproblems import add_disjunction_list as add_disjunction_list +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_transformed_boolean_variable_list as add_transformed_boolean_variable_list, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import add_util_block as add_util_block +from pyomo.contrib.gdpopt.nlp_initialization import ( + restore_vars_to_original_values as restore_vars_to_original_values, +) +from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning as SuppressInfeasibleWarning +from pyomo.contrib.gdpopt.util import copy_var_list_values as copy_var_list_values +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.satsolver.satsolver import satisfiable as satisfiable +from pyomo.core import Constraint as Constraint +from pyomo.core import Suffix as Suffix +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import minimize as minimize +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverStatus as SolverStatus + +class BBNodeData(NamedTuple): + obj_lb: Incomplete + obj_ub: Incomplete + is_screened: Incomplete + is_evaluated: Incomplete + num_unbranched_disjunctions: Incomplete + node_count: Incomplete + unbranched_disjunction_indices: Incomplete + +class GDP_LBB_Solver(_GDPoptAlgorithm): + CONFIG: Incomplete + algorithm: str + def solve(self, model, **kwds): ... diff --git a/stubs/pyomo/contrib/gdpopt/config_options.pyi b/stubs/pyomo/contrib/gdpopt/config_options.pyi new file mode 100644 index 000000000..1ac657af1 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/config_options.pyi @@ -0,0 +1,18 @@ +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigList as ConfigList +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.config import PositiveInt as PositiveInt +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.contrib.gdpopt.discrete_problem_initialize import ( + valid_init_strategies as valid_init_strategies, +) +from pyomo.contrib.gdpopt.nlp_initialization import ( + restore_vars_to_original_values as restore_vars_to_original_values, +) +from pyomo.contrib.gdpopt.util import a_logger as a_logger +from pyomo.core.base import LogicalConstraint as LogicalConstraint +from pyomo.gdp.disjunct import Disjunction as Disjunction +from pyomo.util.config_domains import ComponentDataSet as ComponentDataSet diff --git a/stubs/pyomo/contrib/gdpopt/create_oa_subproblems.pyi b/stubs/pyomo/contrib/gdpopt/create_oa_subproblems.pyi new file mode 100644 index 000000000..b4f9cd235 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/create_oa_subproblems.pyi @@ -0,0 +1,38 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.gdpopt.discrete_problem_initialize import ( + valid_init_strategies as valid_init_strategies, +) +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import ( + move_nonlinear_objective_to_constraints as move_nonlinear_objective_to_constraints, +) +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import LogicalConstraint as LogicalConstraint +from pyomo.core import Objective as Objective +from pyomo.core import SortComponents as SortComponents +from pyomo.core.base import ConstraintList as ConstraintList +from pyomo.core.base import Integers as Integers +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import TraversalStrategy as TraversalStrategy +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.gdp.disjunct import Disjunction as Disjunction +from pyomo.util.vars_from_expressions import get_vars_from_components as get_vars_from_components + +def initialize_discrete_problem(util_block, subprob_util_block, config, solver): ... +def add_util_block(discrete): ... +def add_disjunct_list(util_block) -> None: ... +def add_disjunction_list(util_block) -> None: ... +def add_constraint_list(util_block) -> None: ... +def add_global_constraint_list(util_block) -> None: ... +def add_constraints_by_disjunct(util_block) -> None: ... +def add_algebraic_variable_list(util_block, name=None) -> None: ... +def add_discrete_variable_list(util_block) -> None: ... +def add_boolean_variable_lists(util_block) -> None: ... +def add_transformed_boolean_variable_list(util_block) -> None: ... +def get_subproblem(original_model, util_block): ... +def save_initial_values(subproblem_util_block) -> None: ... diff --git a/stubs/pyomo/contrib/gdpopt/cut_generation.pyi b/stubs/pyomo/contrib/gdpopt/cut_generation.pyi new file mode 100644 index 000000000..66c4a7e2c --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/cut_generation.pyi @@ -0,0 +1,7 @@ +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import value as value + +def add_no_good_cut(target_model_util_block, config) -> None: ... diff --git a/stubs/pyomo/contrib/gdpopt/discrete_problem_initialize.pyi b/stubs/pyomo/contrib/gdpopt/discrete_problem_initialize.pyi new file mode 100644 index 000000000..28c95de52 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/discrete_problem_initialize.pyi @@ -0,0 +1,42 @@ +from collections.abc import Generator +from contextlib import contextmanager + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.contrib.gdpopt.cut_generation import add_no_good_cut as add_no_good_cut +from pyomo.contrib.gdpopt.solve_discrete_problem import ( + solve_MILP_discrete_problem as solve_MILP_discrete_problem, +) +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core import maximize as maximize +from pyomo.core import value as value +from pyomo.gdp import Disjunct as Disjunct + +@contextmanager +def preserve_discrete_problem_feasible_region( + discrete_problem_util_block, config, original_bounds=None +) -> Generator[None]: ... +def init_custom_disjuncts( + util_block, discrete_problem_util_block, subprob_util_block, config, solver +) -> None: ... +def init_fixed_disjuncts( + util_block, discrete_problem_util_block, subprob_util_block, config, solver +) -> None: ... +@contextmanager +def use_discrete_problem_for_max_binary_initialization( + discrete_problem_util_block, +) -> Generator[None]: ... +def init_max_binaries( + util_block, discrete_problem_util_block, subprob_util_block, config, solver +): ... +@contextmanager +def use_discrete_problem_for_set_covering(discrete_problem_util_block) -> Generator[None]: ... +def update_set_covering_objective(discrete_problem_util_block, disj_needs_cover) -> None: ... +def init_set_covering( + util_block, discrete_problem_util_block, subprob_util_block, config, solver +): ... + +valid_init_strategies: Incomplete diff --git a/stubs/pyomo/contrib/gdpopt/enumerate.pyi b/stubs/pyomo/contrib/gdpopt/enumerate.pyi new file mode 100644 index 000000000..3feed0792 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/enumerate.pyi @@ -0,0 +1,25 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.contrib.gdpopt.algorithm_base_class import _GDPoptAlgorithm +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_discrete_variable_list as add_discrete_variable_list, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import add_disjunction_list as add_disjunction_list +from pyomo.contrib.gdpopt.create_oa_subproblems import get_subproblem as get_subproblem +from pyomo.contrib.gdpopt.nlp_initialization import ( + restore_vars_to_original_values_enumerate as restore_vars_to_original_values_enumerate, +) +from pyomo.contrib.gdpopt.solve_subproblem import solve_subproblem as solve_subproblem +from pyomo.contrib.gdpopt.util import ( + fix_discrete_solution_in_subproblem as fix_discrete_solution_in_subproblem, +) +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.core import value as value +from pyomo.opt.base import SolverFactory as SolverFactory + +class GDP_Enumeration_Solver(_GDPoptAlgorithm): + CONFIG: Incomplete + algorithm: str + def solve(self, model, **kwds): ... diff --git a/stubs/pyomo/contrib/gdpopt/gloa.pyi b/stubs/pyomo/contrib/gdpopt/gloa.pyi new file mode 100644 index 000000000..b1dc28d8c --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/gloa.pyi @@ -0,0 +1,32 @@ +from _typeshed import Incomplete +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.gdp_bounds.info import disjunctive_bounds as disjunctive_bounds +from pyomo.contrib.gdpopt.algorithm_base_class import _GDPoptAlgorithm +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_constraints_by_disjunct as add_constraints_by_disjunct, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_global_constraint_list as add_global_constraint_list, +) +from pyomo.contrib.gdpopt.cut_generation import add_no_good_cut as add_no_good_cut +from pyomo.contrib.gdpopt.oa_algorithm_utils import _OAAlgorithmMixIn +from pyomo.contrib.gdpopt.solve_discrete_problem import ( + solve_MILP_discrete_problem as solve_MILP_discrete_problem, +) +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.contrib.mcpp.pyomo_mcpp import MCPP_Error as MCPP_Error +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Objective as Objective +from pyomo.core import value as value +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.opt.base import SolverFactory as SolverFactory + +class GDP_GLOA_Solver(_GDPoptAlgorithm, _OAAlgorithmMixIn): + CONFIG: Incomplete + algorithm: str + def solve(self, model, **kwds): ... diff --git a/stubs/pyomo/contrib/gdpopt/ldsda.pyi b/stubs/pyomo/contrib/gdpopt/ldsda.pyi new file mode 100644 index 000000000..aa6bec733 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/ldsda.pyi @@ -0,0 +1,53 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.contrib.fbbt.fbbt import fbbt as fbbt +from pyomo.contrib.gdpopt.algorithm_base_class import _GDPoptAlgorithm +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_algebraic_variable_list as add_algebraic_variable_list, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_boolean_variable_lists as add_boolean_variable_lists, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import add_disjunct_list as add_disjunct_list +from pyomo.contrib.gdpopt.create_oa_subproblems import add_disjunction_list as add_disjunction_list +from pyomo.contrib.gdpopt.create_oa_subproblems import ( + add_transformed_boolean_variable_list as add_transformed_boolean_variable_list, +) +from pyomo.contrib.gdpopt.create_oa_subproblems import add_util_block as add_util_block +from pyomo.contrib.gdpopt.nlp_initialization import ( + restore_vars_to_original_values as restore_vars_to_original_values, +) +from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning as SuppressInfeasibleWarning +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.satsolver.satsolver import satisfiable as satisfiable +from pyomo.core import Objective as Objective +from pyomo.core import Suffix as Suffix +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.expr.logical_expr import ExactlyExpression as ExactlyExpression +from pyomo.opt import SolverFactory as SolverFactory + +tabulate: Incomplete +tabulate_available: Incomplete + +class ExternalVarInfo(NamedTuple): + exactly_number: Incomplete + Boolean_vars: Incomplete + UB: Incomplete + LB: Incomplete + +class GDP_LDSDA_Solver(_GDPoptAlgorithm): + CONFIG: Incomplete + algorithm: str + def solve(self, model, **kwds): ... + def any_termination_criterion_met(self, config): ... + def fix_disjunctions_with_external_var(self, external_var_values_list) -> None: ... + best_direction: Incomplete + current_point: Incomplete + def neighbor_search(self, config): ... + def line_search(self, config) -> None: ... diff --git a/stubs/pyomo/contrib/gdpopt/loa.pyi b/stubs/pyomo/contrib/gdpopt/loa.pyi new file mode 100644 index 000000000..410028e4b --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/loa.pyi @@ -0,0 +1,40 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.gdpopt.algorithm_base_class import _GDPoptAlgorithm +from pyomo.contrib.gdpopt.create_oa_subproblems import add_constraint_list as add_constraint_list +from pyomo.contrib.gdpopt.cut_generation import add_no_good_cut as add_no_good_cut +from pyomo.contrib.gdpopt.oa_algorithm_utils import _OAAlgorithmMixIn +from pyomo.contrib.gdpopt.solve_discrete_problem import ( + solve_MILP_discrete_problem as solve_MILP_discrete_problem, +) +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core import VarList as VarList +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.expr import differentiate as differentiate +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.gdp import Disjunct as Disjunct +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.repn import generate_standard_repn as generate_standard_repn + +MAX_SYMBOLIC_DERIV_SIZE: int + +class JacInfo(NamedTuple): + mode: Incomplete + vars: Incomplete + jac: Incomplete + +class GDP_LOA_Solver(_GDPoptAlgorithm, _OAAlgorithmMixIn): + CONFIG: Incomplete + algorithm: str + def solve(self, model, **kwds): ... diff --git a/stubs/pyomo/contrib/gdpopt/nlp_initialization.pyi b/stubs/pyomo/contrib/gdpopt/nlp_initialization.pyi new file mode 100644 index 000000000..ef631163d --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/nlp_initialization.pyi @@ -0,0 +1,4 @@ +def restore_vars_to_original_values(solver, nlp_util_block, mip_util_block) -> None: ... +def restore_vars_to_original_values_enumerate( + true_disjuncts, boolean_var_values, discrete_var_values, nlp_util_block +) -> None: ... diff --git a/stubs/pyomo/contrib/gdpopt/oa_algorithm_utils.pyi b/stubs/pyomo/contrib/gdpopt/oa_algorithm_utils.pyi new file mode 100644 index 000000000..2030ec482 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/oa_algorithm_utils.pyi @@ -0,0 +1,7 @@ +from pyomo.contrib.gdpopt.solve_subproblem import solve_subproblem as solve_subproblem +from pyomo.contrib.gdpopt.util import ( + fix_discrete_problem_solution_in_subproblem as fix_discrete_problem_solution_in_subproblem, +) +from pyomo.core import value as value + +class _OAAlgorithmMixIn: ... diff --git a/stubs/pyomo/contrib/gdpopt/plugins.pyi b/stubs/pyomo/contrib/gdpopt/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/gdpopt/ric.pyi b/stubs/pyomo/contrib/gdpopt/ric.pyi new file mode 100644 index 000000000..d8c20d403 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/ric.pyi @@ -0,0 +1,16 @@ +from _typeshed import Incomplete +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.contrib.gdpopt.algorithm_base_class import _GDPoptAlgorithm +from pyomo.contrib.gdpopt.cut_generation import add_no_good_cut as add_no_good_cut +from pyomo.contrib.gdpopt.oa_algorithm_utils import _OAAlgorithmMixIn +from pyomo.contrib.gdpopt.solve_discrete_problem import ( + solve_MILP_discrete_problem as solve_MILP_discrete_problem, +) +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.core import Objective as Objective +from pyomo.opt.base import SolverFactory as SolverFactory + +class GDP_RIC_Solver(_GDPoptAlgorithm, _OAAlgorithmMixIn): + CONFIG: Incomplete + algorithm: str + def solve(self, model, **kwds): ... diff --git a/stubs/pyomo/contrib/gdpopt/solve_discrete_problem.pyi b/stubs/pyomo/contrib/gdpopt/solve_discrete_problem.pyi new file mode 100644 index 000000000..8750ca409 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/solve_discrete_problem.pyi @@ -0,0 +1,13 @@ +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.contrib.fbbt.fbbt import fbbt as fbbt +from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning as SuppressInfeasibleWarning +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.opt import SolutionStatus as SolutionStatus +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver as PersistentSolver + +def solve_MILP_discrete_problem(util_block, solver, config): ... +def distinguish_mip_infeasible_or_unbounded(m, config): ... diff --git a/stubs/pyomo/contrib/gdpopt/solve_subproblem.pyi b/stubs/pyomo/contrib/gdpopt/solve_subproblem.pyi new file mode 100644 index 000000000..c0b64a891 --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/solve_subproblem.pyi @@ -0,0 +1,49 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.contrib import appsi as appsi +from pyomo.contrib.appsi.cmodel import cmodel_available as cmodel_available +from pyomo.contrib.fbbt.fbbt import fbbt as fbbt +from pyomo.contrib.gdpopt.solve_discrete_problem import ( + distinguish_mip_infeasible_or_unbounded as distinguish_mip_infeasible_or_unbounded, +) +from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning as SuppressInfeasibleWarning +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import is_feasible as is_feasible +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverResults as SolverResults + +def configure_and_call_solver(model, solver, args, problem_type, timing, time_limit): ... +def process_nonlinear_problem_results(results, model, problem_type, config): ... +def solve_linear_subproblem(subproblem, config, timing): ... +def solve_NLP(nlp_model, config, timing): ... +def solve_MINLP(util_block, config, timing): ... +def detect_unfixed_discrete_vars(model): ... + +class preprocess_subproblem: + util_block: Incomplete + config: Incomplete + not_infeas: bool + unfixed_vars: Incomplete + original_bounds: Incomplete + constraints_deactivated: Incomplete + constraints_modified: Incomplete + def __init__(self, util_block, config) -> None: ... + def __enter__(self): ... + def __exit__( + self, + type: type[BaseException] | None, + value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +def call_appropriate_subproblem_solver(subprob_util_block, solver, config): ... +def solve_subproblem(subprob_util_block, solver, config): ... diff --git a/stubs/pyomo/contrib/gdpopt/util.pyi b/stubs/pyomo/contrib/gdpopt/util.pyi new file mode 100644 index 000000000..e0447c3ab --- /dev/null +++ b/stubs/pyomo/contrib/gdpopt/util.pyi @@ -0,0 +1,94 @@ +import logging +import types +from collections.abc import Generator +from contextlib import contextmanager + +from _typeshed import Incomplete +from pyomo.common import timing as timing +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.contrib.mcpp.pyomo_mcpp import McCormick as McCormick +from pyomo.contrib.mcpp.pyomo_mcpp import mcpp_available as mcpp_available +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import Reals as Reals +from pyomo.core import Reference as Reference +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.opt import SolverFactory as SolverFactory + +class _DoNothing: + def __init__(self, *args, **kwargs) -> None: ... + def __call__(self, *args, **kwargs) -> None: ... + def __getattr__(self, attr): ... + +class SuppressInfeasibleWarning: + class InfeasibleWarningFilter(logging.Filter): + def filter(self, record): ... + + warning_filter: Incomplete + def __enter__(self): ... + def __exit__( + self, + exception_type: type[BaseException] | None, + exception_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +def solve_continuous_problem(m, config): ... +def move_nonlinear_objective_to_constraints(util_block, logger): ... +def a_logger(str_or_logger): ... +def copy_var_list_values( + from_list, + to_list, + config, + skip_stale: bool = False, + skip_fixed: bool = True, + ignore_integrality: bool = False, +) -> None: ... +def fix_discrete_var(var, val, config) -> None: ... + +class fix_discrete_solution_in_subproblem: + True_disjuncts: Incomplete + boolean_var_values: Incomplete + discrete_var_values: Incomplete + subprob_util_block: Incomplete + config: Incomplete + def __init__( + self, + true_disjuncts, + boolean_var_values, + integer_var_values, + subprob_util_block, + config, + solver, + ) -> None: ... + def __enter__(self): ... + def __exit__( + self, + type: type[BaseException] | None, + value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +class fix_discrete_problem_solution_in_subproblem(fix_discrete_solution_in_subproblem): + discrete_prob_util_block: Incomplete + subprob_util_block: Incomplete + solver: Incomplete + config: Incomplete + def __init__(self, discrete_prob_util_block, subproblem_util_block, solver, config) -> None: ... + def __enter__(self): ... + +def is_feasible(model, config): ... +@contextmanager +def time_code(timing_data_obj, code_block_name, is_main_timer: bool = False) -> Generator[None]: ... +def get_main_elapsed_time(timing_data_obj): ... +@contextmanager +def lower_logger_level_to(logger, level=None, tee: bool = False) -> Generator[None]: ... diff --git a/stubs/pyomo/contrib/gjh/GJH.pyi b/stubs/pyomo/contrib/gjh/GJH.pyi new file mode 100644 index 000000000..074fedb0e --- /dev/null +++ b/stubs/pyomo/contrib/gjh/GJH.pyi @@ -0,0 +1,10 @@ +from _typeshed import Incomplete +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.solvers.plugins.solvers.ASL import ASL as ASL + +logger: Incomplete + +def readgjh(fname=None): ... + +class GJHSolver(ASL): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/contrib/gjh/__init__.pyi b/stubs/pyomo/contrib/gjh/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/gjh/getGJH.pyi b/stubs/pyomo/contrib/gjh/getGJH.pyi new file mode 100644 index 000000000..4bcd06f03 --- /dev/null +++ b/stubs/pyomo/contrib/gjh/getGJH.pyi @@ -0,0 +1,9 @@ +from _typeshed import Incomplete +from pyomo.common.download import FileDownloader as FileDownloader + +logger: Incomplete +urlmap: Incomplete +exemap: Incomplete + +def get_gjh(downloader) -> None: ... +def main(argv) -> None: ... diff --git a/stubs/pyomo/contrib/gjh/plugins.pyi b/stubs/pyomo/contrib/gjh/plugins.pyi new file mode 100644 index 000000000..5eac6f1e5 --- /dev/null +++ b/stubs/pyomo/contrib/gjh/plugins.pyi @@ -0,0 +1,6 @@ +from pyomo.common.download import DownloadFactory as DownloadFactory +from pyomo.contrib.gjh.getGJH import get_gjh as get_gjh +from pyomo.contrib.gjh.GJH import GJHSolver as GJHSolver +from pyomo.opt.base import SolverFactory as SolverFactory + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/iis/__init__.pyi b/stubs/pyomo/contrib/iis/__init__.pyi new file mode 100644 index 000000000..e8c8838e2 --- /dev/null +++ b/stubs/pyomo/contrib/iis/__init__.pyi @@ -0,0 +1,4 @@ +from pyomo.contrib.iis.iis import write_iis as write_iis +from pyomo.contrib.iis.mis import ( + compute_infeasibility_explanation as compute_infeasibility_explanation, +) diff --git a/stubs/pyomo/contrib/iis/iis.pyi b/stubs/pyomo/contrib/iis/iis.pyi new file mode 100644 index 000000000..562a7a2ed --- /dev/null +++ b/stubs/pyomo/contrib/iis/iis.pyi @@ -0,0 +1,29 @@ +import abc + +from _typeshed import Incomplete +from pyomo.environ import SolverFactory as SolverFactory + +logger: Incomplete + +def write_iis(pyomo_model, iis_file_name, solver=None, logger=...): ... + +class _IISBase(abc.ABC, metaclass=abc.ABCMeta): + def __init__(self, solver) -> None: ... + @abc.abstractmethod + def compute(self): ... + @abc.abstractmethod + def write(self, file_name): ... + +class CplexConflict(_IISBase): + def compute(self) -> None: ... + def write(self, file_name): ... + +class GurobiIIS(_IISBase): + def compute(self) -> None: ... + def write(self, file_name): ... + +class XpressIIS(_IISBase): + def compute(self) -> None: ... + def write(self, file_name): ... + +def IISFactory(solver): ... diff --git a/stubs/pyomo/contrib/iis/mis.pyi b/stubs/pyomo/contrib/iis/mis.pyi new file mode 100644 index 000000000..71e394513 --- /dev/null +++ b/stubs/pyomo/contrib/iis/mis.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core.plugins.transform.add_slack_vars import AddSlackVariables as AddSlackVariables +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.opt import WriterFactory as WriterFactory + +logger: Incomplete + +class _VariableBoundsAsConstraints(IsomorphicTransformation): ... + +def compute_infeasibility_explanation( + model, solver, tee: bool = False, tolerance: float = 1e-08, logger=... +): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/__init__.pyi b/stubs/pyomo/contrib/incidence_analysis/__init__.pyi new file mode 100644 index 000000000..91f5447f7 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/__init__.pyi @@ -0,0 +1,12 @@ +from pyomo.common.deprecation import moved_module as moved_module + +from .config import IncidenceMethod as IncidenceMethod +from .incidence import get_incident_variables as get_incident_variables +from .interface import IncidenceGraphInterface as IncidenceGraphInterface +from .interface import get_bipartite_incidence_graph as get_bipartite_incidence_graph +from .matching import maximum_matching as maximum_matching +from .scc_solver import ( + generate_strongly_connected_components as generate_strongly_connected_components, +) +from .scc_solver import solve_strongly_connected_components as solve_strongly_connected_components +from .triangularize import block_triangularize as block_triangularize diff --git a/stubs/pyomo/contrib/incidence_analysis/common/__init__.pyi b/stubs/pyomo/contrib/incidence_analysis/common/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.pyi b/stubs/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.pyi new file mode 100644 index 000000000..93c82bee6 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.pyi @@ -0,0 +1,4 @@ +from networkx.algorithms.components import connected_components as connected_components +from pyomo.common.dependencies import networkx_available as networkx_available + +def dulmage_mendelsohn(bg, top_nodes=None, matching=None): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/config.pyi b/stubs/pyomo/contrib/incidence_analysis/config.pyi new file mode 100644 index 000000000..5d38819a0 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/config.pyi @@ -0,0 +1,24 @@ +import enum + +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import InEnum as InEnum +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.repn.ampl import AMPLRepnVisitor as AMPLRepnVisitor +from pyomo.repn.util import FileDeterminism as FileDeterminism +from pyomo.repn.util import FileDeterminism_to_SortComponents as FileDeterminism_to_SortComponents + +class IncidenceMethod(enum.Enum): + identify_variables = 0 + standard_repn = 1 + standard_repn_compute_values = 2 + ampl_repn = 3 + +class IncidenceOrder(enum.Enum): + dulmage_mendelsohn_upper = 0 + dulmage_mendelsohn_lower = 1 + +IncidenceConfig: Incomplete + +def get_config_from_kwds(**kwds): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/connected.pyi b/stubs/pyomo/contrib/incidence_analysis/connected.pyi new file mode 100644 index 000000000..bbc09e619 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/connected.pyi @@ -0,0 +1 @@ +def get_independent_submatrices(matrix): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.pyi b/stubs/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.pyi new file mode 100644 index 000000000..90a6c003a --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/dulmage_mendelsohn.pyi @@ -0,0 +1,17 @@ +from typing import NamedTuple + +from _typeshed import Incomplete + +class RowPartition(NamedTuple): + unmatched: Incomplete + overconstrained: Incomplete + underconstrained: Incomplete + square: Incomplete + +class ColPartition(NamedTuple): + unmatched: Incomplete + underconstrained: Incomplete + overconstrained: Incomplete + square: Incomplete + +def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/incidence.pyi b/stubs/pyomo/contrib/incidence_analysis/incidence.pyi new file mode 100644 index 000000000..994cbce3d --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/incidence.pyi @@ -0,0 +1,7 @@ +from pyomo.contrib.incidence_analysis.config import IncidenceMethod as IncidenceMethod +from pyomo.contrib.incidence_analysis.config import get_config_from_kwds as get_config_from_kwds +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.util.subsystems import TemporarySubsystemManager as TemporarySubsystemManager + +def get_incident_variables(expr, **kwds): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/interface.pyi b/stubs/pyomo/contrib/incidence_analysis/interface.pyi new file mode 100644 index 000000000..7bfe49c68 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/interface.pyi @@ -0,0 +1,82 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import plotly as plotly +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.contrib.incidence_analysis.config import get_config_from_kwds as get_config_from_kwds +from pyomo.contrib.incidence_analysis.connected import ( + get_independent_submatrices as get_independent_submatrices, +) +from pyomo.contrib.incidence_analysis.dulmage_mendelsohn import ColPartition as ColPartition +from pyomo.contrib.incidence_analysis.dulmage_mendelsohn import RowPartition as RowPartition +from pyomo.contrib.incidence_analysis.dulmage_mendelsohn import ( + dulmage_mendelsohn as dulmage_mendelsohn, +) +from pyomo.contrib.incidence_analysis.incidence import ( + get_incident_variables as get_incident_variables, +) +from pyomo.contrib.incidence_analysis.matching import maximum_matching as maximum_matching +from pyomo.contrib.incidence_analysis.triangularize import ( + block_triangularize as block_triangularize, +) +from pyomo.contrib.incidence_analysis.triangularize import ( + get_blocks_from_maps as get_blocks_from_maps, +) +from pyomo.contrib.incidence_analysis.triangularize import ( + get_diagonal_blocks as get_diagonal_blocks, +) +from pyomo.contrib.incidence_analysis.triangularize import ( + get_scc_of_projection as get_scc_of_projection, +) +from pyomo.contrib.pynumero.asl import AmplInterface as AmplInterface +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.var import Var as Var +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.util.subsystems import create_subsystem_block as create_subsystem_block + +pyomo_nlp: Incomplete +pyomo_nlp_available: Incomplete +asl_available: Incomplete + +def get_incidence_graph(variables, constraints, **kwds): ... +def get_bipartite_incidence_graph(variables, constraints, **kwds): ... +def extract_bipartite_subgraph(graph, nodes0, nodes1): ... +def get_structural_incidence_matrix(variables, constraints, **kwds): ... +def get_numeric_incidence_matrix(variables, constraints): ... + +class IncidenceGraphInterface: + def __init__( + self, model=None, active: bool = True, include_inequality: bool = True, **kwds + ) -> None: ... + @property + def variables(self): ... + @property + def constraints(self): ... + @property + def n_edges(self): ... + @property + def var_index_map(self): ... + @property + def con_index_map(self): ... + @property + def row_block_map(self) -> None: ... + @property + def col_block_map(self) -> None: ... + def get_matrix_coord(self, component): ... + def subgraph(self, variables, constraints): ... + @property + def incidence_matrix(self): ... + def get_adjacent_to(self, component): ... + def maximum_matching(self, variables=None, constraints=None): ... + def get_connected_components(self, variables=None, constraints=None): ... + def map_nodes_to_block_triangular_indices(self, variables=None, constraints=None): ... + def block_triangularize(self, variables=None, constraints=None): ... + def get_diagonal_blocks(self, variables=None, constraints=None): ... + def dulmage_mendelsohn(self, variables=None, constraints=None): ... + def remove_nodes(self, variables=None, constraints=None) -> None: ... + def plot(self, variables=None, constraints=None, title=None, show: bool = True) -> None: ... + def add_edge(self, variable, constraint) -> None: ... diff --git a/stubs/pyomo/contrib/incidence_analysis/matching.pyi b/stubs/pyomo/contrib/incidence_analysis/matching.pyi new file mode 100644 index 000000000..710346d58 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/matching.pyi @@ -0,0 +1 @@ +def maximum_matching(matrix_or_graph, top_nodes=None): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/scc_solver.pyi b/stubs/pyomo/contrib/incidence_analysis/scc_solver.pyi new file mode 100644 index 000000000..aaab51438 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/scc_solver.pyi @@ -0,0 +1,20 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.contrib.incidence_analysis.config import IncidenceMethod as IncidenceMethod +from pyomo.contrib.incidence_analysis.interface import ( + IncidenceGraphInterface as IncidenceGraphInterface, +) +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.util.calc_var_value import ( + calculate_variable_from_constraint as calculate_variable_from_constraint, +) +from pyomo.util.subsystems import TemporarySubsystemManager as TemporarySubsystemManager +from pyomo.util.subsystems import generate_subsystem_blocks as generate_subsystem_blocks + +def generate_strongly_connected_components( + constraints, variables=None, include_fixed: bool = False, igraph=None +) -> Generator[Incomplete]: ... +def solve_strongly_connected_components( + block, *, solver=None, solve_kwds=None, use_calc_var: bool = True, calc_var_kwds=None +): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/triangularize.pyi b/stubs/pyomo/contrib/incidence_analysis/triangularize.pyi new file mode 100644 index 000000000..93286b9c5 --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/triangularize.pyi @@ -0,0 +1,8 @@ +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.contrib.incidence_analysis.matching import maximum_matching as maximum_matching + +def get_scc_of_projection(graph, top_nodes, matching=None): ... +def block_triangularize(matrix, matching=None): ... +def map_coords_to_block_triangular_indices(matrix, matching=None): ... +def get_blocks_from_maps(row_block_map, col_block_map): ... +def get_diagonal_blocks(matrix, matching=None): ... diff --git a/stubs/pyomo/contrib/incidence_analysis/visualize.pyi b/stubs/pyomo/contrib/incidence_analysis/visualize.pyi new file mode 100644 index 000000000..f0f295bca --- /dev/null +++ b/stubs/pyomo/contrib/incidence_analysis/visualize.pyi @@ -0,0 +1,21 @@ +from pyomo.common.dependencies import matplotlib as matplotlib +from pyomo.contrib.incidence_analysis.config import IncidenceOrder as IncidenceOrder +from pyomo.contrib.incidence_analysis.interface import ( + IncidenceGraphInterface as IncidenceGraphInterface, +) +from pyomo.contrib.incidence_analysis.interface import ( + get_structural_incidence_matrix as get_structural_incidence_matrix, +) + +def spy_dulmage_mendelsohn( + model, + *, + incidence_kwds=None, + order=..., + highlight_coarse: bool = True, + highlight_fine: bool = True, + skip_wellconstrained: bool = False, + ax=None, + linewidth: int = 2, + spy_kwds=None, +): ... diff --git a/stubs/pyomo/contrib/interior_point/__init__.pyi b/stubs/pyomo/contrib/interior_point/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/interior_point/examples/__init__.pyi b/stubs/pyomo/contrib/interior_point/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/interior_point/examples/ex1.pyi b/stubs/pyomo/contrib/interior_point/examples/ex1.pyi new file mode 100644 index 000000000..78e999cb9 --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/examples/ex1.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.interior_point.interface import InteriorPointInterface as InteriorPointInterface +from pyomo.contrib.interior_point.interior_point import InteriorPointSolver as InteriorPointSolver +from pyomo.contrib.interior_point.linalg.mumps_interface import MumpsInterface as MumpsInterface + +def solve_qcqp_example() -> None: ... diff --git a/stubs/pyomo/contrib/interior_point/interface.pyi b/stubs/pyomo/contrib/interior_point/interface.pyi new file mode 100644 index 000000000..79624d59c --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/interface.pyi @@ -0,0 +1,187 @@ +from abc import ABCMeta, abstractmethod + +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.pynumero.interfaces import ampl_nlp as ampl_nlp +from pyomo.contrib.pynumero.interfaces import pyomo_nlp as pyomo_nlp +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector + +class BaseInteriorPointInterface(metaclass=ABCMeta): + @abstractmethod + def n_primals(self): ... + @abstractmethod + def nnz_hessian_lag(self): ... + @abstractmethod + def primals_lb(self): ... + @abstractmethod + def primals_ub(self): ... + @abstractmethod + def init_primals(self): ... + @abstractmethod + def set_primals(self, primals): ... + @abstractmethod + def get_primals(self): ... + @abstractmethod + def get_obj_factor(self): ... + @abstractmethod + def set_obj_factor(self, obj_factor): ... + @abstractmethod + def evaluate_objective(self): ... + @abstractmethod + def evaluate_grad_objective(self): ... + @abstractmethod + def n_eq_constraints(self): ... + @abstractmethod + def n_ineq_constraints(self): ... + @abstractmethod + def nnz_jacobian_eq(self): ... + @abstractmethod + def nnz_jacobian_ineq(self): ... + @abstractmethod + def ineq_lb(self): ... + @abstractmethod + def ineq_ub(self): ... + @abstractmethod + def init_duals_eq(self): ... + @abstractmethod + def init_duals_ineq(self): ... + @abstractmethod + def set_duals_eq(self, duals_eq): ... + @abstractmethod + def set_duals_ineq(self, duals_ineq): ... + @abstractmethod + def get_duals_eq(self): ... + @abstractmethod + def get_duals_ineq(self): ... + @abstractmethod + def evaluate_eq_constraints(self): ... + @abstractmethod + def evaluate_ineq_constraints(self): ... + @abstractmethod + def evaluate_jacobian_eq(self): ... + @abstractmethod + def evaluate_jacobian_ineq(self): ... + @abstractmethod + def init_slacks(self): ... + @abstractmethod + def init_duals_primals_lb(self): ... + @abstractmethod + def init_duals_primals_ub(self): ... + @abstractmethod + def init_duals_slacks_lb(self): ... + @abstractmethod + def init_duals_slacks_ub(self): ... + @abstractmethod + def set_slacks(self, slacks): ... + @abstractmethod + def set_duals_primals_lb(self, duals): ... + @abstractmethod + def set_duals_primals_ub(self, duals): ... + @abstractmethod + def set_duals_slacks_lb(self, duals): ... + @abstractmethod + def set_duals_slacks_ub(self, duals): ... + @abstractmethod + def get_slacks(self): ... + @abstractmethod + def get_duals_primals_lb(self): ... + @abstractmethod + def get_duals_primals_ub(self): ... + @abstractmethod + def get_duals_slacks_lb(self): ... + @abstractmethod + def get_duals_slacks_ub(self): ... + @abstractmethod + def set_barrier_parameter(self, barrier): ... + @abstractmethod + def evaluate_primal_dual_kkt_matrix(self, timer=None): ... + @abstractmethod + def evaluate_primal_dual_kkt_rhs(self, timer=None): ... + @abstractmethod + def set_primal_dual_kkt_solution(self, sol): ... + @abstractmethod + def get_delta_primals(self): ... + @abstractmethod + def get_delta_slacks(self): ... + @abstractmethod + def get_delta_duals_eq(self): ... + @abstractmethod + def get_delta_duals_ineq(self): ... + @abstractmethod + def get_delta_duals_primals_lb(self): ... + @abstractmethod + def get_delta_duals_primals_ub(self): ... + @abstractmethod + def get_delta_duals_slacks_lb(self): ... + @abstractmethod + def get_delta_duals_slacks_ub(self): ... + def regularize_equality_gradient(self, kkt, coef, copy_kkt: bool = True) -> None: ... + def regularize_hessian(self, kkt, coef, copy_kkt: bool = True) -> None: ... + +class InteriorPointInterface(BaseInteriorPointInterface): + def __init__(self, pyomo_model) -> None: ... + def n_primals(self): ... + def nnz_hessian_lag(self): ... + def set_obj_factor(self, obj_factor) -> None: ... + def get_obj_factor(self): ... + def n_eq_constraints(self): ... + def n_ineq_constraints(self): ... + def nnz_jacobian_eq(self): ... + def nnz_jacobian_ineq(self): ... + def init_primals(self): ... + def init_slacks(self): ... + def init_duals_eq(self): ... + def init_duals_ineq(self): ... + def init_duals_primals_lb(self): ... + def init_duals_primals_ub(self): ... + def init_duals_slacks_lb(self): ... + def init_duals_slacks_ub(self): ... + def set_primals(self, primals) -> None: ... + def set_slacks(self, slacks) -> None: ... + def set_duals_eq(self, duals) -> None: ... + def set_duals_ineq(self, duals) -> None: ... + def set_duals_primals_lb(self, duals) -> None: ... + def set_duals_primals_ub(self, duals) -> None: ... + def set_duals_slacks_lb(self, duals) -> None: ... + def set_duals_slacks_ub(self, duals) -> None: ... + def get_primals(self): ... + def get_slacks(self): ... + def get_duals_eq(self): ... + def get_duals_ineq(self): ... + def get_duals_primals_lb(self): ... + def get_duals_primals_ub(self): ... + def get_duals_slacks_lb(self): ... + def get_duals_slacks_ub(self): ... + def primals_lb(self): ... + def primals_ub(self): ... + def ineq_lb(self): ... + def ineq_ub(self): ... + def set_barrier_parameter(self, barrier) -> None: ... + def pyomo_nlp(self): ... + def evaluate_primal_dual_kkt_matrix(self, timer=None): ... + def evaluate_primal_dual_kkt_rhs(self, timer=None): ... + def set_primal_dual_kkt_solution(self, sol) -> None: ... + def get_delta_primals(self): ... + def get_delta_slacks(self): ... + def get_delta_duals_eq(self): ... + def get_delta_duals_ineq(self): ... + def get_delta_duals_primals_lb(self): ... + def get_delta_duals_primals_ub(self): ... + def get_delta_duals_slacks_lb(self): ... + def get_delta_duals_slacks_ub(self): ... + def evaluate_objective(self): ... + def evaluate_eq_constraints(self): ... + def evaluate_ineq_constraints(self): ... + def evaluate_grad_objective(self): ... + def evaluate_jacobian_eq(self): ... + def evaluate_jacobian_ineq(self): ... + def regularize_equality_gradient(self, kkt, coef, copy_kkt: bool = True): ... + def regularize_hessian(self, kkt, coef, copy_kkt: bool = True): ... + def load_primals_into_pyomo_model(self) -> None: ... + def pyomo_model(self): ... + def get_pyomo_variables(self): ... + def get_pyomo_constraints(self): ... + def variable_names(self): ... + def constraint_names(self): ... + def get_primal_indices(self, pyomo_variables): ... + def get_constraint_indices(self, pyomo_constraints): ... diff --git a/stubs/pyomo/contrib/interior_point/interior_point.pyi b/stubs/pyomo/contrib/interior_point/interior_point.pyi new file mode 100644 index 000000000..8a95790a4 --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/interior_point.pyi @@ -0,0 +1,89 @@ +import enum +import types + +from _typeshed import Incomplete +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.pynumero.interfaces.utils import build_bounds_mask as build_bounds_mask +from pyomo.contrib.pynumero.interfaces.utils import ( + build_compression_matrix as build_compression_matrix, +) +from pyomo.contrib.pynumero.linalg.base import LinearSolverStatus as LinearSolverStatus + +ip_logger: Incomplete + +class InteriorPointStatus(enum.Enum): + optimal = 0 + error = 1 + +class LinearSolveContext: + interior_point_logger: Incomplete + linear_solver_logger: Incomplete + filename: Incomplete + handler: Incomplete + def __init__( + self, interior_point_logger, linear_solver_logger, filename=None, level=... + ) -> None: ... + def __enter__(self) -> None: ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + +class FactorizationContext: + logger: Incomplete + def __init__(self, logger) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + def log_header(self) -> None: ... + def log_info(self, _iter, reg_iter, num_realloc, coef, neg_eig, status) -> None: ... + +class InteriorPointSolver: + linear_solver: Incomplete + max_iter: Incomplete + tol: Incomplete + linear_solver_log_filename: Incomplete + max_reallocation_iterations: Incomplete + reallocation_factor: Incomplete + base_eq_reg_coef: float + hess_reg_coef: float + max_reg_iter: int + reg_factor_increase: int + logger: Incomplete + factorization_context: Incomplete + linear_solver_logger: Incomplete + linear_solve_context: Incomplete + def __init__( + self, + linear_solver, + max_iter: int = 100, + tol: float = 1e-08, + linear_solver_log_filename=None, + max_reallocation_iterations: int = 5, + reallocation_factor: int = 2, + ) -> None: ... + def update_barrier_parameter(self) -> None: ... + def set_linear_solver(self, linear_solver) -> None: ... + interface: Incomplete + def set_interface(self, interface) -> None: ... + def solve(self, interface, timer=None, report_timing: bool = False): ... + def factorize(self, kkt, timer=None): ... + def process_init(self, x, lb, ub) -> None: ... + def process_init_duals_lb(self, x, lb) -> None: ... + def process_init_duals_ub(self, x, ub) -> None: ... + def check_convergence(self, barrier, timer=None): ... + def fraction_to_the_boundary(self): ... + +def try_factorization_and_reallocation( + kkt, linear_solver, reallocation_factor, max_iter, timer=None +): ... +def fraction_to_the_boundary(interface, tau): ... +def process_init(x, lb, ub) -> None: ... +def process_init_duals_lb(x, lb) -> None: ... +def process_init_duals_ub(x, ub) -> None: ... diff --git a/stubs/pyomo/contrib/interior_point/inverse_reduced_hessian.pyi b/stubs/pyomo/contrib/interior_point/inverse_reduced_hessian.pyi new file mode 100644 index 000000000..acca9fa74 --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/inverse_reduced_hessian.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.opt import check_optimal_termination as check_optimal_termination + +from .interface import InteriorPointInterface as InteriorPointInterface +from .linalg.scipy_interface import ScipyInterface as ScipyInterface + +np: Incomplete +numpy_available: Incomplete + +def inv_reduced_hessian_barrier( + model, + independent_variables, + bound_tolerance: float = 1e-06, + solver_options=None, + tee: bool = False, +): ... diff --git a/stubs/pyomo/contrib/interior_point/linalg/__init__.pyi b/stubs/pyomo/contrib/interior_point/linalg/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.pyi b/stubs/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.pyi new file mode 100644 index 000000000..d7fc6197f --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/linalg/base_linear_solver_interface.pyi @@ -0,0 +1,14 @@ +from abc import ABCMeta, abstractmethod + +from pyomo.contrib.pynumero.linalg.base import ( + DirectLinearSolverInterface as DirectLinearSolverInterface, +) + +class IPLinearSolverInterface(DirectLinearSolverInterface, metaclass=ABCMeta): + @classmethod + def getLoggerName(cls): ... + @classmethod + def getLogger(cls): ... + def increase_memory_allocation(self, factor) -> None: ... + @abstractmethod + def get_inertia(self): ... diff --git a/stubs/pyomo/contrib/interior_point/linalg/ma27_interface.pyi b/stubs/pyomo/contrib/interior_point/linalg/ma27_interface.pyi new file mode 100644 index 000000000..c5e2b8c5d --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/linalg/ma27_interface.pyi @@ -0,0 +1,24 @@ +from pyomo.contrib.pynumero.linalg.base import LinearSolverResults as LinearSolverResults +from pyomo.contrib.pynumero.linalg.base import LinearSolverStatus as LinearSolverStatus +from pyomo.contrib.pynumero.linalg.ma27_interface import MA27 as MA27 +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector +from scipy.sparse import isspmatrix_coo as isspmatrix_coo +from scipy.sparse import spmatrix as spmatrix +from scipy.sparse import tril as tril + +from .base_linear_solver_interface import IPLinearSolverInterface as IPLinearSolverInterface + +class InteriorPointMA27Interface(MA27, IPLinearSolverInterface): + @classmethod + def getLoggerName(cls): ... + def __init__( + self, cntl_options=None, icntl_options=None, iw_factor: float = 1.2, a_factor: int = 2 + ) -> None: ... + def do_symbolic_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_numeric_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def get_inertia(self): ... diff --git a/stubs/pyomo/contrib/interior_point/linalg/mumps_interface.pyi b/stubs/pyomo/contrib/interior_point/linalg/mumps_interface.pyi new file mode 100644 index 000000000..283d404bb --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/linalg/mumps_interface.pyi @@ -0,0 +1,29 @@ +import numpy as np +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.contrib.pynumero.linalg.base import LinearSolverResults as LinearSolverResults +from pyomo.contrib.pynumero.linalg.base import LinearSolverStatus as LinearSolverStatus +from pyomo.contrib.pynumero.linalg.mumps_interface import ( + MumpsCentralizedAssembledLinearSolver as MumpsCentralizedAssembledLinearSolver, +) +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector + +from .base_linear_solver_interface import IPLinearSolverInterface as IPLinearSolverInterface + +mumps: Incomplete +mumps_available: Incomplete + +class MumpsInterface(MumpsCentralizedAssembledLinearSolver, IPLinearSolverInterface): + @classmethod + def getLoggerName(cls): ... + error_level: Incomplete + log_error: Incomplete + logger: Incomplete + def __init__(self, par: int = 1, comm=None, cntl_options=None, icntl_options=None) -> None: ... + def do_back_solve( + self, rhs: np.ndarray | BlockVector, raise_on_error: bool = True + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... + def get_inertia(self): ... + def get_error_info(self): ... + def log_header(self, include_error: bool = True, extra_fields=None) -> None: ... + def log_info(self) -> None: ... diff --git a/stubs/pyomo/contrib/interior_point/linalg/scipy_interface.pyi b/stubs/pyomo/contrib/interior_point/linalg/scipy_interface.pyi new file mode 100644 index 000000000..f5ce6fde5 --- /dev/null +++ b/stubs/pyomo/contrib/interior_point/linalg/scipy_interface.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.contrib.pynumero.linalg.base import LinearSolverResults as LinearSolverResults +from pyomo.contrib.pynumero.linalg.scipy_interface import ScipyLU as ScipyLU +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from scipy.sparse import spmatrix as spmatrix + +from .base_linear_solver_interface import IPLinearSolverInterface as IPLinearSolverInterface + +class ScipyInterface(ScipyLU, IPLinearSolverInterface): + @classmethod + def getLoggerName(cls): ... + compute_inertia: Incomplete + logger: Incomplete + def __init__(self, compute_inertia: bool = False) -> None: ... + def do_numeric_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def get_inertia(self): ... diff --git a/stubs/pyomo/contrib/latex_printer/__init__.pyi b/stubs/pyomo/contrib/latex_printer/__init__.pyi new file mode 100644 index 000000000..8459e5007 --- /dev/null +++ b/stubs/pyomo/contrib/latex_printer/__init__.pyi @@ -0,0 +1 @@ +from pyomo.contrib.latex_printer.latex_printer import latex_printer as latex_printer diff --git a/stubs/pyomo/contrib/latex_printer/latex_printer.pyi b/stubs/pyomo/contrib/latex_printer/latex_printer.pyi new file mode 100644 index 000000000..9f6eaf311 --- /dev/null +++ b/stubs/pyomo/contrib/latex_printer/latex_printer.pyi @@ -0,0 +1,111 @@ +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.collections.component_map import ComponentMap as ComponentMap +from pyomo.common.collections.component_set import ComponentSet as ComponentSet +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.block import IndexedBlock as IndexedBlock +from pyomo.core.base.constraint import IndexedConstraint as IndexedConstraint +from pyomo.core.base.constraint import ScalarConstraint as ScalarConstraint +from pyomo.core.base.enums import SortComponents as SortComponents +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.objective import ScalarObjective as ScalarObjective +from pyomo.core.base.param import IndexedParam as IndexedParam +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.param import ScalarParam as ScalarParam +from pyomo.core.base.set import SetData as SetData +from pyomo.core.base.set import SetOperator as SetOperator +from pyomo.core.base.var import IndexedVar as IndexedVar +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr import AbsExpression as AbsExpression +from pyomo.core.expr import DivisionExpression as DivisionExpression +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.core.expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr import InequalityExpression as InequalityExpression +from pyomo.core.expr import LinearExpression as LinearExpression +from pyomo.core.expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr import NegationExpression as NegationExpression +from pyomo.core.expr import PowExpression as PowExpression +from pyomo.core.expr import ProductExpression as ProductExpression +from pyomo.core.expr import RangedExpression as RangedExpression +from pyomo.core.expr import SumExpression as SumExpression +from pyomo.core.expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.base import ExpressionBase as ExpressionBase +from pyomo.core.expr.numeric_expr import NPV_DivisionExpression as NPV_DivisionExpression +from pyomo.core.expr.numeric_expr import NPV_SumExpression as NPV_SumExpression +from pyomo.core.expr.template_expr import GetAttrExpression as GetAttrExpression +from pyomo.core.expr.template_expr import GetItemExpression as GetItemExpression +from pyomo.core.expr.template_expr import IndexTemplate as IndexTemplate +from pyomo.core.expr.template_expr import ( + NPV_Numeric_GetItemExpression as NPV_Numeric_GetItemExpression, +) +from pyomo.core.expr.template_expr import ( + NPV_Structural_GetItemExpression as NPV_Structural_GetItemExpression, +) +from pyomo.core.expr.template_expr import Numeric_GetAttrExpression as Numeric_GetAttrExpression +from pyomo.core.expr.template_expr import Numeric_GetItemExpression as Numeric_GetItemExpression +from pyomo.core.expr.template_expr import TemplateSumExpression as TemplateSumExpression +from pyomo.core.expr.template_expr import resolve_template as resolve_template +from pyomo.core.expr.template_expr import templatize_constraint as templatize_constraint +from pyomo.core.expr.template_expr import templatize_rule as templatize_rule +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.expr.visitor import identify_components as identify_components +from pyomo.repn.util import ExprType as ExprType + +set_operator_map: Incomplete +latex_reals: str +latex_integers: str +domainMap: Incomplete + +def decoder(num, base): ... +def indexCorrector(ixs, base): ... +def alphabetStringGenerator(num): ... +def templatize_expression(expr): ... +def templatize_passthrough(con): ... +def precedenceChecker(node, arg1, arg2=None): ... +def handle_negation_node(visitor, node, arg1): ... +def handle_product_node(visitor, node, arg1, arg2): ... +def handle_pow_node(visitor, node, arg1, arg2): ... +def handle_division_node(visitor, node, arg1, arg2): ... +def handle_abs_node(visitor, node, arg1): ... +def handle_unary_node(visitor, node, arg1): ... +def handle_equality_node(visitor, node, arg1, arg2): ... +def handle_inequality_node(visitor, node, arg1, arg2): ... +def handle_var_node(visitor, node): ... +def handle_num_node(visitor, node): ... +def handle_sumExpression_node(visitor, node, *args): ... +def handle_monomialTermExpression_node(visitor, node, arg1, arg2): ... +def handle_named_expression_node(visitor, node, arg1): ... +def handle_ranged_inequality_node(visitor, node, arg1, arg2, arg3): ... +def handle_exprif_node(visitor, node, arg1, arg2, arg3): ... +def handle_external_function_node(visitor, node, *args): ... +def handle_functionID_node(visitor, node, *args): ... +def handle_indexTemplate_node(visitor, node, *args): ... +def handle_numericGetItemExpression_node(visitor, node, *args): ... +def handle_templateSumExpression_node(visitor, node, *args): ... +def handle_param_node(visitor, node): ... +def handle_str_node(visitor, node): ... +def handle_npv_structuralGetItemExpression_node(visitor, node, *args): ... +def handle_indexedBlock_node(visitor, node, *args): ... +def handle_numericGetAttrExpression_node(visitor, node, *args): ... + +class _LatexVisitor(StreamBasedExpressionVisitor): + externalFunctionCounter: int + def __init__(self) -> None: ... + def exitNode(self, node, data): ... + +def analyze_variable(vr): ... +def multiple_replace(pstr, rep_dict): ... +def latex_printer( + pyomo_component, + latex_component_map=None, + ostream=None, + use_equation_environment: bool = False, + explicit_set_summation: bool = False, + throw_templatization_error: bool = False, +): ... diff --git a/stubs/pyomo/contrib/mcpp/__init__.pyi b/stubs/pyomo/contrib/mcpp/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/mcpp/build.pyi b/stubs/pyomo/contrib/mcpp/build.pyi new file mode 100644 index 000000000..76c0eeb99 --- /dev/null +++ b/stubs/pyomo/contrib/mcpp/build.pyi @@ -0,0 +1,9 @@ +from pyomo.common.download import FileDownloader as FileDownloader +from pyomo.common.fileutils import find_dir as find_dir +from pyomo.common.fileutils import this_file_dir as this_file_dir + +def build_mcpp(): ... + +class MCPPBuilder: + def __call__(self, parallel): ... + def skip(self): ... diff --git a/stubs/pyomo/contrib/mcpp/getMCPP.pyi b/stubs/pyomo/contrib/mcpp/getMCPP.pyi new file mode 100644 index 000000000..04baabe2b --- /dev/null +++ b/stubs/pyomo/contrib/mcpp/getMCPP.pyi @@ -0,0 +1,7 @@ +from _typeshed import Incomplete +from pyomo.common.download import FileDownloader as FileDownloader + +logger: Incomplete + +def get_mcpp(downloader) -> None: ... +def main(argv) -> None: ... diff --git a/stubs/pyomo/contrib/mcpp/plugins.pyi b/stubs/pyomo/contrib/mcpp/plugins.pyi new file mode 100644 index 000000000..1314f1887 --- /dev/null +++ b/stubs/pyomo/contrib/mcpp/plugins.pyi @@ -0,0 +1,7 @@ +from pyomo.common.download import DownloadFactory as DownloadFactory +from pyomo.common.extensions import ExtensionBuilderFactory as ExtensionBuilderFactory + +from .build import MCPPBuilder as MCPPBuilder +from .getMCPP import get_mcpp as get_mcpp + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/mcpp/pyomo_mcpp.pyi b/stubs/pyomo/contrib/mcpp/pyomo_mcpp.pyi new file mode 100644 index 000000000..5c11e674a --- /dev/null +++ b/stubs/pyomo/contrib/mcpp/pyomo_mcpp.pyi @@ -0,0 +1,72 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.fileutils import Library as Library +from pyomo.core import Expression as Expression +from pyomo.core import value as value +from pyomo.core.base.block import SubclassOf as SubclassOf +from pyomo.core.base.expression import NamedExpressionData as NamedExpressionData +from pyomo.core.expr.numeric_expr import AbsExpression as AbsExpression +from pyomo.core.expr.numeric_expr import DivisionExpression as DivisionExpression +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression +from pyomo.core.expr.numeric_expr import NegationExpression as NegationExpression +from pyomo.core.expr.numeric_expr import NPV_AbsExpression as NPV_AbsExpression +from pyomo.core.expr.numeric_expr import NPV_DivisionExpression as NPV_DivisionExpression +from pyomo.core.expr.numeric_expr import ( + NPV_ExternalFunctionExpression as NPV_ExternalFunctionExpression, +) +from pyomo.core.expr.numeric_expr import NPV_NegationExpression as NPV_NegationExpression +from pyomo.core.expr.numeric_expr import NPV_PowExpression as NPV_PowExpression +from pyomo.core.expr.numeric_expr import NPV_ProductExpression as NPV_ProductExpression +from pyomo.core.expr.numeric_expr import NPV_SumExpression as NPV_SumExpression +from pyomo.core.expr.numeric_expr import NPV_UnaryFunctionExpression as NPV_UnaryFunctionExpression +from pyomo.core.expr.numeric_expr import PowExpression as PowExpression +from pyomo.core.expr.numeric_expr import ProductExpression as ProductExpression +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression +from pyomo.core.expr.numeric_expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.expr.visitor import identify_variables as identify_variables + +logger: Incomplete +path: Incomplete +__version__: str + +def mcpp_available(): ... + +NPV_expressions: Incomplete + +class MCPP_Error(Exception): ... + +class MCPP_visitor(StreamBasedExpressionVisitor): + mcpp: Incomplete + missing_value_warnings: Incomplete + expr: Incomplete + num_vars: Incomplete + known_vars: Incomplete + var_to_idx: Incomplete + refs: Incomplete + def __init__(self, expression, improved_var_bounds=None) -> None: ... + def walk_expression(self): ... + def exitNode(self, node, data): ... + def beforeChild(self, node, child, child_idx): ... + def acceptChildResult(self, node, data, child_result, child_idx): ... + def register_num(self, num): ... + def register_var(self, var, lb, ub): ... + def finalizeResult(self, node_result): ... + +class McCormick: + mc_expr: Incomplete + mcpp: Incomplete + pyomo_expr: Incomplete + visitor: Incomplete + def __init__(self, expression, improved_var_bounds=None) -> None: ... + def __del__(self) -> None: ... + def __repn__(self): ... + def lower(self): ... + def upper(self): ... + def concave(self): ... + def convex(self): ... + def subcc(self): ... + def subcv(self): ... + def changePoint(self, var, point) -> None: ... + def warn_if_var_missing_value(self) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/MindtPy.pyi b/stubs/pyomo/contrib/mindtpy/MindtPy.pyi new file mode 100644 index 000000000..02bc9aa35 --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/MindtPy.pyi @@ -0,0 +1,20 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.contrib.mindtpy import __version__ as __version__ +from pyomo.opt import SolverFactory as SolverFactory + +class MindtPySolver: + CONFIG: Incomplete + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def version(self): ... + def solve(self, model, **kwds): ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/__init__.pyi b/stubs/pyomo/contrib/mindtpy/__init__.pyi new file mode 100644 index 000000000..f27f712b6 --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/__init__.pyi @@ -0,0 +1,3 @@ +from _typeshed import Incomplete + +__version__: Incomplete diff --git a/stubs/pyomo/contrib/mindtpy/algorithm_base_class.pyi b/stubs/pyomo/contrib/mindtpy/algorithm_base_class.pyi new file mode 100644 index 000000000..8e47bf20f --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/algorithm_base_class.pyi @@ -0,0 +1,208 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.contrib.fbbt.fbbt import fbbt as fbbt +from pyomo.contrib.gdpopt.solve_discrete_problem import ( + distinguish_mip_infeasible_or_unbounded as distinguish_mip_infeasible_or_unbounded, +) +from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning as SuppressInfeasibleWarning +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import lower_logger_level_to as lower_logger_level_to +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.contrib.mindtpy import __version__ as __version__ +from pyomo.contrib.mindtpy.cut_generation import add_no_good_cuts as add_no_good_cuts +from pyomo.contrib.mindtpy.util import GurobiPersistent4MindtPy as GurobiPersistent4MindtPy +from pyomo.contrib.mindtpy.util import add_orthogonality_cuts as add_orthogonality_cuts +from pyomo.contrib.mindtpy.util import add_var_bound as add_var_bound +from pyomo.contrib.mindtpy.util import copy_var_list_values as copy_var_list_values +from pyomo.contrib.mindtpy.util import ( + copy_var_list_values_from_solution_pool as copy_var_list_values_from_solution_pool, +) +from pyomo.contrib.mindtpy.util import epigraph_reformulation as epigraph_reformulation +from pyomo.contrib.mindtpy.util import fp_converged as fp_converged +from pyomo.contrib.mindtpy.util import ( + generate_lag_objective_function as generate_lag_objective_function, +) +from pyomo.contrib.mindtpy.util import ( + generate_norm1_objective_function as generate_norm1_objective_function, +) +from pyomo.contrib.mindtpy.util import ( + generate_norm2sq_objective_function as generate_norm2sq_objective_function, +) +from pyomo.contrib.mindtpy.util import generate_norm_constraint as generate_norm_constraint +from pyomo.contrib.mindtpy.util import ( + generate_norm_inf_objective_function as generate_norm_inf_objective_function, +) +from pyomo.contrib.mindtpy.util import get_integer_solution as get_integer_solution +from pyomo.contrib.mindtpy.util import initialize_feas_subproblem as initialize_feas_subproblem +from pyomo.contrib.mindtpy.util import ( + set_solver_constraint_violation_tolerance as set_solver_constraint_violation_tolerance, +) +from pyomo.contrib.mindtpy.util import set_solver_mipgap as set_solver_mipgap +from pyomo.contrib.mindtpy.util import setup_results_object as setup_results_object +from pyomo.contrib.mindtpy.util import update_solver_timelimit as update_solver_timelimit +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import Expression as Expression +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import Objective as Objective +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Reals as Reals +from pyomo.core import Suffix as Suffix +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import VarList as VarList +from pyomo.core import maximize as maximize +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.opt import SolutionStatus as SolutionStatus +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverResults as SolverResults +from pyomo.opt import SolverStatus as SolverStatus +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy as gurobipy +from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver as PersistentSolver +from pyomo.util.vars_from_expressions import get_vars_from_components as get_vars_from_components + +single_tree: Incomplete +single_tree_available: Incomplete +tabu_list: Incomplete +tabu_list_available: Incomplete +egb: Incomplete +egb_available: Incomplete + +class _MindtPyAlgorithm: + working_model: Incomplete + mip: Incomplete + fixed_nlp: Incomplete + results: Incomplete + timing: Incomplete + curr_int_sol: Incomplete + should_terminate: bool + integer_list: Incomplete + integer_solution_to_cuts_index: Incomplete + nlp_iter: int + mip_iter: int + mip_subiter: int + nlp_infeasible_counter: int + fp_iter: int + primal_bound_progress_time: Incomplete + dual_bound_progress_time: Incomplete + abs_gap: Incomplete + rel_gap: Incomplete + log_formatter: str + termination_condition_log_formatter: str + fixed_nlp_log_formatter: str + infeasible_fixed_nlp_log_formatter: str + log_note_formatter: str + primal_bound_improved: bool + dual_bound_improved: bool + best_solution_found: Incomplete + best_solution_found_time: Incomplete + stored_bound: Incomplete + num_no_good_cuts_added: Incomplete + last_iter_cuts: bool + mip_start_lazy_oa_cuts: Incomplete + mip_load_solutions: bool + nlp_load_solutions: bool + regularization_mip_load_solutions: bool + def __init__(self, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def version(self): ... + def set_up_logger(self) -> None: ... + util_block_name: Incomplete + def create_utility_block(self, model, name) -> None: ... + def model_is_valid(self): ... + def build_ordered_component_lists(self, model) -> None: ... + def add_cuts_components(self, model) -> None: ... + def get_dual_integral(self): ... + def get_primal_integral(self): ... + primal_integral: Incomplete + dual_integral: Incomplete + primal_dual_gap_integral: Incomplete + def get_integral_info(self) -> None: ... + def update_gap(self) -> None: ... + dual_bound: Incomplete + def update_dual_bound(self, bound_value) -> None: ... + def update_suboptimal_dual_bound(self, results) -> None: ... + primal_bound: Incomplete + def update_primal_bound(self, bound_value) -> None: ... + objective_sense: Incomplete + def process_objective(self, update_var_con_list: bool = True) -> None: ... + original_model: Incomplete + primal_bound_progress: Incomplete + dual_bound_progress: Incomplete + mip_objective_polynomial_degree: Incomplete + mip_constraint_polynomial_degree: Incomplete + def set_up_solve_data(self, model) -> None: ... + def MindtPy_initialization(self) -> None: ... + rnlp: Incomplete + def init_rNLP(self, add_oa_cuts: bool = True) -> None: ... + def init_max_binaries(self) -> None: ... + def solve_subproblem(self): ... + def handle_nlp_subproblem_tc(self, fixed_nlp, result, cb_opt=None) -> None: ... + def handle_subproblem_optimal(self, fixed_nlp, cb_opt=None, fp: bool = False) -> None: ... + def handle_subproblem_infeasible(self, fixed_nlp, cb_opt=None) -> None: ... + def handle_subproblem_other_termination( + self, fixed_nlp, termination_condition, cb_opt=None + ) -> None: ... + def solve_feasibility_subproblem(self): ... + def handle_feasibility_subproblem_tc(self, subprob_terminate_cond, MindtPy) -> None: ... + def algorithm_should_terminate(self, check_cycling): ... + def fix_dual_bound(self, last_iter_cuts) -> None: ... + def set_up_tabulist_callback(self) -> None: ... + def set_up_lazy_OA_callback(self) -> None: ... + def solve_main(self): ... + def solve_fp_main(self): ... + def solve_regularization_main(self): ... + def set_up_mip_solver(self): ... + def handle_main_optimal(self, main_mip, update_bound: bool = True) -> None: ... + def handle_main_infeasible(self) -> None: ... + def handle_main_max_timelimit(self, main_mip, main_mip_results) -> None: ... + def handle_main_unbounded(self, main_mip): ... + def handle_regularization_main_tc(self, main_mip, main_mip_results) -> None: ... + def setup_main(self) -> None: ... + def setup_fp_main(self) -> None: ... + def setup_regularization_main(self) -> None: ... + def update_result(self) -> None: ... + def load_solution(self) -> None: ... + def check_subsolver_validity(self) -> None: ... + def check_config(self) -> None: ... + def solve_fp_subproblem(self): ... + def handle_fp_subproblem_optimal(self, fp_nlp) -> None: ... + def handle_fp_main_tc(self, fp_main_results): ... + def fp_loop(self) -> None: ... + def initialize_mip_problem(self) -> None: ... + mip_opt: Incomplete + nlp_opt: Incomplete + feasibility_nlp_opt: Incomplete + regularization_mip_opt: Incomplete + def initialize_subsolvers(self) -> None: ... + def set_appsi_solver_update_config(self) -> None: ... + initial_var_values: Incomplete + def solve(self, model, **kwds): ... + def objective_reformulation(self) -> None: ... + def handle_main_mip_termination(self, main_mip, main_mip_results): ... + def MindtPy_iteration_loop(self) -> None: ... + def get_solution_name_obj(self, main_mip_results): ... + def add_regularization(self) -> None: ... + def bounds_converged(self): ... + def reached_iteration_limit(self): ... + def reached_time_limit(self): ... + def reached_stalling_limit(self): ... + def iteration_cycling(self): ... diff --git a/stubs/pyomo/contrib/mindtpy/config_options.pyi b/stubs/pyomo/contrib/mindtpy/config_options.pyi new file mode 100644 index 000000000..0641c3dce --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/config_options.pyi @@ -0,0 +1,8 @@ +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.config import PositiveFloat as PositiveFloat +from pyomo.common.config import PositiveInt as PositiveInt +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.contrib.gdpopt.util import a_logger as a_logger diff --git a/stubs/pyomo/contrib/mindtpy/cut_generation.pyi b/stubs/pyomo/contrib/mindtpy/cut_generation.pyi new file mode 100644 index 000000000..61d76be0d --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/cut_generation.pyi @@ -0,0 +1,33 @@ +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.contrib.mcpp.pyomo_mcpp import MCPP_Error as MCPP_Error +from pyomo.core import minimize as minimize +from pyomo.core import value as value + +def add_oa_cuts( + target_model, + dual_values, + jacobians, + objective_sense, + mip_constraint_polynomial_degree, + mip_iter, + config, + timing, + cb_opt=None, + linearize_active: bool = True, + linearize_violated: bool = True, +) -> None: ... +def add_oa_cuts_for_grey_box( + target_model, jacobians_model, config, objective_sense, mip_iter, cb_opt=None +) -> None: ... +def add_ecp_cuts( + target_model, + jacobians, + config, + timing, + linearize_active: bool = True, + linearize_violated: bool = True, +) -> None: ... +def add_no_good_cuts( + target_model, var_values, config, timing, mip_iter: int = 0, cb_opt=None +) -> None: ... +def add_affine_cuts(target_model, config, timing) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/extended_cutting_plane.pyi b/stubs/pyomo/contrib/mindtpy/extended_cutting_plane.pyi new file mode 100644 index 000000000..bf3b10434 --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/extended_cutting_plane.pyi @@ -0,0 +1,21 @@ +from _typeshed import Incomplete +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm +from pyomo.contrib.mindtpy.cut_generation import add_ecp_cuts as add_ecp_cuts +from pyomo.contrib.mindtpy.util import calc_jacobians as calc_jacobians +from pyomo.core import ConstraintList as ConstraintList +from pyomo.opt import SolverFactory as SolverFactory + +class MindtPy_ECP_Solver(_MindtPyAlgorithm): + CONFIG: Incomplete + last_iter_cuts: bool + def MindtPy_iteration_loop(self) -> None: ... + def check_config(self) -> None: ... + jacobians: Incomplete + def initialize_mip_problem(self) -> None: ... + def init_rNLP(self) -> None: ... + def algorithm_should_terminate(self): ... + primal_bound: Incomplete + best_solution_found: Incomplete + def all_nonlinear_constraint_satisfied(self): ... diff --git a/stubs/pyomo/contrib/mindtpy/feasibility_pump.pyi b/stubs/pyomo/contrib/mindtpy/feasibility_pump.pyi new file mode 100644 index 000000000..a542b0281 --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/feasibility_pump.pyi @@ -0,0 +1,21 @@ +from _typeshed import Incomplete +from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm +from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts as add_oa_cuts +from pyomo.contrib.mindtpy.util import calc_jacobians as calc_jacobians +from pyomo.core import ConstraintList as ConstraintList +from pyomo.opt import SolverFactory as SolverFactory + +class MindtPy_FP_Solver(_MindtPyAlgorithm): + CONFIG: Incomplete + def check_config(self) -> None: ... + jacobians: Incomplete + def initialize_mip_problem(self) -> None: ... + def add_cuts( + self, + dual_values, + linearize_active: bool = True, + linearize_violated: bool = True, + cb_opt=None, + nlp=None, + ) -> None: ... + def MindtPy_iteration_loop(self) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/global_outer_approximation.pyi b/stubs/pyomo/contrib/mindtpy/global_outer_approximation.pyi new file mode 100644 index 000000000..e3a06f470 --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/global_outer_approximation.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm +from pyomo.contrib.mindtpy.cut_generation import add_affine_cuts as add_affine_cuts +from pyomo.core import ConstraintList as ConstraintList +from pyomo.opt import SolverFactory as SolverFactory + +class MindtPy_GOA_Solver(_MindtPyAlgorithm): + CONFIG: Incomplete + def check_config(self) -> None: ... + def initialize_mip_problem(self) -> None: ... + def update_primal_bound(self, bound_value) -> None: ... + def add_cuts( + self, + dual_values=None, + linearize_active: bool = True, + linearize_violated: bool = True, + cb_opt=None, + nlp=None, + ) -> None: ... + integer_list: Incomplete + def deactivate_no_good_cuts_when_fixing_bound(self, no_good_cuts) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/outer_approximation.pyi b/stubs/pyomo/contrib/mindtpy/outer_approximation.pyi new file mode 100644 index 000000000..683a48a77 --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/outer_approximation.pyi @@ -0,0 +1,27 @@ +from _typeshed import Incomplete +from pyomo.contrib.mindtpy.algorithm_base_class import _MindtPyAlgorithm +from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts as add_oa_cuts +from pyomo.contrib.mindtpy.cut_generation import ( + add_oa_cuts_for_grey_box as add_oa_cuts_for_grey_box, +) +from pyomo.contrib.mindtpy.util import calc_jacobians as calc_jacobians +from pyomo.core import ConstraintList as ConstraintList +from pyomo.opt import SolverFactory as SolverFactory + +class MindtPy_OA_Solver(_MindtPyAlgorithm): + CONFIG: Incomplete + regularization_mip_type: str + def check_config(self) -> None: ... + jacobians: Incomplete + def initialize_mip_problem(self) -> None: ... + def add_cuts( + self, + dual_values, + linearize_active: bool = True, + linearize_violated: bool = True, + cb_opt=None, + nlp=None, + ) -> None: ... + integer_list: Incomplete + def deactivate_no_good_cuts_when_fixing_bound(self, no_good_cuts) -> None: ... + def objective_reformulation(self) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/plugins.pyi b/stubs/pyomo/contrib/mindtpy/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/single_tree.pyi b/stubs/pyomo/contrib/mindtpy/single_tree.pyi new file mode 100644 index 000000000..c075d012e --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/single_tree.pyi @@ -0,0 +1,47 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.contrib.mcpp.pyomo_mcpp import MCPP_Error as MCPP_Error +from pyomo.contrib.mindtpy.cut_generation import add_no_good_cuts as add_no_good_cuts +from pyomo.contrib.mindtpy.cut_generation import add_oa_cuts as add_oa_cuts +from pyomo.contrib.mindtpy.util import copy_var_list_values as copy_var_list_values +from pyomo.contrib.mindtpy.util import get_integer_solution as get_integer_solution +from pyomo.contrib.mindtpy.util import set_var_valid_value as set_var_valid_value +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy as gurobipy + +cplex: Incomplete +cplex_available: Incomplete + +class LazyOACallback_cplex: + def copy_lazy_var_list_values( + self, opt, from_list, to_list, config, skip_stale: bool = False, skip_fixed: bool = True + ) -> None: ... + def add_lazy_oa_cuts( + self, + target_model, + dual_values, + mindtpy_solver, + config, + opt, + linearize_active: bool = True, + linearize_violated: bool = True, + ) -> None: ... + def add_lazy_affine_cuts(self, mindtpy_solver, config, opt) -> None: ... + def add_lazy_no_good_cuts( + self, var_values, mindtpy_solver, config, opt, feasible: bool = False + ) -> None: ... + def handle_lazy_main_feasible_solution(self, main_mip, mindtpy_solver, config, opt) -> None: ... + def handle_lazy_subproblem_optimal(self, fixed_nlp, mindtpy_solver, config, opt) -> None: ... + def handle_lazy_subproblem_infeasible(self, fixed_nlp, mindtpy_solver, config, opt) -> None: ... + def handle_lazy_subproblem_other_termination( + self, fixed_nlp, termination_condition, mindtpy_solver, config + ) -> None: ... + def __call__(self) -> None: ... + +def LazyOACallback_gurobi(cb_m, cb_opt, cb_where, mindtpy_solver, config) -> None: ... +def handle_lazy_main_feasible_solution_gurobi(cb_m, cb_opt, mindtpy_solver, config) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/tabu_list.pyi b/stubs/pyomo/contrib/mindtpy/tabu_list.pyi new file mode 100644 index 000000000..78cd16f50 --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/tabu_list.pyi @@ -0,0 +1,9 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import UnavailableClass as UnavailableClass +from pyomo.common.dependencies import attempt_import as attempt_import + +cplex: Incomplete +cplex_available: Incomplete + +class IncumbentCallback_cplex: + def __call__(self) -> None: ... diff --git a/stubs/pyomo/contrib/mindtpy/util.pyi b/stubs/pyomo/contrib/mindtpy/util.pyi new file mode 100644 index 000000000..1303f91ce --- /dev/null +++ b/stubs/pyomo/contrib/mindtpy/util.pyi @@ -0,0 +1,71 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.contrib.gdpopt.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.gdpopt.util import time_code as time_code +from pyomo.contrib.mcpp.pyomo_mcpp import McCormick as McCormick +from pyomo.contrib.mcpp.pyomo_mcpp import mcpp_available as mcpp_available +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import Objective as Objective +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Reals as Reals +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import VarList as VarList +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy as gurobipy +from pyomo.solvers.plugins.solvers.gurobi_persistent import GurobiPersistent as GurobiPersistent +from pyomo.util.model_size import build_model_size_report as build_model_size_report + +pyomo_nlp: Incomplete +numpy: Incomplete + +def calc_jacobians(constraint_list, differentiate_mode): ... +def initialize_feas_subproblem(m, feasibility_norm) -> None: ... +def add_var_bound(model, config) -> None: ... +def generate_norm2sq_objective_function(model, setpoint_model, discrete_only: bool = False): ... +def generate_norm1_objective_function(model, setpoint_model, discrete_only: bool = False): ... +def generate_norm_inf_objective_function(model, setpoint_model, discrete_only: bool = False): ... +def generate_lag_objective_function( + model, setpoint_model, config, timing, discrete_only: bool = False +): ... +def generate_norm1_norm_constraint(model, setpoint_model, config, discrete_only: bool = True): ... +def update_solver_timelimit(opt, solver_name, timing, config) -> None: ... +def set_solver_mipgap(opt, solver_name, config) -> None: ... +def set_solver_constraint_violation_tolerance( + opt, solver_name, config, warm_start: bool = True +) -> None: ... +def get_integer_solution(model, string_zero: bool = False): ... +def copy_var_list_values_from_solution_pool( + from_list, + to_list, + config, + solver_model, + var_map, + solution_name, + ignore_integrality: bool = False, +) -> None: ... + +class GurobiPersistent4MindtPy(GurobiPersistent): ... + +def epigraph_reformulation(exp, slack_var_list, constraint_list, use_mcpp, sense) -> None: ... +def setup_results_object(results, model, config) -> None: ... +def fp_converged(working_model, mip_model, proj_zero_tolerance, discrete_only: bool = True): ... +def add_orthogonality_cuts(working_model, mip_model, config) -> None: ... +def generate_norm_constraint(fp_nlp_model, mip_model, config) -> None: ... +def copy_var_list_values( + from_list, + to_list, + config, + skip_stale: bool = False, + skip_fixed: bool = True, + ignore_integrality: bool = False, +) -> None: ... +def set_var_valid_value( + var, var_val, integer_tolerance, zero_tolerance, ignore_integrality +) -> None: ... diff --git a/stubs/pyomo/contrib/mpc/__init__.pyi b/stubs/pyomo/contrib/mpc/__init__.pyi new file mode 100644 index 000000000..aea682982 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/__init__.pyi @@ -0,0 +1,5 @@ +from .data.get_cuid import get_indexed_cuid as get_indexed_cuid +from .data.interval_data import IntervalData as IntervalData +from .data.scalar_data import ScalarData as ScalarData +from .data.series_data import TimeSeriesData as TimeSeriesData +from .interfaces.model_interface import DynamicModelInterface as DynamicModelInterface diff --git a/stubs/pyomo/contrib/mpc/data/__init__.pyi b/stubs/pyomo/contrib/mpc/data/__init__.pyi new file mode 100644 index 000000000..ee5934aed --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/__init__.pyi @@ -0,0 +1,7 @@ +from .convert import interval_to_series as interval_to_series +from .convert import series_to_interval as series_to_interval +from .interval_data import IntervalData as IntervalData +from .scalar_data import ScalarData as ScalarData +from .series_data import TimeSeriesData as TimeSeriesData + +__doc__: str diff --git a/stubs/pyomo/contrib/mpc/data/convert.pyi b/stubs/pyomo/contrib/mpc/data/convert.pyi new file mode 100644 index 000000000..4808d6044 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/convert.pyi @@ -0,0 +1,15 @@ +from pyomo.contrib.mpc.data.find_nearest_index import ( + find_nearest_interval_index as find_nearest_interval_index, +) +from pyomo.contrib.mpc.data.interval_data import IntervalData as IntervalData +from pyomo.contrib.mpc.data.scalar_data import ScalarData as ScalarData +from pyomo.contrib.mpc.data.series_data import TimeSeriesData as TimeSeriesData + +def interval_to_series( + data, + time_points=None, + tolerance: float = 0.0, + use_left_endpoints: bool = False, + prefer_left: bool = True, +): ... +def series_to_interval(data, use_left_endpoints: bool = False): ... diff --git a/stubs/pyomo/contrib/mpc/data/dynamic_data_base.pyi b/stubs/pyomo/contrib/mpc/data/dynamic_data_base.pyi new file mode 100644 index 000000000..1e708bb36 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/dynamic_data_base.pyi @@ -0,0 +1,13 @@ +from pyomo.contrib.mpc.data.get_cuid import get_indexed_cuid as get_indexed_cuid +from pyomo.core.base.set import Set as Set + +class _DynamicDataBase: + def __init__(self, data, time_set=None, context=None) -> None: ... + def __eq__(self, other): ... + def get_data(self): ... + def get_cuid(self, key, context=None): ... + def get_data_from_key(self, key, context=None): ... + def contains_key(self, key, context=None): ... + def update_data(self, other, context=None) -> None: ... + def to_serializable(self) -> None: ... + def extract_variables(self, variables, context=None, copy_values: bool = False): ... diff --git a/stubs/pyomo/contrib/mpc/data/find_nearest_index.pyi b/stubs/pyomo/contrib/mpc/data/find_nearest_index.pyi new file mode 100644 index 000000000..794591b85 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/find_nearest_index.pyi @@ -0,0 +1,4 @@ +def find_nearest_index(array, target, tolerance=None): ... +def find_nearest_interval_index( + interval_array, target, tolerance=None, prefer_left: bool = True +): ... diff --git a/stubs/pyomo/contrib/mpc/data/get_cuid.pyi b/stubs/pyomo/contrib/mpc/data/get_cuid.pyi new file mode 100644 index 000000000..27f65d39e --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/get_cuid.pyi @@ -0,0 +1,6 @@ +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.indexed_component_slice import IndexedComponent_slice as IndexedComponent_slice +from pyomo.dae.flatten import get_slice_for_set as get_slice_for_set +from pyomo.util.slices import slice_component_along_sets as slice_component_along_sets + +def get_indexed_cuid(var, sets=None, dereference=None, context=None): ... diff --git a/stubs/pyomo/contrib/mpc/data/interval_data.pyi b/stubs/pyomo/contrib/mpc/data/interval_data.pyi new file mode 100644 index 000000000..c5383000b --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/interval_data.pyi @@ -0,0 +1,27 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.contrib.mpc.data.dynamic_data_base import _DynamicDataBase +from pyomo.contrib.mpc.data.find_nearest_index import find_nearest_index as find_nearest_index +from pyomo.contrib.mpc.data.find_nearest_index import ( + find_nearest_interval_index as find_nearest_interval_index, +) +from pyomo.contrib.mpc.data.get_cuid import get_indexed_cuid as get_indexed_cuid +from pyomo.contrib.mpc.data.scalar_data import ScalarData as ScalarData + +class IntervalDataTuple(NamedTuple): + data: Incomplete + intervals: Incomplete + +def assert_disjoint_intervals(intervals) -> None: ... + +class IntervalData(_DynamicDataBase): + def __init__(self, data, intervals, time_set=None, context=None) -> None: ... + def __eq__(self, other): ... + def get_intervals(self): ... + def get_data_at_interval_indices(self, indices): ... + def get_data_at_time(self, time, tolerance=None, prefer_left: bool = True): ... + def to_serializable(self): ... + def concatenate(self, other, tolerance: float = 0.0) -> None: ... + def shift_time_points(self, offset) -> None: ... + def extract_variables(self, variables, context=None, copy_values: bool = False): ... diff --git a/stubs/pyomo/contrib/mpc/data/scalar_data.pyi b/stubs/pyomo/contrib/mpc/data/scalar_data.pyi new file mode 100644 index 000000000..db9e1cd4c --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/scalar_data.pyi @@ -0,0 +1,6 @@ +from pyomo.contrib.mpc.data.dynamic_data_base import _DynamicDataBase +from pyomo.contrib.mpc.data.get_cuid import get_indexed_cuid as get_indexed_cuid + +class ScalarData(_DynamicDataBase): + def __init__(self, data, time_set=None, context=None) -> None: ... + def to_serializable(self): ... diff --git a/stubs/pyomo/contrib/mpc/data/series_data.pyi b/stubs/pyomo/contrib/mpc/data/series_data.pyi new file mode 100644 index 000000000..2e23d1c80 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/data/series_data.pyi @@ -0,0 +1,22 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.contrib.mpc.data.dynamic_data_base import _DynamicDataBase +from pyomo.contrib.mpc.data.find_nearest_index import find_nearest_index as find_nearest_index +from pyomo.contrib.mpc.data.get_cuid import get_indexed_cuid as get_indexed_cuid +from pyomo.contrib.mpc.data.scalar_data import ScalarData as ScalarData + +class TimeSeriesTuple(NamedTuple): + data: Incomplete + time: Incomplete + +class TimeSeriesData(_DynamicDataBase): + def __init__(self, data, time, time_set=None, context=None) -> None: ... + def __eq__(self, other): ... + def get_time_points(self): ... + def get_data_at_time_indices(self, indices): ... + def get_data_at_time(self, time=None, tolerance: float = 0.0): ... + def to_serializable(self): ... + def concatenate(self, other, tolerance: float = 0.0) -> None: ... + def shift_time_points(self, offset) -> None: ... + def extract_variables(self, variables, context=None, copy_values: bool = False): ... diff --git a/stubs/pyomo/contrib/mpc/examples/__init__.pyi b/stubs/pyomo/contrib/mpc/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/mpc/examples/cstr/__init__.pyi b/stubs/pyomo/contrib/mpc/examples/cstr/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/mpc/examples/cstr/model.pyi b/stubs/pyomo/contrib/mpc/examples/cstr/model.pyi new file mode 100644 index 000000000..2a956d041 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/examples/cstr/model.pyi @@ -0,0 +1,6 @@ +from pyomo.contrib.incidence_analysis import IncidenceGraphInterface as IncidenceGraphInterface + +def make_model(dynamic: bool = True, horizon: float = 10.0): ... +def initialize_model(m, dynamic: bool = True, ntfe=None) -> None: ... +def create_instance(dynamic: bool = True, horizon=None, ntfe=None): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/mpc/examples/cstr/run_mpc.pyi b/stubs/pyomo/contrib/mpc/examples/cstr/run_mpc.pyi new file mode 100644 index 000000000..12030ac7d --- /dev/null +++ b/stubs/pyomo/contrib/mpc/examples/cstr/run_mpc.pyi @@ -0,0 +1,14 @@ +from pyomo.contrib.mpc.examples.cstr.model import create_instance as create_instance + +def get_steady_state_data(target, tee: bool = False): ... +def run_cstr_mpc( + initial_data, + setpoint_data, + samples_per_controller_horizon: int = 5, + sample_time: float = 2.0, + ntfe_per_sample_controller: int = 2, + ntfe_plant: int = 5, + simulation_steps: int = 5, + tee: bool = False, +): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/mpc/examples/cstr/run_openloop.pyi b/stubs/pyomo/contrib/mpc/examples/cstr/run_openloop.pyi new file mode 100644 index 000000000..3d2212468 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/examples/cstr/run_openloop.pyi @@ -0,0 +1,11 @@ +from pyomo.contrib.mpc.examples.cstr.model import create_instance as create_instance + +def get_input_sequence(): ... +def run_cstr_openloop( + inputs, + model_horizon: float = 1.0, + ntfe: int = 10, + simulation_steps: int = 15, + tee: bool = False, +): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/mpc/interfaces/__init__.pyi b/stubs/pyomo/contrib/mpc/interfaces/__init__.pyi new file mode 100644 index 000000000..3a03abe59 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/interfaces/__init__.pyi @@ -0,0 +1 @@ +__doc__: str diff --git a/stubs/pyomo/contrib/mpc/interfaces/copy_values.pyi b/stubs/pyomo/contrib/mpc/interfaces/copy_values.pyi new file mode 100644 index 000000000..5b4de98c1 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/interfaces/copy_values.pyi @@ -0,0 +1,7 @@ +from _typeshed import Incomplete + +iterable_scalars: Incomplete + +def copy_values_at_time( + source_vars, target_vars, source_time_points, target_time_points +) -> None: ... diff --git a/stubs/pyomo/contrib/mpc/interfaces/load_data.pyi b/stubs/pyomo/contrib/mpc/interfaces/load_data.pyi new file mode 100644 index 000000000..a6913b70f --- /dev/null +++ b/stubs/pyomo/contrib/mpc/interfaces/load_data.pyi @@ -0,0 +1,16 @@ +from pyomo.contrib.mpc.data.find_nearest_index import find_nearest_index as find_nearest_index +from pyomo.contrib.mpc.data.find_nearest_index import ( + find_nearest_interval_index as find_nearest_interval_index, +) + +def load_data_from_scalar(data, model, time) -> None: ... +def load_data_from_series(data, model, time, tolerance: float = 0.0) -> None: ... +def load_data_from_interval( + data, + model, + time, + tolerance: float = 0.0, + prefer_left: bool = True, + exclude_left_endpoint: bool = True, + exclude_right_endpoint: bool = False, +) -> None: ... diff --git a/stubs/pyomo/contrib/mpc/interfaces/model_interface.pyi b/stubs/pyomo/contrib/mpc/interfaces/model_interface.pyi new file mode 100644 index 000000000..4e2ac9d1a --- /dev/null +++ b/stubs/pyomo/contrib/mpc/interfaces/model_interface.pyi @@ -0,0 +1,63 @@ +from _typeshed import Incomplete +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.contrib.mpc.data.find_nearest_index import find_nearest_index as find_nearest_index +from pyomo.contrib.mpc.data.get_cuid import get_indexed_cuid as get_indexed_cuid +from pyomo.contrib.mpc.data.interval_data import IntervalData as IntervalData +from pyomo.contrib.mpc.data.scalar_data import ScalarData as ScalarData +from pyomo.contrib.mpc.data.series_data import TimeSeriesData as TimeSeriesData +from pyomo.contrib.mpc.interfaces.copy_values import copy_values_at_time as copy_values_at_time +from pyomo.contrib.mpc.interfaces.load_data import ( + load_data_from_interval as load_data_from_interval, +) +from pyomo.contrib.mpc.interfaces.load_data import load_data_from_scalar as load_data_from_scalar +from pyomo.contrib.mpc.interfaces.load_data import load_data_from_series as load_data_from_series +from pyomo.contrib.mpc.modeling.constraints import ( + get_piecewise_constant_constraints as get_piecewise_constant_constraints, +) +from pyomo.contrib.mpc.modeling.cost_expressions import ( + get_penalty_from_constant_target as get_penalty_from_constant_target, +) +from pyomo.contrib.mpc.modeling.cost_expressions import ( + get_penalty_from_target as get_penalty_from_target, +) +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.var import Var as Var +from pyomo.dae.flatten import flatten_dae_components as flatten_dae_components + +iterable_scalars: Incomplete + +class DynamicModelInterface: + model: Incomplete + time: Incomplete + def __init__(self, model, time, context=...) -> None: ... + def get_scalar_variables(self): ... + def get_indexed_variables(self): ... + def get_scalar_expressions(self): ... + def get_indexed_expressions(self): ... + def get_scalar_variable_data(self): ... + def get_data_at_time(self, time=None, include_expr: bool = False): ... + def load_data( + self, + data, + time_points=None, + tolerance: float = 0.0, + prefer_left=None, + exclude_left_endpoint=None, + exclude_right_endpoint=None, + ) -> None: ... + def copy_values_at_time(self, source_time=None, target_time=None) -> None: ... + def shift_values_by_time(self, dt) -> None: ... + def get_penalty_from_target( + self, + target_data, + time=None, + variables=None, + weight_data=None, + variable_set=None, + tolerance=None, + prefer_left=None, + ): ... + def get_piecewise_constant_constraints( + self, variables, sample_points, use_next: bool = True, tolerance: float = 0.0 + ): ... diff --git a/stubs/pyomo/contrib/mpc/interfaces/var_linker.pyi b/stubs/pyomo/contrib/mpc/interfaces/var_linker.pyi new file mode 100644 index 000000000..3f2b75fd4 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/interfaces/var_linker.pyi @@ -0,0 +1,7 @@ +from pyomo.contrib.mpc.interfaces.copy_values import copy_values_at_time as copy_values_at_time + +class DynamicVarLinker: + def __init__( + self, source_variables, target_variables, source_time=None, target_time=None + ) -> None: ... + def transfer(self, t_source=None, t_target=None) -> None: ... diff --git a/stubs/pyomo/contrib/mpc/modeling/__init__.pyi b/stubs/pyomo/contrib/mpc/modeling/__init__.pyi new file mode 100644 index 000000000..3a03abe59 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/modeling/__init__.pyi @@ -0,0 +1 @@ +__doc__: str diff --git a/stubs/pyomo/contrib/mpc/modeling/constraints.pyi b/stubs/pyomo/contrib/mpc/modeling/constraints.pyi new file mode 100644 index 000000000..8608f756b --- /dev/null +++ b/stubs/pyomo/contrib/mpc/modeling/constraints.pyi @@ -0,0 +1,4 @@ +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.set import Set as Set + +def get_piecewise_constant_constraints(inputs, time, sample_points, use_next: bool = True): ... diff --git a/stubs/pyomo/contrib/mpc/modeling/cost_expressions.pyi b/stubs/pyomo/contrib/mpc/modeling/cost_expressions.pyi new file mode 100644 index 000000000..b2f425080 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/modeling/cost_expressions.pyi @@ -0,0 +1,35 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.contrib.mpc.data.convert import interval_to_series as interval_to_series +from pyomo.contrib.mpc.data.interval_data import IntervalData as IntervalData +from pyomo.contrib.mpc.data.scalar_data import ScalarData as ScalarData +from pyomo.contrib.mpc.data.series_data import TimeSeriesData as TimeSeriesData +from pyomo.contrib.mpc.data.series_data import get_indexed_cuid as get_indexed_cuid +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.set import Set as Set + +def get_penalty_from_constant_target( + variables, time, setpoint_data, weight_data=None, variable_set=None +): ... +def get_penalty_from_piecewise_constant_target( + variables, + time, + setpoint_data, + weight_data=None, + variable_set=None, + tolerance: float = 0.0, + prefer_left: bool = True, +): ... +def get_quadratic_penalty_at_time(var, t, setpoint, weight=None): ... +def get_penalty_from_time_varying_target( + variables, time, setpoint_data, weight_data=None, variable_set=None +): ... +def get_penalty_from_target( + variables, + time, + setpoint_data, + weight_data=None, + variable_set=None, + tolerance=None, + prefer_left=None, +): ... diff --git a/stubs/pyomo/contrib/mpc/modeling/terminal.pyi b/stubs/pyomo/contrib/mpc/modeling/terminal.pyi new file mode 100644 index 000000000..e42d249b3 --- /dev/null +++ b/stubs/pyomo/contrib/mpc/modeling/terminal.pyi @@ -0,0 +1,10 @@ +from pyomo.contrib.mpc.data.scalar_data import ScalarData as ScalarData +from pyomo.contrib.mpc.data.series_data import get_indexed_cuid as get_indexed_cuid +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.set import Set as Set + +def get_penalty_at_time( + variables, t, target_data, weight_data=None, time_set=None, variable_set=None +): ... +def get_terminal_penalty(variables, time_set, target_data, weight_data=None, variable_set=None): ... diff --git a/stubs/pyomo/contrib/multistart/__init__.pyi b/stubs/pyomo/contrib/multistart/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/multistart/high_conf_stop.pyi b/stubs/pyomo/contrib/multistart/high_conf_stop.pyi new file mode 100644 index 000000000..2b627e515 --- /dev/null +++ b/stubs/pyomo/contrib/multistart/high_conf_stop.pyi @@ -0,0 +1,2 @@ +def num_one_occurrences(observed_obj_vals, tolerance): ... +def should_stop(solutions, stopping_mass, stopping_delta, tolerance): ... diff --git a/stubs/pyomo/contrib/multistart/multi.pyi b/stubs/pyomo/contrib/multistart/multi.pyi new file mode 100644 index 000000000..64d41dc13 --- /dev/null +++ b/stubs/pyomo/contrib/multistart/multi.pyi @@ -0,0 +1,32 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.multistart.high_conf_stop import should_stop as should_stop +from pyomo.contrib.multistart.reinit import reinitialize_variables as reinitialize_variables +from pyomo.contrib.multistart.reinit import strategies as strategies +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverStatus as SolverStatus + +logger: Incomplete + +class MultiStart: + CONFIG: Incomplete + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def solve(self, model, **kwds): ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... diff --git a/stubs/pyomo/contrib/multistart/plugins.pyi b/stubs/pyomo/contrib/multistart/plugins.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/multistart/plugins.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/multistart/reinit.pyi b/stubs/pyomo/contrib/multistart/reinit.pyi new file mode 100644 index 000000000..ade0dd169 --- /dev/null +++ b/stubs/pyomo/contrib/multistart/reinit.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.core import Var as Var + +logger: Incomplete + +def rand(val, lb, ub): ... +def midpoint_guess_and_bound(val, lb, ub): ... +def rand_guess_and_bound(val, lb, ub): ... +def rand_distributed(val, lb, ub, divisions: int = 9): ... +def simple_midpoint(val, lb, ub): ... +def linspace(lower, upper, n): ... + +strategies: Incomplete + +def reinitialize_variables(model, config) -> None: ... diff --git a/stubs/pyomo/contrib/parmest/__init__.pyi b/stubs/pyomo/contrib/parmest/__init__.pyi new file mode 100644 index 000000000..f7a3bea97 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/__init__.pyi @@ -0,0 +1,2 @@ +from pyomo.common.deprecation import moved_module as moved_module +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute diff --git a/stubs/pyomo/contrib/parmest/examples/__init__.pyi b/stubs/pyomo/contrib/parmest/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/parmest/examples/reaction_kinetics/__init__.pyi b/stubs/pyomo/contrib/parmest/examples/reaction_kinetics/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.pyi b/stubs/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.pyi new file mode 100644 index 000000000..803b704c5 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reaction_kinetics/simple_reaction_parmest_example.pyi @@ -0,0 +1,31 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.experiment import Experiment as Experiment +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import Constraint as Constraint +from pyomo.environ import Expression as Expression +from pyomo.environ import Objective as Objective +from pyomo.environ import Param as Param +from pyomo.environ import PositiveReals as PositiveReals +from pyomo.environ import RangeSet as RangeSet +from pyomo.environ import Var as Var +from pyomo.environ import exp as exp +from pyomo.environ import minimize as minimize +from pyomo.environ import value as value + +def simple_reaction_model(data): ... + +class SimpleReactionExperiment(Experiment): + data: Incomplete + model: Incomplete + def __init__(self, data) -> None: ... + def create_model(self) -> None: ... + def label_model(self): ... + def get_labeled_model(self): ... + +class SimpleReactionExperimentK2Fixed(SimpleReactionExperiment): + def label_model(self): ... + +class SimpleReactionExperimentK2Variable(SimpleReactionExperiment): + def label_model(self): ... + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/__init__.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.pyi new file mode 100644 index 000000000..d60e13593 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/bootstrap_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.pyi new file mode 100644 index 000000000..d60e13593 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/confidence_region_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/datarec_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/datarec_example.pyi new file mode 100644 index 000000000..3599adb52 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/datarec_example.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + reactor_design_model as reactor_design_model, +) + +class ReactorDesignExperimentDataRec(ReactorDesignExperiment): + data_std: Incomplete + def __init__(self, data, data_std, experiment_number) -> None: ... + model: Incomplete + def create_model(self): ... + def label_model(self): ... + +class ReactorDesignExperimentPostDataRec(ReactorDesignExperiment): + data_std: Incomplete + def __init__(self, data, data_std, experiment_number) -> None: ... + def label_model(self): ... + +def generate_data(): ... +def main(): ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.pyi new file mode 100644 index 000000000..d60e13593 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/leaveNout_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.pyi new file mode 100644 index 000000000..d60e13593 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/likelihood_ratio_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.pyi new file mode 100644 index 000000000..098246b2c --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/multisensor_data_example.pyi @@ -0,0 +1,9 @@ +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) + +class MultisensorReactorDesignExperiment(ReactorDesignExperiment): + def finalize_model(self): ... + def label_model(self): ... + +def main(): ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.pyi new file mode 100644 index 000000000..d60e13593 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/parameter_estimation_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/reactor_design.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/reactor_design.pyi new file mode 100644 index 000000000..43cd8ad11 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/reactor_design.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.experiment import Experiment as Experiment + +def reactor_design_model(): ... + +class ReactorDesignExperiment(Experiment): + data: Incomplete + experiment_number: Incomplete + data_i: Incomplete + model: Incomplete + def __init__(self, data, experiment_number) -> None: ... + def create_model(self): ... + def finalize_model(self): ... + def label_model(self): ... + def get_labeled_model(self): ... + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.pyi b/stubs/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.pyi new file mode 100644 index 000000000..a37d732e8 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/reactor_design/timeseries_data_example.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.examples.reactor_design.reactor_design import ( + ReactorDesignExperiment as ReactorDesignExperiment, +) + +class TimeSeriesReactorDesignExperiment(ReactorDesignExperiment): + data: Incomplete + experiment_number: Incomplete + data_i: Incomplete + model: Incomplete + def __init__(self, data, experiment_number) -> None: ... + def finalize_model(self): ... + +def main(): ... diff --git a/stubs/pyomo/contrib/parmest/examples/rooney_biegler/__init__.pyi b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.pyi b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.pyi new file mode 100644 index 000000000..0296d5acc --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/bootstrap_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + RooneyBieglerExperiment as RooneyBieglerExperiment, +) + +def main(): ... diff --git a/stubs/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.pyi b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.pyi new file mode 100644 index 000000000..0296d5acc --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/likelihood_ratio_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + RooneyBieglerExperiment as RooneyBieglerExperiment, +) + +def main(): ... diff --git a/stubs/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.pyi b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.pyi new file mode 100644 index 000000000..0296d5acc --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/parameter_estimation_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.rooney_biegler.rooney_biegler import ( + RooneyBieglerExperiment as RooneyBieglerExperiment, +) + +def main(): ... diff --git a/stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.pyi b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.pyi new file mode 100644 index 000000000..28642a3db --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.experiment import Experiment as Experiment + +def rooney_biegler_model(data): ... + +class RooneyBieglerExperiment(Experiment): + data: Incomplete + model: Incomplete + def __init__(self, data) -> None: ... + def create_model(self) -> None: ... + def label_model(self) -> None: ... + def finalize_model(self) -> None: ... + def get_labeled_model(self): ... + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.pyi b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.pyi new file mode 100644 index 000000000..906d2c485 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/rooney_biegler/rooney_biegler_with_constraint.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.experiment import Experiment as Experiment + +def rooney_biegler_model_with_constraint(data): ... + +class RooneyBieglerExperiment(Experiment): + data: Incomplete + model: Incomplete + def __init__(self, data) -> None: ... + def create_model(self) -> None: ... + def label_model(self) -> None: ... + def finalize_model(self) -> None: ... + def get_labeled_model(self): ... + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/semibatch/__init__.pyi b/stubs/pyomo/contrib/parmest/examples/semibatch/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/parmest/examples/semibatch/parallel_example.pyi b/stubs/pyomo/contrib/parmest/examples/semibatch/parallel_example.pyi new file mode 100644 index 000000000..04cb2771b --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/semibatch/parallel_example.pyi @@ -0,0 +1,3 @@ +from pyomo.contrib.parmest.examples.semibatch.semibatch import generate_model as generate_model + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.pyi b/stubs/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.pyi new file mode 100644 index 000000000..88271cd6c --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/semibatch/parameter_estimation_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.semibatch.semibatch import ( + SemiBatchExperiment as SemiBatchExperiment, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/semibatch/scenario_example.pyi b/stubs/pyomo/contrib/parmest/examples/semibatch/scenario_example.pyi new file mode 100644 index 000000000..88271cd6c --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/semibatch/scenario_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.parmest.examples.semibatch.semibatch import ( + SemiBatchExperiment as SemiBatchExperiment, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/examples/semibatch/semibatch.pyi b/stubs/pyomo/contrib/parmest/examples/semibatch/semibatch.pyi new file mode 100644 index 000000000..e6c071d53 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/examples/semibatch/semibatch.pyi @@ -0,0 +1,31 @@ +from _typeshed import Incomplete +from pyomo.contrib.parmest.experiment import Experiment as Experiment +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.environ import ComponentUID as ComponentUID +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import Constraint as Constraint +from pyomo.environ import ConstraintList as ConstraintList +from pyomo.environ import Expression as Expression +from pyomo.environ import Objective as Objective +from pyomo.environ import Param as Param +from pyomo.environ import Set as Set +from pyomo.environ import SolverFactory as SolverFactory +from pyomo.environ import Suffix as Suffix +from pyomo.environ import TransformationFactory as TransformationFactory +from pyomo.environ import Var as Var +from pyomo.environ import exp as exp +from pyomo.environ import minimize as minimize + +def generate_model(data): ... + +class SemiBatchExperiment(Experiment): + data: Incomplete + model: Incomplete + def __init__(self, data) -> None: ... + def create_model(self) -> None: ... + def label_model(self) -> None: ... + def finalize_model(self) -> None: ... + def get_labeled_model(self): ... + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/parmest/experiment.pyi b/stubs/pyomo/contrib/parmest/experiment.pyi new file mode 100644 index 000000000..bc5ec6cba --- /dev/null +++ b/stubs/pyomo/contrib/parmest/experiment.pyi @@ -0,0 +1,6 @@ +from _typeshed import Incomplete + +class Experiment: + model: Incomplete + def __init__(self, model=None) -> None: ... + def get_labeled_model(self): ... diff --git a/stubs/pyomo/contrib/parmest/graphics.pyi b/stubs/pyomo/contrib/parmest/graphics.pyi new file mode 100644 index 000000000..c93151e33 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/graphics.pyi @@ -0,0 +1,35 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import check_min_version as check_min_version +from pyomo.common.dependencies import matplotlib as matplotlib +from pyomo.common.dependencies import matplotlib_available as matplotlib_available +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.dependencies import pandas_available as pandas_available +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.dependencies import scipy_available as scipy_available +from pyomo.common.dependencies.scipy import stats as stats + +sns: Incomplete +seaborn_available: Incomplete +imports_available: Incomplete + +def pairwise_plot( + theta_values, + theta_star=None, + alpha=None, + distributions=[], + axis_limits=None, + title=None, + add_obj_contour: bool = True, + add_legend: bool = True, + filename=None, +) -> None: ... +def fit_rect_dist(theta_values, alpha): ... +def fit_mvn_dist(theta_values): ... +def fit_kde_dist(theta_values): ... +def grouped_boxplot( + data1, data2, normalize: bool = False, group_names=['data1', 'data2'], filename=None +) -> None: ... +def grouped_violinplot( + data1, data2, normalize: bool = False, group_names=['data1', 'data2'], filename=None +) -> None: ... diff --git a/stubs/pyomo/contrib/parmest/parmest.pyi b/stubs/pyomo/contrib/parmest/parmest.pyi new file mode 100644 index 000000000..8f3164dbd --- /dev/null +++ b/stubs/pyomo/contrib/parmest/parmest.pyi @@ -0,0 +1,119 @@ +from functools import singledispatchmethod + +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.dependencies import pandas_available as pandas_available +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.dependencies import scipy_available as scipy_available +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.tee import capture_output as capture_output +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.environ import Block as Block +from pyomo.environ import ComponentUID as ComponentUID +from pyomo.opt import SolverFactory as SolverFactory + +use_mpisppy: bool +parmest_available: Incomplete +inverse_reduced_hessian: Incomplete +inverse_reduced_hessian_available: Incomplete +logger: Incomplete + +def ef_nonants(ef): ... +def SSE(model): ... + +class Estimator: + exp_list: Incomplete + obj_function: Incomplete + tee: Incomplete + diagnostic_mode: Incomplete + solver_options: Incomplete + pest_deprecated: Incomplete + estimator_theta_names: Incomplete + model_initialized: bool + @singledispatchmethod + def __init__( + self, + experiment_list, + obj_function=None, + tee: bool = False, + diagnostic_mode: bool = False, + solver_options=None, + ) -> None: ... + def theta_est( + self, solver: str = 'ef_ipopt', return_values=[], calc_cov: bool = False, cov_n=None + ): ... + def theta_est_bootstrap( + self, + bootstrap_samples, + samplesize=None, + replacement: bool = True, + seed=None, + return_samples: bool = False, + ): ... + def theta_est_leaveNout( + self, lNo, lNo_samples=None, seed=None, return_samples: bool = False + ): ... + def leaveNout_bootstrap_test( + self, lNo, lNo_samples, bootstrap_samples, distribution, alphas, seed=None + ): ... + theta_names_updated: Incomplete + def objective_at_theta(self, theta_values=None, initialize_parmest_model: bool = False): ... + def likelihood_ratio_test( + self, obj_at_theta, obj_value, alphas, return_thresholds: bool = False + ): ... + def confidence_region_test( + self, theta_values, distribution, alphas, test_theta_values=None + ): ... + +def group_data(data, groupby_column_name, use_mean=None): ... + +class _DeprecatedSecondStageCostExpr: + def __init__(self, ssc_function, data) -> None: ... + def __call__(self, model): ... + +class _DeprecatedEstimator: + model_function: Incomplete + callback_data: Incomplete + theta_names: Incomplete + obj_function: Incomplete + tee: Incomplete + diagnostic_mode: Incomplete + solver_options: Incomplete + model_initialized: bool + def __init__( + self, + model_function, + data, + theta_names, + obj_function=None, + tee: bool = False, + diagnostic_mode: bool = False, + solver_options=None, + ) -> None: ... + def theta_est( + self, solver: str = 'ef_ipopt', return_values=[], calc_cov: bool = False, cov_n=None + ): ... + def theta_est_bootstrap( + self, + bootstrap_samples, + samplesize=None, + replacement: bool = True, + seed=None, + return_samples: bool = False, + ): ... + def theta_est_leaveNout( + self, lNo, lNo_samples=None, seed=None, return_samples: bool = False + ): ... + def leaveNout_bootstrap_test( + self, lNo, lNo_samples, bootstrap_samples, distribution, alphas, seed=None + ): ... + theta_names_updated: Incomplete + def objective_at_theta(self, theta_values=None, initialize_parmest_model: bool = False): ... + def likelihood_ratio_test( + self, obj_at_theta, obj_value, alphas, return_thresholds: bool = False + ): ... + def confidence_region_test( + self, theta_values, distribution, alphas, test_theta_values=None + ): ... diff --git a/stubs/pyomo/contrib/parmest/scenariocreator.pyi b/stubs/pyomo/contrib/parmest/scenariocreator.pyi new file mode 100644 index 000000000..281cb160e --- /dev/null +++ b/stubs/pyomo/contrib/parmest/scenariocreator.pyi @@ -0,0 +1,25 @@ +from _typeshed import Incomplete + +logger: Incomplete + +class ScenarioSet: + name: Incomplete + def __init__(self, name) -> None: ... + def ScensIterator(self): ... + def ScenarioNumber(self, scennum): ... + def addone(self, scen) -> None: ... + def append_bootstrap(self, bootstrap_theta) -> None: ... + def write_csv(self, filename) -> None: ... + +class ParmestScen: + name: Incomplete + ThetaVals: Incomplete + probability: Incomplete + def __init__(self, name, ThetaVals, probability) -> None: ... + +class ScenarioCreator: + pest: Incomplete + solvername: Incomplete + def __init__(self, pest, solvername) -> None: ... + def ScenariosFromExperiments(self, addtoSet) -> None: ... + def ScenariosFromBootstrap(self, addtoSet, numtomake, seed=None) -> None: ... diff --git a/stubs/pyomo/contrib/parmest/utils/__init__.pyi b/stubs/pyomo/contrib/parmest/utils/__init__.pyi new file mode 100644 index 000000000..0872316c7 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/utils/__init__.pyi @@ -0,0 +1,12 @@ +from pyomo.contrib.parmest.utils.create_ef import create_EF as create_EF +from pyomo.contrib.parmest.utils.create_ef import ef_nonants as ef_nonants +from pyomo.contrib.parmest.utils.create_ef import find_active_objective as find_active_objective +from pyomo.contrib.parmest.utils.create_ef import get_objs as get_objs +from pyomo.contrib.parmest.utils.ipopt_solver_wrapper import ( + ipopt_solve_with_stats as ipopt_solve_with_stats, +) +from pyomo.contrib.parmest.utils.model_utils import convert_params_to_vars as convert_params_to_vars +from pyomo.contrib.parmest.utils.mpi_utils import MPIInterface as MPIInterface +from pyomo.contrib.parmest.utils.mpi_utils import ParallelTaskManager as ParallelTaskManager +from pyomo.contrib.parmest.utils.scenario_tree import ScenarioNode as ScenarioNode +from pyomo.contrib.parmest.utils.scenario_tree import build_vardatalist as build_vardatalist diff --git a/stubs/pyomo/contrib/parmest/utils/create_ef.pyi b/stubs/pyomo/contrib/parmest/utils/create_ef.pyi new file mode 100644 index 000000000..2172d6dc3 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/utils/create_ef.pyi @@ -0,0 +1,17 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.core import Objective as Objective +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression + +def get_objs(scenario_instance): ... +def create_EF( + scenario_names, + scenario_creator, + scenario_creator_kwargs=None, + EF_name=None, + suppress_warnings: bool = False, + nonant_for_fixed_vars: bool = True, +): ... +def find_active_objective(pyomomodel): ... +def ef_nonants(ef) -> Generator[Incomplete]: ... diff --git a/stubs/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.pyi b/stubs/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.pyi new file mode 100644 index 000000000..41c7081bb --- /dev/null +++ b/stubs/pyomo/contrib/parmest/utils/ipopt_solver_wrapper.pyi @@ -0,0 +1,4 @@ +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt import TerminationCondition as TerminationCondition + +def ipopt_solve_with_stats(model, solver, max_iter: int = 500, max_cpu_time: int = 120): ... diff --git a/stubs/pyomo/contrib/parmest/utils/model_utils.pyi b/stubs/pyomo/contrib/parmest/utils/model_utils.pyi new file mode 100644 index 000000000..fc7ca5e28 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/utils/model_utils.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.core.base.param import IndexedParam as IndexedParam +from pyomo.core.base.var import IndexedVar as IndexedVar +from pyomo.core.expr import identify_mutable_parameters as identify_mutable_parameters +from pyomo.core.expr import replace_expressions as replace_expressions +from pyomo.environ import ComponentUID as ComponentUID + +logger: Incomplete + +def convert_params_to_vars(model, param_names=None, fix_vars: bool = False): ... diff --git a/stubs/pyomo/contrib/parmest/utils/mpi_utils.pyi b/stubs/pyomo/contrib/parmest/utils/mpi_utils.pyi new file mode 100644 index 000000000..305567ea2 --- /dev/null +++ b/stubs/pyomo/contrib/parmest/utils/mpi_utils.pyi @@ -0,0 +1,20 @@ +from _typeshed import Incomplete + +class MPIInterface: + __have_mpi__: Incomplete + def __init__(self) -> None: ... + @property + def have_mpi(self): ... + @property + def comm(self): ... + @property + def rank(self): ... + @property + def size(self): ... + +class ParallelTaskManager: + def __init__(self, n_total_tasks, mpi_interface=None) -> None: ... + def is_root(self): ... + def global_to_local_data(self, global_data): ... + def allgather_global_data(self, local_data): ... + def gather_global_data(self, local_data): ... diff --git a/stubs/pyomo/contrib/parmest/utils/scenario_tree.pyi b/stubs/pyomo/contrib/parmest/utils/scenario_tree.pyi new file mode 100644 index 000000000..7fe07f09c --- /dev/null +++ b/stubs/pyomo/contrib/parmest/utils/scenario_tree.pyi @@ -0,0 +1,26 @@ +from _typeshed import Incomplete + +def build_vardatalist(self, model, varlist=None): ... + +class ScenarioNode: + name: Incomplete + cond_prob: Incomplete + stage: Incomplete + cost_expression: Incomplete + nonant_list: Incomplete + nonant_ef_suppl_list: Incomplete + parent_name: Incomplete + nonant_vardata_list: Incomplete + nonant_ef_suppl_vardata_list: Incomplete + def __init__( + self, + name, + cond_prob, + stage, + cost_expression, + scen_name_list, + nonant_list, + scen_model, + nonant_ef_suppl_list=None, + parent_name=None, + ) -> None: ... diff --git a/stubs/pyomo/contrib/piecewise/__init__.pyi b/stubs/pyomo/contrib/piecewise/__init__.pyi new file mode 100644 index 000000000..f210e5b9d --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/__init__.pyi @@ -0,0 +1,38 @@ +from pyomo.contrib.piecewise.piecewise_linear_expression import ( + PiecewiseLinearExpression as PiecewiseLinearExpression, +) +from pyomo.contrib.piecewise.piecewise_linear_function import ( + PiecewiseLinearFunction as PiecewiseLinearFunction, +) +from pyomo.contrib.piecewise.transform.convex_combination import ( + ConvexCombinationTransformation as ConvexCombinationTransformation, +) +from pyomo.contrib.piecewise.transform.disaggregated_convex_combination import ( + DisaggregatedConvexCombinationTransformation as DisaggregatedConvexCombinationTransformation, +) +from pyomo.contrib.piecewise.transform.disaggregated_logarithmic import ( + DisaggregatedLogarithmicMIPTransformation as DisaggregatedLogarithmicMIPTransformation, +) +from pyomo.contrib.piecewise.transform.incremental import ( + IncrementalMIPTransformation as IncrementalMIPTransformation, +) +from pyomo.contrib.piecewise.transform.inner_representation_gdp import ( + InnerRepresentationGDPTransformation as InnerRepresentationGDPTransformation, +) +from pyomo.contrib.piecewise.transform.multiple_choice import ( + MultipleChoiceTransformation as MultipleChoiceTransformation, +) +from pyomo.contrib.piecewise.transform.nested_inner_repn import ( + NestedInnerRepresentationGDPTransformation as NestedInnerRepresentationGDPTransformation, +) +from pyomo.contrib.piecewise.transform.nonlinear_to_pwl import ( + DomainPartitioningMethod as DomainPartitioningMethod, +) +from pyomo.contrib.piecewise.transform.nonlinear_to_pwl import NonlinearToPWL as NonlinearToPWL +from pyomo.contrib.piecewise.transform.outer_representation_gdp import ( + OuterRepresentationGDPTransformation as OuterRepresentationGDPTransformation, +) +from pyomo.contrib.piecewise.transform.reduced_inner_representation_gdp import ( + ReducedInnerRepresentationGDPTransformation as ReducedInnerRepresentationGDPTransformation, +) +from pyomo.contrib.piecewise.triangulations import Triangulation as Triangulation diff --git a/stubs/pyomo/contrib/piecewise/ordered_3d_j1_triangulation_data.pyi b/stubs/pyomo/contrib/piecewise/ordered_3d_j1_triangulation_data.pyi new file mode 100644 index 000000000..469c7d987 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/ordered_3d_j1_triangulation_data.pyi @@ -0,0 +1 @@ +def get_hamiltonian_paths(): ... diff --git a/stubs/pyomo/contrib/piecewise/piecewise_linear_expression.pyi b/stubs/pyomo/contrib/piecewise/piecewise_linear_expression.pyi new file mode 100644 index 000000000..c8c7b9b79 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/piecewise_linear_expression.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.core.expr.numeric_expr import NumericExpression as NumericExpression + +class PiecewiseLinearExpression(NumericExpression): + __autoslot_mappers__: Incomplete + def __init__(self, args, pw_linear_function) -> None: ... + def nargs(self): ... + @property + def pw_linear_function(self): ... + def create_node_with_local_data(self, args): ... + def polynomial_degree(self) -> None: ... diff --git a/stubs/pyomo/contrib/piecewise/piecewise_linear_function.pyi b/stubs/pyomo/contrib/piecewise/piecewise_linear_function.pyi new file mode 100644 index 000000000..ce59dcc5f --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/piecewise_linear_function.pyi @@ -0,0 +1,61 @@ +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.dependencies.scipy import spatial as spatial +from pyomo.contrib.piecewise.piecewise_linear_expression import ( + PiecewiseLinearExpression as PiecewiseLinearExpression, +) +from pyomo.contrib.piecewise.triangulations import Triangulation as Triangulation +from pyomo.contrib.piecewise.triangulations import ( + get_ordered_j1_triangulation as get_ordered_j1_triangulation, +) +from pyomo.contrib.piecewise.triangulations import ( + get_unordered_j1_triangulation as get_unordered_j1_triangulation, +) +from pyomo.core import Any as Any +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import value as value +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.initializer import Initializer as Initializer + +ZERO_TOLERANCE: float +logger: Incomplete + +class PiecewiseLinearFunctionData(BlockData): + def __init__(self, component=None) -> None: ... + @property + def triangulation(self): ... + def __call__(self, *args): ... + def map_transformation_var(self, pw_expr, v) -> None: ... + def get_transformation_var(self, pw_expr): ... + +class _univariate_linear_functor(AutoSlots.Mixin): + slope: Incomplete + intercept: Incomplete + def __init__(self, slope, intercept) -> None: ... + def __call__(self, x): ... + +class _multivariate_linear_functor(AutoSlots.Mixin): + normal: Incomplete + def __init__(self, normal) -> None: ... + def __call__(self, *args): ... + +class _tabular_data_functor(AutoSlots.Mixin): + tabular_data: Incomplete + def __init__(self, tabular_data, tupleize: bool = False) -> None: ... + def __call__(self, *args): ... + +class PiecewiseLinearFunction(Block): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwargs) -> None: ... + +class ScalarPiecewiseLinearFunction(PiecewiseLinearFunctionData, PiecewiseLinearFunction): + def __init__(self, *args, **kwds) -> None: ... + +class IndexedPiecewiseLinearFunction(PiecewiseLinearFunction): ... diff --git a/stubs/pyomo/contrib/piecewise/transform/__init__.pyi b/stubs/pyomo/contrib/piecewise/transform/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/piecewise/transform/convex_combination.pyi b/stubs/pyomo/contrib/piecewise/transform/convex_combination.pyi new file mode 100644 index 000000000..d6ff86416 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/convex_combination.pyi @@ -0,0 +1,4 @@ +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory + +class ConvexCombinationTransformation(Transformation): ... diff --git a/stubs/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.pyi b/stubs/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.pyi new file mode 100644 index 000000000..bb0996a1a --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/disaggregated_convex_combination.pyi @@ -0,0 +1,4 @@ +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory + +class DisaggregatedConvexCombinationTransformation(Transformation): ... diff --git a/stubs/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.pyi b/stubs/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.pyi new file mode 100644 index 000000000..a019b87f6 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/disaggregated_logarithmic.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( + PiecewiseLinearTransformationBase as PiecewiseLinearTransformationBase, +) +from pyomo.core import Binary as Binary +from pyomo.core import Constraint as Constraint +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Set as Set +from pyomo.core import Var as Var +from pyomo.core.base import TransformationFactory as TransformationFactory + +class DisaggregatedLogarithmicMIPTransformation(PiecewiseLinearTransformationBase): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/piecewise/transform/incremental.pyi b/stubs/pyomo/contrib/piecewise/transform/incremental.pyi new file mode 100644 index 000000000..e7efa18ca --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/incremental.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( + PiecewiseLinearTransformationBase as PiecewiseLinearTransformationBase, +) +from pyomo.contrib.piecewise.triangulations import Triangulation as Triangulation +from pyomo.core import Binary as Binary +from pyomo.core import Constraint as Constraint +from pyomo.core import Param as Param +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Var as Var +from pyomo.core.base import TransformationFactory as TransformationFactory + +class IncrementalMIPTransformation(PiecewiseLinearTransformationBase): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/piecewise/transform/inner_representation_gdp.pyi b/stubs/pyomo/contrib/piecewise/transform/inner_representation_gdp.pyi new file mode 100644 index 000000000..9ac834797 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/inner_representation_gdp.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( + PiecewiseLinearTransformationBase as PiecewiseLinearTransformationBase, +) +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction + +class InnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/piecewise/transform/multiple_choice.pyi b/stubs/pyomo/contrib/piecewise/transform/multiple_choice.pyi new file mode 100644 index 000000000..865e75ae8 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/multiple_choice.pyi @@ -0,0 +1,4 @@ +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory + +class MultipleChoiceTransformation(Transformation): ... diff --git a/stubs/pyomo/contrib/piecewise/transform/nested_inner_repn.pyi b/stubs/pyomo/contrib/piecewise/transform/nested_inner_repn.pyi new file mode 100644 index 000000000..37d0e9107 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/nested_inner_repn.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( + PiecewiseLinearTransformationBase as PiecewiseLinearTransformationBase, +) +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.gdp import Disjunction as Disjunction + +class NestedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.pyi b/stubs/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.pyi new file mode 100644 index 000000000..d09f46411 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.pyi @@ -0,0 +1,69 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import InEnum as InEnum +from pyomo.common.config import PositiveInt as PositiveInt +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.enums import IntEnum as IntEnum +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.piecewise import PiecewiseLinearExpression as PiecewiseLinearExpression +from pyomo.contrib.piecewise import PiecewiseLinearFunction as PiecewiseLinearFunction +from pyomo.core.expr import SumExpression as SumExpression +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.util import target_list as target_list +from pyomo.environ import Any as Any +from pyomo.environ import Block as Block +from pyomo.environ import BooleanVar as BooleanVar +from pyomo.environ import Connector as Connector +from pyomo.environ import Constraint as Constraint +from pyomo.environ import Expression as Expression +from pyomo.environ import ExternalFunction as ExternalFunction +from pyomo.environ import LogicalConstraint as LogicalConstraint +from pyomo.environ import Objective as Objective +from pyomo.environ import Param as Param +from pyomo.environ import RangeSet as RangeSet +from pyomo.environ import Set as Set +from pyomo.environ import SetOf as SetOf +from pyomo.environ import SortComponents as SortComponents +from pyomo.environ import Suffix as Suffix +from pyomo.environ import Transformation as Transformation +from pyomo.environ import TransformationFactory as TransformationFactory +from pyomo.environ import Var as Var +from pyomo.environ import value as value +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.network import Port as Port +from pyomo.repn.quadratic import QuadraticRepnVisitor as QuadraticRepnVisitor +from pyomo.repn.util import ExprType as ExprType + +lineartree: Incomplete +lineartree_available: Incomplete +sklearn_lm: Incomplete +sklearn_available: Incomplete +logger: Incomplete + +class DomainPartitioningMethod(IntEnum): + RANDOM_GRID = 1 + UNIFORM_GRID = 2 + LINEAR_MODEL_TREE_UNIFORM = 3 + LINEAR_MODEL_TREE_RANDOM = 4 + +class _NonlinearToPWLTransformationData(AutoSlots.Mixin): + transformed_component: Incomplete + src_component: Incomplete + transformed_constraints: Incomplete + transformed_objectives: Incomplete + def __init__(self) -> None: ... + +class NonlinearToPWL(Transformation): + CONFIG: Incomplete + def __init__(self) -> None: ... + def get_src_component(self, cons): ... + def get_transformed_component(self, cons): ... + def get_transformed_nonlinear_constraints(self, model): ... + def get_transformed_quadratic_constraints(self, model): ... + def get_transformed_nonlinear_objectives(self, model): ... + def get_transformed_quadratic_objectives(self, model): ... diff --git a/stubs/pyomo/contrib/piecewise/transform/outer_representation_gdp.pyi b/stubs/pyomo/contrib/piecewise/transform/outer_representation_gdp.pyi new file mode 100644 index 000000000..7809f17ed --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/outer_representation_gdp.pyi @@ -0,0 +1,16 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies.scipy import spatial as spatial +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( + PiecewiseLinearTransformationBase as PiecewiseLinearTransformationBase, +) +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction + +class OuterRepresentationGDPTransformation(PiecewiseLinearTransformationBase): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/piecewise/transform/piecewise_linear_transformation_base.pyi b/stubs/pyomo/contrib/piecewise/transform/piecewise_linear_transformation_base.pyi new file mode 100644 index 000000000..7e605ea6a --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/piecewise_linear_transformation_base.pyi @@ -0,0 +1,36 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.piecewise import PiecewiseLinearFunction as PiecewiseLinearFunction +from pyomo.contrib.piecewise.transform.piecewise_to_mip_visitor import ( + PiecewiseLinearToMIP as PiecewiseLinearToMIP, +) +from pyomo.core import Any as Any +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import Connector as Connector +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import ExternalFunction as ExternalFunction +from pyomo.core import LogicalConstraint as LogicalConstraint +from pyomo.core import Objective as Objective +from pyomo.core import Param as Param +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base.block import Block as Block +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp.util import is_child_of as is_child_of +from pyomo.network import Port as Port + +class PiecewiseLinearTransformationBase(Transformation): + CONFIG: Incomplete + handlers: Incomplete + def __init__(self) -> None: ... diff --git a/stubs/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.pyi b/stubs/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.pyi new file mode 100644 index 000000000..fd64ceed8 --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/piecewise_to_mip_visitor.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.contrib.piecewise.piecewise_linear_expression import ( + PiecewiseLinearExpression as PiecewiseLinearExpression, +) +from pyomo.core import Expression as Expression +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor + +class PiecewiseLinearToMIP(StreamBasedExpressionVisitor): + transform_pw_linear_expression: Incomplete + transBlock: Incomplete + def __init__(self, transform_pw_linear_expression, transBlock) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, data): ... + finalizeResult: Incomplete diff --git a/stubs/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.pyi b/stubs/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.pyi new file mode 100644 index 000000000..24a7fc96f --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/transform/reduced_inner_representation_gdp.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.contrib.piecewise.transform.piecewise_linear_transformation_base import ( + PiecewiseLinearTransformationBase as PiecewiseLinearTransformationBase, +) +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Var as Var +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction + +class ReducedInnerRepresentationGDPTransformation(PiecewiseLinearTransformationBase): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/piecewise/triangulations.pyi b/stubs/pyomo/contrib/piecewise/triangulations.pyi new file mode 100644 index 000000000..5688445ce --- /dev/null +++ b/stubs/pyomo/contrib/piecewise/triangulations.pyi @@ -0,0 +1,29 @@ +from enum import Enum + +from _typeshed import Incomplete +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.contrib.piecewise.ordered_3d_j1_triangulation_data import ( + get_hamiltonian_paths as get_hamiltonian_paths, +) + +class Triangulation(Enum): + Unknown = 0 + AssumeValid = 1 + Delaunay = 2 + J1 = 3 + OrderedJ1 = 4 + +class _Triangulation: + points: Incomplete + simplices: Incomplete + coplanar: Incomplete + def __init__(self, points, simplices, coplanar) -> None: ... + +def get_unordered_j1_triangulation(points, dimension): ... +def get_ordered_j1_triangulation(points, dimension): ... + +class Direction(Enum): + left = 0 + down = 1 + up = 2 + right = 3 diff --git a/stubs/pyomo/contrib/preprocessing/__init__.pyi b/stubs/pyomo/contrib/preprocessing/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/preprocessing/plugins/__init__.pyi b/stubs/pyomo/contrib/preprocessing/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/bounds_to_vars.pyi b/stubs/pyomo/contrib/preprocessing/plugins/bounds_to_vars.pyi new file mode 100644 index 000000000..0dce56ceb --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/bounds_to_vars.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.expr.numvalue import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.repn import generate_standard_repn as generate_standard_repn + +class ConstraintToVarBoundTransform(IsomorphicTransformation): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/preprocessing/plugins/constraint_tightener.pyi b/stubs/pyomo/contrib/preprocessing/plugins/constraint_tightener.pyi new file mode 100644 index 000000000..c39704ff7 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/constraint_tightener.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.common import deprecated as deprecated +from pyomo.core import Constraint as Constraint +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class TightenConstraintFromVars(IsomorphicTransformation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/deactivate_trivial_constraints.pyi b/stubs/pyomo/contrib/preprocessing/plugins/deactivate_trivial_constraints.pyi new file mode 100644 index 000000000..04e7e168e --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/deactivate_trivial_constraints.pyi @@ -0,0 +1,20 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.expr.numvalue import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class TrivialConstraintDeactivator(IsomorphicTransformation): + CONFIG: Incomplete + def revert(self, instance) -> None: ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/detect_fixed_vars.pyi b/stubs/pyomo/contrib/preprocessing/plugins/detect_fixed_vars.pyi new file mode 100644 index 000000000..81d31576b --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/detect_fixed_vars.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.core.base.block import Block as Block +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.numvalue import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.gdp import Disjunct as Disjunct + +class FixedVarDetector(IsomorphicTransformation): + CONFIG: Incomplete + def revert(self, instance) -> None: ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/equality_propagate.pyi b/stubs/pyomo/contrib/preprocessing/plugins/equality_propagate.pyi new file mode 100644 index 000000000..cc04699a5 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/equality_propagate.pyi @@ -0,0 +1,23 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.expr.numvalue import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +class FixedVarPropagator(IsomorphicTransformation): + CONFIG: Incomplete + def revert(self, instance) -> None: ... + +class VarBoundPropagator(IsomorphicTransformation): + CONFIG: Incomplete + def revert(self, instance) -> None: ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/induced_linearity.pyi b/stubs/pyomo/contrib/preprocessing/plugins/induced_linearity.pyi new file mode 100644 index 000000000..70bd7fcd7 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/induced_linearity.pyi @@ -0,0 +1,37 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.preprocessing.util import ( + SuppressConstantObjectiveWarning as SuppressConstantObjectiveWarning, +) +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import Set as Set +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import summation as summation +from pyomo.core import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class InducedLinearity(IsomorphicTransformation): + CONFIG: Incomplete + +def determine_valid_values(block, discr_var_to_constrs_map, config): ... +def prune_possible_values(block_scope, possible_values, config): ... +def zero_if_None(val): ... +def detect_effectively_discrete_vars(block, equality_tolerance): ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/init_vars.pyi b/stubs/pyomo/contrib/preprocessing/plugins/init_vars.pyi new file mode 100644 index 000000000..31494cbce --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/init_vars.pyi @@ -0,0 +1,9 @@ +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.numvalue import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) + +class InitMidpoint(IsomorphicTransformation): ... +class InitZero(IsomorphicTransformation): ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/int_to_binary.pyi b/stubs/pyomo/contrib/preprocessing/plugins/int_to_binary.pyi new file mode 100644 index 000000000..1525c6948 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/int_to_binary.pyi @@ -0,0 +1,25 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Any as Any +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Reals as Reals +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.gdp import Disjunct as Disjunct + +logger: Incomplete + +class IntegerToBinary(IsomorphicTransformation): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/preprocessing/plugins/remove_zero_terms.pyi b/stubs/pyomo/contrib/preprocessing/plugins/remove_zero_terms.pyi new file mode 100644 index 000000000..7ea4ba762 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/remove_zero_terms.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.core import quicksum as quicksum +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.repn import generate_standard_repn as generate_standard_repn + +class RemoveZeroTerms(IsomorphicTransformation): + CONFIG: Incomplete diff --git a/stubs/pyomo/contrib/preprocessing/plugins/strip_bounds.pyi b/stubs/pyomo/contrib/preprocessing/plugins/strip_bounds.pyi new file mode 100644 index 000000000..38575caa8 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/strip_bounds.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.core.base.set_types import Reals as Reals +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.base.var import Var as Var +from pyomo.core.plugins.transform.hierarchy import ( + NonIsomorphicTransformation as NonIsomorphicTransformation, +) + +class VariableBoundStripper(NonIsomorphicTransformation): + CONFIG: Incomplete + def revert(self, instance) -> None: ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/var_aggregator.pyi b/stubs/pyomo/contrib/preprocessing/plugins/var_aggregator.pyi new file mode 100644 index 000000000..340fdfd48 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/var_aggregator.pyi @@ -0,0 +1,23 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Reals as Reals +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import VarList as VarList +from pyomo.core.expr import ExpressionReplacementVisitor as ExpressionReplacementVisitor +from pyomo.core.expr.numvalue import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +def min_if_not_None(iterable): ... +def max_if_not_None(iterable): ... + +class VariableAggregator(IsomorphicTransformation): + def update_variables(self, model) -> None: ... diff --git a/stubs/pyomo/contrib/preprocessing/plugins/zero_sum_propagator.pyi b/stubs/pyomo/contrib/preprocessing/plugins/zero_sum_propagator.pyi new file mode 100644 index 000000000..7c8340652 --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/plugins/zero_sum_propagator.pyi @@ -0,0 +1,9 @@ +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.expr.numvalue import value as value +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +class ZeroSumPropagator(IsomorphicTransformation): ... diff --git a/stubs/pyomo/contrib/preprocessing/util.pyi b/stubs/pyomo/contrib/preprocessing/util.pyi new file mode 100644 index 000000000..6d6b50d1e --- /dev/null +++ b/stubs/pyomo/contrib/preprocessing/util.pyi @@ -0,0 +1,4 @@ +from pyomo.common.log import LoggingIntercept as LoggingIntercept + +class SuppressConstantObjectiveWarning(LoggingIntercept): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/__init__.pyi b/stubs/pyomo/contrib/pynumero/__init__.pyi new file mode 100644 index 000000000..6688e2ba5 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/__init__.pyi @@ -0,0 +1,6 @@ +from .intrinsic import allclose as allclose +from .intrinsic import intersect1d as intersect1d +from .intrinsic import isin as isin +from .intrinsic import norm as norm +from .intrinsic import setdiff1d as setdiff1d +from .intrinsic import where as where diff --git a/stubs/pyomo/contrib/pynumero/algorithms/__init__.pyi b/stubs/pyomo/contrib/pynumero/algorithms/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/algorithms/solvers/__init__.pyi b/stubs/pyomo/contrib/pynumero/algorithms/solvers/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.pyi b/stubs/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.pyi new file mode 100644 index 000000000..9a979b674 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/algorithms/solvers/cyipopt_solver.pyi @@ -0,0 +1,45 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.timing import TicTocTimer as TicTocTimer +from pyomo.core.base import Block as Block +from pyomo.core.base import Objective as Objective +from pyomo.core.base import minimize as minimize +from pyomo.opt import SolverResults as SolverResults +from pyomo.opt import SolverStatus as SolverStatus +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.opt.results.solution import Solution as Solution + +pyomo_nlp: Incomplete +pyomo_grey_box: Incomplete +egb: Incomplete +cyipopt_interface: Incomplete +_: Incomplete +logger: Incomplete + +class CyIpoptSolver: + def __init__(self, problem_interface, options=None) -> None: ... + def solve(self, x0=None, tee: bool = False): ... + +class PyomoCyIpoptSolver: + CONFIG: Incomplete + config: Incomplete + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = False): ... + def license_is_valid(self): ... + def version(self): ... + def solve(self, model, **kwds): ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.pyi b/stubs/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.pyi new file mode 100644 index 000000000..eb7243955 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/algorithms/solvers/implicit_functions.pyi @@ -0,0 +1,82 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.incidence_analysis import IncidenceGraphInterface as IncidenceGraphInterface +from pyomo.contrib.incidence_analysis.scc_solver import ( + generate_strongly_connected_components as generate_strongly_connected_components, +) +from pyomo.contrib.pynumero.algorithms.solvers.cyipopt_solver import CyIpoptSolver as CyIpoptSolver +from pyomo.contrib.pynumero.algorithms.solvers.scipy_solvers import ( + FsolveNlpSolver as FsolveNlpSolver, +) +from pyomo.contrib.pynumero.algorithms.solvers.scipy_solvers import ( + NewtonNlpSolver as NewtonNlpSolver, +) +from pyomo.contrib.pynumero.algorithms.solvers.scipy_solvers import ( + SecantNewtonNlpSolver as SecantNewtonNlpSolver, +) +from pyomo.contrib.pynumero.interfaces.cyipopt_interface import CyIpoptNLP as CyIpoptNLP +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.util.calc_var_value import ( + calculate_variable_from_constraint as calculate_variable_from_constraint, +) +from pyomo.util.subsystems import TemporarySubsystemManager as TemporarySubsystemManager +from pyomo.util.subsystems import create_subsystem_block as create_subsystem_block +from pyomo.util.subsystems import generate_subsystem_blocks as generate_subsystem_blocks + +pyomo_nlp: Incomplete +nlp_proj: Incomplete + +class NlpSolverBase: + def __init__(self, nlp, options=None, timer=None) -> None: ... + def solve(self, **kwds) -> None: ... + +class CyIpoptSolverWrapper(NlpSolverBase): + def __init__(self, nlp, options=None, timer=None) -> None: ... + def solve(self, **kwds): ... + +class ScipySolverWrapper(NlpSolverBase): + def __init__(self, nlp, timer=None, options=None) -> None: ... + def solve(self, x0=None): ... + +class PyomoImplicitFunctionBase: + def __init__(self, variables, constraints, parameters) -> None: ... + def get_variables(self): ... + def get_constraints(self): ... + def get_parameters(self): ... + def get_block(self): ... + def set_parameters(self, values) -> None: ... + def evaluate_outputs(self) -> None: ... + def update_pyomo_model(self) -> None: ... + +class ImplicitFunctionSolver(PyomoImplicitFunctionBase): + def __init__( + self, variables, constraints, parameters, solver_class=None, solver_options=None, timer=None + ) -> None: ... + def set_parameters(self, values, **kwds): ... + def evaluate_outputs(self): ... + def update_pyomo_model(self) -> None: ... + +class DecomposedImplicitFunctionBase(PyomoImplicitFunctionBase): + def __init__( + self, + variables, + constraints, + parameters, + solver_class=None, + solver_options=None, + timer=None, + use_calc_var: bool = True, + ) -> None: ... + def n_subsystems(self): ... + def partition_system(self, variables, constraints) -> None: ... + def set_parameters(self, values) -> None: ... + def evaluate_outputs(self): ... + def update_pyomo_model(self) -> None: ... + +class SccImplicitFunctionSolver(DecomposedImplicitFunctionBase): + def partition_system(self, variables, constraints): ... diff --git a/stubs/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.pyi b/stubs/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.pyi new file mode 100644 index 000000000..c4b0aa496 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/algorithms/solvers/pyomo_ext_cyipopt.pyi @@ -0,0 +1,46 @@ +import abc + +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.pynumero.interfaces.cyipopt_interface import ( + CyIpoptProblemInterface as CyIpoptProblemInterface, +) +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP +from pyomo.contrib.pynumero.sparse.block_vector import BlockVector as BlockVector +from pyomo.core.base.var import VarData as VarData +from pyomo.environ import Constraint as Constraint +from pyomo.environ import Var as Var +from pyomo.environ import value as value + +class ExternalInputOutputModel(metaclass=abc.ABCMeta): + def __init__(self) -> None: ... + @abc.abstractmethod + def set_inputs(self, input_values): ... + @abc.abstractmethod + def evaluate_outputs(self): ... + @abc.abstractmethod + def evaluate_derivatives(self): ... + +class PyomoExternalCyIpoptProblem(CyIpoptProblemInterface): + def __init__( + self, + pyomo_model, + ex_input_output_model, + inputs, + outputs, + outputs_eqn_scaling=None, + nl_file_options=None, + ) -> None: ... + def load_x_into_pyomo(self, primals) -> None: ... + def x_init(self): ... + def x_lb(self): ... + def x_ub(self): ... + def g_lb(self): ... + def g_ub(self): ... + def scaling_factors(self): ... + def objective(self, primals): ... + def gradient(self, primals): ... + def constraints(self, primals): ... + def jacobianstructure(self): ... + def jacobian(self, primals): ... + def hessianstructure(self): ... + def hessian(self, x, y, obj_factor) -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.pyi b/stubs/pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.pyi new file mode 100644 index 000000000..f836b5ab6 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/algorithms/solvers/scipy_solvers.pyi @@ -0,0 +1,77 @@ +import types +from collections import namedtuple as namedtuple + +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.dependencies import scipy_available as scipy_available +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.pynumero.algorithms.solvers.square_solver_base import ( + DenseSquareNlpSolver as DenseSquareNlpSolver, +) +from pyomo.contrib.pynumero.algorithms.solvers.square_solver_base import ( + ScalarDenseSquareNlpSolver as ScalarDenseSquareNlpSolver, +) +from pyomo.core.base.objective import Objective as Objective +from pyomo.opt import SolverResults as SolverResults +from pyomo.opt import TerminationCondition as TerminationCondition + +pyomo_nlp: Incomplete +_: Incomplete + +class FsolveNlpSolver(DenseSquareNlpSolver): + OPTIONS: Incomplete + def solve(self, x0=None): ... + +class RootNlpSolver(DenseSquareNlpSolver): + OPTIONS: Incomplete + def solve(self, x0=None): ... + +class NewtonNlpSolver(ScalarDenseSquareNlpSolver): + OPTIONS: Incomplete + def solve(self, x0=None): ... + +class SecantNewtonNlpSolver(NewtonNlpSolver): + OPTIONS: Incomplete + converged_with_secant: Incomplete + def __init__(self, nlp, timer=None, options=None) -> None: ... + def solve(self, x0=None): ... + +class PyomoScipySolver: + options: Incomplete + def __init__(self, options=None) -> None: ... + def available(self, exception_flag: bool = False): ... + def license_is_valid(self): ... + def version(self): ... + def set_options(self, options) -> None: ... + def solve(self, model, timer=None, tee: bool = False): ... + def get_nlp(self): ... + def create_nlp_solver(self, **kwds) -> None: ... + def get_pyomo_results(self, model, scipy_results) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +class PyomoFsolveSolver(PyomoScipySolver): + def create_nlp_solver(self, **kwds): ... + def get_pyomo_results(self, model, scipy_results): ... + +class PyomoRootSolver(PyomoScipySolver): + def create_nlp_solver(self, **kwds): ... + def get_pyomo_results(self, model, scipy_results): ... + +class PyomoNewtonSolver(PyomoScipySolver): + def create_nlp_solver(self, **kwds): ... + def get_pyomo_results(self, model, scipy_results): ... + +class PyomoSecantNewtonSolver(PyomoNewtonSolver): + def converged_with_secant(self): ... + def create_nlp_solver(self, **kwds): ... diff --git a/stubs/pyomo/contrib/pynumero/algorithms/solvers/square_solver_base.pyi b/stubs/pyomo/contrib/pynumero/algorithms/solvers/square_solver_base.pyi new file mode 100644 index 000000000..500ee2ee9 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/algorithms/solvers/square_solver_base.pyi @@ -0,0 +1,20 @@ +from collections import namedtuple as namedtuple + +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.util.subsystems import create_subsystem_block as create_subsystem_block + +class SquareNlpSolverBase: + OPTIONS: Incomplete + options: Incomplete + def __init__(self, nlp, timer=None, options=None) -> None: ... + def solve(self, x0=None) -> None: ... + def evaluate_function(self, x0): ... + def evaluate_jacobian(self, x0): ... + +class DenseSquareNlpSolver(SquareNlpSolverBase): + def evaluate_jacobian(self, x0): ... + +class ScalarDenseSquareNlpSolver(DenseSquareNlpSolver): + def __init__(self, nlp, timer=None, options=None) -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/asl.pyi b/stubs/pyomo/contrib/pynumero/asl.pyi new file mode 100644 index 000000000..53c7281a0 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/asl.pyi @@ -0,0 +1,37 @@ +from _typeshed import Incomplete +from pyomo.common.fileutils import find_library as find_library +from pyomo.contrib.pynumero.exceptions import PyNumeroEvaluationError as PyNumeroEvaluationError + +logger: Incomplete +CURRENT_INTERFACE_VERSION: int + +class _NotSet: ... + +class AmplInterface: + libname: Incomplete + ASLib: Incomplete + interface_version: Incomplete + asl_date: Incomplete + @classmethod + def available(cls): ... + def __init__(self, filename=None, nl_buffer=None) -> None: ... + def __del__(self) -> None: ... + def get_n_vars(self): ... + def get_n_constraints(self): ... + def get_nnz_jac_g(self): ... + def get_nnz_hessian_lag(self): ... + def get_bounds_info(self, xl, xu, gl, gu) -> None: ... + def get_x_lower_bounds(self, invec) -> None: ... + def get_x_upper_bounds(self, invec) -> None: ... + def get_g_lower_bounds(self, invec) -> None: ... + def get_g_upper_bounds(self, invec) -> None: ... + def get_init_x(self, invec) -> None: ... + def get_init_multipliers(self, invec) -> None: ... + def eval_f(self, x): ... + def eval_deriv_f(self, x, df) -> None: ... + def struct_jac_g(self, irow, jcol) -> None: ... + def struct_hes_lag(self, irow, jcol) -> None: ... + def eval_jac_g(self, x, jac_g_values) -> None: ... + def eval_g(self, x, g) -> None: ... + def eval_hes_lag(self, x, lam, hes_lag, obj_factor: float = 1.0) -> None: ... + def finalize_solution(self, ampl_solve_status_num, msg, x, lam) -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/build.pyi b/stubs/pyomo/contrib/pynumero/build.pyi new file mode 100644 index 000000000..b703e6e5c --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/build.pyi @@ -0,0 +1,6 @@ +from pyomo.common.cmake_builder import build_cmake_project as build_cmake_project + +def build_pynumero(user_args=[], parallel=None): ... + +class PyNumeroBuilder: + def __call__(self, parallel): ... diff --git a/stubs/pyomo/contrib/pynumero/dependencies.pyi b/stubs/pyomo/contrib/pynumero/dependencies.pyi new file mode 100644 index 000000000..3dc179abb --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/dependencies.pyi @@ -0,0 +1,7 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.dependencies import scipy_available as scipy_available + +numpy: Incomplete +numpy_available: Incomplete diff --git a/stubs/pyomo/contrib/pynumero/examples/__init__.pyi b/stubs/pyomo/contrib/pynumero/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/examples/callback/__init__.pyi b/stubs/pyomo/contrib/pynumero/examples/callback/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.pyi b/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.pyi new file mode 100644 index 000000000..d4c67f1ab --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_callback.pyi @@ -0,0 +1,15 @@ +def iteration_callback( + nlp, + alg_mod, + iter_count, + obj_value, + inf_pr, + inf_du, + mu, + d_norm, + regularization_size, + alpha_du, + alpha_pr, + ls_trials, +) -> None: ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.pyi b/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.pyi new file mode 100644 index 000000000..469113051 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_callback_halt.pyi @@ -0,0 +1,15 @@ +def iteration_callback( + nlp, + alg_mod, + iter_count, + obj_value, + inf_pr, + inf_du, + mu, + d_norm, + regularization_size, + alpha_du, + alpha_pr, + ls_trials, +): ... +def main(): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.pyi b/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.pyi new file mode 100644 index 000000000..410d9baa3 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/callback/cyipopt_functor_callback.pyi @@ -0,0 +1,20 @@ +class ResidualsTableCallback: + def __init__(self) -> None: ... + def __call__( + self, + nlp, + alg_mod, + iter_count, + obj_value, + inf_pr, + inf_du, + mu, + d_norm, + regularization_size, + alpha_du, + alpha_pr, + ls_trials, + ) -> None: ... + def get_residual_dataframe(self): ... + +def main(): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/callback/reactor_design.pyi b/stubs/pyomo/contrib/pynumero/examples/callback/reactor_design.pyi new file mode 100644 index 000000000..69e59b2e0 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/callback/reactor_design.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete +from pyomo.core import * + +model: Incomplete +k1: Incomplete +k2: Incomplete +k3: Incomplete +caf: float diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/__init__.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/external_with_objective.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/external_with_objective.pyi new file mode 100644 index 000000000..13ca053d3 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/external_with_objective.pyi @@ -0,0 +1,32 @@ +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxBlock as ExternalGreyBoxBlock, +) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxModel as ExternalGreyBoxModel, +) + +class Unconstrained(ExternalGreyBoxModel): + def input_names(self): ... + def set_input_values(self, input_values) -> None: ... + def has_objective(self): ... + def evaluate_objective(self): ... + def evaluate_grad_objective(self): ... + +class Constrained(ExternalGreyBoxModel): + def input_names(self): ... + def set_input_values(self, input_values) -> None: ... + def has_objective(self): ... + def evaluate_objective(self): ... + def evaluate_grad_objective(self): ... + def equality_constraint_names(self): ... + def evaluate_equality_constraints(self): ... + def evaluate_jacobian_equality_constraints(self): ... + +class ConstrainedWithHessian(Constrained): + def evaluate_hessian_objective(self): ... + def set_equality_constraint_multipliers(self, eq_con_multiplier_values) -> None: ... + def evaluate_hessian_equality_constraints(self): ... + +def solve_unconstrained(): ... +def solve_constrained(): ... +def solve_constrained_with_hessian(): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.pyi new file mode 100644 index 000000000..0c68ad346 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/generate_data.pyi @@ -0,0 +1,2 @@ +def generate_data(N, UA_mean, UA_std, seed: int = 42): ... +def generate_data_external(N, UA_mean, UA_std, seed: int = 42): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.pyi new file mode 100644 index 000000000..41c54915f --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/models.pyi @@ -0,0 +1,24 @@ +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxBlock as ExternalGreyBoxBlock, +) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxModel as ExternalGreyBoxModel, +) + +def build_single_point_model_pyomo_only(m) -> None: ... +def build_single_point_model_external(m) -> None: ... + +class UAModelExternal(ExternalGreyBoxModel): + def __init__(self) -> None: ... + def n_inputs(self): ... + def n_equality_constraints(self): ... + def n_outputs(self): ... + def input_names(self): ... + def equality_constraint_names(self): ... + def output_names(self): ... + def finalize_block_construction(self, pyomo_block) -> None: ... + def set_input_values(self, input_values) -> None: ... + def set_equality_constraint_multipliers(self, eq_con_multiplier_values) -> None: ... + def evaluate_equality_constraints(self): ... + def evaluate_jacobian_equality_constraints(self): ... + def evaluate_hessian_equality_constraints(self): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.pyi new file mode 100644 index 000000000..7699f2d5d --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/param_est/perform_estimation.pyi @@ -0,0 +1,2 @@ +def perform_estimation_pyomo_only(data_fname, solver_trace: bool = False): ... +def perform_estimation_external(data_fname, solver_trace: bool = False): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.pyi new file mode 100644 index 000000000..efd309eae --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_outputs.pyi @@ -0,0 +1,8 @@ +from pyomo.contrib.pynumero.examples.external_grey_box.react_example.reactor_model_outputs import ( + ReactorConcentrationsOutputModel as ReactorConcentrationsOutputModel, +) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxBlock as ExternalGreyBoxBlock, +) + +def maximize_cb_outputs(show_solver_log: bool = False): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_ratio_residuals.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_ratio_residuals.pyi new file mode 100644 index 000000000..88d772ca0 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/maximize_cb_ratio_residuals.pyi @@ -0,0 +1,35 @@ +from pyomo.contrib.pynumero.examples.external_grey_box.react_example.reactor_model_residuals import ( + ReactorModel as ReactorModel, +) +from pyomo.contrib.pynumero.examples.external_grey_box.react_example.reactor_model_residuals import ( + ReactorModelNoOutputs as ReactorModelNoOutputs, +) +from pyomo.contrib.pynumero.examples.external_grey_box.react_example.reactor_model_residuals import ( + ReactorModelScaled as ReactorModelScaled, +) +from pyomo.contrib.pynumero.examples.external_grey_box.react_example.reactor_model_residuals import ( + ReactorModelWithHessian as ReactorModelWithHessian, +) +from pyomo.contrib.pynumero.examples.external_grey_box.react_example.reactor_model_residuals import ( + create_pyomo_reactor_model as create_pyomo_reactor_model, +) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxBlock as ExternalGreyBoxBlock, +) + +def maximize_cb_ratio_residuals_with_output( + show_solver_log: bool = False, additional_options={} +): ... +def maximize_cb_ratio_residuals_with_hessian_with_output( + show_solver_log: bool = False, additional_options={} +): ... +def maximize_cb_ratio_residuals_with_hessian_with_output_pyomo( + show_solver_log: bool = False, additional_options={} +): ... +def maximize_cb_ratio_residuals_with_output_scaling( + show_solver_log: bool = False, additional_options={} +): ... +def maximize_cb_ratio_residuals_with_obj(show_solver_log: bool = False, additional_options={}): ... +def maximize_cb_ratio_residuals_with_pyomo_variables( + show_solver_log: bool = False, additional_options={} +): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.pyi new file mode 100644 index 000000000..81223e517 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_outputs.pyi @@ -0,0 +1,13 @@ +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxModel as ExternalGreyBoxModel, +) + +def reactor_outlet_concentrations(sv, caf, k1, k2, k3): ... + +class ReactorConcentrationsOutputModel(ExternalGreyBoxModel): + def input_names(self): ... + def output_names(self): ... + def set_input_values(self, input_values) -> None: ... + def finalize_block_construction(self, pyomo_block) -> None: ... + def evaluate_outputs(self): ... + def evaluate_jacobian_outputs(self): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.pyi b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.pyi new file mode 100644 index 000000000..b69c97920 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/external_grey_box/react_example/reactor_model_residuals.pyi @@ -0,0 +1,48 @@ +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxModel as ExternalGreyBoxModel, +) + +def create_pyomo_reactor_model(): ... + +class ReactorModel(ExternalGreyBoxModel): + def __init__(self, use_exact_derivatives: bool = True) -> None: ... + def input_names(self): ... + def equality_constraint_names(self): ... + def output_names(self): ... + def finalize_block_construction(self, pyomo_block) -> None: ... + def set_input_values(self, input_values) -> None: ... + def evaluate_equality_constraints(self): ... + def evaluate_outputs(self): ... + def evaluate_jacobian_equality_constraints(self): ... + def evaluate_jacobian_outputs(self): ... + +class ReactorModelWithHessian(ReactorModel): + def __init__(self) -> None: ... + def set_equality_constraint_multipliers(self, eq_con_multiplier_values) -> None: ... + def set_output_constraint_multipliers(self, output_con_multiplier_values) -> None: ... + def evaluate_hessian_equality_constraints(self): ... + def evaluate_hessian_outputs(self): ... + +class ReactorModelNoOutputs(ExternalGreyBoxModel): + def input_names(self): ... + def equality_constraint_names(self): ... + def output_names(self): ... + def finalize_block_construction(self, pyomo_block) -> None: ... + def set_input_values(self, input_values) -> None: ... + def evaluate_equality_constraints(self): ... + def evaluate_outputs(self) -> None: ... + def evaluate_jacobian_equality_constraints(self): ... + def evaluate_jacobian_outputs(self) -> None: ... + +class ReactorModelScaled(ExternalGreyBoxModel): + def input_names(self): ... + def equality_constraint_names(self): ... + def output_names(self): ... + def finalize_block_construction(self, pyomo_block) -> None: ... + def get_equality_constraint_scaling_factors(self): ... + def get_output_constraint_scaling_factors(self): ... + def set_input_values(self, input_values) -> None: ... + def evaluate_equality_constraints(self): ... + def evaluate_outputs(self): ... + def evaluate_jacobian_equality_constraints(self): ... + def evaluate_jacobian_outputs(self): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/feasibility.pyi b/stubs/pyomo/contrib/pynumero/examples/feasibility.pyi new file mode 100644 index 000000000..fee0341c7 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/feasibility.pyi @@ -0,0 +1,9 @@ +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP +from pyomo.contrib.pynumero.interfaces.utils import build_bounds_mask as build_bounds_mask +from pyomo.contrib.pynumero.interfaces.utils import ( + build_compression_matrix as build_compression_matrix, +) +from pyomo.contrib.pynumero.interfaces.utils import full_to_compressed as full_to_compressed + +def create_basic_model(): ... +def main(): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/mumps_example.pyi b/stubs/pyomo/contrib/pynumero/examples/mumps_example.pyi new file mode 100644 index 000000000..ba0207876 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/mumps_example.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.pynumero.linalg.mumps_interface import ( + MumpsCentralizedAssembledLinearSolver as MumpsCentralizedAssembledLinearSolver, +) + +def main() -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/examples/nlp_interface.pyi b/stubs/pyomo/contrib/pynumero/examples/nlp_interface.pyi new file mode 100644 index 000000000..8a5693886 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/nlp_interface.pyi @@ -0,0 +1,4 @@ +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP + +def create_model(): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/examples/nlp_interface_2.pyi b/stubs/pyomo/contrib/pynumero/examples/nlp_interface_2.pyi new file mode 100644 index 000000000..50d0be32d --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/nlp_interface_2.pyi @@ -0,0 +1,5 @@ +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix + +def create_problem(begin, end): ... +def main(show_plot: bool = True) -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/examples/parallel_matvec.pyi b/stubs/pyomo/contrib/pynumero/examples/parallel_matvec.pyi new file mode 100644 index 000000000..2c7f22cb6 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/parallel_matvec.pyi @@ -0,0 +1,5 @@ +from pyomo.common.dependencies import mpi4py as mpi4py +from pyomo.contrib.pynumero.sparse.mpi_block_matrix import MPIBlockMatrix as MPIBlockMatrix +from pyomo.contrib.pynumero.sparse.mpi_block_vector import MPIBlockVector as MPIBlockVector + +def main(): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/parallel_vector_ops.pyi b/stubs/pyomo/contrib/pynumero/examples/parallel_vector_ops.pyi new file mode 100644 index 000000000..31422e5da --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/parallel_vector_ops.pyi @@ -0,0 +1,4 @@ +from pyomo.common.dependencies import mpi4py as mpi4py +from pyomo.contrib.pynumero.sparse.mpi_block_vector import MPIBlockVector as MPIBlockVector + +def main(): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/sensitivity.pyi b/stubs/pyomo/contrib/pynumero/examples/sensitivity.pyi new file mode 100644 index 000000000..d132e08ad --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/sensitivity.pyi @@ -0,0 +1,7 @@ +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector + +def create_model(eta1, eta2): ... +def compute_init_lam(nlp, x=None, lam_max: float = 1000.0): ... +def main(): ... diff --git a/stubs/pyomo/contrib/pynumero/examples/sqp.pyi b/stubs/pyomo/contrib/pynumero/examples/sqp.pyi new file mode 100644 index 000000000..d2567dcff --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/examples/sqp.pyi @@ -0,0 +1,21 @@ +from pyomo import dae as dae +from pyomo.common.timing import TicTocTimer as TicTocTimer +from pyomo.contrib.pynumero.interfaces.nlp import NLP as NLP +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP +from pyomo.contrib.pynumero.linalg.base import LinearSolverInterface as LinearSolverInterface +from pyomo.contrib.pynumero.linalg.base import LinearSolverStatus as LinearSolverStatus +from pyomo.contrib.pynumero.linalg.ma27_interface import MA27 as MA27 +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector +from scipy.sparse import tril as tril + +def build_burgers_model(nfe_x: int = 100, nfe_t: int = 200, start_t: int = 0, end_t: int = 1): ... +def sqp( + nlp: NLP, + linear_solver: LinearSolverInterface, + max_iter: int = 100, + tol: float = 1e-08, + output: bool = True, +): ... +def load_solution(m: None, nlp: PyomoNLP): ... +def main(linear_solver, nfe_x: int = 100, nfe_t: int = 200): ... diff --git a/stubs/pyomo/contrib/pynumero/exceptions.pyi b/stubs/pyomo/contrib/pynumero/exceptions.pyi new file mode 100644 index 000000000..2ac2d0aad --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/exceptions.pyi @@ -0,0 +1 @@ +class PyNumeroEvaluationError(ArithmeticError): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/__init__.pyi b/stubs/pyomo/contrib/pynumero/interfaces/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/pynumero/interfaces/ampl_nlp.pyi b/stubs/pyomo/contrib/pynumero/interfaces/ampl_nlp.pyi new file mode 100644 index 000000000..a33b782d3 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/ampl_nlp.pyi @@ -0,0 +1,62 @@ +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.contrib.pynumero.interfaces.nlp import ExtendedNLP as ExtendedNLP + +class AslNLP(ExtendedNLP): + def __init__(self, nl_file) -> None: ... + def n_primals(self): ... + def n_constraints(self): ... + def n_eq_constraints(self): ... + def n_ineq_constraints(self): ... + def nnz_jacobian(self): ... + def nnz_jacobian_eq(self): ... + def nnz_jacobian_ineq(self): ... + def nnz_hessian_lag(self): ... + def primals_lb(self): ... + def primals_ub(self): ... + def constraints_lb(self): ... + def constraints_ub(self): ... + def ineq_lb(self): ... + def ineq_ub(self): ... + def init_primals(self): ... + def init_duals(self): ... + def init_duals_eq(self): ... + def init_duals_ineq(self): ... + def create_new_vector(self, vector_type): ... + def set_primals(self, primals) -> None: ... + def get_primals(self): ... + def set_duals(self, duals) -> None: ... + def get_duals(self): ... + def set_obj_factor(self, obj_factor) -> None: ... + def get_obj_factor(self): ... + def set_duals_eq(self, duals_eq) -> None: ... + def get_duals_eq(self): ... + def set_duals_ineq(self, duals_ineq) -> None: ... + def get_duals_ineq(self): ... + def get_obj_scaling(self) -> None: ... + def get_primals_scaling(self) -> None: ... + def get_constraints_scaling(self) -> None: ... + def get_eq_constraints_scaling(self): ... + def get_ineq_constraints_scaling(self): ... + def evaluate_objective(self): ... + def evaluate_grad_objective(self, out=None): ... + def evaluate_constraints(self, out=None): ... + def evaluate_eq_constraints(self, out=None): ... + def evaluate_ineq_constraints(self, out=None): ... + def evaluate_jacobian(self, out=None): ... + def evaluate_jacobian_eq(self, out=None): ... + def evaluate_jacobian_ineq(self, out=None): ... + def evaluate_hessian_lag(self, out=None): ... + def report_solver_status(self, status_code, status_message) -> None: ... + +class AmplNLP(AslNLP): + def __init__(self, nl_file, row_filename=None, col_filename=None) -> None: ... + def primals_names(self): ... + def variable_names(self): ... + def constraint_names(self): ... + def eq_constraint_names(self): ... + def ineq_constraint_names(self): ... + def variable_idx(self, var_name): ... + def primal_idx(self, var_name): ... + def constraint_idx(self, con_name): ... + def eq_constraint_idx(self, con_name): ... + def ineq_constraint_idx(self, con_name): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/cyipopt_interface.pyi b/stubs/pyomo/contrib/pynumero/interfaces/cyipopt_interface.pyi new file mode 100644 index 000000000..d0760d228 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/cyipopt_interface.pyi @@ -0,0 +1,84 @@ +import abc + +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.contrib.pynumero.exceptions import PyNumeroEvaluationError as PyNumeroEvaluationError + +cyipopt: Incomplete +cyipopt_available: Incomplete +cyipopt_Problem: Incomplete + +class CyIpoptProblemInterface(cyipopt_Problem, metaclass=abc.ABCMeta): + def __init__(self) -> None: ... + def solve(self, x, lagrange=None, zl=None, zu=None): ... + @abc.abstractmethod + def x_init(self): ... + @abc.abstractmethod + def x_lb(self): ... + @abc.abstractmethod + def x_ub(self): ... + @abc.abstractmethod + def g_lb(self): ... + @abc.abstractmethod + def g_ub(self): ... + @abc.abstractmethod + def scaling_factors(self): ... + @abc.abstractmethod + def objective(self, x): ... + @abc.abstractmethod + def gradient(self, x): ... + @abc.abstractmethod + def constraints(self, x): ... + @abc.abstractmethod + def jacobianstructure(self): ... + @abc.abstractmethod + def jacobian(self, x): ... + @abc.abstractmethod + def hessianstructure(self): ... + @abc.abstractmethod + def hessian(self, x, y, obj_factor): ... + def intermediate( + self, + alg_mod, + iter_count, + obj_value, + inf_pr, + inf_du, + mu, + d_norm, + regularization_size, + alpha_du, + alpha_pr, + ls_trials, + ) -> None: ... + +class CyIpoptNLP(CyIpoptProblemInterface): + def __init__(self, nlp, intermediate_callback=None, halt_on_evaluation_error=None) -> None: ... + def x_init(self): ... + def x_lb(self): ... + def x_ub(self): ... + def g_lb(self): ... + def g_ub(self): ... + def scaling_factors(self): ... + def objective(self, x): ... + def gradient(self, x): ... + def constraints(self, x): ... + def jacobianstructure(self): ... + def jacobian(self, x): ... + def hessianstructure(self): ... + def hessian(self, x, y, obj_factor): ... + def intermediate( + self, + alg_mod, + iter_count, + obj_value, + inf_pr, + inf_du, + mu, + d_norm, + regularization_size, + alpha_du, + alpha_pr, + ls_trials, + ): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/external_grey_box.pyi b/stubs/pyomo/contrib/pynumero/interfaces/external_grey_box.pyi new file mode 100644 index 000000000..22d3bb9a5 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/external_grey_box.pyi @@ -0,0 +1,62 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Set as Set +from pyomo.core.base import Var as Var +from pyomo.core.base import value as value +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.block import declare_custom_block as declare_custom_block +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.base.reference import Reference as Reference +from pyomo.core.base.set import UnindexedComponent_set as UnindexedComponent_set +from scipy.sparse import coo_matrix as coo_matrix + +from ..sparse.block_matrix import BlockMatrix as BlockMatrix + +logger: Incomplete + +class ExternalGreyBoxModel: + def n_inputs(self): ... + def n_equality_constraints(self): ... + def n_outputs(self): ... + def input_names(self) -> None: ... + def equality_constraint_names(self): ... + def output_names(self): ... + def finalize_block_construction(self, pyomo_block) -> None: ... + def set_input_values(self, input_values) -> None: ... + def set_equality_constraint_multipliers(self, eq_con_multiplier_values) -> None: ... + def set_output_constraint_multipliers(self, output_con_multiplier_values) -> None: ... + def get_equality_constraint_scaling_factors(self) -> None: ... + def get_output_constraint_scaling_factors(self) -> None: ... + def evaluate_equality_constraints(self) -> None: ... + def evaluate_outputs(self) -> None: ... + def evaluate_jacobian_equality_constraints(self) -> None: ... + def evaluate_jacobian_outputs(self) -> None: ... + def has_objective(self): ... + def evaluate_objective(self) -> float: ... + def evaluate_grad_objective(self, out=None) -> None: ... + +class ExternalGreyBoxBlockData(BlockData): + inputs: Incomplete + outputs: Incomplete + def set_external_model(self, external_grey_box_model, inputs=None, outputs=None) -> None: ... + def get_external_model(self): ... + +class ExternalGreyBoxBlock(Block): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwds) -> None: ... + def construct(self, data=None) -> None: ... + +class ScalarExternalGreyBoxBlock(ExternalGreyBoxBlockData, ExternalGreyBoxBlock): + def __init__(self, *args, **kwds) -> None: ... + display: Incomplete + +class SimpleExternalGreyBoxBlock(metaclass=RenamedClass): + __renamed__new_class__ = ScalarExternalGreyBoxBlock + __renamed__version__: str + +class IndexedExternalGreyBoxBlock(ExternalGreyBoxBlock): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/external_pyomo_model.pyi b/stubs/pyomo/contrib/pynumero/interfaces/external_pyomo_model.pyi new file mode 100644 index 000000000..02cb6e213 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/external_pyomo_model.pyi @@ -0,0 +1,52 @@ +from _typeshed import Incomplete +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.pynumero.algorithms.solvers.implicit_functions import ( + SccImplicitFunctionSolver as SccImplicitFunctionSolver, +) +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxModel as ExternalGreyBoxModel, +) +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.util.subsystems import create_subsystem_block as create_subsystem_block + +def get_hessian_of_constraint(constraint, wrt1=None, wrt2=None, nlp=None): ... + +class ExternalPyomoModel(ExternalGreyBoxModel): + input_vars: Incomplete + external_vars: Incomplete + residual_cons: Incomplete + external_cons: Incomplete + residual_con_multipliers: Incomplete + residual_scaling_factors: Incomplete + def __init__( + self, + input_vars, + external_vars, + residual_cons, + external_cons, + solver_class=None, + solver_options=None, + timer=None, + ) -> None: ... + def n_inputs(self): ... + def n_equality_constraints(self): ... + def input_names(self): ... + def equality_constraint_names(self): ... + def set_input_values(self, input_values) -> None: ... + def set_equality_constraint_multipliers(self, eq_con_multipliers) -> None: ... + def set_external_constraint_multipliers(self, eq_con_multipliers) -> None: ... + def calculate_external_constraint_multipliers(self, resid_multipliers): ... + def get_full_space_lagrangian_hessians(self): ... + def calculate_reduced_hessian_lagrangian(self, hlxx, hlxy, hlyy): ... + def evaluate_equality_constraints(self): ... + def evaluate_jacobian_equality_constraints(self): ... + def evaluate_jacobian_external_variables(self): ... + def evaluate_hessian_external_variables(self): ... + def evaluate_hessians_of_residuals(self): ... + def evaluate_hessian_equality_constraints(self): ... + def set_equality_constraint_scaling_factors(self, scaling_factors) -> None: ... + def get_equality_constraint_scaling_factors(self): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/nlp.pyi b/stubs/pyomo/contrib/pynumero/interfaces/nlp.pyi new file mode 100644 index 000000000..a43d228c1 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/nlp.pyi @@ -0,0 +1,99 @@ +import abc + +class NLP(metaclass=abc.ABCMeta): + def __init__(self) -> None: ... + @abc.abstractmethod + def n_primals(self): ... + def primals_names(self): ... + @abc.abstractmethod + def n_constraints(self): ... + def constraint_names(self): ... + @abc.abstractmethod + def nnz_jacobian(self): ... + @abc.abstractmethod + def nnz_hessian_lag(self): ... + @abc.abstractmethod + def primals_lb(self): ... + @abc.abstractmethod + def primals_ub(self): ... + @abc.abstractmethod + def constraints_lb(self): ... + @abc.abstractmethod + def constraints_ub(self): ... + @abc.abstractmethod + def init_primals(self): ... + @abc.abstractmethod + def init_duals(self): ... + @abc.abstractmethod + def create_new_vector(self, vector_type): ... + @abc.abstractmethod + def set_primals(self, primals): ... + @abc.abstractmethod + def get_primals(self): ... + @abc.abstractmethod + def set_duals(self, duals): ... + @abc.abstractmethod + def get_duals(self): ... + @abc.abstractmethod + def set_obj_factor(self, obj_factor): ... + @abc.abstractmethod + def get_obj_factor(self): ... + @abc.abstractmethod + def get_obj_scaling(self): ... + @abc.abstractmethod + def get_primals_scaling(self): ... + @abc.abstractmethod + def get_constraints_scaling(self): ... + @abc.abstractmethod + def evaluate_objective(self): ... + @abc.abstractmethod + def evaluate_grad_objective(self, out=None): ... + @abc.abstractmethod + def evaluate_constraints(self, out=None): ... + @abc.abstractmethod + def evaluate_jacobian(self, out=None): ... + @abc.abstractmethod + def evaluate_hessian_lag(self, out=None): ... + @abc.abstractmethod + def report_solver_status(self, status_code, status_message): ... + +class ExtendedNLP(NLP, metaclass=abc.ABCMeta): + def __init__(self) -> None: ... + @abc.abstractmethod + def n_eq_constraints(self): ... + @abc.abstractmethod + def n_ineq_constraints(self): ... + @abc.abstractmethod + def nnz_jacobian_eq(self): ... + @abc.abstractmethod + def nnz_jacobian_ineq(self): ... + @abc.abstractmethod + def ineq_lb(self): ... + @abc.abstractmethod + def ineq_ub(self): ... + @abc.abstractmethod + def init_duals_eq(self): ... + @abc.abstractmethod + def init_duals_ineq(self): ... + @abc.abstractmethod + def create_new_vector(self, vector_type): ... + @abc.abstractmethod + def set_duals_eq(self, duals_eq): ... + @abc.abstractmethod + def get_duals_eq(self): ... + @abc.abstractmethod + def set_duals_ineq(self, duals_ineq): ... + @abc.abstractmethod + def get_duals_ineq(self): ... + @abc.abstractmethod + def get_eq_constraints_scaling(self): ... + @abc.abstractmethod + def get_ineq_constraints_scaling(self): ... + @abc.abstractmethod + def evaluate_eq_constraints(self, out=None): ... + @abc.abstractmethod + def evaluate_ineq_constraints(self, out=None): ... + @abc.abstractmethod + def evaluate_jacobian_eq(self, out=None): ... + @abc.abstractmethod + def evaluate_jacobian_ineq(self, out=None): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/nlp_projections.pyi b/stubs/pyomo/contrib/pynumero/interfaces/nlp_projections.pyi new file mode 100644 index 000000000..79d33b381 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/nlp_projections.pyi @@ -0,0 +1,69 @@ +from pyomo.contrib.pynumero.interfaces.nlp import NLP as NLP +from pyomo.contrib.pynumero.interfaces.nlp import ExtendedNLP as ExtendedNLP + +class _BaseNLPDelegator(NLP): + def __init__(self, original_nlp) -> None: ... + def n_primals(self): ... + def primals_names(self): ... + def n_constraints(self): ... + def constraint_names(self): ... + def nnz_jacobian(self): ... + def nnz_hessian_lag(self): ... + def primals_lb(self): ... + def primals_ub(self): ... + def constraints_lb(self): ... + def constraints_ub(self): ... + def init_primals(self): ... + def init_duals(self): ... + def create_new_vector(self, vector_type): ... + def set_primals(self, primals) -> None: ... + def get_primals(self): ... + def set_duals(self, duals) -> None: ... + def get_duals(self): ... + def set_obj_factor(self, obj_factor) -> None: ... + def get_obj_factor(self): ... + def get_obj_scaling(self): ... + def get_primals_scaling(self): ... + def get_constraints_scaling(self): ... + def evaluate_objective(self): ... + def evaluate_grad_objective(self, out=None): ... + def evaluate_constraints(self, out=None): ... + def evaluate_jacobian(self, out=None): ... + def evaluate_hessian_lag(self, out=None): ... + def report_solver_status(self, status_code, status_message) -> None: ... + +class _ExtendedNLPDelegator(_BaseNLPDelegator): + def __init__(self, original_nlp) -> None: ... + def n_eq_constraints(self): ... + def n_ineq_constraints(self): ... + def evaluate_eq_constraints(self): ... + def evaluate_jacobian_eq(self): ... + def evaluate_ineq_constraints(self): ... + def evaluate_jacobian_ineq(self): ... + +class RenamedNLP(_BaseNLPDelegator): + def __init__(self, original_nlp, primals_name_map) -> None: ... + def primals_names(self): ... + +class ProjectedNLP(_BaseNLPDelegator): + def __init__(self, original_nlp, primals_ordering) -> None: ... + def n_primals(self): ... + def primals_names(self): ... + def nnz_jacobian(self): ... + def nnz_hessian_lag(self): ... + def primals_lb(self): ... + def primals_ub(self): ... + def init_primals(self): ... + def create_new_vector(self, vector_type): ... + def set_primals(self, primals) -> None: ... + def get_primals(self): ... + def get_primals_scaling(self): ... + def evaluate_grad_objective(self, out=None): ... + def evaluate_jacobian(self, out=None): ... + def evaluate_hessian_lag(self, out=None): ... + def report_solver_status(self, status_code, status_message) -> None: ... + +class ProjectedExtendedNLP(ProjectedNLP, _ExtendedNLPDelegator): + def __init__(self, original_nlp, primals_ordering) -> None: ... + def evaluate_jacobian_eq(self, out=None): ... + def evaluate_jacobian_ineq(self, out=None): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.pyi b/stubs/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.pyi new file mode 100644 index 000000000..58b3a46ee --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/pyomo_grey_box_nlp.pyi @@ -0,0 +1,82 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.pynumero.interfaces.external_grey_box import ( + ExternalGreyBoxBlock as ExternalGreyBoxBlock, +) +from pyomo.contrib.pynumero.interfaces.nlp import NLP as NLP +from pyomo.contrib.pynumero.interfaces.nlp_projections import ProjectedNLP as ProjectedNLP +from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP as PyomoNLP +from pyomo.contrib.pynumero.interfaces.utils import ( + CondensedSparseSummation as CondensedSparseSummation, +) +from pyomo.contrib.pynumero.interfaces.utils import ( + make_lower_triangular_full as make_lower_triangular_full, +) +from pyomo.contrib.pynumero.sparse.block_matrix import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse.block_vector import BlockVector as BlockVector +from pyomo.core.base.suffix import SuffixFinder as SuffixFinder + +class PyomoNLPWithGreyBoxBlocks(NLP): + def __init__(self, pyomo_model) -> None: ... + def n_primals(self): ... + def primals_names(self): ... + def n_constraints(self): ... + def constraint_names(self): ... + def nnz_jacobian(self): ... + def nnz_hessian_lag(self): ... + def primals_lb(self): ... + def primals_ub(self): ... + def constraints_lb(self): ... + def constraints_ub(self): ... + def init_primals(self): ... + def init_duals(self): ... + def create_new_vector(self, vector_type): ... + def set_primals(self, primals) -> None: ... + def get_primals(self): ... + def set_duals(self, duals) -> None: ... + def get_duals(self): ... + def set_obj_factor(self, obj_factor) -> None: ... + def get_obj_factor(self): ... + def get_obj_scaling(self): ... + def get_primals_scaling(self): ... + def get_constraints_scaling(self): ... + def evaluate_objective(self): ... + def evaluate_grad_objective(self, out=None): ... + def evaluate_constraints(self, out=None): ... + def evaluate_jacobian(self, out=None): ... + def evaluate_hessian_lag(self, out=None): ... + def report_solver_status(self, status_code, status_message) -> None: ... + def load_state_into_pyomo(self, bound_multipliers=None) -> None: ... + +class _ExternalGreyBoxAsNLP(NLP): + def __init__(self, external_grey_box_block) -> None: ... + def n_primals(self): ... + def primals_names(self): ... + def n_constraints(self): ... + def constraint_names(self): ... + def nnz_jacobian(self): ... + def nnz_hessian_lag(self): ... + def primals_lb(self): ... + def primals_ub(self): ... + def constraints_lb(self): ... + def constraints_ub(self): ... + def init_primals(self): ... + def init_duals(self): ... + def create_new_vector(self, vector_type): ... + def set_primals(self, primals) -> None: ... + def get_primals(self): ... + def set_duals(self, duals) -> None: ... + def get_duals(self): ... + def set_obj_factor(self, obj_factor) -> None: ... + def get_obj_factor(self): ... + def get_obj_scaling(self) -> None: ... + def get_primals_scaling(self) -> None: ... + def get_constraints_scaling(self): ... + def evaluate_objective(self): ... + def evaluate_grad_objective(self, out=None): ... + def evaluate_constraints(self, out=None): ... + def evaluate_jacobian(self, out=None): ... + def has_hessian_support(self): ... + def evaluate_hessian_lag(self, out=None): ... + def report_solver_status(self, status_code, status_message) -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/pyomo_nlp.pyi b/stubs/pyomo/contrib/pynumero/interfaces/pyomo_nlp.pyi new file mode 100644 index 000000000..86b33e1f6 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/pyomo_nlp.pyi @@ -0,0 +1,94 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.env import CtypesEnviron as CtypesEnviron +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.contrib.pynumero.interfaces.ampl_nlp import AslNLP as AslNLP +from pyomo.contrib.pynumero.interfaces.nlp import NLP as NLP +from pyomo.core.base.suffix import SuffixFinder as SuffixFinder +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.solvers.amplfunc_merge import amplfunc_merge as amplfunc_merge + +from ..sparse.block_matrix import BlockMatrix as BlockMatrix +from .external_grey_box import ExternalGreyBoxBlock as ExternalGreyBoxBlock + +class PyomoNLP(AslNLP): + def __init__(self, pyomo_model, nl_file_options=None) -> None: ... + @property + def symbol_map(self): ... + def pyomo_model(self): ... + def get_pyomo_objective(self): ... + def get_pyomo_variables(self): ... + def get_pyomo_constraints(self): ... + def get_pyomo_equality_constraints(self): ... + def get_pyomo_inequality_constraints(self): ... + def variable_names(self): ... + def primals_names(self): ... + def constraint_names(self): ... + def equality_constraint_names(self): ... + def inequality_constraint_names(self): ... + def get_primal_indices(self, pyomo_variables): ... + def get_constraint_indices(self, pyomo_constraints): ... + def get_equality_constraint_indices(self, constraints): ... + def get_inequality_constraint_indices(self, constraints): ... + def get_obj_scaling(self): ... + def get_primals_scaling(self): ... + def get_constraints_scaling(self): ... + def extract_subvector_grad_objective(self, pyomo_variables): ... + def extract_subvector_constraints(self, pyomo_constraints): ... + def extract_submatrix_jacobian(self, pyomo_variables, pyomo_constraints): ... + def extract_submatrix_hessian_lag(self, pyomo_variables_rows, pyomo_variables_cols): ... + def load_state_into_pyomo(self, bound_multipliers=None) -> None: ... + +class PyomoGreyBoxNLP(NLP): + def __init__(self, pyomo_model) -> None: ... + def n_primals(self): ... + def n_constraints(self): ... + def n_eq_constraints(self): ... + def n_ineq_constraints(self): ... + def nnz_jacobian(self): ... + def nnz_jacobian_eq(self): ... + def nnz_hessian_lag(self): ... + def primals_lb(self): ... + def primals_ub(self): ... + def constraints_lb(self): ... + def constraints_ub(self): ... + def init_primals(self): ... + def init_duals(self): ... + def init_duals_eq(self): ... + def create_new_vector(self, vector_type): ... + def set_primals(self, primals) -> None: ... + def get_primals(self): ... + def set_duals(self, duals) -> None: ... + def get_duals(self): ... + def set_duals_eq(self, duals) -> None: ... + def get_duals_eq(self) -> None: ... + def set_obj_factor(self, obj_factor) -> None: ... + def get_obj_factor(self): ... + def get_obj_scaling(self): ... + def get_primals_scaling(self): ... + def get_constraints_scaling(self): ... + def evaluate_objective(self): ... + def evaluate_grad_objective(self, out=None): ... + def evaluate_constraints(self, out=None): ... + def evaluate_eq_constraints(self, out=None) -> None: ... + def evaluate_jacobian(self, out=None): ... + def evaluate_hessian_lag(self, out=None): ... + def report_solver_status(self, status_code, status_message) -> None: ... + def variable_names(self): ... + def primals_names(self): ... + def constraint_names(self): ... + def pyomo_model(self): ... + def get_pyomo_objective(self): ... + def get_pyomo_variables(self): ... + def get_pyomo_constraints(self) -> None: ... + def load_state_into_pyomo(self, bound_multipliers=None) -> None: ... + +class _ExternalGreyBoxModelHelper: + def __init__(self, ex_grey_box_block, vardata_to_idx, con_offset) -> None: ... + def set_primals(self, primals) -> None: ... + def set_duals(self, duals) -> None: ... + def n_residuals(self): ... + def get_residual_scaling(self): ... + def evaluate_residuals(self): ... + def evaluate_jacobian(self): ... + def evaluate_hessian(self): ... diff --git a/stubs/pyomo/contrib/pynumero/interfaces/utils.pyi b/stubs/pyomo/contrib/pynumero/interfaces/utils.pyi new file mode 100644 index 000000000..6a4587681 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/interfaces/utils.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector + +mpi_block_vector: Incomplete +mpi_block_vector_available: Incomplete + +def build_bounds_mask(vector): ... +def build_compression_matrix(compression_mask): ... +def build_compression_mask_for_finite_values(vector): ... +def full_to_compressed(full_array, compression_mask, out=None): ... +def compressed_to_full(compressed_array, compression_mask, out=None, default=None): ... +def make_lower_triangular_full(lower_triangular_matrix): ... + +class CondensedSparseSummation: + def __init__(self, list_of_matrices) -> None: ... + def sum(self, list_of_matrices): ... diff --git a/stubs/pyomo/contrib/pynumero/intrinsic.pyi b/stubs/pyomo/contrib/pynumero/intrinsic.pyi new file mode 100644 index 000000000..7943ddef3 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/intrinsic.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import + +block_vector: Incomplete + +def norm(x, ord=None): ... +def allclose(x1, x2, rtol, atol): ... +def concatenate(arrays): ... +def where(*args): ... +def isin(element, test_elements, assume_unique: bool = False, invert: bool = False): ... +def intersect1d(ar1, ar2, assume_unique: bool = False, return_indices: bool = False): ... +def setdiff1d(ar1, ar2, assume_unique: bool = False): ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/__init__.pyi b/stubs/pyomo/contrib/pynumero/linalg/__init__.pyi new file mode 100644 index 000000000..527ce7db7 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/__init__.pyi @@ -0,0 +1,2 @@ +from ..dependencies import numpy_available as numpy_available +from ..dependencies import scipy_available as scipy_available diff --git a/stubs/pyomo/contrib/pynumero/linalg/base.pyi b/stubs/pyomo/contrib/pynumero/linalg/base.pyi new file mode 100644 index 000000000..3fe20ab6d --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/base.pyi @@ -0,0 +1,51 @@ +import enum +from abc import ABCMeta, abstractmethod + +import numpy as np +from _typeshed import Incomplete +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector +from pyomo.contrib.pynumero.sparse.base_block import BaseBlockMatrix as BaseBlockMatrix +from pyomo.contrib.pynumero.sparse.base_block import BaseBlockVector as BaseBlockVector +from scipy.sparse import spmatrix as spmatrix + +class LinearSolverStatus(enum.Enum): + successful = 0 + not_enough_memory = 1 + singular = 2 + error = 3 + warning = 4 + max_iter = 5 + +class LinearSolverResults: + status: Incomplete + def __init__(self, status: LinearSolverStatus | None = None) -> None: ... + +class LinearSolverInterface(metaclass=ABCMeta): + @abstractmethod + def solve( + self, + matrix: spmatrix | BlockMatrix, + rhs: np.ndarray | BlockVector, + raise_on_error: bool = True, + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... + +class DirectLinearSolverInterface(LinearSolverInterface, metaclass=ABCMeta): + @abstractmethod + def do_symbolic_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + @abstractmethod + def do_numeric_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + @abstractmethod + def do_back_solve( + self, rhs: np.ndarray | BlockVector, raise_on_error: bool = True + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... + def solve( + self, + matrix: spmatrix | BlockMatrix, + rhs: np.ndarray | BlockVector, + raise_on_error: bool = True, + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/ma27.pyi b/stubs/pyomo/contrib/pynumero/linalg/ma27.pyi new file mode 100644 index 000000000..d44bf19a3 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/ma27.pyi @@ -0,0 +1,25 @@ +from _typeshed import Incomplete +from pyomo.common.fileutils import find_library as find_library +from pyomo.contrib.pynumero.linalg.utils import validate_index as validate_index +from pyomo.contrib.pynumero.linalg.utils import validate_value as validate_value + +class MA27Interface: + libname: Incomplete + @classmethod + def available(cls): ... + iw_factor: Incomplete + a_factor: Incomplete + lib: Incomplete + icntl_len: int + cntl_len: int + info_len: int + def __init__(self, iw_factor=None, a_factor=None) -> None: ... + def __del__(self) -> None: ... + def set_icntl(self, i, val) -> None: ... + def get_icntl(self, i): ... + def set_cntl(self, i, val) -> None: ... + def get_cntl(self, i): ... + def get_info(self, i): ... + def do_symbolic_factorization(self, dim, irn, icn): ... + def do_numeric_factorization(self, irn, icn, dim, entries): ... + def do_backsolve(self, rhs, copy: bool = True): ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/ma27_interface.pyi b/stubs/pyomo/contrib/pynumero/linalg/ma27_interface.pyi new file mode 100644 index 000000000..c9486d15b --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/ma27_interface.pyi @@ -0,0 +1,29 @@ +import numpy as np +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector +from scipy.sparse import spmatrix as spmatrix + +from .base import DirectLinearSolverInterface as DirectLinearSolverInterface +from .base import LinearSolverResults as LinearSolverResults +from .base import LinearSolverStatus as LinearSolverStatus +from .ma27 import MA27Interface as MA27Interface + +class MA27(DirectLinearSolverInterface): + def __init__( + self, cntl_options=None, icntl_options=None, iw_factor: float = 1.2, a_factor: int = 2 + ) -> None: ... + def do_symbolic_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_numeric_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_back_solve( + self, rhs: np.ndarray | BlockVector, raise_on_error: bool = True + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... + def increase_memory_allocation(self, factor) -> None: ... + def set_icntl(self, key, value) -> None: ... + def set_cntl(self, key, value) -> None: ... + def get_icntl(self, key): ... + def get_cntl(self, key): ... + def get_info(self, key): ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/ma57.pyi b/stubs/pyomo/contrib/pynumero/linalg/ma57.pyi new file mode 100644 index 000000000..ad5a28de8 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/ma57.pyi @@ -0,0 +1,30 @@ +from _typeshed import Incomplete +from pyomo.common.fileutils import find_library as find_library +from pyomo.contrib.pynumero.linalg.utils import validate_index as validate_index +from pyomo.contrib.pynumero.linalg.utils import validate_value as validate_value + +class MA57Interface: + libname: Incomplete + @classmethod + def available(cls): ... + work_factor: Incomplete + fact_factor: Incomplete + ifact_factor: Incomplete + lib: Incomplete + icntl_len: int + cntl_len: int + info_len: int + rinfo_len: int + def __init__(self, work_factor=None, fact_factor=None, ifact_factor=None) -> None: ... + def __del__(self) -> None: ... + def set_icntl(self, i, val) -> None: ... + def get_icntl(self, i): ... + def set_cntl(self, i, val) -> None: ... + def get_cntl(self, i): ... + def get_info(self, i): ... + def get_rinfo(self, i): ... + ne_cached: Incomplete + dim_cached: Incomplete + def do_symbolic_factorization(self, dim, irn, jcn): ... + def do_numeric_factorization(self, dim, entries): ... + def do_backsolve(self, rhs, copy: bool = True): ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/ma57_interface.pyi b/stubs/pyomo/contrib/pynumero/linalg/ma57_interface.pyi new file mode 100644 index 000000000..cbe2c2855 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/ma57_interface.pyi @@ -0,0 +1,35 @@ +import numpy as np +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector +from scipy.sparse import spmatrix as spmatrix + +from .base import DirectLinearSolverInterface as DirectLinearSolverInterface +from .base import LinearSolverResults as LinearSolverResults +from .base import LinearSolverStatus as LinearSolverStatus +from .ma57 import MA57Interface as MA57Interface + +class MA57(DirectLinearSolverInterface): + def __init__( + self, + cntl_options=None, + icntl_options=None, + work_factor: float = 1.2, + fact_factor: int = 2, + ifact_factor: int = 2, + ) -> None: ... + def do_symbolic_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_numeric_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_back_solve( + self, rhs: np.ndarray | BlockVector, raise_on_error: bool = True + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... + def increase_memory_allocation(self, factor) -> None: ... + def set_icntl(self, key, value) -> None: ... + def set_cntl(self, key, value) -> None: ... + def get_icntl(self, key): ... + def get_cntl(self, key): ... + def get_info(self, key): ... + def get_rinfo(self, key): ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/mumps_interface.pyi b/stubs/pyomo/contrib/pynumero/linalg/mumps_interface.pyi new file mode 100644 index 000000000..1d5a49ac3 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/mumps_interface.pyi @@ -0,0 +1,38 @@ +import numpy as np +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector +from scipy.sparse import coo_matrix as coo_matrix +from scipy.sparse import spmatrix as spmatrix + +from .base import DirectLinearSolverInterface as DirectLinearSolverInterface +from .base import LinearSolverResults as LinearSolverResults +from .base import LinearSolverStatus as LinearSolverStatus + +mumps: Incomplete +mumps_available: Incomplete + +class MumpsCentralizedAssembledLinearSolver(DirectLinearSolverInterface): + def __init__( + self, sym: int = 0, par: int = 1, comm=None, cntl_options=None, icntl_options=None + ) -> None: ... + def do_symbolic_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_numeric_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_back_solve( + self, rhs: np.ndarray | BlockVector, raise_on_error: bool = True + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... + def increase_memory_allocation(self, factor): ... + def __del__(self) -> None: ... + def set_icntl(self, key, value) -> None: ... + def set_cntl(self, key, value) -> None: ... + def get_icntl(self, key): ... + def get_cntl(self, key): ... + def get_info(self, key): ... + def get_infog(self, key): ... + def get_rinfo(self, key): ... + def get_rinfog(self, key): ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/scipy_interface.pyi b/stubs/pyomo/contrib/pynumero/linalg/scipy_interface.pyi new file mode 100644 index 000000000..d442000f5 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/scipy_interface.pyi @@ -0,0 +1,40 @@ +from typing import Callable + +import numpy as np +from _typeshed import Incomplete +from pyomo.contrib.pynumero.sparse import BlockMatrix as BlockMatrix +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector +from scipy.linalg import eigvals as eigvals +from scipy.sparse import spmatrix as spmatrix +from scipy.sparse.linalg import LinearOperator + +from .base import DirectLinearSolverInterface as DirectLinearSolverInterface +from .base import LinearSolverInterface as LinearSolverInterface +from .base import LinearSolverResults as LinearSolverResults +from .base import LinearSolverStatus as LinearSolverStatus + +class ScipyLU(DirectLinearSolverInterface): + def __init__(self) -> None: ... + def do_symbolic_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_numeric_factorization( + self, matrix: spmatrix | BlockMatrix, raise_on_error: bool = True + ) -> LinearSolverResults: ... + def do_back_solve( + self, rhs: np.ndarray | BlockVector, raise_on_error: bool = True + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... + +class _LinearOperator(LinearOperator): + def __init__(self, matrix: spmatrix | BlockMatrix) -> None: ... + +class ScipyIterative(LinearSolverInterface): + method: Incomplete + options: Incomplete + def __init__(self, method: Callable, options=None) -> None: ... + def solve( + self, + matrix: spmatrix | BlockMatrix, + rhs: np.ndarray | BlockVector, + raise_on_error: bool = True, + ) -> tuple[np.ndarray | BlockVector | None, LinearSolverResults]: ... diff --git a/stubs/pyomo/contrib/pynumero/linalg/utils.pyi b/stubs/pyomo/contrib/pynumero/linalg/utils.pyi new file mode 100644 index 000000000..3161e1fa9 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/linalg/utils.pyi @@ -0,0 +1,4 @@ +def validate_index(i, array_len, array_name: str = '') -> None: ... +def validate_value(val, dtype, array_name: str = '') -> None: ... + +class _NotSet: ... diff --git a/stubs/pyomo/contrib/pynumero/plugins.pyi b/stubs/pyomo/contrib/pynumero/plugins.pyi new file mode 100644 index 000000000..7619d749b --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/plugins.pyi @@ -0,0 +1,11 @@ +from pyomo.common.extensions import ExtensionBuilderFactory as ExtensionBuilderFactory +from pyomo.opt import SolverFactory as SolverFactory + +from .algorithms.solvers.cyipopt_solver import PyomoCyIpoptSolver as PyomoCyIpoptSolver +from .algorithms.solvers.scipy_solvers import PyomoFsolveSolver as PyomoFsolveSolver +from .algorithms.solvers.scipy_solvers import PyomoNewtonSolver as PyomoNewtonSolver +from .algorithms.solvers.scipy_solvers import PyomoRootSolver as PyomoRootSolver +from .algorithms.solvers.scipy_solvers import PyomoSecantNewtonSolver as PyomoSecantNewtonSolver +from .build import PyNumeroBuilder as PyNumeroBuilder + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/pynumero/sparse/__init__.pyi b/stubs/pyomo/contrib/pynumero/sparse/__init__.pyi new file mode 100644 index 000000000..ccaeb5fd8 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/sparse/__init__.pyi @@ -0,0 +1,6 @@ +from ..dependencies import numpy_available as numpy_available +from ..dependencies import scipy_available as scipy_available +from .block_matrix import BlockMatrix as BlockMatrix +from .block_matrix import NotFullyDefinedBlockMatrixError as NotFullyDefinedBlockMatrixError +from .block_vector import BlockVector as BlockVector +from .block_vector import NotFullyDefinedBlockVectorError as NotFullyDefinedBlockVectorError diff --git a/stubs/pyomo/contrib/pynumero/sparse/base_block.pyi b/stubs/pyomo/contrib/pynumero/sparse/base_block.pyi new file mode 100644 index 000000000..02ae40917 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/sparse/base_block.pyi @@ -0,0 +1,50 @@ +from _typeshed import Incomplete + +class BaseBlockVector: + def __init__(self) -> None: ... + def argpartition(self, kth, axis: int = -1, kind: str = 'introselect', order=None) -> None: ... + def argsort(self, axis: int = -1, kind: str = 'quicksort', order=None) -> None: ... + def byteswap(self, inplace: bool = False) -> None: ... + def choose(self, choices, out=None, mode: str = 'raise') -> None: ... + def diagonal(self, offset: int = 0, axis1: int = 0, axis2: int = 1) -> None: ... + def getfield(self, dtype, offset: int = 0) -> None: ... + def item(self, *args) -> None: ... + def itemset(self, *args) -> None: ... + def newbyteorder(self, new_order: str = 'S') -> None: ... + def put(self, indices, values, mode: str = 'raise') -> None: ... + def partition(self, kth, axis: int = -1, kind: str = 'introselect', order=None) -> None: ... + def repeat(self, repeats, axis=None) -> None: ... + def reshape(self, shape, order: str = 'C') -> None: ... + def resize(self, new_shape, refcheck: bool = True) -> None: ... + def searchsorted(self, v, side: str = 'left', sorter=None) -> None: ... + def setfield(self, val, dtype, offset: int = 0) -> None: ... + def setflags(self, write=None, align=None, uic=None) -> None: ... + def sort(self, axis: int = -1, kind: str = 'quicksort', order=None) -> None: ... + def squeeze(self, axis=None) -> None: ... + def swapaxes(self, axis1, axis2) -> None: ... + def trace( + self, offset: int = 0, axis1: int = 0, axis2: int = 1, dtype=None, out=None + ) -> None: ... + def argmax(self, axis=None, out=None) -> None: ... + def argmin(self, axis=None, out=None) -> None: ... + def take(self, indices, axis=None, out=None, mode: str = 'raise') -> None: ... + def dump(self, file) -> None: ... + def dumps(self) -> None: ... + def tobytes(self, order: str = 'C') -> None: ... + +class BaseBlockMatrix: + def __init__(self) -> None: ... + def tolil(self, copy: bool = False) -> None: ... + def todia(self, copy: bool = False) -> None: ... + def tobsr(self, blocksize=None, copy: bool = False) -> None: ... + def sum(self, axis=None, dtype=None, out=None) -> None: ... + def mean(self, axis=None, dtype=None, out=None) -> None: ... + def diagonal(self, k: int = 0) -> None: ... + def nonzero(self) -> None: ... + def setdiag(self, values, k: int = 0) -> None: ... + def transpose(self, *axes) -> None: ... + def tostring(self, order: str = 'C') -> None: ... + +vec_unary_ufuncs: Incomplete +vec_binary_ufuncs: Incomplete +vec_associative_reductions: Incomplete diff --git a/stubs/pyomo/contrib/pynumero/sparse/block_matrix.pyi b/stubs/pyomo/contrib/pynumero/sparse/block_matrix.pyi new file mode 100644 index 000000000..841676de0 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/sparse/block_matrix.pyi @@ -0,0 +1,86 @@ +from _typeshed import Incomplete +from pyomo.contrib.pynumero.sparse.block_vector import BlockVector as BlockVector + +from .base_block import BaseBlockMatrix as BaseBlockMatrix + +logger: Incomplete + +class NotFullyDefinedBlockMatrixError(Exception): ... + +def assert_block_structure(mat) -> None: ... + +class BlockMatrix(BaseBlockMatrix): + format: str + def __init__(self, nbrows, nbcols) -> None: ... + @property + def bshape(self): ... + @property + def shape(self): ... + @property + def nnz(self): ... + @property + def dtype(self): ... + @property + def T(self): ... + def row_block_sizes(self, copy: bool = True): ... + def col_block_sizes(self, copy: bool = True): ... + def get_row_size(self, row): ... + def get_col_size(self, col): ... + def set_row_size(self, row, size) -> None: ... + def set_col_size(self, col, size) -> None: ... + def is_row_size_defined(self, row): ... + def is_col_size_defined(self, col): ... + def block_shapes(self): ... + def get_block_mask(self, copy: bool = True): ... + def dot(self, other): ... + def reset_brow(self, idx) -> None: ... + def reset_bcol(self, jdx) -> None: ... + def coo_data(self): ... + def tocoo(self, copy: bool = True): ... + def tocsr(self, copy: bool = True): ... + def tocsc(self, copy: bool = True): ... + def toarray(self, order=None, out=None): ... + def transpose(self, axes=None, copy: bool = True): ... + def is_empty_block(self, idx, jdx): ... + def has_undefined_row_sizes(self): ... + def has_undefined_col_sizes(self): ... + def copyfrom(self, other, deep: bool = True) -> None: ... + def copyto(self, other, deep: bool = True) -> None: ... + def copy(self, deep: bool = True): ... + def copy_structure(self): ... + def get_block(self, row, col): ... + def set_block(self, row, col, value) -> None: ... + def __getitem__(self, item) -> None: ... + def __setitem__(self, item, val) -> None: ... + def __add__(self, other): ... + def __radd__(self, other): ... + def __sub__(self, other): ... + def __rsub__(self, other): ... + def __mul__(self, other): ... + def __truediv__(self, other): ... + def __rtruediv__(self, other) -> None: ... + def __rmul__(self, other): ... + def __pow__(self, other) -> None: ... + def __abs__(self): ... + def __iadd__(self, other): ... + def __isub__(self, other): ... + def __imul__(self, other): ... + def __itruediv__(self, other): ... + def __div__(self, other): ... + def __rdiv__(self, other): ... + def __idiv__(self, other): ... + def __ifloordiv__(self, other) -> None: ... + def __neg__(self): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __le__(self, other): ... + def __lt__(self, other): ... + def __ge__(self, other): ... + def __gt__(self, other): ... + def __len__(self) -> int: ... + def __matmul__(self, other): ... + def __rmatmul__(self, other): ... + def get_block_column_index(self, index): ... + def get_block_row_index(self, index): ... + def getcol(self, j): ... + def getrow(self, i): ... diff --git a/stubs/pyomo/contrib/pynumero/sparse/block_vector.pyi b/stubs/pyomo/contrib/pynumero/sparse/block_vector.pyi new file mode 100644 index 000000000..7cbc2e319 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/sparse/block_vector.pyi @@ -0,0 +1,105 @@ +from ..dependencies import numpy as np +from .base_block import BaseBlockVector as BaseBlockVector +from .base_block import vec_associative_reductions as vec_associative_reductions +from .base_block import vec_binary_ufuncs as vec_binary_ufuncs +from .base_block import vec_unary_ufuncs as vec_unary_ufuncs + +class NotFullyDefinedBlockVectorError(Exception): ... + +def assert_block_structure(vec) -> None: ... + +class BlockVector(BaseBlockVector, np.ndarray): + def __new__(cls, nblocks): ... + def __init__(self, nblocks) -> None: ... + def __array_finalize__(self, obj) -> None: ... + def __array_prepare__(self, out_arr, context=None): ... + def __array_wrap__(self, out_arr, context=None): ... + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): ... + @property + def nblocks(self): ... + @property + def bshape(self): ... + @property + def shape(self): ... + @property + def size(self): ... + @property + def ndim(self): ... + @property + def has_none(self): ... + def block_sizes(self, copy: bool = True): ... + def get_block_size(self, ndx): ... + def is_block_defined(self, ndx): ... + def dot(self, other, out=None): ... + def sum(self, axis=None, dtype=None, out=None, keepdims: bool = False): ... + def all(self, axis=None, out=None, keepdims: bool = False): ... + def any(self, axis=None, out=None, keepdims: bool = False): ... + def max(self, axis=None, out=None, keepdims: bool = False): ... + def astype( + self, + dtype, + order: str = 'K', + casting: str = 'unsafe', + subok: bool = True, + copy: bool = True, + ): ... + def clip(self, min=None, max=None, out=None): ... + def compress(self, condition, axis=None, out=None): ... + def conj(self): ... + def conjugate(self): ... + def nonzero(self): ... + def ptp(self, axis=None, out=None, keepdims: bool = False): ... + def round(self, decimals: int = 0, out=None): ... + def std(self, axis=None, dtype=None, out=None, ddof: int = 0, keepdims: bool = False): ... + def var(self, axis=None, dtype=None, out=None, ddof: int = 0, keepdims: bool = False): ... + def tofile(self, fid, sep: str = '', format: str = '%s') -> None: ... + def min(self, axis=None, out=None, keepdims: bool = False): ... + def mean(self, axis=None, dtype=None, out=None, keepdims: bool = False): ... + def prod(self, axis=None, dtype=None, out=None, keepdims: bool = False): ... + def fill(self, value) -> None: ... + def tolist(self): ... + def flatten(self, order: str = 'C'): ... + def ravel(self, order: str = 'C'): ... + def argmax(self, axis=None, out=None): ... + def argmin(self, axis=None, out=None): ... + def cumprod(self, axis=None, dtype=None, out=None): ... + def cumsum(self, axis=None, dtype=None, out=None): ... + def clone(self, value=None, copy: bool = True): ... + def copyfrom(self, other) -> None: ... + def copyto(self, other) -> None: ... + def copy(self, order: str = 'C'): ... + def copy_structure(self): ... + def set_blocks(self, blocks) -> None: ... + def __iter__(self): ... + def __add__(self, other): ... + def __radd__(self, other): ... + def __sub__(self, other): ... + def __rsub__(self, other): ... + def __mul__(self, other): ... + def __rmul__(self, other): ... + def __truediv__(self, other): ... + def __rtruediv__(self, other): ... + def __floordiv__(self, other): ... + def __rfloordiv__(self, other): ... + def __iadd__(self, other): ... + def __isub__(self, other): ... + def __imul__(self, other): ... + def __itruediv__(self, other): ... + def __div__(self, other): ... + def __rdiv__(self, other): ... + def __idiv__(self, other): ... + def get_block(self, key): ... + def set_block(self, key, value) -> None: ... + def __getitem__(self, item) -> None: ... + def __setitem__(self, key, value) -> None: ... + def __le__(self, other): ... + def __lt__(self, other): ... + def __ge__(self, other): ... + def __gt__(self, other): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __neg__(self): ... + def __contains__(self, item) -> bool: ... + def __len__(self) -> int: ... + def pprint(self) -> None: ... + def toMPIBlockVector(self, rank_ownership, mpi_comm, assert_correct_owners: bool = False): ... diff --git a/stubs/pyomo/contrib/pynumero/sparse/mpi_block_matrix.pyi b/stubs/pyomo/contrib/pynumero/sparse/mpi_block_matrix.pyi new file mode 100644 index 000000000..e50116a1b --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/sparse/mpi_block_matrix.pyi @@ -0,0 +1,99 @@ +from pyomo.common.dependencies import mpi4py as mpi4py + +from .base_block import BaseBlockMatrix as BaseBlockMatrix +from .block_matrix import BlockMatrix as BlockMatrix +from .block_matrix import NotFullyDefinedBlockMatrixError as NotFullyDefinedBlockMatrixError +from .block_vector import BlockVector as BlockVector +from .mpi_block_vector import MPIBlockVector as MPIBlockVector + +def assert_block_structure(mat: MPIBlockMatrix): ... + +class MPIBlockMatrix(BaseBlockMatrix): + def __init__( + self, nbrows, nbcols, rank_ownership, mpi_comm, assert_correct_owners: bool = False + ) -> None: ... + @property + def bshape(self): ... + @property + def shape(self): ... + @property + def nnz(self): ... + @property + def owned_blocks(self): ... + @property + def shared_blocks(self): ... + @property + def rank_ownership(self): ... + @property + def ownership_mask(self): ... + @property + def mpi_comm(self): ... + def get_row_size(self, row): ... + def get_col_size(self, col): ... + def set_row_size(self, row, size) -> None: ... + def set_col_size(self, col, size) -> None: ... + def is_row_size_defined(self, row, this_process_only: bool = True): ... + def is_col_size_defined(self, col, this_process_only: bool = True): ... + def get_block_mask(self, copy: bool = True): ... + @property + def T(self): ... + def dot(self, other): ... + def transpose(self, axes=None, copy: bool = True): ... + def tocoo(self) -> None: ... + def tocsr(self) -> None: ... + def tocsc(self) -> None: ... + def tolil(self, copy: bool = False) -> None: ... + def todia(self, copy: bool = False) -> None: ... + def tobsr(self, blocksize=None, copy: bool = False) -> None: ... + def coo_data(self) -> None: ... + def toarray(self) -> None: ... + def to_local_array(self): ... + def is_empty_block(self, idx, jdx, this_process_only: bool = True): ... + def broadcast_block_sizes(self) -> None: ... + def row_block_sizes(self, copy: bool = True): ... + def col_block_sizes(self, copy: bool = True): ... + def block_shapes(self): ... + def has_undefined_row_sizes(self): ... + def has_undefined_col_sizes(self): ... + def reset_bcol(self, jdx) -> None: ... + def reset_brow(self, idx) -> None: ... + def copy(self): ... + def copy_structure(self): ... + def get_block(self, row, col): ... + def set_block(self, row, col, value) -> None: ... + def __getitem__(self, item) -> None: ... + def __setitem__(self, item, val) -> None: ... + def __add__(self, other): ... + def __radd__(self, other): ... + def __sub__(self, other): ... + def __rsub__(self, other): ... + def __mul__(self, other): ... + def __rmul__(self, other): ... + def __pow__(self, other) -> None: ... + def __truediv__(self, other): ... + def __rtruediv__(self, other) -> None: ... + def __floordiv__(self, other): ... + def __rfloordiv__(self, other) -> None: ... + def __iadd__(self, other): ... + def __isub__(self, other): ... + def __imul__(self, other): ... + def __itruediv__(self, other): ... + def __div__(self, other): ... + def __rdiv__(self, other): ... + def __idiv__(self, other): ... + def __neg__(self): ... + def __abs__(self): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __le__(self, other): ... + def __lt__(self, other): ... + def __ge__(self, other): ... + def __gt__(self, other): ... + def get_block_column_index(self, index): ... + def get_block_row_index(self, index): ... + def getcol(self, j): ... + def getrow(self, i): ... + @staticmethod + def fromBlockMatrix( + block_matrix, rank_ownership, mpi_comm, assert_correct_owners: bool = False + ): ... diff --git a/stubs/pyomo/contrib/pynumero/sparse/mpi_block_vector.pyi b/stubs/pyomo/contrib/pynumero/sparse/mpi_block_vector.pyi new file mode 100644 index 000000000..d89083c51 --- /dev/null +++ b/stubs/pyomo/contrib/pynumero/sparse/mpi_block_vector.pyi @@ -0,0 +1,113 @@ +import numpy as np +from pyomo.common.dependencies import mpi4py as mpi4py +from pyomo.contrib.pynumero.sparse import BlockVector as BlockVector + +from .base_block import BaseBlockVector as BaseBlockVector +from .base_block import vec_associative_reductions as vec_associative_reductions +from .base_block import vec_binary_ufuncs as vec_binary_ufuncs +from .base_block import vec_unary_ufuncs as vec_unary_ufuncs +from .block_vector import NotFullyDefinedBlockVectorError as NotFullyDefinedBlockVectorError + +def assert_block_structure(vec) -> None: ... + +class MPIBlockVector(BaseBlockVector, np.ndarray): + def __new__(cls, nblocks, rank_owner, mpi_comm, assert_correct_owners: bool = False): ... + def __init__( + self, nblocks, rank_owner, mpi_comm, assert_correct_owners: bool = False + ) -> None: ... + def __array_prepare__(self, out_arr, context=None): ... + def __array_wrap__(self, out_arr, context=None): ... + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): ... + @property + def nblocks(self): ... + @property + def bshape(self): ... + @property + def shape(self): ... + @property + def size(self): ... + @property + def ndim(self): ... + @property + def has_none(self): ... + @property + def owned_blocks(self): ... + @property + def shared_blocks(self): ... + @property + def rank_ownership(self): ... + @property + def ownership_mask(self): ... + @property + def mpi_comm(self): ... + def is_broadcasted(self): ... + def block_sizes(self, copy: bool = True): ... + def get_block_size(self, ndx): ... + def broadcast_block_sizes(self) -> None: ... + def finalize_block_sizes(self, broadcast: bool = True, block_sizes=None) -> None: ... + def all(self, axis=None, out=None, keepdims: bool = False): ... + def any(self, axis=None, out=None, keepdims: bool = False): ... + def min(self, axis=None, out=None, keepdims: bool = False): ... + def max(self, axis=None, out=None, keepdims: bool = False): ... + def sum(self, axis=None, dtype=None, out=None, keepdims: bool = False): ... + def prod(self, axis=None, dtype=None, out=None, keepdims: bool = False): ... + def mean(self, axis=None, dtype=None, out=None, keepdims: bool = False): ... + def conj(self): ... + def conjugate(self): ... + def nonzero(self): ... + def round(self, decimals: int = 0, out=None): ... + def clip(self, min=None, max=None, out=None): ... + def compress(self, condition, axis=None, out=None): ... + def copyfrom(self, other) -> None: ... + def copyto(self, other) -> None: ... + def set_blocks(self, blocks) -> None: ... + def clone(self, value=None, copy: bool = True): ... + def copy(self, order: str = 'C'): ... + def copy_structure(self): ... + def fill(self, value) -> None: ... + def dot(self, other, out=None): ... + def make_local_structure_copy(self): ... + def make_local_copy(self): ... + def __add__(self, other): ... + def __radd__(self, other): ... + def __sub__(self, other): ... + def __rsub__(self, other): ... + def __mul__(self, other): ... + def __rmul__(self, other): ... + def __truediv__(self, other): ... + def __rtruediv__(self, other): ... + def __floordiv__(self, other): ... + def __rfloordiv__(self, other): ... + def __neg__(self): ... + def __iadd__(self, other): ... + def __isub__(self, other): ... + def __imul__(self, other): ... + def __itruediv__(self, other): ... + def __div__(self, other): ... + def __rdiv__(self, other): ... + def __idiv__(self, other): ... + def __le__(self, other): ... + def __lt__(self, other): ... + def __ge__(self, other): ... + def __gt__(self, other): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __contains__(self, item) -> bool: ... + def get_block(self, key): ... + def set_block(self, key, value) -> None: ... + def __getitem__(self, item) -> None: ... + def __setitem__(self, key, value) -> None: ... + def pprint(self, root: int = 0) -> None: ... + def __len__(self) -> int: ... + def __iter__(self): ... + def std( + self, axis=None, dtype=None, out=None, ddof: int = 0, keepdims: bool = False + ) -> None: ... + def var( + self, axis=None, dtype=None, out=None, ddof: int = 0, keepdims: bool = False + ) -> None: ... + def cumprod(self, axis=None, dtype=None, out=None) -> None: ... + def cumsum(self, axis=None, dtype=None, out=None) -> None: ... + def tolist(self) -> None: ... + def flatten(self, order: str = 'C') -> None: ... + def ravel(self, order: str = 'C') -> None: ... diff --git a/stubs/pyomo/contrib/pyros/__init__.pyi b/stubs/pyomo/contrib/pyros/__init__.pyi new file mode 100644 index 000000000..c76a4a64a --- /dev/null +++ b/stubs/pyomo/contrib/pyros/__init__.pyi @@ -0,0 +1,15 @@ +from pyomo.contrib.pyros.pyros import PyROS as PyROS +from pyomo.contrib.pyros.uncertainty_sets import ( + AxisAlignedEllipsoidalSet as AxisAlignedEllipsoidalSet, +) +from pyomo.contrib.pyros.uncertainty_sets import BoxSet as BoxSet +from pyomo.contrib.pyros.uncertainty_sets import BudgetSet as BudgetSet +from pyomo.contrib.pyros.uncertainty_sets import CardinalitySet as CardinalitySet +from pyomo.contrib.pyros.uncertainty_sets import DiscreteScenarioSet as DiscreteScenarioSet +from pyomo.contrib.pyros.uncertainty_sets import EllipsoidalSet as EllipsoidalSet +from pyomo.contrib.pyros.uncertainty_sets import FactorModelSet as FactorModelSet +from pyomo.contrib.pyros.uncertainty_sets import IntersectionSet as IntersectionSet +from pyomo.contrib.pyros.uncertainty_sets import PolyhedralSet as PolyhedralSet +from pyomo.contrib.pyros.uncertainty_sets import UncertaintySet as UncertaintySet +from pyomo.contrib.pyros.util import ObjectiveType as ObjectiveType +from pyomo.contrib.pyros.util import pyrosTerminationCondition as pyrosTerminationCondition diff --git a/stubs/pyomo/contrib/pyros/config.pyi b/stubs/pyomo/contrib/pyros/config.pyi new file mode 100644 index 000000000..5e7c248ac --- /dev/null +++ b/stubs/pyomo/contrib/pyros/config.pyi @@ -0,0 +1,71 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import InEnum as InEnum +from pyomo.common.config import IsInstance as IsInstance +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import Path as Path +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.contrib.pyros.uncertainty_sets import UncertaintySet as UncertaintySet +from pyomo.contrib.pyros.util import ObjectiveType as ObjectiveType +from pyomo.contrib.pyros.util import setup_pyros_logger as setup_pyros_logger +from pyomo.contrib.pyros.util import standardize_component_data as standardize_component_data +from pyomo.core.base import Var as Var +from pyomo.core.base import VarData as VarData +from pyomo.core.base.param import Param as Param +from pyomo.core.base.param import ParamData as ParamData +from pyomo.opt import SolverFactory as SolverFactory + +default_pyros_solver_logger: Incomplete + +def logger_domain(obj): ... +def positive_int_or_minus_one(obj): ... +def uncertain_param_validator(uncertain_obj) -> None: ... +def uncertain_param_data_validator(uncertain_obj) -> None: ... + +class InputDataStandardizer: + ctype: Incomplete + cdatatype: Incomplete + ctype_validator: Incomplete + cdatatype_validator: Incomplete + allow_repeats: Incomplete + def __init__( + self, + ctype, + cdatatype, + ctype_validator=None, + cdatatype_validator=None, + allow_repeats: bool = False, + ) -> None: ... + def __call__(self, obj, from_iterable=None, allow_repeats=None): ... + def domain_name(self): ... + +class SolverNotResolvable(PyomoException): ... + +class SolverResolvable: + require_available: Incomplete + solver_desc: Incomplete + def __init__(self, require_available: bool = True, solver_desc: str = 'solver') -> None: ... + @staticmethod + def is_solver_type(obj): ... + def __call__(self, obj, require_available=None, solver_desc=None): ... + def domain_name(self): ... + +class SolverIterable: + require_available: Incomplete + filter_by_availability: Incomplete + solver_desc: Incomplete + def __init__( + self, + require_available: bool = True, + filter_by_availability: bool = True, + solver_desc: str = 'solver', + ) -> None: ... + def __call__( + self, obj, require_available=None, filter_by_availability=None, solver_desc=None + ): ... + def domain_name(self): ... + +def pyros_config(): ... diff --git a/stubs/pyomo/contrib/pyros/master_problem_methods.pyi b/stubs/pyomo/contrib/pyros/master_problem_methods.pyi new file mode 100644 index 000000000..a70cb47a5 --- /dev/null +++ b/stubs/pyomo/contrib/pyros/master_problem_methods.pyi @@ -0,0 +1,56 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.pyros.solve_data import MasterResults as MasterResults +from pyomo.contrib.pyros.util import ( + DR_POLISHING_PARAM_PRODUCT_ZERO_TOL as DR_POLISHING_PARAM_PRODUCT_ZERO_TOL, +) +from pyomo.contrib.pyros.util import TIC_TOC_SOLVE_TIME_ATTR as TIC_TOC_SOLVE_TIME_ATTR +from pyomo.contrib.pyros.util import ObjectiveType as ObjectiveType +from pyomo.contrib.pyros.util import call_solver as call_solver +from pyomo.contrib.pyros.util import check_time_limit_reached as check_time_limit_reached +from pyomo.contrib.pyros.util import enforce_dr_degree as enforce_dr_degree +from pyomo.contrib.pyros.util import ( + generate_all_decision_rule_var_data_objects as generate_all_decision_rule_var_data_objects, +) +from pyomo.contrib.pyros.util import get_all_first_stage_eq_cons as get_all_first_stage_eq_cons +from pyomo.contrib.pyros.util import get_dr_expression as get_dr_expression +from pyomo.contrib.pyros.util import pyrosTerminationCondition as pyrosTerminationCondition +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core.base import Block as Block +from pyomo.core.base import ConcreteModel as ConcreteModel +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Var as Var +from pyomo.core.base.set_types import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core.base.set_types import NonNegativeReals as NonNegativeReals +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.expr import value as value +from pyomo.core.util import prod as prod +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +def construct_initial_master_problem(model_data): ... +def add_scenario_block_to_master_problem( + master_model, scenario_idx, param_realization, from_block, clone_first_stage_components +) -> None: ... +def construct_master_feasibility_problem(master_data): ... +def solve_master_feasibility_problem(master_data): ... +def construct_dr_polishing_problem(master_data): ... +def minimize_dr_vars(master_data): ... +def get_master_dr_degree(master_data): ... +def higher_order_decision_rule_efficiency(master_data) -> None: ... +def log_master_solve_results(master_model, config, results, desc: str = 'Optimized'): ... +def process_termination_condition_master_problem(config, results): ... +def solver_call_master(master_data): ... +def solve_master(master_data): ... + +class MasterProblemData: + master_model: Incomplete + original_model_name: Incomplete + iteration: int + timing: Incomplete + config: Incomplete + def __init__(self, model_data) -> None: ... + def solve_master(self): ... + def solve_dr_polishing(self): ... diff --git a/stubs/pyomo/contrib/pyros/pyros.pyi b/stubs/pyomo/contrib/pyros/pyros.pyi new file mode 100644 index 000000000..3d52148e5 --- /dev/null +++ b/stubs/pyomo/contrib/pyros/pyros.pyi @@ -0,0 +1,48 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.contrib.pyros.config import logger_domain as logger_domain +from pyomo.contrib.pyros.config import pyros_config as pyros_config +from pyomo.contrib.pyros.pyros_algorithm_methods import ( + ROSolver_iterative_solve as ROSolver_iterative_solve, +) +from pyomo.contrib.pyros.solve_data import ROSolveResults as ROSolveResults +from pyomo.contrib.pyros.util import IterationLogRecord as IterationLogRecord +from pyomo.contrib.pyros.util import ModelData as ModelData +from pyomo.contrib.pyros.util import TimingData as TimingData +from pyomo.contrib.pyros.util import load_final_solution as load_final_solution +from pyomo.contrib.pyros.util import log_model_statistics as log_model_statistics +from pyomo.contrib.pyros.util import pyrosTerminationCondition as pyrosTerminationCondition +from pyomo.contrib.pyros.util import setup_pyros_logger as setup_pyros_logger +from pyomo.contrib.pyros.util import time_code as time_code +from pyomo.contrib.pyros.util import validate_pyros_inputs as validate_pyros_inputs +from pyomo.core.expr import value as value +from pyomo.opt import SolverFactory as SolverFactory + +__version__: str +default_pyros_solver_logger: Incomplete + +class PyROS: + CONFIG: Incomplete + def available(self, exception_flag: bool = True): ... + def version(self): ... + def license_is_valid(self): ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + def solve( + self, + model, + first_stage_variables, + second_stage_variables, + uncertain_params, + uncertainty_set, + local_solver, + global_solver, + **kwds, + ): ... diff --git a/stubs/pyomo/contrib/pyros/pyros_algorithm_methods.pyi b/stubs/pyomo/contrib/pyros/pyros_algorithm_methods.pyi new file mode 100644 index 000000000..686e9c02b --- /dev/null +++ b/stubs/pyomo/contrib/pyros/pyros_algorithm_methods.pyi @@ -0,0 +1,29 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.contrib.pyros.util import IterationLogRecord as IterationLogRecord +from pyomo.contrib.pyros.util import ObjectiveType as ObjectiveType +from pyomo.contrib.pyros.util import check_time_limit_reached as check_time_limit_reached +from pyomo.contrib.pyros.util import get_dr_var_to_monomial_map as get_dr_var_to_monomial_map +from pyomo.contrib.pyros.util import get_main_elapsed_time as get_main_elapsed_time +from pyomo.contrib.pyros.util import pyrosTerminationCondition as pyrosTerminationCondition +from pyomo.core.base import value as value + +class GRCSResults: + master_results: Incomplete + separation_results: Incomplete + pyros_termination_condition: Incomplete + iterations: Incomplete + def __init__( + self, master_results, separation_results, pyros_termination_condition, iterations + ) -> None: ... + +class VariableValueData(NamedTuple): + first_stage_variables: Incomplete + second_stage_variables: Incomplete + decision_rule_monomials: Incomplete + +def get_variable_value_data(working_blk, dr_var_to_monomial_map): ... +def evaluate_variable_shifts(current_var_data, previous_var_data, initial_var_data): ... +def ROSolver_iterative_solve(model_data): ... diff --git a/stubs/pyomo/contrib/pyros/separation_problem_methods.pyi b/stubs/pyomo/contrib/pyros/separation_problem_methods.pyi new file mode 100644 index 000000000..8cd3fae34 --- /dev/null +++ b/stubs/pyomo/contrib/pyros/separation_problem_methods.pyi @@ -0,0 +1,62 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.contrib.pyros.solve_data import ( + DiscreteSeparationSolveCallResults as DiscreteSeparationSolveCallResults, +) +from pyomo.contrib.pyros.solve_data import SeparationLoopResults as SeparationLoopResults +from pyomo.contrib.pyros.solve_data import SeparationResults as SeparationResults +from pyomo.contrib.pyros.solve_data import SeparationSolveCallResults as SeparationSolveCallResults +from pyomo.contrib.pyros.uncertainty_sets import Geometry as Geometry +from pyomo.contrib.pyros.util import ABS_CON_CHECK_FEAS_TOL as ABS_CON_CHECK_FEAS_TOL +from pyomo.contrib.pyros.util import call_solver as call_solver +from pyomo.contrib.pyros.util import check_time_limit_reached as check_time_limit_reached +from pyomo.contrib.pyros.util import get_all_first_stage_eq_cons as get_all_first_stage_eq_cons +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Var as Var +from pyomo.core.base import maximize as maximize +from pyomo.core.base import value as value +from pyomo.core.expr import identify_mutable_parameters as identify_mutable_parameters +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.expr import replace_expressions as replace_expressions + +def add_uncertainty_set_constraints(separation_model, config) -> None: ... +def construct_separation_problem(model_data): ... +def get_sep_objective_values(separation_data, ss_ineq_cons): ... +def get_argmax_sum_violations(solver_call_results_map, ss_ineq_cons_to_evaluate): ... +def solve_separation_problem(separation_data, master_data): ... +def evaluate_violations_by_nominal_master(separation_data, master_data, ss_ineq_cons): ... +def group_ss_ineq_constraints_by_priority(separation_data): ... +def get_worst_discrete_separation_solution( + ss_ineq_con, config, ss_ineq_cons_to_evaluate, discrete_solve_results +): ... +def get_con_name_repr(separation_model, con, with_obj_name: bool = True): ... +def perform_separation_loop(separation_data, master_data, solve_globally): ... +def evaluate_ss_ineq_con_violations( + separation_data, ss_ineq_con_to_maximize, ss_ineq_cons_to_evaluate +): ... +def initialize_separation(ss_ineq_con_to_maximize, separation_data, master_data): ... + +locally_acceptable: Incomplete +globally_acceptable: Incomplete + +def solver_call_separation( + separation_data, master_data, solve_globally, ss_ineq_con_to_maximize, ss_ineq_cons_to_evaluate +): ... +def discrete_solve( + separation_data, master_data, solve_globally, ss_ineq_con_to_maximize, ss_ineq_cons_to_evaluate +): ... + +class SeparationProblemData: + separation_model: Incomplete + timing: Incomplete + separation_priority_order: Incomplete + iteration: int + config: Incomplete + points_added_to_master: Incomplete + auxiliary_values_for_master_points: Incomplete + idxs_of_master_scenarios: Incomplete + def __init__(self, model_data) -> None: ... + def solve_separation(self, master_data): ... diff --git a/stubs/pyomo/contrib/pyros/solve_data.pyi b/stubs/pyomo/contrib/pyros/solve_data.pyi new file mode 100644 index 000000000..816206500 --- /dev/null +++ b/stubs/pyomo/contrib/pyros/solve_data.pyi @@ -0,0 +1,130 @@ +from _typeshed import Incomplete + +class ROSolveResults: + config: Incomplete + iterations: Incomplete + time: Incomplete + final_objective_value: Incomplete + pyros_termination_condition: Incomplete + def __init__( + self, + config=None, + iterations=None, + time=None, + final_objective_value=None, + pyros_termination_condition=None, + ) -> None: ... + +class MasterResults: + master_model: Incomplete + feasibility_problem_results: Incomplete + master_results_list: Incomplete + pyros_termination_condition: Incomplete + def __init__( + self, + master_model=None, + feasibility_problem_results=None, + master_results_list=None, + pyros_termination_condition=None, + ) -> None: ... + +class SeparationSolveCallResults: + results_list: Incomplete + solved_globally: Incomplete + scaled_violations: Incomplete + violating_param_realization: Incomplete + auxiliary_param_values: Incomplete + variable_values: Incomplete + found_violation: Incomplete + time_out: Incomplete + subsolver_error: Incomplete + discrete_set_scenario_index: Incomplete + def __init__( + self, + solved_globally, + results_list=None, + scaled_violations=None, + violating_param_realization=None, + auxiliary_param_values=None, + variable_values=None, + found_violation=None, + time_out=None, + subsolver_error=None, + discrete_set_scenario_index=None, + ) -> None: ... + def termination_acceptable(self, acceptable_terminations): ... + +class DiscreteSeparationSolveCallResults: + solved_globally: Incomplete + solver_call_results: Incomplete + second_stage_ineq_con: Incomplete + def __init__( + self, solved_globally, solver_call_results=None, second_stage_ineq_con=None + ) -> None: ... + @property + def time_out(self): ... + @property + def subsolver_error(self): ... + +class SeparationLoopResults: + solver_call_results: Incomplete + solved_globally: Incomplete + worst_case_ss_ineq_con: Incomplete + all_discrete_scenarios_exhausted: Incomplete + def __init__( + self, + solved_globally, + solver_call_results, + worst_case_ss_ineq_con, + all_discrete_scenarios_exhausted: bool = False, + ) -> None: ... + @property + def found_violation(self): ... + @property + def violating_param_realization(self): ... + @property + def auxiliary_param_values(self): ... + @property + def scaled_violations(self): ... + @property + def violating_separation_variable_values(self): ... + @property + def violated_second_stage_ineq_cons(self): ... + @property + def subsolver_error(self): ... + @property + def time_out(self): ... + +class SeparationResults: + local_separation_loop_results: Incomplete + global_separation_loop_results: Incomplete + def __init__(self, local_separation_loop_results, global_separation_loop_results) -> None: ... + @property + def time_out(self): ... + @property + def subsolver_error(self): ... + @property + def solved_locally(self): ... + @property + def solved_globally(self): ... + def get_violating_attr(self, attr_name): ... + @property + def all_discrete_scenarios_exhausted(self): ... + @property + def worst_case_ss_ineq_con(self): ... + @property + def main_loop_results(self): ... + @property + def found_violation(self): ... + @property + def violating_param_realization(self): ... + @property + def auxiliary_param_values(self): ... + @property + def scaled_violations(self): ... + @property + def violating_separation_variable_values(self): ... + @property + def violated_second_stage_ineq_cons(self): ... + @property + def robustness_certified(self): ... diff --git a/stubs/pyomo/contrib/pyros/uncertainty_sets.pyi b/stubs/pyomo/contrib/pyros/uncertainty_sets.pyi new file mode 100644 index 000000000..7cb030f5e --- /dev/null +++ b/stubs/pyomo/contrib/pyros/uncertainty_sets.pyi @@ -0,0 +1,296 @@ +import abc +from collections.abc import MutableSequence +from enum import Enum +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.pyros.util import PARAM_IS_CERTAIN_ABS_TOL as PARAM_IS_CERTAIN_ABS_TOL +from pyomo.contrib.pyros.util import PARAM_IS_CERTAIN_REL_TOL as PARAM_IS_CERTAIN_REL_TOL +from pyomo.contrib.pyros.util import POINT_IN_UNCERTAINTY_SET_TOL as POINT_IN_UNCERTAINTY_SET_TOL +from pyomo.contrib.pyros.util import copy_docstring as copy_docstring +from pyomo.contrib.pyros.util import standardize_component_data as standardize_component_data +from pyomo.core.base import Block as Block +from pyomo.core.base import ConcreteModel as ConcreteModel +from pyomo.core.base import ConstraintList as ConstraintList +from pyomo.core.base import Var as Var +from pyomo.core.base import VarData as VarData +from pyomo.core.base import maximize as maximize +from pyomo.core.base import minimize as minimize +from pyomo.core.expr import mutable_expression as mutable_expression +from pyomo.core.expr import native_numeric_types as native_numeric_types +from pyomo.core.expr import value as value +from pyomo.core.util import dot_product as dot_product +from pyomo.core.util import quicksum as quicksum +from pyomo.opt.results import check_optimal_termination as check_optimal_termination + +valid_num_types: Incomplete + +def standardize_uncertain_param_vars(obj, dim): ... + +class UncertaintyQuantification(NamedTuple): + block: Incomplete + uncertainty_cons: Incomplete + uncertain_param_vars: Incomplete + auxiliary_vars: Incomplete + +def validate_arg_type( + arg_name, + arg_val, + valid_types, + valid_type_desc=None, + is_entry_of_arg: bool = False, + check_numeric_type_finite: bool = True, +) -> None: ... +def is_ragged(arr, arr_types=None): ... +def validate_dimensions(arr_name, arr, dim, display_value: bool = False) -> None: ... +def validate_array( + arr, + arr_name, + dim, + valid_types, + valid_type_desc=None, + required_shape=None, + required_shape_qual: str = '', +): ... + +class Geometry(Enum): + LINEAR = 1 + CONVEX_NONLINEAR = 2 + GENERAL_NONLINEAR = 3 + DISCRETE_SCENARIOS = 4 + +class UncertaintySet(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def dim(self): ... + @property + @abc.abstractmethod + def geometry(self): ... + @property + @abc.abstractmethod + def parameter_bounds(self): ... + def is_bounded(self, config): ... + def is_nonempty(self, config): ... + def is_valid(self, config): ... + @abc.abstractmethod + def set_as_constraint(self, uncertain_params=None, block=None): ... + def point_in_set(self, point): ... + def compute_auxiliary_uncertain_param_vals(self, point, solver=None) -> None: ... + +class UncertaintySetList(MutableSequence): + def __init__(self, uncertainty_sets=[], name=None, min_length=None) -> None: ... + def __len__(self) -> int: ... + def __getitem__(self, idx): ... + def __setitem__(self, idx, value) -> None: ... + def __delitem__(self, idx) -> None: ... + def clear(self) -> None: ... + def insert(self, idx, value) -> None: ... + @property + def dim(self): ... + +class BoxSet(UncertaintySet): + def __init__(self, bounds) -> None: ... + @property + def type(self): ... + @property + def bounds(self): ... + @bounds.setter + def bounds(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... + +class CardinalitySet(UncertaintySet): + def __init__(self, origin, positive_deviation, gamma) -> None: ... + @property + def type(self): ... + @property + def origin(self): ... + @origin.setter + def origin(self, val) -> None: ... + @property + def positive_deviation(self): ... + @positive_deviation.setter + def positive_deviation(self, val) -> None: ... + @property + def gamma(self): ... + @gamma.setter + def gamma(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... + def compute_auxiliary_uncertain_param_vals(self, point, solver=None): ... + def point_in_set(self, point): ... + +class PolyhedralSet(UncertaintySet): + def __init__(self, lhs_coefficients_mat, rhs_vec) -> None: ... + @property + def type(self): ... + @property + def coefficients_mat(self): ... + @coefficients_mat.setter + def coefficients_mat(self, val) -> None: ... + @property + def rhs_vec(self): ... + @rhs_vec.setter + def rhs_vec(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... + +class BudgetSet(UncertaintySet): + def __init__(self, budget_membership_mat, rhs_vec, origin=None) -> None: ... + @property + def type(self): ... + @property + def coefficients_mat(self): ... + @property + def rhs_vec(self): ... + @property + def budget_membership_mat(self): ... + @budget_membership_mat.setter + def budget_membership_mat(self, val) -> None: ... + @property + def budget_rhs_vec(self): ... + @budget_rhs_vec.setter + def budget_rhs_vec(self, val) -> None: ... + @property + def origin(self): ... + @origin.setter + def origin(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def set_as_constraint(self, **kwargs): ... + +class FactorModelSet(UncertaintySet): + def __init__(self, origin, number_of_factors, psi_mat, beta) -> None: ... + @property + def type(self): ... + @property + def origin(self): ... + @origin.setter + def origin(self, val) -> None: ... + @property + def number_of_factors(self): ... + @number_of_factors.setter + def number_of_factors(self, val) -> None: ... + @property + def psi_mat(self): ... + @psi_mat.setter + def psi_mat(self, val) -> None: ... + @property + def beta(self): ... + @beta.setter + def beta(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... + def compute_auxiliary_uncertain_param_vals(self, point, solver=None): ... + def point_in_set(self, point): ... + +class AxisAlignedEllipsoidalSet(UncertaintySet): + def __init__(self, center, half_lengths) -> None: ... + @property + def type(self): ... + @property + def center(self): ... + @center.setter + def center(self, val) -> None: ... + @property + def half_lengths(self): ... + @half_lengths.setter + def half_lengths(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... + +class EllipsoidalSet(UncertaintySet): + def __init__(self, center, shape_matrix, scale: int = 1, gaussian_conf_lvl=None) -> None: ... + @property + def type(self): ... + @property + def center(self): ... + @center.setter + def center(self, val) -> None: ... + @property + def shape_matrix(self): ... + @shape_matrix.setter + def shape_matrix(self, val) -> None: ... + @property + def scale(self): ... + @scale.setter + def scale(self, val) -> None: ... + @property + def gaussian_conf_lvl(self): ... + @gaussian_conf_lvl.setter + def gaussian_conf_lvl(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def point_in_set(self, point): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... + +class DiscreteScenarioSet(UncertaintySet): + def __init__(self, scenarios) -> None: ... + @property + def type(self): ... + @property + def scenarios(self): ... + @scenarios.setter + def scenarios(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def is_bounded(self, config): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... + def point_in_set(self, point): ... + +class IntersectionSet(UncertaintySet): + def __init__(self, **unc_sets) -> None: ... + @property + def type(self): ... + @property + def all_sets(self): ... + @all_sets.setter + def all_sets(self, val) -> None: ... + @property + def dim(self): ... + @property + def geometry(self): ... + @property + def parameter_bounds(self): ... + def point_in_set(self, point): ... + @staticmethod + def intersect(Q1, Q2): ... + def set_as_constraint(self, uncertain_params=None, block=None): ... diff --git a/stubs/pyomo/contrib/pyros/util.pyi b/stubs/pyomo/contrib/pyros/util.pyi new file mode 100644 index 000000000..52cfd84e4 --- /dev/null +++ b/stubs/pyomo/contrib/pyros/util.pyi @@ -0,0 +1,223 @@ +import logging +from collections.abc import Generator +from contextlib import contextmanager +from enum import Enum +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.errors import InvalidValueError as InvalidValueError +from pyomo.common.log import Preformatted as Preformatted +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.common.timing import TicTocTimer as TicTocTimer +from pyomo.core.base import Any as Any +from pyomo.core.base import Block as Block +from pyomo.core.base import Component as Component +from pyomo.core.base import ConcreteModel as ConcreteModel +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Expression as Expression +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import ParamData as ParamData +from pyomo.core.base import Reals as Reals +from pyomo.core.base import Var as Var +from pyomo.core.base import VarData as VarData +from pyomo.core.base import maximize as maximize +from pyomo.core.base import minimize as minimize +from pyomo.core.base import value as value +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.visitor import identify_mutable_parameters as identify_mutable_parameters +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.core.expr.visitor import replace_expressions as replace_expressions +from pyomo.core.util import prod as prod +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.repn.parameterized_quadratic import ( + ParameterizedQuadraticRepnVisitor as ParameterizedQuadraticRepnVisitor, +) +from pyomo.repn.util import OrderedVarRecorder as OrderedVarRecorder +from pyomo.util.vars_from_expressions import get_vars_from_components as get_vars_from_components + +PARAM_IS_CERTAIN_REL_TOL: float +PARAM_IS_CERTAIN_ABS_TOL: int +COEFF_MATCH_REL_TOL: float +COEFF_MATCH_ABS_TOL: int +ABS_CON_CHECK_FEAS_TOL: float +PRETRIANGULAR_VAR_COEFF_TOL: float +POINT_IN_UNCERTAINTY_SET_TOL: float +DR_POLISHING_PARAM_PRODUCT_ZERO_TOL: float +TIC_TOC_SOLVE_TIME_ATTR: str +DEFAULT_LOGGER_NAME: str +DEFAULT_SEPARATION_PRIORITY: int + +class TimingData: + hierarchical_timer_full_ids: Incomplete + def __init__(self) -> None: ... + def start_timer(self, full_identifier): ... + def stop_timer(self, full_identifier): ... + def get_total_time(self, full_identifier): ... + def get_main_elapsed_time(self): ... + +@contextmanager +def time_code(timing_data_obj, code_block_name, is_main_timer: bool = False) -> Generator[None]: ... +def get_main_elapsed_time(timing_data_obj): ... +def adjust_solver_time_settings(timing_data_obj, solver, config): ... +def revert_solver_max_time_adjustment( + solver, original_max_time_setting, custom_setting_present, config +) -> None: ... + +class PreformattedLogger(logging.Logger): + def critical(self, msg, *args, **kwargs): ... + def error(self, msg, *args, **kwargs): ... + def warning(self, msg, *args, **kwargs): ... + def info(self, msg, *args, **kwargs): ... + def debug(self, msg, *args, **kwargs): ... + def log(self, level, msg, *args, **kwargs): ... + +def setup_pyros_logger(name=...): ... + +class pyrosTerminationCondition(Enum): + robust_feasible = 0 + robust_optimal = 1 + robust_infeasible = 2 + max_iter = 3 + subsolver_error = 4 + time_out = 5 + @property + def message(self): ... + +class SeparationStrategy(Enum): + all_violations = ... + max_violation = ... + +class SolveMethod(Enum): + local_solve = ... + global_solve = ... + +class ObjectiveType(Enum): + worst_case = ... + nominal = ... + +def standardize_component_data( + obj, + valid_ctype, + valid_cdatatype, + ctype_validator=None, + cdatatype_validator=None, + allow_repeats: bool = False, + from_iterable=None, +): ... +def check_components_descended_from_model(model, components, components_name, config) -> None: ... +def check_variables_continuous(model, vars, config) -> None: ... +def validate_model(model, config) -> None: ... + +class VariablePartitioning(NamedTuple): + first_stage_variables: Incomplete + second_stage_variables: Incomplete + state_variables: Incomplete + +def validate_variable_partitioning(model, config): ... +def validate_uncertainty_specification(model, config) -> None: ... +def validate_separation_problem_options(model, config) -> None: ... +def validate_pyros_inputs(model, config): ... + +class ModelData: + original_model: Incomplete + timing: Incomplete + config: Incomplete + separation_priority_order: Incomplete + working_model: Incomplete + def __init__(self, original_model, config, timing) -> None: ... + def preprocess(self, user_var_partitioning): ... + +def setup_quadratic_expression_visitor( + wrt, subexpression_cache=None, var_map=None, var_order=None, sorter=None +): ... + +class BoundType: + LOWER: str + EQ: str + UPPER: str + +def get_var_bound_pairs(var): ... +def determine_certain_and_uncertain_bound( + domain_bound, declared_bound, uncertain_params, bound_type +): ... + +BoundTriple: Incomplete + +def rearrange_bound_pair_to_triple(lower_bound, upper_bound): ... +def get_var_certain_uncertain_bounds(var, uncertain_params): ... +def get_effective_var_partitioning(model_data): ... +def add_effective_var_partitioning(model_data) -> None: ... +def create_bound_constraint_expr(expr, bound, bound_type, standardize: bool = True): ... +def remove_var_declared_bound(var, bound_type) -> None: ... +def remove_all_var_bounds(var) -> None: ... +def turn_nonadjustable_var_bounds_to_constraints(model_data) -> None: ... +def turn_adjustable_var_bounds_to_constraints(model_data) -> None: ... +def replace_vars_with_params(block, var_to_param_map) -> None: ... +def setup_working_model(model_data, user_var_partitioning) -> None: ... +def standardize_inequality_constraints(model_data) -> None: ... +def standardize_equality_constraints(model_data) -> None: ... +def get_summands(expr): ... +def declare_objective_expressions(working_model, objective, sense=...) -> None: ... +def standardize_active_objective(model_data) -> None: ... +def get_all_nonadjustable_variables(working_model): ... +def get_all_first_stage_eq_cons(working_model): ... +def get_all_adjustable_variables(working_model): ... +def generate_all_decision_rule_var_data_objects( + working_blk, +) -> Generator[Incomplete, Incomplete]: ... +def generate_all_decision_rule_eqns(working_blk) -> Generator[Incomplete, Incomplete]: ... +def get_dr_expression(working_blk, second_stage_var): ... +def get_dr_var_to_monomial_map(working_blk): ... +def check_time_limit_reached(timing_data, config): ... +def reformulate_state_var_independent_eq_cons(model_data): ... +def get_effective_uncertain_dimensions(model_data): ... +def preprocess_model_data(model_data, user_var_partitioning): ... +def log_model_statistics(model_data) -> None: ... +def add_decision_rule_variables(model_data) -> None: ... +def add_decision_rule_constraints(model_data) -> None: ... +def enforce_dr_degree(working_blk, config, degree) -> None: ... +def load_final_solution(model_data, master_soln, original_user_var_partitioning): ... +def call_solver(model, solver, config, timing_obj, timer_name, err_msg): ... + +class IterationLogRecord: + iteration: Incomplete + objective: Incomplete + first_stage_var_shift: Incomplete + second_stage_var_shift: Incomplete + dr_var_shift: Incomplete + dr_polishing_success: Incomplete + num_violated_cons: Incomplete + all_sep_problems_solved: Incomplete + global_separation: Incomplete + max_violation: Incomplete + elapsed_time: Incomplete + def __init__( + self, + iteration, + objective, + first_stage_var_shift, + second_stage_var_shift, + dr_var_shift, + dr_polishing_success, + num_violated_cons, + all_sep_problems_solved, + global_separation, + max_violation, + elapsed_time, + ) -> None: ... + def get_log_str(self): ... + def log(self, log_func, **log_func_kwargs) -> None: ... + @staticmethod + def get_log_header_str(): ... + @staticmethod + def log_header(log_func, with_rules: bool = True, **log_func_kwargs) -> None: ... + @staticmethod + def log_header_rule(log_func, fillchar: str = '-', **log_func_kwargs) -> None: ... + +def copy_docstring(source_func): ... diff --git a/stubs/pyomo/contrib/satsolver/__init__.pyi b/stubs/pyomo/contrib/satsolver/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/satsolver/satsolver.pyi b/stubs/pyomo/contrib/satsolver/satsolver.pyi new file mode 100644 index 000000000..a6b050fd1 --- /dev/null +++ b/stubs/pyomo/contrib/satsolver/satsolver.pyi @@ -0,0 +1,49 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.core import Constraint as Constraint +from pyomo.core import NumericLabeler as NumericLabeler +from pyomo.core import SymbolMap as SymbolMap +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.expr import AbsExpression as AbsExpression +from pyomo.core.expr import DivisionExpression as DivisionExpression +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.core.expr import InequalityExpression as InequalityExpression +from pyomo.core.expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr import NegationExpression as NegationExpression +from pyomo.core.expr import PowExpression as PowExpression +from pyomo.core.expr import ProductExpression as ProductExpression +from pyomo.core.expr import SumExpression as SumExpression +from pyomo.core.expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.gdp import Disjunction as Disjunction + +z3: Incomplete +z3_available: Incomplete + +def satisfiable(model, logger=None): ... + +class SMTSatSolver: + variable_label_map: Incomplete + prefix_expr_list: Incomplete + variable_list: Incomplete + bounds_list: Incomplete + expression_list: Incomplete + disjunctions_list: Incomplete + walker: Incomplete + solver: Incomplete + logger: Incomplete + def __init__(self, model=None, logger=None) -> None: ... + def add_var(self, var): ... + def add_expr(self, expression) -> None: ... + def get_SMT_string(self): ... + def get_var_dict(self): ... + def check(self): ... + +class SMT_visitor(StreamBasedExpressionVisitor): + variable_label_map: Incomplete + def __init__(self, varmap) -> None: ... + def exitNode(self, node, data): ... + def beforeChild(self, node, child, child_idx): ... + def finalizeResult(self, node_result): ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/__init__.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.pyi new file mode 100644 index 000000000..fb14a8811 --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/examples/HIV_Transmission.pyi @@ -0,0 +1,20 @@ +from pyomo.contrib.sensitivity_toolbox.sens import ( + sensitivity_calculation as sensitivity_calculation, +) +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.dae.simulator import Simulator as Simulator +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import Constraint as Constraint +from pyomo.environ import Expression as Expression +from pyomo.environ import Objective as Objective +from pyomo.environ import Param as Param +from pyomo.environ import Set as Set +from pyomo.environ import Suffix as Suffix +from pyomo.environ import TransformationFactory as TransformationFactory +from pyomo.environ import Var as Var +from pyomo.environ import exp as exp +from pyomo.environ import value as value + +def create_model(): ... +def initialize_model(m, n_sim, n_nfe, n_ncp) -> None: ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/examples/__init__.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/examples/feedbackController.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/examples/feedbackController.pyi new file mode 100644 index 000000000..540249ded --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/examples/feedbackController.pyi @@ -0,0 +1,19 @@ +from pyomo.contrib.sensitivity_toolbox.sens import ( + sensitivity_calculation as sensitivity_calculation, +) +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.dae.simulator import Simulator as Simulator +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import Constraint as Constraint +from pyomo.environ import Objective as Objective +from pyomo.environ import Param as Param +from pyomo.environ import SolverFactory as SolverFactory +from pyomo.environ import Suffix as Suffix +from pyomo.environ import TransformationFactory as TransformationFactory +from pyomo.environ import Var as Var +from pyomo.environ import value as value + +def create_model(): ... +def initialize_model(m, nfe) -> None: ... +def plot_optimal_solution(m): ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/examples/parameter.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/examples/parameter.pyi new file mode 100644 index 000000000..fe139402f --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/examples/parameter.pyi @@ -0,0 +1,13 @@ +from pyomo.contrib.sensitivity_toolbox.sens import ( + sensitivity_calculation as sensitivity_calculation, +) +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import Constraint as Constraint +from pyomo.environ import NonNegativeReals as NonNegativeReals +from pyomo.environ import Objective as Objective +from pyomo.environ import Param as Param +from pyomo.environ import Var as Var +from pyomo.environ import value as value + +def create_model(): ... +def run_example(print_flag: bool = True): ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.pyi new file mode 100644 index 000000000..77fc9bfc8 --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/examples/parameter_kaug.pyi @@ -0,0 +1,7 @@ +from pyomo.contrib.sensitivity_toolbox.sens import ( + sensitivity_calculation as sensitivity_calculation, +) +from pyomo.environ import * + +def create_model(): ... +def run_example(print_flag: bool = True): ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.pyi new file mode 100644 index 000000000..cf3141188 --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/examples/rangeInequality.pyi @@ -0,0 +1,10 @@ +from pyomo.contrib.sensitivity_toolbox.sens import ( + sensitivity_calculation as sensitivity_calculation, +) +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import Constraint as Constraint +from pyomo.environ import Param as Param +from pyomo.environ import Var as Var +from pyomo.environ import inequality as inequality + +def create_model(): ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.pyi new file mode 100644 index 000000000..f100efac7 --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/examples/rooney_biegler.pyi @@ -0,0 +1,2 @@ +def rooney_biegler_model(data): ... +def rooney_biegler_model_opt(): ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/k_aug.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/k_aug.pyi new file mode 100644 index 000000000..004c223ea --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/k_aug.pyi @@ -0,0 +1,27 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.environ import SolverFactory as SolverFactory + +debug_dir: str +gjh_dir: str +known_files: Incomplete + +class InTempDir: + def __init__(self, suffix=None, prefix=None, dir=None) -> None: ... + def __enter__(self) -> None: ... + def __exit__( + self, + ex_type: type[BaseException] | None, + ex_val: BaseException | None, + ex_bt: types.TracebackType | None, + ) -> None: ... + +class K_augInterface: + data: Incomplete + def __init__(self, k_aug=None, dot_sens=None) -> None: ... + def k_aug(self, model, **kwargs): ... + def dot_sens(self, model, **kwargs): ... + def set_k_aug_options(self, **options) -> None: ... + def set_dot_sens_options(self, **options) -> None: ... diff --git a/stubs/pyomo/contrib/sensitivity_toolbox/sens.pyi b/stubs/pyomo/contrib/sensitivity_toolbox/sens.pyi new file mode 100644 index 000000000..6694fbe84 --- /dev/null +++ b/stubs/pyomo/contrib/sensitivity_toolbox/sens.pyi @@ -0,0 +1,72 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.sorting import sorted_robust as sorted_robust +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.contrib.sensitivity_toolbox.k_aug import InTempDir as InTempDir +from pyomo.contrib.sensitivity_toolbox.k_aug import K_augInterface as K_augInterface +from pyomo.core.expr import ExpressionReplacementVisitor as ExpressionReplacementVisitor +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.environ import Block as Block +from pyomo.environ import ComponentMap as ComponentMap +from pyomo.environ import ComponentUID as ComponentUID +from pyomo.environ import Constraint as Constraint +from pyomo.environ import ConstraintList as ConstraintList +from pyomo.environ import Objective as Objective +from pyomo.environ import Param as Param +from pyomo.environ import Suffix as Suffix +from pyomo.environ import Var as Var +from pyomo.environ import value as value +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverStatus as SolverStatus + +logger: Incomplete + +def sipopt( + instance, + paramSubList, + perturbList, + cloneModel: bool = True, + tee: bool = False, + keepfiles: bool = False, + streamSoln: bool = False, +): ... +def kaug( + instance, + paramSubList, + perturbList, + cloneModel: bool = True, + tee: bool = False, + keepfiles: bool = False, + solver_options=None, + streamSoln: bool = False, +): ... + +class _NotAnIndex: ... + +def sensitivity_calculation( + method, + instance, + paramList, + perturbList, + cloneModel: bool = True, + tee: bool = False, + keepfiles: bool = False, + solver_options=None, +): ... +def get_dsdp(model, theta_names, theta, tee: bool = False): ... +def get_dfds_dcds(model, theta_names, tee: bool = False, solver_options=None): ... +def line_num(file_name, target): ... + +class SensitivityInterface: + model_instance: Incomplete + def __init__(self, instance, clone_model: bool = True) -> None: ... + @classmethod + def get_default_block_name(self): ... + @staticmethod + def get_default_var_name(name): ... + @staticmethod + def get_default_param_name(name): ... + def setup_sensitivity(self, paramList) -> None: ... + def perturb_parameters(self, perturbList) -> None: ... diff --git a/stubs/pyomo/contrib/simplification/__init__.pyi b/stubs/pyomo/contrib/simplification/__init__.pyi new file mode 100644 index 000000000..f07c4d223 --- /dev/null +++ b/stubs/pyomo/contrib/simplification/__init__.pyi @@ -0,0 +1 @@ +from .simplify import Simplifier as Simplifier diff --git a/stubs/pyomo/contrib/simplification/build.pyi b/stubs/pyomo/contrib/simplification/build.pyi new file mode 100644 index 000000000..3b39a912b --- /dev/null +++ b/stubs/pyomo/contrib/simplification/build.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.download import FileDownloader as FileDownloader +from pyomo.common.envvar import PYOMO_CONFIG_DIR as PYOMO_CONFIG_DIR +from pyomo.common.fileutils import find_library as find_library +from pyomo.common.fileutils import this_file_dir as this_file_dir +from pyomo.common.tempfiles import TempfileManager as TempfileManager + +logger: Incomplete + +def build_ginac_library(parallel=None, argv=None, env=None) -> None: ... +def build_ginac_interface(parallel=None, args=None) -> None: ... + +class GiNaCInterfaceBuilder: + def __call__(self, parallel): ... + def skip(self): ... diff --git a/stubs/pyomo/contrib/simplification/ginac/__init__.pyi b/stubs/pyomo/contrib/simplification/ginac/__init__.pyi new file mode 100644 index 000000000..ab638b87f --- /dev/null +++ b/stubs/pyomo/contrib/simplification/ginac/__init__.pyi @@ -0,0 +1,4 @@ +from _typeshed import Incomplete + +interface: Incomplete +interface_available: Incomplete diff --git a/stubs/pyomo/contrib/simplification/plugins.pyi b/stubs/pyomo/contrib/simplification/plugins.pyi new file mode 100644 index 000000000..09727cccc --- /dev/null +++ b/stubs/pyomo/contrib/simplification/plugins.pyi @@ -0,0 +1,5 @@ +from pyomo.common.extensions import ExtensionBuilderFactory as ExtensionBuilderFactory + +from .build import GiNaCInterfaceBuilder as GiNaCInterfaceBuilder + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/simplification/simplify.pyi b/stubs/pyomo/contrib/simplification/simplify.pyi new file mode 100644 index 000000000..23ac2239f --- /dev/null +++ b/stubs/pyomo/contrib/simplification/simplify.pyi @@ -0,0 +1,20 @@ +from _typeshed import Incomplete +from pyomo.common.enums import NamedIntEnum as NamedIntEnum +from pyomo.core.expr.numeric_expr import NumericExpression as NumericExpression +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.sympy_tools import sympy2pyomo_expression as sympy2pyomo_expression +from pyomo.core.expr.sympy_tools import sympyify_expression as sympyify_expression + +def simplify_with_sympy(expr: NumericExpression): ... +def simplify_with_ginac(expr: NumericExpression, ginac_interface): ... + +class Simplifier: + class Mode(NamedIntEnum): + auto = 0 + sympy = 1 + ginac = 2 + + gi: Incomplete + simplify: Incomplete + def __init__(self, suppress_no_ginac_warnings: bool = False, mode: Mode = ...) -> None: ... diff --git a/stubs/pyomo/contrib/solver/__init__.pyi b/stubs/pyomo/contrib/solver/__init__.pyi new file mode 100644 index 000000000..460fc3dc3 --- /dev/null +++ b/stubs/pyomo/contrib/solver/__init__.pyi @@ -0,0 +1 @@ +from pyomo.common.deprecation import moved_module as moved_module diff --git a/stubs/pyomo/contrib/solver/common/__init__.pyi b/stubs/pyomo/contrib/solver/common/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/solver/common/base.pyi b/stubs/pyomo/contrib/solver/common/base.pyi new file mode 100644 index 000000000..c85bc53ed --- /dev/null +++ b/stubs/pyomo/contrib/solver/common/base.pyi @@ -0,0 +1,112 @@ +import types +from typing import Sequence + +from _typeshed import Incomplete +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.enums import IntEnum as IntEnum +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.contrib.solver.common.config import PersistentSolverConfig as PersistentSolverConfig +from pyomo.contrib.solver.common.config import SolverConfig as SolverConfig +from pyomo.contrib.solver.common.results import Results as Results +from pyomo.contrib.solver.common.results import ( + legacy_solution_status_map as legacy_solution_status_map, +) +from pyomo.contrib.solver.common.results import legacy_solver_status_map as legacy_solver_status_map +from pyomo.contrib.solver.common.results import ( + legacy_termination_condition_map as legacy_termination_condition_map, +) +from pyomo.contrib.solver.common.util import get_objective as get_objective +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.label import NumericLabeler as NumericLabeler +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.scripting.solve_config import default_config_block as default_config_block + +class Availability(IntEnum): + FullLicense = 2 + LimitedLicense = 1 + NotFound = 0 + BadVersion = -1 + BadLicense = -2 + NeedsCompiledExtension = -3 + def __bool__(self) -> bool: ... + def __format__(self, format_spec) -> str: ... + +class SolverBase: + CONFIG: Incomplete + name: Incomplete + config: Incomplete + def __init__(self, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + exc_traceback: types.TracebackType | None, + ) -> None: ... + def solve(self, model: BlockData, **kwargs) -> Results: ... + def available(self) -> Availability: ... + def version(self) -> tuple: ... + def is_persistent(self) -> bool: ... + +class PersistentSolverBase(SolverBase): + CONFIG: Incomplete + def __init__(self, **kwds) -> None: ... + def solve(self, model: BlockData, **kwargs) -> Results: ... + def is_persistent(self) -> bool: ... + def set_instance(self, model: BlockData): ... + def set_objective(self, obj: ObjectiveData): ... + def add_variables(self, variables: list[VarData]): ... + def add_parameters(self, params: list[ParamData]): ... + def add_constraints(self, cons: list[ConstraintData]): ... + def add_block(self, block: BlockData): ... + def remove_variables(self, variables: list[VarData]): ... + def remove_parameters(self, params: list[ParamData]): ... + def remove_constraints(self, cons: list[ConstraintData]): ... + def remove_block(self, block: BlockData): ... + def update_variables(self, variables: list[VarData]): ... + def update_parameters(self) -> None: ... + +class LegacySolverWrapper: + options: Incomplete + def __init__(self, **kwargs) -> None: ... + def __enter__(self): ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + exc_traceback: types.TracebackType | None, + ) -> None: ... + def __setattr__(self, attr, value) -> None: ... + config: Incomplete + def solve( + self, + model: BlockData, + tee: bool = False, + load_solutions: bool = True, + logfile: str | None = None, + solnfile: str | None = None, + timelimit: float | None = None, + report_timing: bool = False, + solver_io: str | None = None, + suffixes: Sequence | None = None, + options: dict | None = None, + keepfiles: bool = False, + symbolic_solver_labels: bool = False, + raise_exception_on_nonoptimal_result: bool = False, + solver_options: dict | None = None, + writer_config: dict | None = None, + ): ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self) -> bool: ... + def config_block(self, init: bool = False): ... + def set_options(self, options) -> None: ... diff --git a/stubs/pyomo/contrib/solver/common/config.pyi b/stubs/pyomo/contrib/solver/common/config.pyi new file mode 100644 index 000000000..700eface9 --- /dev/null +++ b/stubs/pyomo/contrib/solver/common/config.pyi @@ -0,0 +1,84 @@ +from typing import TextIO + +from pyomo.common.config import Bool as Bool +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import NonNegativeInt as NonNegativeInt +from pyomo.common.config import Path as Path +from pyomo.common.log import LogStream as LogStream +from pyomo.common.numeric_types import native_logical_types as native_logical_types +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer + +def TextIO_or_Logger(val): ... + +class SolverConfig(ConfigDict): + tee: list[TextIO] + working_dir: Path | None + load_solutions: bool + raise_exception_on_nonoptimal_result: bool + symbolic_solver_labels: bool + timer: HierarchicalTimer | None + threads: int | None + time_limit: float | None + solver_options: ConfigDict + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class BranchAndBoundConfig(SolverConfig): + rel_gap: float | None + abs_gap: float | None + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class AutoUpdateConfig(ConfigDict): + check_for_new_or_removed_constraints: bool + check_for_new_or_removed_vars: bool + check_for_new_or_removed_params: bool + check_for_new_objective: bool + update_constraints: bool + update_vars: bool + update_parameters: bool + update_named_expressions: bool + update_objective: bool + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class PersistentSolverConfig(SolverConfig): + auto_updates: AutoUpdateConfig + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class PersistentBranchAndBoundConfig(PersistentSolverConfig, BranchAndBoundConfig): + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... diff --git a/stubs/pyomo/contrib/solver/common/factory.pyi b/stubs/pyomo/contrib/solver/common/factory.pyi new file mode 100644 index 000000000..b46112b8e --- /dev/null +++ b/stubs/pyomo/contrib/solver/common/factory.pyi @@ -0,0 +1,9 @@ +from _typeshed import Incomplete +from pyomo.common.factory import Factory as Factory +from pyomo.contrib.solver.common.base import LegacySolverWrapper as LegacySolverWrapper +from pyomo.opt.base.solvers import LegacySolverFactory as LegacySolverFactory + +class SolverFactoryClass(Factory): + def register(self, name, legacy_name=None, doc=None): ... + +SolverFactory: Incomplete diff --git a/stubs/pyomo/contrib/solver/common/persistent.pyi b/stubs/pyomo/contrib/solver/common/persistent.pyi new file mode 100644 index 000000000..ecebdf69c --- /dev/null +++ b/stubs/pyomo/contrib/solver/common/persistent.pyi @@ -0,0 +1,42 @@ +import abc + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.solver.common.results import Results as Results +from pyomo.contrib.solver.common.util import ( + collect_vars_and_named_exprs as collect_vars_and_named_exprs, +) +from pyomo.contrib.solver.common.util import get_objective as get_objective +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.param import Param as Param +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraint as SOSConstraint +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +class PersistentSolverUtils(abc.ABC, metaclass=abc.ABCMeta): + def __init__(self, treat_fixed_vars_as_params: bool = True) -> None: ... + config: Incomplete + def set_instance(self, model) -> None: ... + def add_variables(self, variables: list[VarData]): ... + def add_parameters(self, params: list[ParamData]): ... + def add_constraints(self, cons: list[ConstraintData]): ... + def add_sos_constraints(self, cons: list[SOSConstraintData]): ... + def set_objective(self, obj: ObjectiveData): ... + def add_block(self, block) -> None: ... + def remove_constraints(self, cons: list[ConstraintData]): ... + def remove_sos_constraints(self, cons: list[SOSConstraintData]): ... + def remove_variables(self, variables: list[VarData]): ... + def remove_parameters(self, params: list[ParamData]): ... + def remove_block(self, block) -> None: ... + def update_variables(self, variables: list[VarData]): ... + @abc.abstractmethod + def update_parameters(self): ... + def update(self, timer: HierarchicalTimer = None): ... + +class PersistentSolverMixin: + def solve(self, model, **kwds) -> Results: ... diff --git a/stubs/pyomo/contrib/solver/common/results.pyi b/stubs/pyomo/contrib/solver/common/results.pyi new file mode 100644 index 000000000..be554b8f6 --- /dev/null +++ b/stubs/pyomo/contrib/solver/common/results.pyi @@ -0,0 +1,63 @@ +import enum + +from _typeshed import Incomplete +from pyomo.common.config import ADVANCED_OPTION as ADVANCED_OPTION +from pyomo.common.config import DEVELOPER_OPTION as DEVELOPER_OPTION +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import IsInstance as IsInstance +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import NonNegativeInt as NonNegativeInt + +class TerminationCondition(enum.Enum): + convergenceCriteriaSatisfied = 0 + maxTimeLimit = 1 + iterationLimit = 2 + objectiveLimit = 3 + minStepLength = 4 + unbounded = 5 + provenInfeasible = 6 + locallyInfeasible = 7 + infeasibleOrUnbounded = 8 + error = 9 + interrupted = 10 + licensingProblems = 11 + emptyModel = 12 + unknown = 42 + +class SolutionStatus(enum.Enum): + noSolution = 0 + infeasible = 10 + feasible = 20 + optimal = 30 + +class Results(ConfigDict): + solution_loader: Incomplete + termination_condition: TerminationCondition + solution_status: SolutionStatus + incumbent_objective: float | None + objective_bound: float | None + solver_name: str | None + solver_version: tuple[int, ...] | None + iteration_count: int | None + timing_info: ConfigDict + extra_info: ConfigDict + solver_config: ConfigDict + solver_log: str + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + def display( + self, content_filter=None, indent_spacing: int = 2, ostream=None, visibility: int = 0 + ): ... + +legacy_termination_condition_map: Incomplete +legacy_solver_status_map: Incomplete + +def legacy_solution_status_map(results): ... diff --git a/stubs/pyomo/contrib/solver/common/solution_loader.pyi b/stubs/pyomo/contrib/solver/common/solution_loader.pyi new file mode 100644 index 000000000..983480434 --- /dev/null +++ b/stubs/pyomo/contrib/solver/common/solution_loader.pyi @@ -0,0 +1,28 @@ +from typing import Mapping, NoReturn, Sequence + +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +class SolutionLoaderBase: + def load_vars(self, vars_to_load: Sequence[VarData] | None = None) -> NoReturn: ... + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + +class PersistentSolutionLoader(SolutionLoaderBase): + def __init__(self, solver) -> None: ... + def get_primals(self, vars_to_load=None): ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def invalidate(self) -> None: ... diff --git a/stubs/pyomo/contrib/solver/common/util.pyi b/stubs/pyomo/contrib/solver/common/util.pyi new file mode 100644 index 000000000..eb509a81c --- /dev/null +++ b/stubs/pyomo/contrib/solver/common/util.pyi @@ -0,0 +1,35 @@ +from _typeshed import Incomplete +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.expr.visitor import ExpressionValueVisitor as ExpressionValueVisitor +from pyomo.core.expr.visitor import nonpyomo_leaf_types as nonpyomo_leaf_types + +class NoFeasibleSolutionError(PyomoException): + def __init__(self) -> None: ... + +class NoOptimalSolutionError(PyomoException): + def __init__(self) -> None: ... + +class NoSolutionError(PyomoException): + def __init__(self) -> None: ... + +class NoDualsError(PyomoException): + def __init__(self) -> None: ... + +class NoReducedCostsError(PyomoException): + def __init__(self) -> None: ... + +class IncompatibleModelError(PyomoException): + def __init__(self) -> None: ... + +def get_objective(block): ... + +class _VarAndNamedExprCollector(ExpressionValueVisitor): + named_expressions: Incomplete + variables: Incomplete + fixed_vars: Incomplete + def __init__(self) -> None: ... + def visit(self, node, values) -> None: ... + def visiting_potential_leaf(self, node): ... + +def collect_vars_and_named_exprs(expr): ... diff --git a/stubs/pyomo/contrib/solver/plugins.pyi b/stubs/pyomo/contrib/solver/plugins.pyi new file mode 100644 index 000000000..8ce770db2 --- /dev/null +++ b/stubs/pyomo/contrib/solver/plugins.pyi @@ -0,0 +1,7 @@ +from .common.factory import SolverFactory as SolverFactory +from .solvers.gurobi_direct import GurobiDirect as GurobiDirect +from .solvers.gurobi_persistent import GurobiPersistent as GurobiPersistent +from .solvers.highs import Highs as Highs +from .solvers.ipopt import Ipopt as Ipopt + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/solver/solvers/__init__.pyi b/stubs/pyomo/contrib/solver/solvers/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/solver/solvers/gurobi_direct.pyi b/stubs/pyomo/contrib/solver/solvers/gurobi_direct.pyi new file mode 100644 index 000000000..6fad352e8 --- /dev/null +++ b/stubs/pyomo/contrib/solver/solvers/gurobi_direct.pyi @@ -0,0 +1,66 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.enums import ObjectiveSense as ObjectiveSense +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.errors import MouseTrap as MouseTrap +from pyomo.common.shutdown import python_is_shutting_down as python_is_shutting_down +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.solver.common.base import Availability as Availability +from pyomo.contrib.solver.common.base import SolverBase as SolverBase +from pyomo.contrib.solver.common.config import BranchAndBoundConfig as BranchAndBoundConfig +from pyomo.contrib.solver.common.results import Results as Results +from pyomo.contrib.solver.common.results import SolutionStatus as SolutionStatus +from pyomo.contrib.solver.common.results import TerminationCondition as TerminationCondition +from pyomo.contrib.solver.common.solution_loader import SolutionLoaderBase as SolutionLoaderBase +from pyomo.contrib.solver.common.util import IncompatibleModelError as IncompatibleModelError +from pyomo.contrib.solver.common.util import NoDualsError as NoDualsError +from pyomo.contrib.solver.common.util import NoFeasibleSolutionError as NoFeasibleSolutionError +from pyomo.contrib.solver.common.util import NoOptimalSolutionError as NoOptimalSolutionError +from pyomo.contrib.solver.common.util import NoReducedCostsError as NoReducedCostsError +from pyomo.contrib.solver.common.util import NoSolutionError as NoSolutionError +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn.plugins.standard_form import ( + LinearStandardFormCompiler as LinearStandardFormCompiler, +) + +gurobipy: Incomplete +gurobipy_available: Incomplete + +class GurobiConfigMixin: + use_mipstart: bool + def __init__(self) -> None: ... + +class GurobiConfig(BranchAndBoundConfig, GurobiConfigMixin): + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class GurobiDirectSolutionLoader(SolutionLoaderBase): + def __init__(self, grb_model, grb_cons, grb_vars, pyo_cons, pyo_vars, pyo_obj) -> None: ... + def __del__(self) -> None: ... + def load_vars(self, vars_to_load=None, solution_number: int = 0): ... + def get_primals(self, vars_to_load=None, solution_number: int = 0): ... + def get_duals(self, cons_to_load=None): ... + def get_reduced_costs(self, vars_to_load=None): ... + +class GurobiSolverMixin: + def available(self): ... + def version(self): ... + +class GurobiDirect(GurobiSolverMixin, SolverBase): + CONFIG: Incomplete + def __init__(self, **kwds) -> None: ... + @staticmethod + def release_license() -> None: ... + def __del__(self) -> None: ... + def solve(self, model, **kwds) -> Results: ... diff --git a/stubs/pyomo/contrib/solver/solvers/gurobi_persistent.pyi b/stubs/pyomo/contrib/solver/solvers/gurobi_persistent.pyi new file mode 100644 index 000000000..0e8cb2d78 --- /dev/null +++ b/stubs/pyomo/contrib/solver/solvers/gurobi_persistent.pyi @@ -0,0 +1,163 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.collections import OrderedSet as OrderedSet +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.shutdown import python_is_shutting_down as python_is_shutting_down +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.solver.common.base import Availability as Availability +from pyomo.contrib.solver.common.base import PersistentSolverBase as PersistentSolverBase +from pyomo.contrib.solver.common.config import ( + PersistentBranchAndBoundConfig as PersistentBranchAndBoundConfig, +) +from pyomo.contrib.solver.common.persistent import PersistentSolverMixin as PersistentSolverMixin +from pyomo.contrib.solver.common.persistent import PersistentSolverUtils as PersistentSolverUtils +from pyomo.contrib.solver.common.results import Results as Results +from pyomo.contrib.solver.common.results import SolutionStatus as SolutionStatus +from pyomo.contrib.solver.common.results import TerminationCondition as TerminationCondition +from pyomo.contrib.solver.common.solution_loader import ( + PersistentSolutionLoader as PersistentSolutionLoader, +) +from pyomo.contrib.solver.common.util import IncompatibleModelError as IncompatibleModelError +from pyomo.contrib.solver.common.util import NoDualsError as NoDualsError +from pyomo.contrib.solver.common.util import NoFeasibleSolutionError as NoFeasibleSolutionError +from pyomo.contrib.solver.common.util import NoOptimalSolutionError as NoOptimalSolutionError +from pyomo.contrib.solver.common.util import NoReducedCostsError as NoReducedCostsError +from pyomo.contrib.solver.common.util import NoSolutionError as NoSolutionError +from pyomo.contrib.solver.solvers.gurobi_direct import GurobiConfigMixin as GurobiConfigMixin +from pyomo.contrib.solver.solvers.gurobi_direct import GurobiSolverMixin as GurobiSolverMixin +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numeric_expr import NPV_MaxExpression as NPV_MaxExpression +from pyomo.core.expr.numeric_expr import NPV_MinExpression as NPV_MinExpression +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete +gurobipy: Incomplete +gurobipy_available: Incomplete + +class GurobiConfig(PersistentBranchAndBoundConfig, GurobiConfigMixin): + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class GurobiSolutionLoader(PersistentSolutionLoader): + def load_vars(self, vars_to_load=None, solution_number: int = 0) -> None: ... + def get_primals(self, vars_to_load=None, solution_number: int = 0): ... + +class _MutableLowerBound: + var: Incomplete + expr: Incomplete + def __init__(self, expr) -> None: ... + def update(self) -> None: ... + +class _MutableUpperBound: + var: Incomplete + expr: Incomplete + def __init__(self, expr) -> None: ... + def update(self) -> None: ... + +class _MutableLinearCoefficient: + expr: Incomplete + var: Incomplete + con: Incomplete + gurobi_model: Incomplete + def __init__(self) -> None: ... + def update(self) -> None: ... + +class _MutableRangeConstant: + lhs_expr: Incomplete + rhs_expr: Incomplete + con: Incomplete + slack_name: Incomplete + gurobi_model: Incomplete + def __init__(self) -> None: ... + def update(self) -> None: ... + +class _MutableConstant: + expr: Incomplete + con: Incomplete + def __init__(self) -> None: ... + def update(self) -> None: ... + +class _MutableQuadraticConstraint: + con: Incomplete + gurobi_model: Incomplete + constant: Incomplete + last_constant_value: Incomplete + linear_coefs: Incomplete + last_linear_coef_values: Incomplete + quadratic_coefs: Incomplete + last_quadratic_coef_values: Incomplete + def __init__( + self, gurobi_model, gurobi_con, constant, linear_coefs, quadratic_coefs + ) -> None: ... + def get_updated_expression(self): ... + def get_updated_rhs(self): ... + +class _MutableObjective: + gurobi_model: Incomplete + constant: Incomplete + linear_coefs: Incomplete + quadratic_coefs: Incomplete + last_quadratic_coef_values: Incomplete + def __init__(self, gurobi_model, constant, linear_coefs, quadratic_coefs) -> None: ... + def get_updated_expression(self): ... + +class _MutableQuadraticCoefficient: + expr: Incomplete + var1: Incomplete + var2: Incomplete + def __init__(self) -> None: ... + +class GurobiPersistent( + GurobiSolverMixin, PersistentSolverMixin, PersistentSolverUtils, PersistentSolverBase +): + CONFIG: Incomplete + def __init__(self, **kwds) -> None: ... + def release_license(self) -> None: ... + def __del__(self) -> None: ... + @property + def symbol_map(self): ... + def set_instance(self, model) -> None: ... + def update_parameters(self) -> None: ... + def update(self, timer: HierarchicalTimer = None): ... + def get_model_attr(self, attr): ... + def write(self, filename) -> None: ... + def set_linear_constraint_attr(self, con, attr, val) -> None: ... + def set_var_attr(self, var, attr, val) -> None: ... + def get_var_attr(self, var, attr): ... + def get_linear_constraint_attr(self, con, attr): ... + def get_sos_attr(self, con, attr): ... + def get_quadratic_constraint_attr(self, con, attr): ... + def set_gurobi_param(self, param, val) -> None: ... + def get_gurobi_param_info(self, param): ... + def set_callback(self, func=None) -> None: ... + def cbCut(self, con) -> None: ... + def cbGet(self, what): ... + def cbGetNodeRel(self, variables) -> None: ... + def cbGetSolution(self, variables) -> None: ... + def cbLazy(self, con) -> None: ... + def cbSetSolution(self, variables, solution) -> None: ... + def cbUseSolution(self): ... + def reset(self) -> None: ... diff --git a/stubs/pyomo/contrib/solver/solvers/highs.pyi b/stubs/pyomo/contrib/solver/solvers/highs.pyi new file mode 100644 index 000000000..1c9c9dcbc --- /dev/null +++ b/stubs/pyomo/contrib/solver/solvers/highs.pyi @@ -0,0 +1,91 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.contrib.solver.common.base import Availability as Availability +from pyomo.contrib.solver.common.base import PersistentSolverBase as PersistentSolverBase +from pyomo.contrib.solver.common.config import ( + PersistentBranchAndBoundConfig as PersistentBranchAndBoundConfig, +) +from pyomo.contrib.solver.common.persistent import PersistentSolverMixin as PersistentSolverMixin +from pyomo.contrib.solver.common.persistent import PersistentSolverUtils as PersistentSolverUtils +from pyomo.contrib.solver.common.results import Results as Results +from pyomo.contrib.solver.common.results import SolutionStatus as SolutionStatus +from pyomo.contrib.solver.common.results import TerminationCondition as TerminationCondition +from pyomo.contrib.solver.common.solution_loader import ( + PersistentSolutionLoader as PersistentSolutionLoader, +) +from pyomo.contrib.solver.common.util import IncompatibleModelError as IncompatibleModelError +from pyomo.contrib.solver.common.util import NoDualsError as NoDualsError +from pyomo.contrib.solver.common.util import NoFeasibleSolutionError as NoFeasibleSolutionError +from pyomo.contrib.solver.common.util import NoOptimalSolutionError as NoOptimalSolutionError +from pyomo.contrib.solver.common.util import NoReducedCostsError as NoReducedCostsError +from pyomo.contrib.solver.common.util import NoSolutionError as NoSolutionError +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numeric_expr import NPV_MaxExpression as NPV_MaxExpression +from pyomo.core.expr.numeric_expr import NPV_MinExpression as NPV_MinExpression +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete +highspy: Incomplete +highspy_available: Incomplete + +class _MutableVarBounds: + pyomo_var_id: Incomplete + lower_expr: Incomplete + upper_expr: Incomplete + var_map: Incomplete + highs: Incomplete + def __init__(self, lower_expr, upper_expr, pyomo_var_id, var_map, highs) -> None: ... + def update(self) -> None: ... + +class _MutableLinearCoefficient: + expr: Incomplete + highs: Incomplete + pyomo_var_id: Incomplete + pyomo_con: Incomplete + con_map: Incomplete + var_map: Incomplete + def __init__(self, pyomo_con, pyomo_var_id, con_map, var_map, expr, highs) -> None: ... + def update(self) -> None: ... + +class _MutableObjectiveCoefficient: + expr: Incomplete + highs: Incomplete + pyomo_var_id: Incomplete + var_map: Incomplete + def __init__(self, pyomo_var_id, var_map, expr, highs) -> None: ... + def update(self) -> None: ... + +class _MutableObjectiveOffset: + expr: Incomplete + highs: Incomplete + def __init__(self, expr, highs) -> None: ... + def update(self) -> None: ... + +class _MutableConstraintBounds: + lower_expr: Incomplete + upper_expr: Incomplete + con: Incomplete + con_map: Incomplete + highs: Incomplete + def __init__(self, lower_expr, upper_expr, pyomo_con, con_map, highs) -> None: ... + def update(self) -> None: ... + +class Highs(PersistentSolverMixin, PersistentSolverUtils, PersistentSolverBase): + CONFIG: Incomplete + def __init__(self, **kwds) -> None: ... + def available(self): ... + def version(self): ... + def set_instance(self, model) -> None: ... + def update_parameters(self) -> None: ... diff --git a/stubs/pyomo/contrib/solver/solvers/ipopt.pyi b/stubs/pyomo/contrib/solver/solvers/ipopt.pyi new file mode 100644 index 000000000..838c9c456 --- /dev/null +++ b/stubs/pyomo/contrib/solver/solvers/ipopt.pyi @@ -0,0 +1,62 @@ +from typing import Mapping, Sequence + +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.common.timing import HierarchicalTimer as HierarchicalTimer +from pyomo.contrib.solver.common.base import Availability as Availability +from pyomo.contrib.solver.common.base import SolverBase as SolverBase +from pyomo.contrib.solver.common.config import SolverConfig as SolverConfig +from pyomo.contrib.solver.common.results import Results as Results +from pyomo.contrib.solver.common.results import SolutionStatus as SolutionStatus +from pyomo.contrib.solver.common.results import TerminationCondition as TerminationCondition +from pyomo.contrib.solver.common.util import NoFeasibleSolutionError as NoFeasibleSolutionError +from pyomo.contrib.solver.common.util import NoOptimalSolutionError as NoOptimalSolutionError +from pyomo.contrib.solver.common.util import NoSolutionError as NoSolutionError +from pyomo.contrib.solver.solvers.sol_reader import SolSolutionLoader as SolSolutionLoader +from pyomo.contrib.solver.solvers.sol_reader import parse_sol_file as parse_sol_file +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import replace_expressions as replace_expressions +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn.plugins.nl_writer import NLWriter as NLWriter +from pyomo.repn.plugins.nl_writer import NLWriterInfo as NLWriterInfo +from pyomo.solvers.amplfunc_merge import amplfunc_merge as amplfunc_merge + +logger: Incomplete + +class IpoptConfig(SolverConfig): + executable: Executable + writer_config: ConfigDict + def __init__( + self, + description=None, + doc=None, + implicit: bool = False, + implicit_domain=None, + visibility: int = 0, + ) -> None: ... + +class IpoptSolutionLoader(SolSolutionLoader): + def get_reduced_costs( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + +ipopt_command_line_options: Incomplete + +class Ipopt(SolverBase): + CONFIG: Incomplete + def __init__(self, **kwds) -> None: ... + def available(self, config=None): ... + def version(self, config=None): ... + def has_linear_solver(self, linear_solver): ... + def solve(self, model, **kwds): ... diff --git a/stubs/pyomo/contrib/solver/solvers/sol_reader.pyi b/stubs/pyomo/contrib/solver/solvers/sol_reader.pyi new file mode 100644 index 000000000..d86b5edbd --- /dev/null +++ b/stubs/pyomo/contrib/solver/solvers/sol_reader.pyi @@ -0,0 +1,40 @@ +import io +from typing import Any, Mapping, NoReturn, Sequence + +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.contrib.solver.common.results import Results as Results +from pyomo.contrib.solver.common.results import SolutionStatus as SolutionStatus +from pyomo.contrib.solver.common.results import TerminationCondition as TerminationCondition +from pyomo.contrib.solver.common.solution_loader import SolutionLoaderBase as SolutionLoaderBase +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr import value as value +from pyomo.core.expr.visitor import replace_expressions as replace_expressions +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.repn.plugins.nl_writer import NLWriterInfo as NLWriterInfo + +class SolFileData: + primals: list[float] + duals: list[float] + var_suffixes: dict[str, dict[int, Any]] + con_suffixes: dict[str, dict[Any]] + obj_suffixes: dict[str, dict[int, Any]] + problem_suffixes: dict[str, list[Any]] + other: None + def __init__(self) -> None: ... + +class SolSolutionLoader(SolutionLoaderBase): + def __init__(self, sol_data: SolFileData, nl_info: NLWriterInfo) -> None: ... + def load_vars(self, vars_to_load: Sequence[VarData] | None = None) -> NoReturn: ... + def get_primals( + self, vars_to_load: Sequence[VarData] | None = None + ) -> Mapping[VarData, float]: ... + def get_duals( + self, cons_to_load: Sequence[ConstraintData] | None = None + ) -> dict[ConstraintData, float]: ... + +def parse_sol_file( + sol_file: io.TextIOBase, nl_info: NLWriterInfo, result: Results +) -> tuple[Results, SolFileData]: ... diff --git a/stubs/pyomo/contrib/trustregion/TRF.pyi b/stubs/pyomo/contrib/trustregion/TRF.pyi new file mode 100644 index 000000000..812a56f60 --- /dev/null +++ b/stubs/pyomo/contrib/trustregion/TRF.pyi @@ -0,0 +1,39 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.config import Bool as Bool +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import PositiveFloat as PositiveFloat +from pyomo.common.config import PositiveInt as PositiveInt +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.contrib.trustregion.filter import Filter as Filter +from pyomo.contrib.trustregion.filter import FilterElement as FilterElement +from pyomo.contrib.trustregion.interface import TRFInterface as TRFInterface +from pyomo.contrib.trustregion.util import IterationLogger as IterationLogger +from pyomo.core.base.range import NumericRange as NumericRange +from pyomo.opt import SolverFactory as SolverFactory + +logger: Incomplete +__version__: Incomplete + +def trust_region_method(model, decision_variables, ext_fcn_surrogate_map_rule, config): ... + +class TrustRegionSolver: + CONFIG: Incomplete + config: Incomplete + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def version(self): ... + def license_is_valid(self): ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + def solve( + self, model, degrees_of_freedom_variables, ext_fcn_surrogate_map_rule=None, **kwds + ): ... diff --git a/stubs/pyomo/contrib/trustregion/__init__.pyi b/stubs/pyomo/contrib/trustregion/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/trustregion/examples/__init__.pyi b/stubs/pyomo/contrib/trustregion/examples/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/trustregion/examples/example1.pyi b/stubs/pyomo/contrib/trustregion/examples/example1.pyi new file mode 100644 index 000000000..6705516eb --- /dev/null +++ b/stubs/pyomo/contrib/trustregion/examples/example1.pyi @@ -0,0 +1,15 @@ +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import Constraint as Constraint +from pyomo.environ import ExternalFunction as ExternalFunction +from pyomo.environ import Objective as Objective +from pyomo.environ import Reals as Reals +from pyomo.environ import Var as Var +from pyomo.environ import cos as cos +from pyomo.environ import sin as sin +from pyomo.environ import sqrt as sqrt +from pyomo.opt import SolverFactory as SolverFactory + +def ext_fcn(a, b): ... +def grad_ext_fcn(args, fixed): ... +def create_model(): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/trustregion/examples/example2.pyi b/stubs/pyomo/contrib/trustregion/examples/example2.pyi new file mode 100644 index 000000000..9c229c397 --- /dev/null +++ b/stubs/pyomo/contrib/trustregion/examples/example2.pyi @@ -0,0 +1,11 @@ +from pyomo.environ import ConcreteModel as ConcreteModel +from pyomo.environ import ExternalFunction as ExternalFunction +from pyomo.environ import Objective as Objective +from pyomo.environ import Var as Var +from pyomo.opt import SolverFactory as SolverFactory + +def ext_fcn(a, b): ... +def grad_ext_fcn(args, fixed): ... +def create_model(): ... +def basis_rule(component, ef_expr): ... +def main() -> None: ... diff --git a/stubs/pyomo/contrib/trustregion/filter.pyi b/stubs/pyomo/contrib/trustregion/filter.pyi new file mode 100644 index 000000000..ba36ff06d --- /dev/null +++ b/stubs/pyomo/contrib/trustregion/filter.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete + +class FilterElement: + objective: Incomplete + feasible: Incomplete + def __init__(self, objective, feasible) -> None: ... + def compare(self, filterElement): ... + +class Filter: + TrustRegionFilter: Incomplete + def __init__(self) -> None: ... + def addToFilter(self, filterElement) -> None: ... + def isAcceptable(self, filterElement, maximum_feasibility): ... diff --git a/stubs/pyomo/contrib/trustregion/interface.pyi b/stubs/pyomo/contrib/trustregion/interface.pyi new file mode 100644 index 000000000..d281fcff7 --- /dev/null +++ b/stubs/pyomo/contrib/trustregion/interface.pyi @@ -0,0 +1,57 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.trustregion.util import maxIgnoreNone as maxIgnoreNone +from pyomo.contrib.trustregion.util import minIgnoreNone as minIgnoreNone +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import ExternalFunction as ExternalFunction +from pyomo.core import Objective as Objective +from pyomo.core import Param as Param +from pyomo.core import Set as Set +from pyomo.core import VarList as VarList +from pyomo.core import maximize as maximize +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.expr.calculus.derivatives import differentiate as differentiate +from pyomo.core.expr.numeric_expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.visitor import ExpressionReplacementVisitor as ExpressionReplacementVisitor +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import check_optimal_termination as check_optimal_termination + +logger: Incomplete + +class EFReplacement(ExpressionReplacementVisitor): + trfData: Incomplete + efSet: Incomplete + def __init__(self, trfData, efSet) -> None: ... + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, data): ... + +class TRFInterface: + original_model: Incomplete + config: Incomplete + model: Incomplete + decision_variables: Incomplete + data: Incomplete + basis_expression_rule: Incomplete + efSet: Incomplete + solver: Incomplete + def __init__(self, model, decision_variables, ext_fcn_surrogate_map_rule, config) -> None: ... + def replaceEF(self, expr): ... + degrees_of_freedom: Incomplete + def replaceExternalFunctionsWithVariables(self) -> None: ... + def createConstraints(self): ... + def getCurrentDecisionVariableValues(self): ... + def updateDecisionVariableBounds(self, radius) -> None: ... + def updateSurrogateModel(self) -> None: ... + def getCurrentModelState(self): ... + def calculateFeasibility(self): ... + def calculateStepSizeInfNorm(self, original_values, new_values): ... + initial_decision_bounds: Incomplete + def initializeProblem(self): ... + def solveModel(self): ... + def rejectStep(self) -> None: ... diff --git a/stubs/pyomo/contrib/trustregion/plugins.pyi b/stubs/pyomo/contrib/trustregion/plugins.pyi new file mode 100644 index 000000000..3fa961402 --- /dev/null +++ b/stubs/pyomo/contrib/trustregion/plugins.pyi @@ -0,0 +1,5 @@ +from _typeshed import Incomplete + +logger: Incomplete + +def load() -> None: ... diff --git a/stubs/pyomo/contrib/trustregion/util.pyi b/stubs/pyomo/contrib/trustregion/util.pyi new file mode 100644 index 000000000..e2b2d04f1 --- /dev/null +++ b/stubs/pyomo/contrib/trustregion/util.pyi @@ -0,0 +1,31 @@ +from _typeshed import Incomplete + +logger: Incomplete + +def minIgnoreNone(a, b): ... +def maxIgnoreNone(a, b): ... + +class IterationRecord: + iteration: Incomplete + feasibility: Incomplete + objectiveValue: Incomplete + trustRadius: Incomplete + stepNorm: Incomplete + def __init__( + self, iteration, feasibility=None, objectiveValue=None, trustRadius=None, stepNorm=None + ) -> None: ... + def detailLogger(self) -> None: ... + def verboseLogger(self) -> None: ... + +class IterationLogger: + iterations: Incomplete + def __init__(self) -> None: ... + iterrecord: Incomplete + def newIteration( + self, iteration, feasibility, objectiveValue, trustRadius, stepNorm + ) -> None: ... + def updateIteration( + self, feasibility=None, objectiveValue=None, trustRadius=None, stepNorm=None + ) -> None: ... + def logIteration(self) -> None: ... + def printIteration(self) -> None: ... diff --git a/stubs/pyomo/contrib/viewer/__init__.pyi b/stubs/pyomo/contrib/viewer/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/contrib/viewer/model_browser.pyi b/stubs/pyomo/contrib/viewer/model_browser.pyi new file mode 100644 index 000000000..af069f4e6 --- /dev/null +++ b/stubs/pyomo/contrib/viewer/model_browser.pyi @@ -0,0 +1,64 @@ +import pyomo.contrib.viewer.qt as myqt +from _typeshed import Incomplete +from pyomo.common.fileutils import this_file_dir as this_file_dir +from pyomo.common.flags import building_documentation as building_documentation +from pyomo.contrib.viewer.report import get_residual as get_residual +from pyomo.contrib.viewer.report import value_no_exception as value_no_exception +from pyomo.core.base.param import ParamData as ParamData +from pyomo.environ import Block as Block +from pyomo.environ import BooleanVar as BooleanVar +from pyomo.environ import Constraint as Constraint +from pyomo.environ import Expression as Expression +from pyomo.environ import Param as Param +from pyomo.environ import Var as Var +from pyomo.environ import units as units +from pyomo.environ import value as value + +class _ModelBrowserUI: ... +class _ModelBrowser: ... + +mypath: Incomplete + +class LineEditCreator(myqt.QItemEditorCreatorBase): + def createWidget(self, parent): ... + +class NumberDelegate(myqt.QItemDelegate): + def __init__(self, parent) -> None: ... + def setModelData(self, editor, model, index) -> None: ... + +class ModelBrowser(_ModelBrowser, _ModelBrowserUI): + ui_data: Incomplete + datmodel: Incomplete + def __init__(self, ui_data, standard: str = 'Var') -> None: ... + def refresh(self) -> None: ... + def update_model(self) -> None: ... + +class ComponentDataItem: + ui_data: Incomplete + data: Incomplete + parent: Incomplete + children: Incomplete + ids: Incomplete + get_callback: Incomplete + set_callback: Incomplete + def __init__(self, parent, o, ui_data) -> None: ... + def add_child(self, o): ... + def get(self, a): ... + def set(self, a, val): ... + +class ComponentDataModel(myqt.QAbstractItemModel): + column: Incomplete + ui_data: Incomplete + components: Incomplete + def __init__( + self, parent, ui_data, columns=['name', 'value'], components=..., editable=[] + ) -> None: ... + rootItems: Incomplete + def update_model(self) -> None: ... + def parent(self, index): ... + def index(self, row, column, parent=...): ... + def columnCount(self, parent=...): ... + def rowCount(self, parent=...): ... + def data(self, index=..., role=...): ... + def headerData(self, i, orientation, role=...): ... + def flags(self, index=...): ... diff --git a/stubs/pyomo/contrib/viewer/model_select.pyi b/stubs/pyomo/contrib/viewer/model_select.pyi new file mode 100644 index 000000000..23c5c66c3 --- /dev/null +++ b/stubs/pyomo/contrib/viewer/model_select.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.fileutils import this_file_dir as this_file_dir +from pyomo.common.flags import building_documentation as building_documentation + +class _ModelSelectUI: ... +class _ModelSelect: ... + +mypath: Incomplete + +class ModelSelect(_ModelSelect, _ModelSelectUI): + ui_data: Incomplete + def __init__(self, parent, ui_data) -> None: ... + def select_model(self) -> None: ... + models: Incomplete + def update_models(self) -> None: ... diff --git a/stubs/pyomo/contrib/viewer/pyomo_viewer.pyi b/stubs/pyomo/contrib/viewer/pyomo_viewer.pyi new file mode 100644 index 000000000..7f2897e1d --- /dev/null +++ b/stubs/pyomo/contrib/viewer/pyomo_viewer.pyi @@ -0,0 +1,21 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import UnavailableClass as UnavailableClass +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.scripting.pyomo_parser import add_subparser as add_subparser + +qtconsole_app: Incomplete +qtconsole_available: Incomplete + +class QtApp: + def active_widget_name(self): ... + def show_ui(self) -> None: ... + def hide_ui(self) -> None: ... + def run_script(self, checked: bool = False, filename=None) -> None: ... + def kernel_pyomo_init(self, kc) -> None: ... + run_script_act: Incomplete + show_ui_act: Incomplete + hide_ui_act: Incomplete + def init_qt_elements(self) -> None: ... + def new_frontend_master(self): ... + +def main(*args) -> None: ... diff --git a/stubs/pyomo/contrib/viewer/qt.pyi b/stubs/pyomo/contrib/viewer/qt.pyi new file mode 100644 index 000000000..d2ea133da --- /dev/null +++ b/stubs/pyomo/contrib/viewer/qt.pyi @@ -0,0 +1,53 @@ +import enum + +from _typeshed import Incomplete +from pyomo.common.flags import building_documentation as building_documentation +from PyQt5 import uic as uic +from PyQt5.QtWidgets import QAction as QAction + +supported: Incomplete +import_errors: Incomplete +available: bool +qt_package: Incomplete +QtWidgets: Incomplete +QtCore: Incomplete +QtGui: Incomplete +available = module_str + +class Qt: + class ItemDataRole(enum.Enum): + EditRole = 1 + DisplayRole = 2 + ToolTipRole = 3 + ForegroundRole = 4 + +class QtCore: + class QModelIndex: ... + Qt = Qt + +class QAbstractItemModel: + def __init__(*args, **kwargs) -> None: ... + +class QAbstractTableModel: + def __init__(*args, **kwargs) -> None: ... + +class QItemEditorCreatorBase: ... +class QItemDelegate: ... + +QAbstractItemView: Incomplete +QFileDialog: Incomplete +QMainWindow: Incomplete +QMdiArea: Incomplete +QApplication: Incomplete +QTableWidgetItem: Incomplete +QStatusBar: Incomplete +QLineEdit: Incomplete +QItemEditorFactory: Incomplete +QStyledItemDelegate: Incomplete +QComboBox: Incomplete +QMessageBox: Incomplete +QColor: Incomplete +QMetaType: Incomplete + +class QAbstractItemModel: ... +class QAbstractTableModel: ... diff --git a/stubs/pyomo/contrib/viewer/report.pyi b/stubs/pyomo/contrib/viewer/report.pyi new file mode 100644 index 000000000..53531ecdc --- /dev/null +++ b/stubs/pyomo/contrib/viewer/report.pyi @@ -0,0 +1,18 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.environ import Constraint as Constraint +from pyomo.environ import value as value + +def value_no_exception(c, div0=None): ... +def get_residual(ui_data, c): ... +def active_equalities(blk) -> Generator[Incomplete]: ... +def active_constraint_set(blk): ... +def active_equality_set(blk): ... +def count_free_variables(blk): ... +def count_equality_constraints(blk): ... +def count_constraints(blk): ... +def degrees_of_freedom(blk): ... +def free_variables_in_active_equalities_set(blk): ... diff --git a/stubs/pyomo/contrib/viewer/residual_table.pyi b/stubs/pyomo/contrib/viewer/residual_table.pyi new file mode 100644 index 000000000..e876e9823 --- /dev/null +++ b/stubs/pyomo/contrib/viewer/residual_table.pyi @@ -0,0 +1,31 @@ +import pyomo.contrib.viewer.qt as myqt +from _typeshed import Incomplete +from pyomo.common.fileutils import this_file_dir as this_file_dir +from pyomo.common.flags import building_documentation as building_documentation +from pyomo.contrib.viewer.report import get_residual as get_residual +from pyomo.contrib.viewer.report import value_no_exception as value_no_exception + +class _ResidualTableUI: ... +class _ResidualTable: ... + +mypath: Incomplete + +class ResidualTable(_ResidualTable, _ResidualTableUI): + ui_data: Incomplete + datmodel: Incomplete + def __init__(self, ui_data) -> None: ... + def sort(self) -> None: ... + def refresh(self) -> None: ... + def calculate(self) -> None: ... + +class ResidualDataModel(myqt.QAbstractTableModel): + column: Incomplete + ui_data: Incomplete + include_inactive: bool + def __init__(self, parent, ui_data) -> None: ... + def update_model(self) -> None: ... + def sort(self): ... + def rowCount(self, parent=...): ... + def columnCount(self, parent=...): ... + def data(self, index=..., role=...): ... + def headerData(self, i, orientation, role=...): ... diff --git a/stubs/pyomo/contrib/viewer/ui.pyi b/stubs/pyomo/contrib/viewer/ui.pyi new file mode 100644 index 000000000..88a97f4c3 --- /dev/null +++ b/stubs/pyomo/contrib/viewer/ui.pyi @@ -0,0 +1,41 @@ +from _typeshed import Incomplete +from pyomo.common.fileutils import this_file_dir as this_file_dir +from pyomo.common.flags import building_documentation as building_documentation +from pyomo.contrib.viewer.model_browser import ModelBrowser as ModelBrowser +from pyomo.contrib.viewer.model_select import ModelSelect as ModelSelect +from pyomo.contrib.viewer.residual_table import ResidualTable as ResidualTable +from pyomo.contrib.viewer.ui_data import UIData as UIData + +class _MainWindowUI: ... +class _MainWindow: ... + +def get_mainwindow( + model=None, + show: bool = True, + ask_close: bool = True, + model_var_name_in_main=None, + testing: bool = False, +): ... + +class MainWindow(_MainWindow, _MainWindowUI): + testing: Incomplete + ui_data: Incomplete + variables: Incomplete + constraints: Incomplete + expressions: Incomplete + parameters: Incomplete + residuals: Incomplete + def __init__(self, *args, **kwargs) -> None: ... + def toggle_tabs(self) -> None: ... + def variables_restart(self) -> None: ... + def expressions_restart(self) -> None: ... + def parameters_restart(self) -> None: ... + def constraints_restart(self) -> None: ... + def residuals_restart(self) -> None: ... + def set_model(self, model) -> None: ... + def update_model(self) -> None: ... + def model_information(self) -> None: ... + def refresh_on_execute(self) -> None: ... + def show_model_select(self): ... + def exit_action(self) -> None: ... + def closeEvent(self, event) -> None: ... diff --git a/stubs/pyomo/contrib/viewer/ui_data.pyi b/stubs/pyomo/contrib/viewer/ui_data.pyi new file mode 100644 index 000000000..fe2289a04 --- /dev/null +++ b/stubs/pyomo/contrib/viewer/ui_data.pyi @@ -0,0 +1,29 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.contrib.viewer.qt import * + +class UIDataNoUi: + model_var_name_in_main: Incomplete + value_cache: Incomplete + value_cache_units: Incomplete + def __init__(self, model=None, model_var_name_in_main=None) -> None: ... + def begin_update(self) -> None: ... + def end_update(self, emit: bool = True) -> None: ... + def emit_update(self) -> None: ... + def emit_exec_refresh(self) -> None: ... + @property + def model(self): ... + @model.setter + def model(self, value) -> None: ... + def calculate_constraints(self) -> None: ... + def calculate_expressions(self) -> None: ... + +class UIData(UIDataNoUi): ... + +class UIData(UIDataNoUi, QtCore.QObject): + updated: Incomplete + exec_refresh: Incomplete + def __init__(self, *args, **kwargs) -> None: ... + def end_update(self, emit: bool = True) -> None: ... + def emit_update(self) -> None: ... + def emit_exec_refresh(self) -> None: ... diff --git a/stubs/pyomo/core/__init__.pyi b/stubs/pyomo/core/__init__.pyi new file mode 100644 index 000000000..ec599f8d9 --- /dev/null +++ b/stubs/pyomo/core/__init__.pyi @@ -0,0 +1,162 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.core import expr as expr +from pyomo.core import kernel as kernel +from pyomo.core import util as util +from pyomo.core.base import maximize as maximize +from pyomo.core.base import minimize as minimize +from pyomo.core.base.action import BuildAction as BuildAction +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import ScalarBlock as ScalarBlock +from pyomo.core.base.block import SortComponents as SortComponents +from pyomo.core.base.block import TraversalStrategy as TraversalStrategy +from pyomo.core.base.block import active_components as active_components +from pyomo.core.base.block import active_components_data as active_components_data +from pyomo.core.base.block import components as components +from pyomo.core.base.block import components_data as components_data +from pyomo.core.base.boolean_var import BooleanVar as BooleanVar +from pyomo.core.base.boolean_var import BooleanVarList as BooleanVarList +from pyomo.core.base.boolean_var import ScalarBooleanVar as ScalarBooleanVar +from pyomo.core.base.check import BuildCheck as BuildCheck +from pyomo.core.base.component import Component as Component +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.component import name as name +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.config import PyomoOptions as PyomoOptions +from pyomo.core.base.connector import Connector as Connector +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.constraint import ConstraintList as ConstraintList +from pyomo.core.base.constraint import simple_constraint_rule as simple_constraint_rule +from pyomo.core.base.constraint import simple_constraintlist_rule as simple_constraintlist_rule +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.external import ExternalFunction as ExternalFunction +from pyomo.core.base.instance2dat import instance2dat as instance2dat +from pyomo.core.base.label import AlphaNumericTextLabeler as AlphaNumericTextLabeler +from pyomo.core.base.label import CNameLabeler as CNameLabeler +from pyomo.core.base.label import CounterLabeler as CounterLabeler +from pyomo.core.base.label import CuidLabeler as CuidLabeler +from pyomo.core.base.label import NameLabeler as NameLabeler +from pyomo.core.base.label import NumericLabeler as NumericLabeler +from pyomo.core.base.label import ShortNameLabeler as ShortNameLabeler +from pyomo.core.base.label import TextLabeler as TextLabeler +from pyomo.core.base.logical_constraint import LogicalConstraint as LogicalConstraint +from pyomo.core.base.logical_constraint import LogicalConstraintList as LogicalConstraintList +from pyomo.core.base.misc import display as display +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.objective import ObjectiveList as ObjectiveList +from pyomo.core.base.objective import simple_objective_rule as simple_objective_rule +from pyomo.core.base.objective import simple_objectivelist_rule as simple_objectivelist_rule +from pyomo.core.base.param import Param as Param +from pyomo.core.base.piecewise import Piecewise as Piecewise +from pyomo.core.base.PyomoModel import AbstractModel as AbstractModel +from pyomo.core.base.PyomoModel import ConcreteModel as ConcreteModel +from pyomo.core.base.PyomoModel import Model as Model +from pyomo.core.base.PyomoModel import global_option as global_option +from pyomo.core.base.reference import Reference as Reference +from pyomo.core.base.set import Any as Any +from pyomo.core.base.set import AnyWithNone as AnyWithNone +from pyomo.core.base.set import Binary as Binary +from pyomo.core.base.set import Boolean as Boolean +from pyomo.core.base.set import BooleanSet as BooleanSet +from pyomo.core.base.set import EmptySet as EmptySet +from pyomo.core.base.set import IntegerInterval as IntegerInterval +from pyomo.core.base.set import Integers as Integers +from pyomo.core.base.set import IntegerSet as IntegerSet +from pyomo.core.base.set import NegativeIntegers as NegativeIntegers +from pyomo.core.base.set import NegativeReals as NegativeReals +from pyomo.core.base.set import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core.base.set import NonNegativeReals as NonNegativeReals +from pyomo.core.base.set import NonPositiveIntegers as NonPositiveIntegers +from pyomo.core.base.set import NonPositiveReals as NonPositiveReals +from pyomo.core.base.set import PercentFraction as PercentFraction +from pyomo.core.base.set import PositiveIntegers as PositiveIntegers +from pyomo.core.base.set import PositiveReals as PositiveReals +from pyomo.core.base.set import RangeSet as RangeSet +from pyomo.core.base.set import RealInterval as RealInterval +from pyomo.core.base.set import Reals as Reals +from pyomo.core.base.set import RealSet as RealSet +from pyomo.core.base.set import Set as Set +from pyomo.core.base.set import SetOf as SetOf +from pyomo.core.base.set import UnitInterval as UnitInterval +from pyomo.core.base.set import set_options as set_options +from pyomo.core.base.set import simple_set_rule as simple_set_rule +from pyomo.core.base.sos import SOSConstraint as SOSConstraint +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.base.suffix import active_export_suffix_generator as active_export_suffix_generator +from pyomo.core.base.suffix import active_import_suffix_generator as active_import_suffix_generator +from pyomo.core.base.symbol_map import symbol_map_from_instance as symbol_map_from_instance +from pyomo.core.base.transformation import ReverseTransformationToken as ReverseTransformationToken +from pyomo.core.base.transformation import Transformation as Transformation +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.base.var import Var as Var +from pyomo.core.base.var import VarList as VarList +from pyomo.core.expr import Expr_if as Expr_if +from pyomo.core.expr import acos as acos +from pyomo.core.expr import acosh as acosh +from pyomo.core.expr import all_different as all_different +from pyomo.core.expr import asin as asin +from pyomo.core.expr import asinh as asinh +from pyomo.core.expr import atan as atan +from pyomo.core.expr import atanh as atanh +from pyomo.core.expr import atleast as atleast +from pyomo.core.expr import atmost as atmost +from pyomo.core.expr import boolean_value as boolean_value +from pyomo.core.expr import calculus as calculus +from pyomo.core.expr import ceil as ceil +from pyomo.core.expr import cos as cos +from pyomo.core.expr import cosh as cosh +from pyomo.core.expr import count_if as count_if +from pyomo.core.expr import equivalent as equivalent +from pyomo.core.expr import exactly as exactly +from pyomo.core.expr import exp as exp +from pyomo.core.expr import expr_common as expr_common +from pyomo.core.expr import expr_errors as expr_errors +from pyomo.core.expr import floor as floor +from pyomo.core.expr import implies as implies +from pyomo.core.expr import inequality as inequality +from pyomo.core.expr import land as land +from pyomo.core.expr import linear_expression as linear_expression +from pyomo.core.expr import lnot as lnot +from pyomo.core.expr import log as log +from pyomo.core.expr import log10 as log10 +from pyomo.core.expr import logical_expr as logical_expr +from pyomo.core.expr import lor as lor +from pyomo.core.expr import nonlinear_expression as nonlinear_expression +from pyomo.core.expr import numeric_expr as numeric_expr +from pyomo.core.expr import numvalue as numvalue +from pyomo.core.expr import sin as sin +from pyomo.core.expr import sinh as sinh +from pyomo.core.expr import sqrt as sqrt +from pyomo.core.expr import symbol_map as symbol_map +from pyomo.core.expr import sympy_tools as sympy_tools +from pyomo.core.expr import tan as tan +from pyomo.core.expr import tanh as tanh +from pyomo.core.expr import taylor_series as taylor_series +from pyomo.core.expr import visitor as visitor +from pyomo.core.expr import xor as xor +from pyomo.core.expr.boolean_value import BooleanConstant as BooleanConstant +from pyomo.core.expr.boolean_value import BooleanValue as BooleanValue +from pyomo.core.expr.boolean_value import as_boolean as as_boolean +from pyomo.core.expr.boolean_value import native_logical_values as native_logical_values +from pyomo.core.expr.calculus.derivatives import differentiate as differentiate +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import PyomoObject as PyomoObject +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.core.expr.numvalue import is_variable_type as is_variable_type +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import polynomial_degree as polynomial_degree +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.symbol_map import SymbolMap as SymbolMap +from pyomo.core.expr.taylor_series import taylor_series_expansion as taylor_series_expansion +from pyomo.core.util import dot_product as dot_product +from pyomo.core.util import prod as prod +from pyomo.core.util import quicksum as quicksum +from pyomo.core.util import sequence as sequence +from pyomo.core.util import sum_product as sum_product +from pyomo.core.util import summation as summation diff --git a/stubs/pyomo/core/base/PyomoModel.pyi b/stubs/pyomo/core/base/PyomoModel.pyi new file mode 100644 index 000000000..94a118cb7 --- /dev/null +++ b/stubs/pyomo/core/base/PyomoModel.pyi @@ -0,0 +1,111 @@ +from _typeshed import Incomplete +from pyomo.common import timing as timing +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import pympler as pympler +from pyomo.common.dependencies import pympler_available as pympler_available +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.numeric_types import value as value +from pyomo.core.base.block import ScalarBlock as ScalarBlock +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.label import CNameLabeler as CNameLabeler +from pyomo.core.base.label import CuidLabeler as CuidLabeler +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.set import Set as Set +from pyomo.core.base.suffix import active_import_suffix_generator as active_import_suffix_generator +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.symbol_map import SymbolMap as SymbolMap +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.dataportal.DataPortal import DataPortal as DataPortal +from pyomo.opt.results import Solution as Solution +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import UndefinedData as UndefinedData + +logger: Incomplete +id_func = id + +def global_option(function, name, value): ... + +class PyomoConfig(Bunch): + def __init__(self, *args, **kw) -> None: ... + +class ModelSolution: + def __init__(self) -> None: ... + def __getattr__(self, name): ... + def __setattr__(self, name, val) -> None: ... + +class ModelSolutions: + def __init__(self, instance) -> None: ... + symbol_map: Incomplete + solutions: Incomplete + index: Incomplete + def clear(self, clear_symbol_maps: bool = True) -> None: ... + def __len__(self) -> int: ... + def __getitem__(self, index): ... + def add_symbol_map(self, symbol_map) -> None: ... + def delete_symbol_map(self, smap_id) -> None: ... + def load_from( + self, + results, + allow_consistent_values_for_fixed_vars: bool = False, + comparison_tolerance_for_fixed_vars: float = 1e-05, + ignore_invalid_labels: bool = False, + id=None, + delete_symbol_map: bool = True, + clear: bool = True, + default_variable_value=None, + select: int = 0, + ignore_fixed_vars: bool = True, + ) -> None: ... + def store_to(self, results, cuid: bool = False, skip_stale_vars: bool = False) -> None: ... + def add_solution( + self, + solution, + smap_id, + delete_symbol_map: bool = True, + cache=None, + ignore_invalid_labels: bool = False, + ignore_missing_symbols: bool = True, + default_variable_value=None, + ): ... + def select( + self, + index: int = 0, + allow_consistent_values_for_fixed_vars: bool = False, + comparison_tolerance_for_fixed_vars: float = 1e-05, + ignore_invalid_labels: bool = False, + ignore_fixed_vars: bool = True, + ) -> None: ... + +class Model(ScalarBlock): + def __new__(cls, *args, **kwds): ... + statistics: Incomplete + config: Incomplete + solutions: Incomplete + def __init__(self, name: str = 'unknown', **kwargs) -> None: ... + def compute_statistics(self, active: bool = True) -> None: ... + def nvariables(self): ... + def nconstraints(self): ... + def nobjectives(self): ... + def create_instance( + self, + filename=None, + data=None, + name=None, + namespace=None, + namespaces=None, + profile_memory: int = 0, + report_timing: bool = False, + **kwds, + ): ... + def preprocess(self, preprocessor=None) -> None: ... + def load(self, arg, namespaces=[None], profile_memory: int = 0) -> None: ... + +class ConcreteModel(Model): + def __init__(self, *args, **kwds) -> None: ... + +class AbstractModel(Model): + def __init__(self, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/core/base/__init__.pyi b/stubs/pyomo/core/base/__init__.pyi new file mode 100644 index 000000000..faaeb745e --- /dev/null +++ b/stubs/pyomo/core/base/__init__.pyi @@ -0,0 +1,122 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.deprecation import moved_module as moved_module +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.enums import maximize as maximize +from pyomo.common.enums import minimize as minimize +from pyomo.core.base.action import BuildAction as BuildAction +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.block import ScalarBlock as ScalarBlock +from pyomo.core.base.block import active_components as active_components +from pyomo.core.base.block import active_components_data as active_components_data +from pyomo.core.base.block import components as components +from pyomo.core.base.block import components_data as components_data +from pyomo.core.base.boolean_var import BooleanVar as BooleanVar +from pyomo.core.base.boolean_var import BooleanVarData as BooleanVarData +from pyomo.core.base.boolean_var import BooleanVarList as BooleanVarList +from pyomo.core.base.boolean_var import ScalarBooleanVar as ScalarBooleanVar +from pyomo.core.base.check import BuildCheck as BuildCheck +from pyomo.core.base.component import Component as Component +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.component import name as name +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.config import PyomoOptions as PyomoOptions +from pyomo.core.base.connector import Connector as Connector +from pyomo.core.base.connector import ConnectorData as ConnectorData +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.constraint import ConstraintList as ConstraintList +from pyomo.core.base.constraint import simple_constraint_rule as simple_constraint_rule +from pyomo.core.base.constraint import simple_constraintlist_rule as simple_constraintlist_rule +from pyomo.core.base.enums import SortComponents as SortComponents +from pyomo.core.base.enums import TraversalStrategy as TraversalStrategy +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import NamedExpressionData as NamedExpressionData +from pyomo.core.base.external import ExternalFunction as ExternalFunction +from pyomo.core.base.instance2dat import instance2dat as instance2dat +from pyomo.core.base.label import AlphaNumericTextLabeler as AlphaNumericTextLabeler +from pyomo.core.base.label import CNameLabeler as CNameLabeler +from pyomo.core.base.label import CounterLabeler as CounterLabeler +from pyomo.core.base.label import CuidLabeler as CuidLabeler +from pyomo.core.base.label import NameLabeler as NameLabeler +from pyomo.core.base.label import NumericLabeler as NumericLabeler +from pyomo.core.base.label import ShortNameLabeler as ShortNameLabeler +from pyomo.core.base.label import TextLabeler as TextLabeler +from pyomo.core.base.logical_constraint import LogicalConstraint as LogicalConstraint +from pyomo.core.base.logical_constraint import LogicalConstraintData as LogicalConstraintData +from pyomo.core.base.logical_constraint import LogicalConstraintList as LogicalConstraintList +from pyomo.core.base.misc import display as display +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.objective import ObjectiveList as ObjectiveList +from pyomo.core.base.objective import simple_objective_rule as simple_objective_rule +from pyomo.core.base.objective import simple_objectivelist_rule as simple_objectivelist_rule +from pyomo.core.base.param import Param as Param +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.piecewise import Piecewise as Piecewise +from pyomo.core.base.piecewise import PiecewiseData as PiecewiseData +from pyomo.core.base.PyomoModel import AbstractModel as AbstractModel +from pyomo.core.base.PyomoModel import ConcreteModel as ConcreteModel +from pyomo.core.base.PyomoModel import Model as Model +from pyomo.core.base.PyomoModel import ModelSolution as ModelSolution +from pyomo.core.base.PyomoModel import ModelSolutions as ModelSolutions +from pyomo.core.base.PyomoModel import global_option as global_option +from pyomo.core.base.reference import Reference as Reference +from pyomo.core.base.set import Any as Any +from pyomo.core.base.set import AnyWithNone as AnyWithNone +from pyomo.core.base.set import Binary as Binary +from pyomo.core.base.set import Boolean as Boolean +from pyomo.core.base.set import BooleanSet as BooleanSet +from pyomo.core.base.set import EmptySet as EmptySet +from pyomo.core.base.set import IntegerInterval as IntegerInterval +from pyomo.core.base.set import Integers as Integers +from pyomo.core.base.set import IntegerSet as IntegerSet +from pyomo.core.base.set import NegativeIntegers as NegativeIntegers +from pyomo.core.base.set import NegativeReals as NegativeReals +from pyomo.core.base.set import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core.base.set import NonNegativeReals as NonNegativeReals +from pyomo.core.base.set import NonPositiveIntegers as NonPositiveIntegers +from pyomo.core.base.set import NonPositiveReals as NonPositiveReals +from pyomo.core.base.set import PercentFraction as PercentFraction +from pyomo.core.base.set import PositiveIntegers as PositiveIntegers +from pyomo.core.base.set import PositiveReals as PositiveReals +from pyomo.core.base.set import RangeSet as RangeSet +from pyomo.core.base.set import RealInterval as RealInterval +from pyomo.core.base.set import Reals as Reals +from pyomo.core.base.set import RealSet as RealSet +from pyomo.core.base.set import Set as Set +from pyomo.core.base.set import SetData as SetData +from pyomo.core.base.set import SetOf as SetOf +from pyomo.core.base.set import UnitInterval as UnitInterval +from pyomo.core.base.set import set_options as set_options +from pyomo.core.base.set import simple_set_rule as simple_set_rule +from pyomo.core.base.sos import SOSConstraint as SOSConstraint +from pyomo.core.base.sos import SOSConstraintData as SOSConstraintData +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.base.suffix import active_export_suffix_generator as active_export_suffix_generator +from pyomo.core.base.suffix import active_import_suffix_generator as active_import_suffix_generator +from pyomo.core.base.symbol_map import symbol_map_from_instance as symbol_map_from_instance +from pyomo.core.base.transformation import ReverseTransformationToken as ReverseTransformationToken +from pyomo.core.base.transformation import Transformation as Transformation +from pyomo.core.base.transformation import TransformationFactory as TransformationFactory +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.base.var import Var as Var +from pyomo.core.base.var import VarData as VarData +from pyomo.core.base.var import VarList as VarList +from pyomo.core.expr.boolean_value import BooleanConstant as BooleanConstant +from pyomo.core.expr.boolean_value import BooleanValue as BooleanValue +from pyomo.core.expr.boolean_value import as_boolean as as_boolean +from pyomo.core.expr.boolean_value import native_logical_values as native_logical_values +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.core.expr.numvalue import is_variable_type as is_variable_type +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import polynomial_degree as polynomial_degree +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.symbol_map import SymbolMap as SymbolMap diff --git a/stubs/pyomo/core/base/action.pyi b/stubs/pyomo/core/base/action.pyi new file mode 100644 index 000000000..a2afbf4b4 --- /dev/null +++ b/stubs/pyomo/core/base/action.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule + +logger: Incomplete + +class BuildAction(IndexedComponent): + def __init__(self, *args, **kwd) -> None: ... + def construct(self, data=None) -> None: ... diff --git a/stubs/pyomo/core/base/block.pyi b/stubs/pyomo/core/base/block.pyi new file mode 100644 index 000000000..3c2539ee3 --- /dev/null +++ b/stubs/pyomo/core/base/block.pyi @@ -0,0 +1,184 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import Mapping as Mapping +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.formatting import StreamIndenter as StreamIndenter +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.component import Component as Component +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.componentuid import ComponentUID as ComponentUID +from pyomo.core.base.enums import SortComponents as SortComponents +from pyomo.core.base.enums import TraversalStrategy as TraversalStrategy +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.base.set import Any as Any +from pyomo.core.base.var import Var as Var +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import guess_format as guess_format + +logger: Incomplete + +class _generic_component_decorator: + def __init__(self, component, block, *args, **kwds) -> None: ... + def __call__(self, rule): ... + +class _component_decorator: + def __init__(self, block, component) -> None: ... + def __call__(self, *args, **kwds): ... + +class SubclassOf: + ctype: Incomplete + def __init__(self, *ctype) -> None: ... + def __contains__(self, item) -> bool: ... + def __len__(self) -> int: ... + def __getitem__(self, item): ... + def __iter__(self): ... + +class _DeduplicateInfo: + seen_components: Incomplete + seen_comp_thru_reference: Incomplete + seen_data: Incomplete + def __init__(self) -> None: ... + def unique(self, comp, items, are_values): ... + +class _BlockConstruction: + data: Incomplete + +class PseudoMap(AutoSlots.Mixin): + def __init__(self, block, ctype, active=None, sort: bool = False) -> None: ... + def __iter__(self): ... + def __getitem__(self, key): ... + def __nonzero__(self): ... + __bool__ = __nonzero__ + def __len__(self) -> int: ... + def __contains__(self, key) -> bool: ... + def keys(self): ... + def values(self): ... + def items(self) -> Generator[Incomplete]: ... + def iterkeys(self): ... + def itervalues(self): ... + def iteritems(self): ... + +class BlockData(ActiveComponentData): + def __init__(self, component) -> None: ... + def __getattr__(self, val) -> Component | IndexedComponent | Any: ... + def __setattr__(self, name: str, val: Component | IndexedComponent | Any): ... + def __delattr__(self, name) -> None: ... + def set_value(self, val) -> None: ... + def clear(self) -> None: ... + def transfer_attributes_from(self, src) -> None: ... + def collect_ctypes(self, active=None, descend_into: bool = True): ... + def model(self): ... + def find_component(self, label_or_component): ... + def add_component(self, name, val) -> None: ... + def del_component(self, name_or_object) -> None: ... + def reclassify_component_type( + self, name_or_object, new_ctype, preserve_declaration_order: bool = True + ) -> None: ... + def clone(self, memo=None): ... + def contains_component(self, ctype): ... + def component(self, name_or_object): ... + def component_map(self, ctype=None, active=None, sort: bool = False): ... + def all_components(self, *args, **kwargs): ... + def active_components(self, *args, **kwargs): ... + def all_component_data(self, *args, **kwargs): ... + def active_component_data(self, *args, **kwargs): ... + def component_objects( + self, + ctype=None, + active=None, + sort: bool = False, + descend_into: bool = True, + descent_order=None, + ) -> Generator[Incomplete, Incomplete]: ... + def component_data_objects( + self, + ctype=None, + active=None, + sort: bool = False, + descend_into: bool = True, + descent_order=None, + ) -> Generator[Incomplete, Incomplete]: ... + def component_data_iterindex( + self, + ctype=None, + active=None, + sort: bool = False, + descend_into: bool = True, + descent_order=None, + ) -> Generator[Incomplete, Incomplete]: ... + def all_blocks(self, *args, **kwargs): ... + def active_blocks(self, *args, **kwargs): ... + def block_data_objects( + self, active=None, sort: bool = False, descend_into: bool = True, descent_order=None + ): ... + def fix_all_vars(self) -> None: ... + def unfix_all_vars(self) -> None: ... + def is_constructed(self): ... + def display(self, filename=None, ostream=None, prefix: str = '') -> None: ... + def valid_problem_types(self): ... + solutions: Incomplete + def write( + self, + filename=None, + format=None, + solver_capability=None, + io_options={}, + int_marker: bool = False, + ): ... + def private_data(self, scope=None): ... + +class _BlockData(metaclass=RenamedClass): + __renamed__new_class__ = BlockData + __renamed__version__: str + +class Block(ActiveIndexedComponent): + def construct(self, data=None) -> None: ... + def display(self, filename=None, ostream=None, prefix: str = '') -> None: ... + @staticmethod + def register_private_data_initializer(initializer, scope=None) -> None: ... + +class ScalarBlock(BlockData, Block): + def __init__(self, *args, **kwds) -> None: ... + display: Incomplete + +class SimpleBlock(metaclass=RenamedClass): + __renamed__new_class__ = ScalarBlock + __renamed__version__: str + +class IndexedBlock(Block): + def __init__(self, *args, **kwds) -> None: ... + @overload + def __getitem__(self, index) -> BlockData: ... + __getitem__: Incomplete + +def generate_cuid_names(block, ctype=None, descend_into: bool = True): ... +def active_components(block, ctype, sort_by_names: bool = False, sort_by_keys: bool = False): ... +def components(block, ctype, sort_by_names: bool = False, sort_by_keys: bool = False): ... +def active_components_data( + block, ctype, sort=None, sort_by_keys: bool = False, sort_by_names: bool = False +): ... +def components_data( + block, ctype, sort=None, sort_by_keys: bool = False, sort_by_names: bool = False +): ... + +class ScalarCustomBlockMixin: + def __init__(self, *args, **kwargs) -> None: ... + +class CustomBlock(Block): + def __init__(self, *args, **kwargs) -> None: ... + def __new__(cls, *args, **kwargs): ... + +def declare_custom_block(name, new_ctype=None): ... diff --git a/stubs/pyomo/core/base/blockutil.pyi b/stubs/pyomo/core/base/blockutil.pyi new file mode 100644 index 000000000..bfe1d2e18 --- /dev/null +++ b/stubs/pyomo/core/base/blockutil.pyi @@ -0,0 +1,4 @@ +from pyomo.common import deprecated as deprecated +from pyomo.core.base import Var as Var + +def has_discrete_variables(block): ... diff --git a/stubs/pyomo/core/base/boolean_var.pyi b/stubs/pyomo/core/base/boolean_var.pyi new file mode 100644 index 000000000..d1bc187d6 --- /dev/null +++ b/stubs/pyomo/core/base/boolean_var.pyi @@ -0,0 +1,103 @@ +from weakref import ReferenceType as ReferenceType + +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ComponentData as ComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.core.base.set import Binary as Binary +from pyomo.core.base.set import BooleanSet as BooleanSet +from pyomo.core.base.set import Set as Set +from pyomo.core.base.util import is_functor as is_functor +from pyomo.core.base.var import Var as Var +from pyomo.core.expr import GetItemExpression as GetItemExpression +from pyomo.core.expr.boolean_value import BooleanValue as BooleanValue +from pyomo.core.expr.numvalue import value as value +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +logger: Incomplete + +class _DeprecatedImplicitAssociatedBinaryVariable: + def __init__(self, boolvar) -> None: ... + def __call__(self): ... + +class BooleanVarData(ComponentData, BooleanValue): + __autoslot_mappers__: Incomplete + fixed: bool + def __init__(self, component=None) -> None: ... + def is_fixed(self): ... + def is_constant(self): ... + def is_variable_type(self): ... + def is_potentially_variable(self): ... + def set_value(self, val, skip_validation: bool = False) -> None: ... + def clear(self) -> None: ... + def __call__(self, exception=...): ... + @property + def value(self): ... + @value.setter + def value(self, val) -> None: ... + @property + def domain(self): ... + @property + def stale(self): ... + @stale.setter + def stale(self, val) -> None: ... + def get_associated_binary(self): ... + def associate_binary_var(self, binary_var) -> None: ... + def fix(self, value=..., skip_validation: bool = False) -> None: ... + def unfix(self) -> None: ... + def free(self): ... + +class _BooleanVarData(metaclass=RenamedClass): + __renamed__new_class__ = BooleanVarData + __renamed__version__: str + +class _GeneralBooleanVarData(metaclass=RenamedClass): + __renamed__new_class__ = BooleanVarData + __renamed__version__: str + +class BooleanVar(IndexedComponent): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwd) -> None: ... + def flag_as_stale(self) -> None: ... + def get_values(self, include_fixed_values: bool = True): ... + extract_values = get_values + def set_values(self, new_values, skip_validation: bool = False) -> None: ... + def construct(self, data=None) -> None: ... + def add(self, index): ... + +class ScalarBooleanVar(BooleanVarData, BooleanVar): + def __init__(self, *args, **kwd) -> None: ... + @property + def value(self): ... + @value.setter + def value(self, val): ... + @property + def domain(self): ... + def fix(self, value=..., skip_validation: bool = False): ... + def unfix(self): ... + +class SimpleBooleanVar(metaclass=RenamedClass): + __renamed__new_class__ = ScalarBooleanVar + __renamed__version__: str + +class IndexedBooleanVar(BooleanVar): + def fix(self, value=..., skip_validation: bool = False) -> None: ... + def unfix(self) -> None: ... + def free(self): ... + @property + def domain(self): ... + def __getitem__(self, args): ... + +class BooleanVarList(IndexedBooleanVar): + def __init__(self, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + def add(self): ... diff --git a/stubs/pyomo/core/base/check.pyi b/stubs/pyomo/core/base/check.pyi new file mode 100644 index 000000000..7f1ba2744 --- /dev/null +++ b/stubs/pyomo/core/base/check.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule + +logger: Incomplete + +class BuildCheck(IndexedComponent): + def __init__(self, *args, **kwd) -> None: ... + def construct(self, data=None) -> None: ... diff --git a/stubs/pyomo/core/base/component.pyi b/stubs/pyomo/core/base/component.pyi new file mode 100644 index 000000000..dcf415e88 --- /dev/null +++ b/stubs/pyomo/core/base/component.pyi @@ -0,0 +1,170 @@ +from copy import deepcopy as deepcopy +from pickle import PickleError as PickleError + +import pyomo.common +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.autoslots import fast_deepcopy as fast_deepcopy +from pyomo.common.collections import OrderedDict as OrderedDict +from pyomo.common.deprecation import ( + RenamedClass as RenamedClass, +) +from pyomo.common.deprecation import ( + deprecated as deprecated, +) +from pyomo.common.deprecation import ( + deprecation_warning as deprecation_warning, +) +from pyomo.common.deprecation import ( + relocated_module_attribute as relocated_module_attribute, +) +from pyomo.common.factory import Factory as Factory +from pyomo.common.formatting import ( + StreamIndenter as StreamIndenter, +) +from pyomo.common.formatting import ( + tabular_writer as tabular_writer, +) +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.sorting import sorted_robust as sorted_robust +from pyomo.core.base.component_namer import index_repr as index_repr +from pyomo.core.base.component_namer import name_repr as name_repr +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +logger: Incomplete + +class ModelComponentFactoryClass(Factory): + def register(self, doc=None): ... + +ModelComponentFactory: Incomplete + +def name(component, index=..., fully_qualified: bool = False, relative_to=None): ... +def cname(*args, **kwds): ... + +class CloneError(pyomo.common.errors.PyomoException): ... + +class ComponentBase(PyomoObject): + def is_component_type(self): ... + def __deepcopy__(self, memo): ... + def __deepcopy_field__(self, value, memo, slot_name): ... + def cname(self, *args, **kwds): ... + def pprint(self, ostream=None, verbose: bool = False, prefix: str = '') -> None: ... + @property + def name(self): ... + @name.setter + def name(self, val) -> None: ... + @property + def local_name(self): ... + @property + def active(self): ... + @active.setter + def active(self, value) -> None: ... + def __iter__(self): ... + def at(self, index: int) -> typingAny: ... + def ord(self, item: typingAny) -> int: ... + def first(self) -> typingAny: ... + def last(self) -> typingAny: ... + def next(self, item: typingAny, step: int = 1) -> typingAny: ... + def nextw(self, item: typingAny, step: int = 1) -> typingAny: ... + def prev(self, item: typingAny, step: int = 1) -> typingAny: ... + def prevw(self, item: typingAny, step: int = 1) -> typingAny: ... + +class _ComponentBase(metaclass=RenamedClass): + __renamed__new_class__ = ComponentBase + __renamed__version__: str + +class Component(ComponentBase): + __autoslot_mappers__: Incomplete + doc: Incomplete + def __init__(self, **kwds) -> None: ... + @property + def ctype(self): ... + def type(self): ... + def construct(self, data=None) -> None: ... + def is_constructed(self): ... + def reconstruct(self, data=None) -> None: ... + def valid_model_component(self): ... + def pprint(self, ostream=None, verbose: bool = False, prefix: str = '') -> None: ... + def display(self, ostream=None, verbose: bool = False, prefix: str = '') -> None: ... + def parent_component(self): ... + def parent_block(self): ... + def model(self): ... + def root_block(self): ... + def getname(self, fully_qualified: bool = False, name_buffer=None, relative_to=None): ... + @property + def name(self): ... + @name.setter + def name(self, val) -> None: ... + def is_indexed(self): ... + def clear_suffix_value(self, suffix_or_name, expand: bool = True) -> None: ... + def set_suffix_value(self, suffix_or_name, value, expand: bool = True) -> None: ... + def get_suffix_value(self, suffix_or_name, default=None): ... + def __getitem__(self, key: typingAny) -> typingAny: ... + +class ActiveComponent(Component): + def __init__(self, **kwds) -> None: ... + @property + def active(self): ... + @active.setter + def active(self, value) -> None: ... + def activate(self) -> None: ... + def deactivate(self) -> None: ... + +class ComponentData(ComponentBase): + __autoslot_mappers__: Incomplete + def __init__(self, component) -> None: ... + @property + def ctype(self): ... + def type(self): ... + def parent_component(self): ... + def parent_block(self): ... + def model(self): ... + def index(self): ... + def getname(self, fully_qualified: bool = False, name_buffer=None, relative_to=None): ... + def is_indexed(self): ... + def clear_suffix_value(self, suffix_or_name, expand: bool = True) -> None: ... + def set_suffix_value(self, suffix_or_name, value, expand: bool = True) -> None: ... + def get_suffix_value(self, suffix_or_name, default=None): ... + def __len__(self) -> int: ... + def __getitem__(self, key: typingAny) -> typingAny: ... + def __bool__(self) -> bool: ... + def __int__(self) -> int: ... + def __float__(self) -> float: ... + def __lt__(self, other: typingAny) -> typingAny: ... + def __le__(self, other: typingAny) -> typingAny: ... + def __eq__(self, other: typingAny) -> typingAny: ... + def __ne__(self, other: typingAny) -> typingAny: ... + def __gt__(self, other: typingAny) -> typingAny: ... + def __ge__(self, other: typingAny) -> typingAny: ... + def __neg__(self) -> typingAny: ... + def __pos__(self) -> typingAny: ... + def __abs__(self) -> typingAny: ... + def __add__(self, other: typingAny) -> typingAny: ... + def __sub__(self, other: typingAny) -> typingAny: ... + def __mul__(self, other: typingAny) -> typingAny: ... + def __div__(self, other: typingAny) -> typingAny: ... + def __truediv__(self, other: typingAny) -> typingAny: ... + def __pow__(self, other: typingAny) -> typingAny: ... + def __radd__(self, other: typingAny) -> typingAny: ... + def __rsub__(self, other: typingAny) -> typingAny: ... + def __rmul__(self, other: typingAny) -> typingAny: ... + def __rdiv__(self, other: typingAny) -> typingAny: ... + def __rtruediv__(self, other: typingAny) -> typingAny: ... + def __rpow__(self, other: typingAny) -> typingAny: ... + def __iadd__(self, other: typingAny) -> typingAny: ... + def __isub__(self, other: typingAny) -> typingAny: ... + def __imul__(self, other: typingAny) -> typingAny: ... + def __idiv__(self, other: typingAny) -> typingAny: ... + def __itruediv__(self, other: typingAny) -> typingAny: ... + def __ipow__(self, other: typingAny) -> typingAny: ... + +class ActiveComponentData(ComponentData): + def __init__(self, component) -> None: ... + @property + def active(self): ... + @active.setter + def active(self, value) -> None: ... + def activate(self) -> None: ... + def deactivate(self) -> None: ... diff --git a/stubs/pyomo/core/base/component_namer.pyi b/stubs/pyomo/core/base/component_namer.pyi new file mode 100644 index 000000000..7bb59c3f4 --- /dev/null +++ b/stubs/pyomo/core/base/component_namer.pyi @@ -0,0 +1,10 @@ +from _typeshed import Incomplete + +literals: str +special_chars: Incomplete +re_number: Incomplete +re_special_char: Incomplete + +def name_repr(x, unknown_handler=...): ... +def tuple_repr(x, unknown_handler=...): ... +def index_repr(idx, unknown_handler=...): ... diff --git a/stubs/pyomo/core/base/component_order.pyi b/stubs/pyomo/core/base/component_order.pyi new file mode 100644 index 000000000..e29fdb10a --- /dev/null +++ b/stubs/pyomo/core/base/component_order.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.core.base.block import Block as Block +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.param import Param as Param +from pyomo.core.base.set import RangeSet as RangeSet +from pyomo.core.base.set import Set as Set +from pyomo.core.base.sos import SOSConstraint as SOSConstraint +from pyomo.core.base.var import Var as Var + +items: Incomplete +display_items: Incomplete +display_name: Incomplete diff --git a/stubs/pyomo/core/base/componentuid.pyi b/stubs/pyomo/core/base/componentuid.pyi new file mode 100644 index 000000000..369a813e3 --- /dev/null +++ b/stubs/pyomo/core/base/componentuid.pyi @@ -0,0 +1,41 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.dependencies import pickle as pickle +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.core.base.component_namer import literals as literals +from pyomo.core.base.component_namer import special_chars as special_chars +from pyomo.core.base.indexed_component_slice import IndexedComponent_slice as IndexedComponent_slice +from pyomo.core.base.reference import Reference as Reference + +class _NotSpecified: ... + +class ComponentUID: + def __init__(self, component, cuid_buffer=None, context=None) -> None: ... + def get_repr(self, version: int = 2): ... + def __hash__(self): ... + def __lt__(self, other): ... + def __le__(self, other): ... + def __gt__(self, other): ... + def __ge__(self, other): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + @staticmethod + def generate_cuid_string_map( + block, ctype=None, descend_into: bool = True, repr_version: int = 2 + ): ... + def find_component(self, block): ... + def find_component_on(self, block): ... + def list_components(self, block) -> Generator[Incomplete]: ... + def matches(self, component, context=None): ... + +t_ignore: str +tokens: Incomplete + +def t_NUMBER(t): ... +def t_WORD(t): ... +def t_STRING(t): ... +def t_STAR(t): ... +def t_PICKLE(t): ... +def t_error(t) -> None: ... diff --git a/stubs/pyomo/core/base/config.pyi b/stubs/pyomo/core/base/config.pyi new file mode 100644 index 000000000..9e654f0b9 --- /dev/null +++ b/stubs/pyomo/core/base/config.pyi @@ -0,0 +1,37 @@ +from _typeshed import Incomplete +from pyomo.common.config import ADVANCED_OPTION as ADVANCED_OPTION +from pyomo.common.config import ConfigBase as ConfigBase +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.dependencies import yaml as yaml +from pyomo.common.dependencies import yaml_available as yaml_available +from pyomo.common.dependencies import yaml_load_args as yaml_load_args + +logger: Incomplete + +class _PyomoOptions: + def __init__(self) -> None: ... + def active_config(self): ... + def __getitem__(self, key): ... + def get(self, key, default=...): ... + def __setitem__(self, key, val) -> None: ... + def __contains__(self, key) -> bool: ... + def __len__(self) -> int: ... + def __iter__(self): ... + def __getattr__(self, name): ... + def __setattr__(self, name, value): ... + def iterkeys(self): ... + def itervalues(self): ... + def iteritems(self): ... + def keys(self): ... + def values(self): ... + def items(self): ... + def declare(self, name, config): ... + def add(self, name, config): ... + def value(self, accessValue: bool = True): ... + def set_value(self, value): ... + def reset(self): ... + +def default_pyomo_config(): ... + +PyomoOptions: Incomplete diff --git a/stubs/pyomo/core/base/connector.pyi b/stubs/pyomo/core/base/connector.pyi new file mode 100644 index 000000000..9fb4a6013 --- /dev/null +++ b/stubs/pyomo/core/base/connector.pyi @@ -0,0 +1,49 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.formatting import tabular_writer as tabular_writer +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import value as value +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ComponentData as ComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.core.expr.numvalue import NumericValue as NumericValue + +logger: Incomplete + +class ConnectorData(ComponentData, NumericValue): + vars: Incomplete + aggregators: Incomplete + def __init__(self, component=None) -> None: ... + def set_value(self, value) -> None: ... + def is_fixed(self): ... + def is_constant(self): ... + def is_potentially_variable(self): ... + def polynomial_degree(self): ... + def is_binary(self): ... + def is_integer(self): ... + def is_continuous(self): ... + def add(self, var, name=None, aggregate=None) -> None: ... + +class _ConnectorData(metaclass=RenamedClass): + __renamed__new_class__ = ConnectorData + __renamed__version__: str + +class Connector(IndexedComponent): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwd) -> None: ... + def construct(self, data=None) -> None: ... + def display(self, prefix: str = '', ostream=None) -> None: ... + +class ScalarConnector(Connector, ConnectorData): + def __init__(self, *args, **kwd) -> None: ... + +class SimpleConnector(metaclass=RenamedClass): + __renamed__new_class__ = ScalarConnector + __renamed__version__: str + +class IndexedConnector(Connector): ... diff --git a/stubs/pyomo/core/base/constraint.pyi b/stubs/pyomo/core/base/constraint.pyi new file mode 100644 index 000000000..8211d0da5 --- /dev/null +++ b/stubs/pyomo/core/base/constraint.pyi @@ -0,0 +1,136 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import TemplateExpressionError as TemplateExpressionError +from pyomo.common.formatting import tabular_writer as tabular_writer +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.disable_methods import disable_methods as disable_methods +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.indexed_component import rule_wrapper as rule_wrapper +from pyomo.core.base.initializer import CountedCallInitializer as CountedCallInitializer +from pyomo.core.base.initializer import IndexedCallInitializer as IndexedCallInitializer +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.base.set import Set as Set +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.core.expr import ExpressionType as ExpressionType +from pyomo.core.expr import InequalityExpression as InequalityExpression +from pyomo.core.expr import RangedExpression as RangedExpression +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import as_numeric as as_numeric +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import native_logical_types as native_logical_types +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.template_expr import templatize_constraint as templatize_constraint + +logger: Incomplete +TEMPLATIZE_CONSTRAINTS: bool + +def simple_constraint_rule(rule): ... +def simple_constraintlist_rule(rule): ... + +class ConstraintData(ActiveComponentData): + def __init__(self, expr=None, component=None) -> None: ... + def __call__(self, exception=...): ... + def to_bounded_expression(self, evaluate_bounds: bool = False): ... + @property + def body(self): ... + @property + def lower(self): ... + @property + def upper(self): ... + @property + def lb(self): ... + @property + def ub(self): ... + @property + def equality(self): ... + @property + def strict_lower(self): ... + @property + def strict_upper(self): ... + def has_lb(self): ... + def has_ub(self): ... + @property + def expr(self): ... + def get_value(self): ... + def set_value(self, expr) -> None: ... + def lslack(self): ... + def uslack(self): ... + def slack(self): ... + +class _ConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = ConstraintData + __renamed__version__: str + +class _GeneralConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = ConstraintData + __renamed__version__: str + +class TemplateConstraintData(ConstraintData): + def __init__(self, template_info, component, index) -> None: ... + @property + def expr(self): ... + def template_expr(self): ... + __class__: Incomplete + def set_value(self, expr): ... + def to_bounded_expression(self): ... + +class Constraint(ActiveIndexedComponent): + class Infeasible: ... + Feasible: Incomplete + NoConstraint: Incomplete + Violated = Infeasible + Satisfied = Feasible + def construct(self, data=None) -> None: ... + def display(self, prefix: str = '', ostream=None): ... + +class ScalarConstraint(ConstraintData, Constraint): + def __init__(self, *args, **kwds) -> None: ... + @property + def body(self): ... + @property + def lower(self): ... + @property + def upper(self): ... + @property + def equality(self): ... + @property + def strict_lower(self): ... + @property + def strict_upper(self): ... + def clear(self) -> None: ... + def set_value(self, expr): ... + def add(self, index, expr): ... + +class SimpleConstraint(metaclass=RenamedClass): + __renamed__new_class__ = ScalarConstraint + __renamed__version__: str + +class AbstractScalarConstraint(ScalarConstraint): ... + +class AbstractSimpleConstraint(metaclass=RenamedClass): + __renamed__new_class__ = AbstractScalarConstraint + __renamed__version__: str + +class IndexedConstraint(Constraint): + def add(self, index, expr): ... + @overload + def __getitem__(self, index) -> ConstraintData: ... + __getitem__: Incomplete + +class ConstraintList(IndexedConstraint): + class End: ... + rule: Incomplete + def __init__(self, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + def add(self, expr): ... diff --git a/stubs/pyomo/core/base/disable_methods.pyi b/stubs/pyomo/core/base/disable_methods.pyi new file mode 100644 index 000000000..740876010 --- /dev/null +++ b/stubs/pyomo/core/base/disable_methods.pyi @@ -0,0 +1,3 @@ +from pyomo.common import DeveloperError as DeveloperError + +def disable_methods(methods): ... diff --git a/stubs/pyomo/core/base/enums.pyi b/stubs/pyomo/core/base/enums.pyi new file mode 100644 index 000000000..876037ce6 --- /dev/null +++ b/stubs/pyomo/core/base/enums.pyi @@ -0,0 +1,41 @@ +import enum + +from _typeshed import Incomplete + +strictEnum: Incomplete + +class TraversalStrategy(enum.Enum): + BreadthFirstSearch = 1 + PrefixDepthFirstSearch = 2 + PostfixDepthFirstSearch = 3 + BFS = BreadthFirstSearch + ParentLastDepthFirstSearch = PostfixDepthFirstSearch + PostfixDFS = PostfixDepthFirstSearch + ParentFirstDepthFirstSearch = PrefixDepthFirstSearch + PrefixDFS = PrefixDepthFirstSearch + DepthFirstSearch = PrefixDepthFirstSearch + DFS = DepthFirstSearch + +class SortComponents(enum.Flag): + UNSORTED = 0 + ORDERED_INDICES = 2 + SORTED_INDICES = 4 + ALPHABETICAL = 8 + unsorted = UNSORTED + indices = SORTED_INDICES + declOrder = UNSORTED + declarationOrder = declOrder + alphaOrder = ALPHABETICAL + alphabeticalOrder = alphaOrder + alphabetical = alphaOrder + deterministic = ORDERED_INDICES + sortBoth = indices | alphabeticalOrder + alphabetizeComponentAndIndex = sortBoth + @staticmethod + def default(): ... + @staticmethod + def sorter(sort_by_names: bool = False, sort_by_keys: bool = False): ... + @staticmethod + def sort_names(flag): ... + @staticmethod + def sort_indices(flag): ... diff --git a/stubs/pyomo/core/base/expression.pyi b/stubs/pyomo/core/base/expression.pyi new file mode 100644 index 000000000..cde0a8129 --- /dev/null +++ b/stubs/pyomo/core/base/expression.pyi @@ -0,0 +1,128 @@ +from typing import Any as typingAny + +import pyomo.core.expr.numeric_expr as numeric_expr +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.formatting import tabular_writer as tabular_writer +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import ( + check_if_numeric_type as check_if_numeric_type, +) +from pyomo.common.numeric_types import ( + native_numeric_types as native_numeric_types, +) +from pyomo.common.numeric_types import ( + native_types as native_types, +) +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ( + ComponentData as ComponentData, +) +from pyomo.core.base.component import ( + ModelComponentFactory as ModelComponentFactory, +) +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ( + IndexedComponent as IndexedComponent, +) +from pyomo.core.base.indexed_component import ( + UnindexedComponent_set as UnindexedComponent_set, +) +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.expr.numvalue import as_numeric as as_numeric + +logger: Incomplete + +class NamedExpressionData(numeric_expr.NumericValue): + EXPRESSION_SYSTEM: Incomplete + PRECEDENCE: int + ASSOCIATIVITY: Incomplete + def __call__(self, exception=...): ... + def create_node_with_local_data(self, values, classtype=None): ... + def is_named_expression_type(self): ... + def is_expression_type(self, expression_system=None): ... + def arg(self, index): ... + @property + def args(self): ... + def nargs(self): ... + def clone(self): ... + def polynomial_degree(self): ... + def is_potentially_variable(self): ... + @property + def expr(self): ... + @expr.setter + def expr(self, value) -> None: ... + def set_value(self, expr) -> None: ... + def is_constant(self): ... + def is_fixed(self): ... + def __iadd__(self, other): ... + def __imul__(self, other): ... + def __idiv__(self, other): ... + def __itruediv__(self, other): ... + def __ipow__(self, other): ... + +class _ExpressionData(metaclass=RenamedClass): + __renamed__new_class__ = NamedExpressionData + __renamed__version__: str + +class _GeneralExpressionDataImpl(metaclass=RenamedClass): + __renamed__new_class__ = NamedExpressionData + __renamed__version__: str + +class ExpressionData(NamedExpressionData, ComponentData): + def __init__(self, expr=None, component=None) -> None: ... + +class _GeneralExpressionData(metaclass=RenamedClass): + __renamed__new_class__ = ExpressionData + __renamed__version__: str + +class Expression(IndexedComponent): + NoConstraint = IndexedComponent.Skip + def __new__(cls, *args, **kwds): ... + def display(self, prefix: str = '', ostream=None): ... + def extract_values(self): ... + def store_values(self, new_values) -> None: ... + def construct(self, data=None) -> None: ... + def __lt__(self, other: typingAny) -> typingAny: ... + def __le__(self, other: typingAny) -> typingAny: ... + def __eq__(self, other: typingAny) -> typingAny: ... + def __ne__(self, other: typingAny) -> typingAny: ... + def __gt__(self, other: typingAny) -> typingAny: ... + def __ge__(self, other: typingAny) -> typingAny: ... + def __neg__(self) -> typingAny: ... + def __pos__(self) -> typingAny: ... + def __abs__(self) -> typingAny: ... + def __add__(self, other: typingAny) -> typingAny: ... + def __sub__(self, other: typingAny) -> typingAny: ... + def __mul__(self, other: typingAny) -> typingAny: ... + def __div__(self, other: typingAny) -> typingAny: ... + def __truediv__(self, other: typingAny) -> typingAny: ... + def __pow__(self, other: typingAny) -> typingAny: ... + def __radd__(self, other: typingAny) -> typingAny: ... + def __rsub__(self, other: typingAny) -> typingAny: ... + def __rmul__(self, other: typingAny) -> typingAny: ... + def __rdiv__(self, other: typingAny) -> typingAny: ... + def __rtruediv__(self, other: typingAny) -> typingAny: ... + def __rpow__(self, other: typingAny) -> typingAny: ... + +class ScalarExpression(ExpressionData, Expression): + def __init__(self, *args, **kwds) -> None: ... + def __call__(self, exception=...): ... + @property + def expr(self): ... + @expr.setter + def expr(self, expr) -> None: ... + def clear(self) -> None: ... + def set_value(self, expr): ... + def is_constant(self): ... + def is_fixed(self): ... + def add(self, index, expr): ... + +class SimpleExpression(metaclass=RenamedClass): + __renamed__new_class__ = ScalarExpression + __renamed__version__: str + +class IndexedExpression(Expression): + def add(self, index, expr): ... diff --git a/stubs/pyomo/core/base/external.pyi b/stubs/pyomo/core/base/external.pyi new file mode 100644 index 000000000..4f917079b --- /dev/null +++ b/stubs/pyomo/core/base/external.pyi @@ -0,0 +1,63 @@ +from ctypes import Structure + +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.fileutils import find_library as find_library +from pyomo.common.numeric_types import check_if_native_type as check_if_native_type +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import value as value +from pyomo.common.pyomo_typing import overload as overload +from pyomo.core.base.component import Component as Component +from pyomo.core.base.units_container import units as units +from pyomo.core.expr.numvalue import NonNumericValue as NonNumericValue +from pyomo.core.expr.numvalue import NumericConstant as NumericConstant + +logger: Incomplete +nan: Incomplete + +class ExternalFunction(Component): + def __new__(cls, *args, **kwargs): ... + def get_units(self): ... + def get_arg_units(self): ... + def __call__(self, *args): ... + def evaluate(self, args): ... + def evaluate_fgh(self, args, fixed=None, fgh: int = 2): ... + +class AMPLExternalFunction(ExternalFunction): + __autoslot_mappers__: Incomplete + def __init__(self, *args, **kwargs) -> None: ... + def load_library(self) -> None: ... + +class _PythonCallbackFunctionID(NumericConstant): + __autoslot_mappers__: Incomplete + def is_constant(self): ... + +class PythonCallbackFunction(ExternalFunction): + __autoslot_mappers__: Incomplete + global_registry: Incomplete + global_id_to_fid: Incomplete + @classmethod + def register_instance(cls, instance): ... + def __init__(self, *args, **kwargs) -> None: ... + def __call__(self, *args): ... + +class _ARGLIST(Structure): + n: Incomplete + at: Incomplete + nr: Incomplete + ra: Incomplete + sa: Incomplete + derivs: Incomplete + hes: Incomplete + dig: Incomplete + def __init__(self, args, fgh: int = 0, fixed=None) -> None: ... + +class _AMPLEXPORTS(Structure): ... + +class _AMPLEXPORTS(Structure): + AMPLFUNC: Incomplete + ADDFUNC: Incomplete + RANDSEEDSETTER: Incomplete + ADDRANDINIT: Incomplete + ATRESET: Incomplete diff --git a/stubs/pyomo/core/base/global_set.pyi b/stubs/pyomo/core/base/global_set.pyi new file mode 100644 index 000000000..e495ae697 --- /dev/null +++ b/stubs/pyomo/core/base/global_set.pyi @@ -0,0 +1,51 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.core.base.range import NonNumericRange as NonNumericRange +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +GlobalSets: Incomplete + +class GlobalSetBase(PyomoObject): + def __reduce__(self): ... + def __deepcopy__(self, memo): ... + +class _UnindexedComponent_set(GlobalSetBase): + local_name: str + name: Incomplete + def __init__(self, name) -> None: ... + def __contains__(self, val) -> bool: ... + def get(self, value, default): ... + def __iter__(self): ... + def __reversed__(self): ... + def ordered_iter(self): ... + def sorted_iter(self): ... + def data(self): ... + def ordered_data(self): ... + def sorted_data(self): ... + def subsets(self, expand_all_set_operators=None): ... + def construct(self) -> None: ... + def ranges(self) -> Generator[Incomplete]: ... + def bounds(self): ... + def get_interval(self): ... + def __len__(self) -> int: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + @property + def dimen(self): ... + def isdiscrete(self): ... + def isfinite(self): ... + def isordered(self): ... + def at(self, index) -> None: ... + def ord(self, item): ... + def first(self) -> None: ... + def last(self) -> None: ... + def next(self, item, step: int = 1) -> None: ... + def nextw(self, item, step: int = 1) -> None: ... + def prev(self, item, step: int = 1): ... + def prevw(self, item, step: int = 1): ... + def parent_block(self) -> None: ... + def parent_component(self): ... + +UnindexedComponent_set: Incomplete +UnindexedComponent_index: Incomplete diff --git a/stubs/pyomo/core/base/indexed_component.pyi b/stubs/pyomo/core/base/indexed_component.pyi new file mode 100644 index 000000000..99cca95e2 --- /dev/null +++ b/stubs/pyomo/core/base/indexed_component.pyi @@ -0,0 +1,65 @@ +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.autoslots import fast_deepcopy as fast_deepcopy +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import TemplateExpressionError as TemplateExpressionError +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.sorting import sorted_robust as sorted_robust +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.base.component import Component as Component +from pyomo.core.base.component import ComponentData as ComponentData +from pyomo.core.base.config import PyomoOptions as PyomoOptions +from pyomo.core.base.enums import SortComponents as SortComponents +from pyomo.core.base.global_set import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.indexed_component_slice import IndexedComponent_slice as IndexedComponent_slice +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +logger: Incomplete +sequence_types: Incomplete +slicer_types: Incomplete + +def normalize_index(x): ... + +class _NotFound: ... +class _NotSpecified: ... + +def rule_result_substituter(result_map, map_types): ... +def rule_wrapper(rule, wrapping_fcn, positional_arg_map=None, map_types=None): ... + +class IndexedComponent(Component): + class Skip: ... + + def __init__(self, *args, **kwds) -> None: ... + def to_dense_data(self) -> None: ... + def clear(self) -> None: ... + def index_set(self): ... + def is_indexed(self): ... + def is_reference(self): ... + def dim(self): ... + def __len__(self) -> int: ... + def __contains__(self, idx) -> bool: ... + def __iter__(self): ... + def keys(self, sort=..., ordered=...): ... + def values(self, sort=..., ordered=...): ... + def items(self, sort=..., ordered=...): ... + def iterkeys(self): ... + def itervalues(self): ... + def iteritems(self): ... + def __getitem__(self, index) -> ComponentData: ... + def __setitem__(self, index, val) -> None: ... + def __delitem__(self, index) -> None: ... + def set_value(self, value) -> None: ... + def id_index_map(self): ... + +class ActiveIndexedComponent(IndexedComponent, ActiveComponent): + def __init__(self, *args, **kwds) -> None: ... + def activate(self) -> None: ... + def deactivate(self) -> None: ... + +class IndexedComponent_NDArrayMixin: + def __array__(self, dtype=None): ... + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): ... diff --git a/stubs/pyomo/core/base/indexed_component_slice.pyi b/stubs/pyomo/core/base/indexed_component_slice.pyi new file mode 100644 index 000000000..d0141fcac --- /dev/null +++ b/stubs/pyomo/core/base/indexed_component_slice.pyi @@ -0,0 +1,67 @@ +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.collections import Sequence as Sequence +from pyomo.core.base.enums import SortComponents as SortComponents +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index + +class IndexedComponent_slice: + ATTR_MASK: int + ITEM_MASK: int + CALL_MASK: int + GET_MASK: int + SET_MASK: int + DEL_MASK: int + slice_info: int + get_attribute = ATTR_MASK | GET_MASK + set_attribute = ATTR_MASK | SET_MASK + del_attribute = ATTR_MASK | DEL_MASK + get_item = ITEM_MASK | GET_MASK + set_item = ITEM_MASK | SET_MASK + del_item = ITEM_MASK | DEL_MASK + call = CALL_MASK + def __init__(self, component, fixed=None, sliced=None, ellipsis=None) -> None: ... + def __deepcopy__(self, memo): ... + def __iter__(self): ... + def __getattr__(self, name): ... + def __setattr__(self, name, value): ... + def __getitem__(self, idx): ... + def __setitem__(self, idx, val) -> None: ... + def __delitem__(self, idx) -> None: ... + def __call__(self, *args, **kwds): ... + def __hash__(self): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def duplicate(self): ... + def index_wildcard_keys(self, sort): ... + def wildcard_keys(self, sort=...): ... + def wildcard_values(self, sort=...): ... + def wildcard_items(self, sort=...): ... + def expanded_keys(self): ... + def expanded_items(self): ... + +class _slice_generator: + component: Incomplete + fixed: Incomplete + sliced: Incomplete + ellipsis: Incomplete + iter_over_index: Incomplete + last_index: Incomplete + tuplize_unflattened_index: Incomplete + explicit_index_count: int + component_iter: Incomplete + def __init__(self, component, fixed, sliced, ellipsis, iter_over_index, sort) -> None: ... + def next(self): ... + def __next__(self): ... + +class _NotIterable: ... + +class _IndexedComponent_slice_iter: + advance_iter: Incomplete + def __init__( + self, component_slice, advance_iter=..., iter_over_index: bool = False, sort: bool = False + ) -> None: ... + def __iter__(self): ... + def next(self): ... + def __next__(self): ... + def get_last_index(self): ... + def get_last_index_wildcards(self): ... diff --git a/stubs/pyomo/core/base/initializer.pyi b/stubs/pyomo/core/base/initializer.pyi new file mode 100644 index 000000000..a5712dd64 --- /dev/null +++ b/stubs/pyomo/core/base/initializer.pyi @@ -0,0 +1,92 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.dependencies import numpy as numpy +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.dependencies import pandas as pandas +from pyomo.common.dependencies import pandas_available as pandas_available +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +initializer_map: Incomplete +sequence_types: Incomplete +function_types: Incomplete + +def Initializer( + arg, + allow_generators: bool = False, + treat_sequences_as_mappings: bool = True, + arg_not_specified=None, + additional_args: int = 0, +): ... + +class InitializerBase(AutoSlots.Mixin): + verified: bool + def constant(self): ... + def contains_indices(self): ... + def indices(self) -> None: ... + +class ConstantInitializer(InitializerBase): + val: Incomplete + verified: bool + def __init__(self, val) -> None: ... + def __call__(self, parent, idx): ... + def constant(self): ... + +class ItemInitializer(InitializerBase): + def __init__(self, _dict) -> None: ... + def __call__(self, parent, idx): ... + def contains_indices(self): ... + def indices(self): ... + +class DataFrameInitializer(InitializerBase): + def __init__(self, dataframe, column=None) -> None: ... + def __call__(self, parent, idx): ... + def contains_indices(self): ... + def indices(self): ... + +class IndexedCallInitializer(InitializerBase): + def __init__(self, _fcn) -> None: ... + def __call__(self, parent, idx): ... + +class ParameterizedIndexedCallInitializer(IndexedCallInitializer): + def __call__(self, parent, idx, *args): ... + +class CountedCallGenerator: + def __init__(self, ctype, fcn, scalar, parent, idx, start_at) -> None: ... + def __iter__(self): ... + def __next__(self): ... + next = __next__ + +class CountedCallInitializer(InitializerBase): + def __init__(self, obj, _indexed_init, starting_index: int = 1) -> None: ... + def __call__(self, parent, idx): ... + +class ScalarCallInitializer(InitializerBase): + def __init__(self, _fcn, constant: bool = True) -> None: ... + def __call__(self, parent, idx): ... + def constant(self): ... + +class ParameterizedScalarCallInitializer(ScalarCallInitializer): + def __call__(self, parent, idx, *args): ... + +class DefaultInitializer(InitializerBase): + def __init__(self, initializer, default, exceptions) -> None: ... + def __call__(self, parent, index): ... + def constant(self): ... + def contains_indices(self): ... + def indices(self): ... + +class ParameterizedInitializer(InitializerBase): + def __init__(self, base) -> None: ... + def constant(self): ... + def contains_indices(self): ... + def indices(self): ... + def __call__(self, parent, idx, *args): ... + +class BoundInitializer(InitializerBase): + def __new__(cls, arg=None, obj=...): ... + def __init__(self, arg, obj=...) -> None: ... + def __call__(self, parent, index): ... + def constant(self): ... + def contains_indices(self): ... + def indices(self): ... diff --git a/stubs/pyomo/core/base/instance2dat.pyi b/stubs/pyomo/core/base/instance2dat.pyi new file mode 100644 index 000000000..cfb5fa593 --- /dev/null +++ b/stubs/pyomo/core/base/instance2dat.pyi @@ -0,0 +1,5 @@ +from pyomo.core.base import Param as Param +from pyomo.core.base import Set as Set +from pyomo.core.base import value as value + +def instance2dat(instance, output_filename) -> None: ... diff --git a/stubs/pyomo/core/base/label.pyi b/stubs/pyomo/core/base/label.pyi new file mode 100644 index 000000000..51ec93d0e --- /dev/null +++ b/stubs/pyomo/core/base/label.pyi @@ -0,0 +1,63 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.core.base.componentuid import ComponentUID as ComponentUID + +class _CharMapper: + table: Incomplete + other: Incomplete + def __init__(self, preserve, translate, other) -> None: ... + def __getitem__(self, c): ... + def make_table(self): ... + +def cpxlp_label_from_name(name): ... +def alphanum_label_from_name(name): ... + +class CuidLabeler: + def __call__(self, obj=None): ... + +class CounterLabeler: + def __init__(self, start: int = 0) -> None: ... + def __call__(self, obj=None): ... + +class NumericLabeler: + id: Incomplete + prefix: Incomplete + def __init__(self, prefix, start: int = 0) -> None: ... + def __call__(self, obj=None): ... + def remove_obj(self, obj) -> None: ... + +class CNameLabeler: + def __call__(self, obj): ... + +class LPFileLabeler: + def __call__(self, obj): ... + def remove_obj(self, obj) -> None: ... + +TextLabeler = LPFileLabeler + +class AlphaNumericTextLabeler: + def __call__(self, obj): ... + +class NameLabeler: + def __call__(self, obj): ... + +class ShortNameLabeler: + id: Incomplete + prefix: Incomplete + suffix: Incomplete + limit: Incomplete + labeler: Incomplete + known_labels: Incomplete + caseInsensitive: Incomplete + legalRegex: Incomplete + def __init__( + self, + limit, + suffix, + start: int = 0, + labeler=None, + prefix: str = '', + caseInsensitive: bool = False, + legalRegex=None, + ) -> None: ... + def __call__(self, obj=None): ... diff --git a/stubs/pyomo/core/base/logical_constraint.pyi b/stubs/pyomo/core/base/logical_constraint.pyi new file mode 100644 index 000000000..7d5793758 --- /dev/null +++ b/stubs/pyomo/core/base/logical_constraint.pyi @@ -0,0 +1,69 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.formatting import tabular_writer as tabular_writer +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.core.base.set import Set as Set +from pyomo.core.expr.boolean_value import BooleanConstant as BooleanConstant +from pyomo.core.expr.boolean_value import as_boolean as as_boolean +from pyomo.core.expr.numvalue import native_logical_types as native_logical_types +from pyomo.core.expr.numvalue import native_types as native_types + +logger: Incomplete + +class LogicalConstraintData(ActiveComponentData): + def __init__(self, expr=None, component=None) -> None: ... + def __call__(self, exception=...): ... + @property + def body(self): ... + @property + def expr(self): ... + def set_value(self, expr) -> None: ... + def get_value(self): ... + +class _LogicalConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = LogicalConstraintData + __renamed__version__: str + +class _GeneralLogicalConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = LogicalConstraintData + __renamed__version__: str + +class LogicalConstraint(ActiveIndexedComponent): + class Infeasible: ... + Feasible: Incomplete + NoConstraint: Incomplete + Violated = Infeasible + Satisfied = Feasible + def __new__(cls, *args, **kwds): ... + rule: Incomplete + def __init__(self, *args, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + def display(self, prefix: str = '', ostream=None): ... + +class ScalarLogicalConstraint(LogicalConstraintData, LogicalConstraint): + def __init__(self, *args, **kwds) -> None: ... + @property + def body(self): ... + def set_value(self, expr): ... + def add(self, index, expr): ... + +class SimpleLogicalConstraint(metaclass=RenamedClass): + __renamed__new_class__ = ScalarLogicalConstraint + __renamed__version__: str + +class IndexedLogicalConstraint(LogicalConstraint): + def add(self, index, expr): ... + +class LogicalConstraintList(IndexedLogicalConstraint): + End: Incomplete + def __init__(self, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + def add(self, expr): ... diff --git a/stubs/pyomo/core/base/matrix_constraint.pyi b/stubs/pyomo/core/base/matrix_constraint.pyi new file mode 100644 index 000000000..08cafabe1 --- /dev/null +++ b/stubs/pyomo/core/base/matrix_constraint.pyi @@ -0,0 +1,49 @@ +from collections.abc import Mapping + +from _typeshed import Incomplete +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.constraint import IndexedConstraint as IndexedConstraint +from pyomo.core.base.set_types import Any as Any +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression +from pyomo.core.expr.numvalue import value as value +from pyomo.repn.standard_repn import StandardRepn as StandardRepn + +logger: Incomplete + +class _MatrixConstraintData(ConstraintData): + def canonical_form(self, compute_values: bool = True): ... + def __init__(self, index, component_ref) -> None: ... + def __call__(self, exception=...): ... + def has_lb(self): ... + def has_ub(self): ... + def lslack(self): ... + def uslack(self): ... + def slack(self): ... + def index(self): ... + @property + def body(self): ... + @property + def lower(self): ... + @property + def upper(self): ... + @property + def equality(self): ... + @property + def strict_lower(self): ... + @property + def strict_upper(self): ... + def set_value(self, expr) -> None: ... + +class MatrixConstraint(Mapping, IndexedConstraint): + def __init__(self, A_data, A_indices, A_indptr, lb, ub, x) -> None: ... + def construct(self, data=None) -> None: ... + def __getitem__(self, key): ... + def __len__(self) -> int: ... + def __iter__(self): ... + def add(self, index, expr) -> None: ... + def __delitem__(self) -> None: ... + def __setitem__(self, key, value) -> None: ... diff --git a/stubs/pyomo/core/base/misc.pyi b/stubs/pyomo/core/base/misc.pyi new file mode 100644 index 000000000..64e9b18e3 --- /dev/null +++ b/stubs/pyomo/core/base/misc.pyi @@ -0,0 +1,9 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute + +logger: Incomplete + +def display(obj, ostream=None) -> None: ... +def create_name(name, ndx): ... +def apply_indexed_rule(obj, rule, model, index, options=None): ... +def apply_parameterized_indexed_rule(obj, rule, model, param, index): ... diff --git a/stubs/pyomo/core/base/numvalue.pyi b/stubs/pyomo/core/base/numvalue.pyi new file mode 100644 index 000000000..22e2f61a8 --- /dev/null +++ b/stubs/pyomo/core/base/numvalue.pyi @@ -0,0 +1,43 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import check_if_numeric_type as check_if_numeric_type +from pyomo.common.numeric_types import native_integer_types as native_integer_types +from pyomo.common.numeric_types import native_logical_types as native_logical_types +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.common.numeric_types import value as value +from pyomo.core.expr.expr_common import ExpressionType as ExpressionType +from pyomo.core.expr.numeric_expr import NumericValue as NumericValue +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +logger: Incomplete + +class NonNumericValue(PyomoObject): + value: Incomplete + def __init__(self, value) -> None: ... + def __call__(self, exception=...): ... + def is_constant(self): ... + def is_fixed(self): ... + +def is_constant(obj): ... +def is_fixed(obj): ... +def is_variable_type(obj): ... +def is_potentially_variable(obj): ... +def is_numeric_data(obj): ... +def polynomial_degree(obj): ... +def as_numeric(obj): ... +def check_if_numeric_type_and_cache(obj): ... + +class NumericConstant(NumericValue): + value: Incomplete + def __init__(self, value) -> None: ... + def is_constant(self): ... + def is_fixed(self): ... + def __call__(self, exception=...): ... + def pprint(self, ostream=None, verbose: bool = False) -> None: ... + +ZeroConstant: Incomplete diff --git a/stubs/pyomo/core/base/objective.pyi b/stubs/pyomo/core/base/objective.pyi new file mode 100644 index 000000000..c2454742e --- /dev/null +++ b/stubs/pyomo/core/base/objective.pyi @@ -0,0 +1,92 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.enums import ObjectiveSense as ObjectiveSense +from pyomo.common.enums import maximize as maximize +from pyomo.common.enums import minimize as minimize +from pyomo.common.errors import TemplateExpressionError as TemplateExpressionError +from pyomo.common.formatting import tabular_writer as tabular_writer +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.expression import NamedExpressionData as NamedExpressionData +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.indexed_component import rule_wrapper as rule_wrapper +from pyomo.core.base.initializer import CountedCallInitializer as CountedCallInitializer +from pyomo.core.base.initializer import IndexedCallInitializer as IndexedCallInitializer +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.base.set import Set as Set +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.template_expr import templatize_rule as templatize_rule + +logger: Incomplete +TEMPLATIZE_OBJECTIVES: bool + +def simple_objective_rule(rule): ... +def simple_objectivelist_rule(rule): ... + +class ObjectiveData(NamedExpressionData, ActiveComponentData): + def __init__(self, expr=None, sense=..., component=None) -> None: ... + def is_minimizing(self): ... + def set_value(self, expr): ... + @property + def sense(self): ... + @sense.setter + def sense(self, sense) -> None: ... + def set_sense(self, sense) -> None: ... + +class _ObjectiveData(metaclass=RenamedClass): + __renamed__new_class__ = ObjectiveData + __renamed__version__: str + +class _GeneralObjectiveData(metaclass=RenamedClass): + __renamed__new_class__ = ObjectiveData + __renamed__version__: str + +class TemplateObjectiveData(ObjectiveData): + def __init__(self, template_info, component, index, sense) -> None: ... + @property + def args(self): ... + def template_expr(self): ... + __class__: Incomplete + def set_value(self, expr): ... + +class Objective(ActiveIndexedComponent): + NoObjective: Incomplete + def __new__(cls, *args, **kwds): ... + def construct(self, data=None) -> None: ... + def display(self, prefix: str = '', ostream=None): ... + +class ScalarObjective(ObjectiveData, Objective): + def __init__(self, *args, **kwd) -> None: ... + def __call__(self, exception=...): ... + @property + def expr(self): ... + @expr.setter + def expr(self, expr) -> None: ... + @property + def sense(self): ... + @sense.setter + def sense(self, sense) -> None: ... + def clear(self) -> None: ... + def set_value(self, expr): ... + def set_sense(self, sense): ... + def add(self, index, expr): ... + +class SimpleObjective(metaclass=RenamedClass): + __renamed__new_class__ = ScalarObjective + __renamed__version__: str + +class IndexedObjective(Objective): + def add(self, index, expr): ... + +class ObjectiveList(IndexedObjective): + class End: ... + rule: Incomplete + def __init__(self, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + def add(self, expr, sense=...): ... diff --git a/stubs/pyomo/core/base/param.pyi b/stubs/pyomo/core/base/param.pyi new file mode 100644 index 000000000..c8d310a77 --- /dev/null +++ b/stubs/pyomo/core/base/param.pyi @@ -0,0 +1,90 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ComponentData as ComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.indexed_component import ( + IndexedComponent_NDArrayMixin as IndexedComponent_NDArrayMixin, +) +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.core.base.misc import ( + apply_parameterized_indexed_rule as apply_parameterized_indexed_rule, +) +from pyomo.core.base.set import Reals as Reals +from pyomo.core.base.set import SetInitializer as SetInitializer +from pyomo.core.base.set import _AnySet +from pyomo.core.base.units_container import units as units +from pyomo.core.expr import GetItemExpression as GetItemExpression +from pyomo.core.expr.numvalue import NumericValue as NumericValue + +logger: Incomplete + +class _ImplicitAny(_AnySet): + __autoslot_mappers__: Incomplete + def __new__(cls, **kwargs): ... + def __init__(self, owner, **kwargs) -> None: ... + def __contains__(self, val) -> bool: ... + def getname(self, fully_qualified: bool = False, name_buffer=None, relative_to=None): ... + +class ParamData(ComponentData, NumericValue): + def __init__(self, component) -> None: ... + def clear(self) -> None: ... + def set_value(self, value, idx=...) -> None: ... + def __call__(self, exception=...): ... + @property + def value(self): ... + @value.setter + def value(self, val) -> None: ... + def get_units(self): ... + def is_fixed(self): ... + def is_constant(self): ... + def is_parameter_type(self): ... + +class _ParamData(metaclass=RenamedClass): + __renamed__new_class__ = ParamData + __renamed__version__: str + +class Param(IndexedComponent, IndexedComponent_NDArrayMixin): + DefaultMutable: bool + class NoValue: ... + + def __len__(self) -> int: ... + def __contains__(self, idx) -> bool: ... + @property + def mutable(self): ... + def get_units(self): ... + def sparse_keys(self): ... + def sparse_values(self): ... + def sparse_items(self): ... + def sparse_keys(self): ... + def sparse_itervalues(self): ... + def sparse_iteritems(self): ... + def extract_values(self): ... + def extract_values_sparse(self): ... + def store_values(self, new_values, check: bool = True) -> None: ... + def set_default(self, val) -> None: ... + def default(self): ... + def construct(self, data=None) -> None: ... + +class ScalarParam(ParamData, Param): + def __init__(self, *args, **kwds) -> None: ... + def __call__(self, exception=...): ... + def set_value(self, value, index=...) -> None: ... + def is_constant(self): ... + +class SimpleParam(metaclass=RenamedClass): + __renamed__new_class__ = ScalarParam + __renamed__version__: str + +class IndexedParam(Param): + def __getitem__(self, args) -> ParamData: ... diff --git a/stubs/pyomo/core/base/piecewise.pyi b/stubs/pyomo/core/base/piecewise.pyi new file mode 100644 index 000000000..0f8c426ef --- /dev/null +++ b/stubs/pyomo/core/base/piecewise.pyi @@ -0,0 +1,95 @@ +import enum + +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.numeric_types import value as value +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.constraint import ConstraintList as ConstraintList +from pyomo.core.base.set_types import Binary as Binary +from pyomo.core.base.set_types import NonNegativeReals as NonNegativeReals +from pyomo.core.base.set_types import PositiveReals as PositiveReals +from pyomo.core.base.sos import SOSConstraint as SOSConstraint +from pyomo.core.base.util import flatten_tuple as flatten_tuple +from pyomo.core.base.var import IndexedVar as IndexedVar +from pyomo.core.base.var import Var as Var +from pyomo.core.base.var import VarData as VarData + +logger: Incomplete + +class PWRepn(str, enum.Enum): + SOS2 = 'SOS2' + BIGM_BIN = 'BIGM_BIN' + BIGM_SOS1 = 'BIGM_SOS1' + CC = 'CC' + DCC = 'DCC' + DLOG = 'DLOG' + LOG = 'LOG' + MC = 'MC' + INC = 'INC' + +class Bound(str, enum.Enum): + Lower = 'Lower' + Upper = 'Upper' + Equal = 'Equal' + +class PiecewiseData(BlockData): + def __init__(self, parent) -> None: ... + def updateBoundType(self, bound_type) -> None: ... + def updatePoints(self, domain_pts, range_pts) -> None: ... + def build_constraints(self, functor, x_var, y_var) -> None: ... + def referenced_variables(self): ... + def __call__(self, x): ... + +class _PiecewiseData(metaclass=RenamedClass): + __renamed__new_class__ = PiecewiseData + __renamed__version__: str + +class _SimpleSinglePiecewise: + def construct(self, pblock, x_var, y_var) -> None: ... + +class _SimplifiedPiecewise: + def construct(self, pblock, x_var, y_var) -> None: ... + +class _SOS2Piecewise: + def construct(self, pblock, x_var, y_var): ... + +class _DCCPiecewise: + def construct(self, pblock, x_var, y_var): ... + +class _DLOGPiecewise: + def construct(self, pblock, x_var, y_var): ... + +class _CCPiecewise: + def construct(self, pblock, x_var, y_var): ... + +class _LOGPiecewise: + def construct(self, pblock, x_var, y_var): ... + +class _MCPiecewise: + def construct(self, pblock, x_var, y_var): ... + +class _INCPiecewise: + def construct(self, pblock, x_var, y_var): ... + +class _BIGMPiecewise: + binary: Incomplete + def __init__(self, binary: bool = True) -> None: ... + def construct(self, pblock, x_var, y_var): ... + +class Piecewise(Block): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwds) -> None: ... + def construct(self, *args, **kwds) -> None: ... + def add(self, index, _is_indexed=None) -> None: ... + +class SimplePiecewise(PiecewiseData, Piecewise): + def __init__(self, *args, **kwds) -> None: ... + +class IndexedPiecewise(Piecewise): + def __init__(self, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/core/base/range.pyi b/stubs/pyomo/core/base/range.pyi new file mode 100644 index 000000000..3f77df4d4 --- /dev/null +++ b/stubs/pyomo/core/base/range.pyi @@ -0,0 +1,60 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.numeric_types import check_if_numeric_type as check_if_numeric_type + +class RangeDifferenceError(ValueError): ... + +class NumericRange(AutoSlots.Mixin): + start: Incomplete + end: Incomplete + step: Incomplete + closed: Incomplete + def __init__(self, start, end, step, closed=(True, True)) -> None: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __contains__(self, value) -> bool: ... + def isdiscrete(self): ... + def isfinite(self): ... + def isdisjoint(self, other): ... + def issubset(self, other): ... + def normalize_bounds(self): ... + def range_difference(self, other_ranges): ... + def range_intersection(self, other_ranges): ... + +class NonNumericRange: + value: Incomplete + def __init__(self, val) -> None: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __contains__(self, value) -> bool: ... + def isdiscrete(self): ... + def isfinite(self): ... + def isdisjoint(self, other): ... + def issubset(self, other): ... + def range_difference(self, other_ranges): ... + def range_intersection(self, other_ranges): ... + +class AnyRange: + def __init__(self) -> None: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __contains__(self, value) -> bool: ... + def isdiscrete(self): ... + def isfinite(self): ... + def isdisjoint(self, other): ... + def issubset(self, other): ... + def range_difference(self, other_ranges): ... + def range_intersection(self, other_ranges): ... + +class RangeProduct: + range_lists: Incomplete + def __init__(self, range_lists) -> None: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __contains__(self, value) -> bool: ... + def isdiscrete(self): ... + def isfinite(self): ... + def isdisjoint(self, other): ... + def issubset(self, other): ... + def range_difference(self, other_ranges): ... + def range_intersection(self, other_ranges): ... diff --git a/stubs/pyomo/core/base/reference.pyi b/stubs/pyomo/core/base/reference.pyi new file mode 100644 index 000000000..32d9aea34 --- /dev/null +++ b/stubs/pyomo/core/base/reference.pyi @@ -0,0 +1,64 @@ +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.collections import Mapping as Mapping +from pyomo.common.collections import MutableMapping as MutableMapping +from pyomo.common.collections import Sequence as Sequence +from pyomo.common.collections import Set as collections_Set +from pyomo.common.collections import UserDict as UserDict +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.base.component import Component as Component +from pyomo.core.base.component import ComponentData as ComponentData +from pyomo.core.base.enums import SortComponents as SortComponents +from pyomo.core.base.global_set import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.indexed_component import normalize_index as normalize_index +from pyomo.core.base.indexed_component_slice import IndexedComponent_slice as IndexedComponent_slice +from pyomo.core.base.set import DeclareGlobalSet as DeclareGlobalSet +from pyomo.core.base.set import OrderedSetOf as OrderedSetOf +from pyomo.core.base.set import Set as Set +from pyomo.core.base.set import SetData as SetData +from pyomo.core.base.set import SetOf as SetOf +from pyomo.core.base.util import flatten_tuple as flatten_tuple + +class _fill_in_known_wildcards: + base_key: Incomplete + key: Incomplete + known_slices: Incomplete + look_in_index: Incomplete + get_if_not_present: Incomplete + def __init__( + self, wildcard_values, look_in_index: bool = False, get_if_not_present: bool = False + ) -> None: ... + def __call__(self, _slice): ... + def check_complete(self) -> None: ... + +class SliceEllipsisLookupError(LookupError): ... + +class _ReferenceDict(MutableMapping): + def __init__(self, component_slice) -> None: ... + def __contains__(self, key) -> bool: ... + def __getitem__(self, key): ... + def __setitem__(self, key, val) -> None: ... + def __delitem__(self, key) -> None: ... + def __iter__(self): ... + def __len__(self) -> int: ... + def keys(self, sort=...): ... + def items(self, sort=...): ... + def values(self, sort=...): ... + def iteritems(self): ... + def itervalues(self): ... + +class _ReferenceDict_mapping(UserDict): + data: Incomplete + def __init__(self, data) -> None: ... + +class _ReferenceSet(collections_Set): + def __init__(self, component_slice) -> None: ... + def __contains__(self, key) -> bool: ... + def __iter__(self): ... + def __len__(self) -> int: ... + def ordered_iter(self): ... + def sorted_iter(self): ... + +def Reference(reference, ctype=...): ... diff --git a/stubs/pyomo/core/base/set.pyi b/stubs/pyomo/core/base/set.pyi new file mode 100644 index 000000000..98978b8fb --- /dev/null +++ b/stubs/pyomo/core/base/set.pyi @@ -0,0 +1,609 @@ +from collections.abc import Generator, Iterator +from functools import partial as partial +from typing import Any as typingAny + +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.deprecation import ( + RenamedClass as RenamedClass, +) +from pyomo.common.deprecation import ( + deprecated as deprecated, +) +from pyomo.common.deprecation import ( + deprecation_warning as deprecation_warning, +) +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.sorting import sorted_robust as sorted_robust +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ( + Component as Component, +) +from pyomo.core.base.component import ( + ComponentBase as ComponentBase, +) +from pyomo.core.base.component import ( + ComponentData as ComponentData, +) +from pyomo.core.base.component import ( + ModelComponentFactory as ModelComponentFactory, +) +from pyomo.core.base.disable_methods import disable_methods as disable_methods +from pyomo.core.base.global_set import ( + GlobalSetBase as GlobalSetBase, +) +from pyomo.core.base.global_set import ( + GlobalSets as GlobalSets, +) +from pyomo.core.base.global_set import ( + UnindexedComponent_index as UnindexedComponent_index, +) +from pyomo.core.base.indexed_component import ( + IndexedComponent as IndexedComponent, +) +from pyomo.core.base.indexed_component import ( + UnindexedComponent_set as UnindexedComponent_set, +) +from pyomo.core.base.indexed_component import ( + normalize_index as normalize_index, +) +from pyomo.core.base.indexed_component import ( + rule_wrapper as rule_wrapper, +) +from pyomo.core.base.initializer import ( + CountedCallInitializer as CountedCallInitializer, +) +from pyomo.core.base.initializer import ( + IndexedCallInitializer as IndexedCallInitializer, +) +from pyomo.core.base.initializer import ( + Initializer as Initializer, +) +from pyomo.core.base.initializer import ( + InitializerBase as InitializerBase, +) +from pyomo.core.base.initializer import ( + ParameterizedIndexedCallInitializer as ParameterizedIndexedCallInitializer, +) +from pyomo.core.base.initializer import ( + ParameterizedInitializer as ParameterizedInitializer, +) +from pyomo.core.base.initializer import ( + ParameterizedScalarCallInitializer as ParameterizedScalarCallInitializer, +) +from pyomo.core.base.range import ( + AnyRange as AnyRange, +) +from pyomo.core.base.range import ( + NonNumericRange as NonNumericRange, +) +from pyomo.core.base.range import ( + NumericRange as NumericRange, +) +from pyomo.core.base.range import ( + RangeDifferenceError as RangeDifferenceError, +) +from pyomo.core.base.range import ( + RangeProduct as RangeProduct, +) +from pyomo.core.expr.numvalue import ( + as_numeric as as_numeric, +) +from pyomo.core.expr.numvalue import ( + is_constant as is_constant, +) +from pyomo.core.expr.numvalue import ( + native_numeric_types as native_numeric_types, +) +from pyomo.core.expr.numvalue import ( + native_types as native_types, +) +from pyomo.core.expr.numvalue import ( + value as value, +) + +logger: Incomplete +FLATTEN_CROSS_PRODUCT: bool + +def process_setarg(arg): ... +def set_options(**kwds): ... +def simple_set_rule(rule): ... + +class UnknownSetDimen: ... + +class SetInitializer(InitializerBase): + verified: bool + def __init__(self, init, allow_generators: bool = True) -> None: ... + def intersect(self, other) -> None: ... + def __call__(self, parent, idx, obj): ... + def constant(self): ... + def contains_indices(self): ... + def indices(self): ... + def setdefault(self, val) -> None: ... + +class SetIntersectInitializer(InitializerBase): + def __init__(self, setA, setB) -> None: ... + def __call__(self, parent, idx): ... + def constant(self): ... + def contains_indices(self): ... + def indices(self): ... + +class BoundsInitializer(InitializerBase): + default_step: Incomplete + def __init__(self, init, default_step: int = 0) -> None: ... + def __call__(self, parent, idx): ... + def constant(self): ... + def setdefault(self, val) -> None: ... + +class TuplizeError(PyomoException): ... + +class TuplizeValuesInitializer(InitializerBase): + def __new__(cls, *args): ... + def __init__(self, _init) -> None: ... + def __call__(self, parent, index): ... + def constant(self): ... + def contains_indices(self): ... + def indices(self): ... + +class _NotFound: ... + +class SetData(ComponentData): + def __contains__(self, value) -> bool: ... + def get(self, value, default=None) -> None: ... + def isdiscrete(self): ... + def isfinite(self): ... + def isordered(self): ... + def subsets(self, expand_all_set_operators=None): ... + def __iter__(self) -> Iterator[typingAny]: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + @property + def dimen(self) -> None: ... + @property + def domain(self) -> None: ... + def ranges(self) -> None: ... + def bounds(self): ... + def get_interval(self): ... + @property + def virtual(self): ... + @virtual.setter + def virtual(self, value) -> None: ... + @property + def concrete(self): ... + @concrete.setter + def concrete(self, value) -> None: ... + @property + def ordered(self): ... + @property + def filter(self) -> None: ... + def check_values(self): ... + def isdisjoint(self, other): ... + def issubset(self, other): ... + def issuperset(self, other): ... + def union(self, *args): ... + def intersection(self, *args): ... + def difference(self, *args): ... + def symmetric_difference(self, other): ... + def cross(self, *args): ... + __le__ = issubset + __ge__ = issuperset + __or__ = union + __and__ = intersection + __sub__ = difference + __xor__ = symmetric_difference + __mul__ = cross + def __ror__(self, other): ... + def __rand__(self, other): ... + def __rsub__(self, other): ... + def __rxor__(self, other): ... + def __rmul__(self, other): ... + def __lt__(self, other): ... + def __gt__(self, other): ... + +class _SetData(metaclass=RenamedClass): + __renamed__new_class__ = SetData + __renamed__version__: str + +class _SetDataBase(metaclass=RenamedClass): + __renamed__new_class__ = SetData + __renamed__version__: str + +class _FiniteSetMixin: + def __len__(self) -> int: ... + def __iter__(self): ... + def __reversed__(self): ... + def sorted_iter(self): ... + def ordered_iter(self): ... + def isdiscrete(self): ... + def isfinite(self): ... + def data(self): ... + @property + def value(self): ... + @property + def value_list(self): ... + def sorted_data(self): ... + def ordered_data(self): ... + def bounds(self): ... + def ranges(self) -> Generator[Incomplete]: ... + +class FiniteSetData(_FiniteSetMixin, SetData): + def __init__(self, component) -> None: ... + def get(self, value, default=None): ... + def __reversed__(self): ... + def __len__(self) -> int: ... + @property + def dimen(self): ... + @property + def domain(self): ... + @property + def filter(self): ... + def add(self, *values): ... + def remove(self, val) -> None: ... + def discard(self, val) -> None: ... + def clear(self) -> None: ... + def set_value(self, val) -> None: ... + def update(self, values) -> None: ... + def pop(self): ... + +class _FiniteSetData(metaclass=RenamedClass): + __renamed__new_class__ = FiniteSetData + __renamed__version__: str + +class _ScalarOrderedSetMixin: + def values(self) -> Generator[Incomplete]: ... + def items(self) -> Generator[Incomplete]: ... + +class _OrderedSetMixin: + def at(self, index) -> None: ... + def ord(self, val) -> None: ... + def __getitem__(self, key): ... + def card(self, index): ... + def isordered(self): ... + def ordered_data(self): ... + def ordered_iter(self): ... + def first(self): ... + def last(self): ... + def next(self, item, step: int = 1): ... + def nextw(self, item, step: int = 1): ... + def prev(self, item, step: int = 1): ... + def prevw(self, item, step: int = 1): ... + +class OrderedSetData(_OrderedSetMixin, FiniteSetData): + def __init__(self, component) -> None: ... + def __reversed__(self): ... + def remove(self, val) -> None: ... + def discard(self, val) -> None: ... + def clear(self) -> None: ... + def pop(self): ... + def at(self, index): ... + def ord(self, item): ... + +class _OrderedSetData(metaclass=RenamedClass): + __renamed__new_class__ = OrderedSetData + __renamed__version__: str + +class InsertionOrderSetData(OrderedSetData): + def set_value(self, val) -> None: ... + def update(self, values) -> None: ... + +class _InsertionOrderSetData(metaclass=RenamedClass): + __renamed__new_class__ = InsertionOrderSetData + __renamed__version__: str + +class _SortedSetMixin: + def ordered_iter(self): ... + def sorted_iter(self): ... + +class SortedSetData(_SortedSetMixin, OrderedSetData): + def __reversed__(self): ... + def sorted_data(self): ... + +class _SortedSetData(metaclass=RenamedClass): + __renamed__new_class__ = SortedSetData + __renamed__version__: str + +class Set(IndexedComponent): + class _SetEndException(Exception): ... + + class _SetEndType(type): + def __hash__(self): ... + + class End(metaclass=_SetEndType): ... + class InsertionOrder: ... + class SortedOrder: ... + + def check_values(self): ... + def construct(self, data=None) -> None: ... + def add(self, *values: typingAny) -> None: ... + def remove(self, val: typingAny) -> None: ... + def discard(self, val: typingAny) -> None: ... + def clear(self) -> None: ... + def update(self, values: typingAny) -> None: ... + def pop(self) -> typingAny: ... + def difference(self, *args: typingAny) -> 'SetDifference': ... + def at(self, index: int) -> typingAny: ... + def ord(self, item: typingAny) -> int: ... + def first(self) -> typingAny: ... + def last(self) -> typingAny: ... + def next(self, item: typingAny, step: int = 1) -> typingAny: ... + def nextw(self, item: typingAny, step: int = 1) -> typingAny: ... + def prev(self, item: typingAny, step: int = 1) -> typingAny: ... + def prevw(self, item: typingAny, step: int = 1) -> typingAny: ... + def difference(self, *args: Any) -> 'SetDifference': ... + def __or__(self, other: Any) -> 'SetUnion': ... + def __and__(self, other: Any) -> 'SetIntersection': ... + def __sub__(self, other: Any) -> 'SetDifference': ... + def __xor__(self, other: Any) -> 'SetSymmetricDifference': ... + def __mul__(self, other: Any) -> 'SetProduct': ... + +class IndexedSet(Set): + def data(self): ... + @overload + def __getitem__(self, index) -> SetData: ... + __getitem__: Incomplete + +class FiniteScalarSet(FiniteSetData, Set): + def __init__(self, **kwds) -> None: ... + +class FiniteSimpleSet(metaclass=RenamedClass): + __renamed__new_class__ = FiniteScalarSet + __renamed__version__: str + +class OrderedScalarSet(_ScalarOrderedSetMixin, InsertionOrderSetData, Set): + def __init__(self, **kwds) -> None: ... + +class OrderedSimpleSet(metaclass=RenamedClass): + __renamed__new_class__ = OrderedScalarSet + __renamed__version__: str + +class SortedScalarSet(_ScalarOrderedSetMixin, SortedSetData, Set): + def __init__(self, **kwds) -> None: ... + +class SortedSimpleSet(metaclass=RenamedClass): + __renamed__new_class__ = SortedScalarSet + __renamed__version__: str + +class AbstractFiniteScalarSet(FiniteScalarSet): ... + +class AbstractFiniteSimpleSet(metaclass=RenamedClass): + __renamed__new_class__ = AbstractFiniteScalarSet + __renamed__version__: str + +class AbstractOrderedScalarSet(OrderedScalarSet): ... + +class AbstractOrderedSimpleSet(metaclass=RenamedClass): + __renamed__new_class__ = AbstractOrderedScalarSet + __renamed__version__: str + +class AbstractSortedScalarSet(SortedScalarSet): ... + +class AbstractSortedSimpleSet(metaclass=RenamedClass): + __renamed__new_class__ = AbstractSortedScalarSet + __renamed__version__: str + +class SetOf(SetData, Component): + def __new__(cls, *args, **kwds): ... + def __init__(self, reference, **kwds) -> None: ... + def construct(self, data=None) -> None: ... + @property + def dimen(self): ... + @property + def domain(self): ... + +class InfiniteSetOf(SetOf): + def ranges(self): ... + +class FiniteSetOf(_FiniteSetMixin, SetOf): + def get(self, value, default=None): ... + def __len__(self) -> int: ... + def __reversed__(self): ... + +class UnorderedSetOf(metaclass=RenamedClass): + __renamed__new_class__ = FiniteSetOf + __renamed__version__: str + +class OrderedSetOf(_ScalarOrderedSetMixin, _OrderedSetMixin, FiniteSetOf): + def at(self, index): ... + def ord(self, item): ... + +class InfiniteRangeSetData(SetData): + def __init__(self, component) -> None: ... + def get(self, value, default=None): ... + def isdiscrete(self): ... + @property + def dimen(self): ... + @property + def domain(self): ... + def clear(self) -> None: ... + def ranges(self): ... + +class _InfiniteRangeSetData(metaclass=RenamedClass): + __renamed__new_class__ = InfiniteRangeSetData + __renamed__version__: str + +class FiniteRangeSetData(_SortedSetMixin, _OrderedSetMixin, _FiniteSetMixin, InfiniteRangeSetData): + def __len__(self) -> int: ... + def at(self, index): ... + def ord(self, item): ... + bounds: Incomplete + ranges: Incomplete + domain: Incomplete + +class _FiniteRangeSetData(metaclass=RenamedClass): + __renamed__new_class__ = FiniteRangeSetData + __renamed__version__: str + +class RangeSet(Component): + def __new__(cls, *args, **kwds): ... + def construct(self, data=None) -> None: ... + def dim(self): ... + def index_set(self): ... + +class InfiniteScalarRangeSet(InfiniteRangeSetData, RangeSet): + def __init__(self, *args, **kwds) -> None: ... + +class InfiniteSimpleRangeSet(metaclass=RenamedClass): + __renamed__new_class__ = InfiniteScalarRangeSet + __renamed__version__: str + +class FiniteScalarRangeSet(_ScalarOrderedSetMixin, FiniteRangeSetData, RangeSet): + def __init__(self, *args, **kwds) -> None: ... + +class FiniteSimpleRangeSet(metaclass=RenamedClass): + __renamed__new_class__ = FiniteScalarRangeSet + __renamed__version__: str + +class AbstractInfiniteScalarRangeSet(InfiniteScalarRangeSet): ... + +class AbstractInfiniteSimpleRangeSet(metaclass=RenamedClass): + __renamed__new_class__ = AbstractInfiniteScalarRangeSet + __renamed__version__: str + +class AbstractFiniteScalarRangeSet(FiniteScalarRangeSet): ... + +class AbstractFiniteSimpleRangeSet(metaclass=RenamedClass): + __renamed__new_class__ = AbstractFiniteScalarRangeSet + __renamed__version__: str + +class SetOperator(SetData, Set): + def __init__(self, *args, **kwds) -> None: ... + def construct(self, data=None) -> None: ... + def __len__(self) -> int: ... + def isdiscrete(self): ... + def subsets(self, expand_all_set_operators=None) -> Generator[Incomplete, Incomplete]: ... + @property + def set_tuple(self): ... + @property + def domain(self): ... + +class SetUnion(SetOperator): + def __new__(cls, *args): ... + def ranges(self): ... + @property + def dimen(self): ... + +class SetUnion_InfiniteSet(SetUnion): + def get(self, val, default=None): ... + +class SetUnion_FiniteSet(_FiniteSetMixin, SetUnion_InfiniteSet): + def __len__(self) -> int: ... + +class SetUnion_OrderedSet(_ScalarOrderedSetMixin, _OrderedSetMixin, SetUnion_FiniteSet): + def at(self, index): ... + def ord(self, item): ... + +class SetIntersection(SetOperator): + def __new__(cls, *args): ... + __class__: Incomplete + def construct(self, data=None) -> None: ... + def ranges(self) -> Generator[Incomplete, Incomplete]: ... + @property + def dimen(self): ... + +class SetIntersection_InfiniteSet(SetIntersection): + def get(self, val, default=None): ... + +class SetIntersection_FiniteSet(_FiniteSetMixin, SetIntersection_InfiniteSet): + def __len__(self) -> int: ... + +class SetIntersection_OrderedSet( + _ScalarOrderedSetMixin, _OrderedSetMixin, SetIntersection_FiniteSet +): + def at(self, index): ... + def ord(self, item): ... + +class SetDifference(SetOperator): + def __new__(cls, *args): ... + def ranges(self) -> Generator[Incomplete, Incomplete]: ... + @property + def dimen(self): ... + +class SetDifference_InfiniteSet(SetDifference): + def get(self, val, default=None): ... + +class SetDifference_FiniteSet(_FiniteSetMixin, SetDifference_InfiniteSet): + def __len__(self) -> int: ... + +class SetDifference_OrderedSet(_ScalarOrderedSetMixin, _OrderedSetMixin, SetDifference_FiniteSet): + def at(self, index): ... + def ord(self, item): ... + +class SetSymmetricDifference(SetOperator): + def __new__(cls, *args): ... + def ranges(self) -> Generator[Incomplete, Incomplete]: ... + @property + def dimen(self): ... + +class SetSymmetricDifference_InfiniteSet(SetSymmetricDifference): + def get(self, val, default=None): ... + +class SetSymmetricDifference_FiniteSet(_FiniteSetMixin, SetSymmetricDifference_InfiniteSet): + def __len__(self) -> int: ... + +class SetSymmetricDifference_OrderedSet( + _ScalarOrderedSetMixin, _OrderedSetMixin, SetSymmetricDifference_FiniteSet +): + def at(self, index): ... + def ord(self, item): ... + +class SetProduct(SetOperator): + def __new__(cls, *args): ... + def ranges(self) -> Generator[Incomplete]: ... + def bounds(self): ... + @property + def dimen(self): ... + +class SetProduct_InfiniteSet(SetProduct): + def get(self, val, default=None): ... + +class SetProduct_FiniteSet(_FiniteSetMixin, SetProduct_InfiniteSet): + def __len__(self) -> int: ... + +class SetProduct_OrderedSet(_ScalarOrderedSetMixin, _OrderedSetMixin, SetProduct_FiniteSet): + def at(self, index): ... + def ord(self, item): ... + +class _AnySet(SetData, Set): + def __init__(self, **kwds) -> None: ... + def get(self, val, default=None): ... + def ranges(self) -> Generator[Incomplete]: ... + def bounds(self): ... + def clear(self) -> None: ... + def __len__(self) -> int: ... + @property + def dimen(self) -> None: ... + @property + def domain(self): ... + +class _AnyWithNoneSet(_AnySet): + def get(self, val, default=None): ... + +class _EmptySet(_FiniteSetMixin, SetData, Set): + def __init__(self, **kwds) -> None: ... + def get(self, val, default=None): ... + def clear(self) -> None: ... + def __len__(self) -> int: ... + @property + def dimen(self): ... + @property + def domain(self): ... + +def DeclareGlobalSet(obj, caller_globals=None): ... + +real_global_set_ids: Incomplete +integer_global_set_ids: Incomplete +RealSet: Incomplete +IntegerSet: Incomplete +BinarySet: Incomplete +BooleanSet: Incomplete + +class RealInterval(RealSet): + def __new__(cls, **kwds): ... + +class IntegerInterval(IntegerSet): + def __new__(cls, **kwds): ... diff --git a/stubs/pyomo/core/base/set_types.pyi b/stubs/pyomo/core/base/set_types.pyi new file mode 100644 index 000000000..92ea6dba7 --- /dev/null +++ b/stubs/pyomo/core/base/set_types.pyi @@ -0,0 +1,19 @@ +from pyomo.core.base.set import Any as Any +from pyomo.core.base.set import AnyWithNone as AnyWithNone +from pyomo.core.base.set import Binary as Binary +from pyomo.core.base.set import Boolean as Boolean +from pyomo.core.base.set import EmptySet as EmptySet +from pyomo.core.base.set import IntegerInterval as IntegerInterval +from pyomo.core.base.set import Integers as Integers +from pyomo.core.base.set import NegativeIntegers as NegativeIntegers +from pyomo.core.base.set import NegativeReals as NegativeReals +from pyomo.core.base.set import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core.base.set import NonNegativeReals as NonNegativeReals +from pyomo.core.base.set import NonPositiveIntegers as NonPositiveIntegers +from pyomo.core.base.set import NonPositiveReals as NonPositiveReals +from pyomo.core.base.set import PercentFraction as PercentFraction +from pyomo.core.base.set import PositiveIntegers as PositiveIntegers +from pyomo.core.base.set import PositiveReals as PositiveReals +from pyomo.core.base.set import RealInterval as RealInterval +from pyomo.core.base.set import Reals as Reals +from pyomo.core.base.set import UnitInterval as UnitInterval diff --git a/stubs/pyomo/core/base/sos.pyi b/stubs/pyomo/core/base/sos.pyi new file mode 100644 index 000000000..d2bf5e4a1 --- /dev/null +++ b/stubs/pyomo/core/base/sos.pyi @@ -0,0 +1,51 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.core.base.set_types import PositiveIntegers as PositiveIntegers + +logger: Incomplete + +class SOSConstraintData(ActiveComponentData): + def __init__(self, owner) -> None: ... + def num_variables(self): ... + def items(self): ... + @property + def level(self): ... + @level.setter + def level(self, level) -> None: ... + @property + def variables(self): ... + def get_variables(self) -> Generator[Incomplete]: ... + def get_items(self) -> Generator[Incomplete]: ... + def set_items(self, variables, weights) -> None: ... + +class _SOSConstraintData(metaclass=RenamedClass): + __renamed__new_class__ = SOSConstraintData + __renamed__version__: str + +class SOSConstraint(ActiveIndexedComponent): + Skip: Incomplete + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwargs) -> None: ... + def construct(self, data=None): ... + def add(self, index, variables, weights=None) -> None: ... + def pprint(self, ostream=None, verbose: bool = False, prefix: str = '') -> None: ... + +class ScalarSOSConstraint(SOSConstraint, SOSConstraintData): + def __init__(self, *args, **kwd) -> None: ... + +class SimpleSOSConstraint(metaclass=RenamedClass): + __renamed__new_class__ = ScalarSOSConstraint + __renamed__version__: str + +class IndexedSOSConstraint(SOSConstraint): + def __init__(self, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/core/base/suffix.pyi b/stubs/pyomo/core/base/suffix.pyi new file mode 100644 index 000000000..779cd84d3 --- /dev/null +++ b/stubs/pyomo/core/base/suffix.pyi @@ -0,0 +1,74 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import In as In +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.enums import IntEnum as IntEnum +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.disable_methods import disable_methods as disable_methods +from pyomo.core.base.initializer import Initializer as Initializer + +logger: Incomplete + +def suffix_generator(a_block, datatype=..., direction=..., active=None): ... +def active_export_suffix_generator(a_block, datatype=...): ... +def export_suffix_generator(a_block, datatype=...): ... +def active_import_suffix_generator(a_block, datatype=...): ... +def import_suffix_generator(a_block, datatype=...): ... +def active_local_suffix_generator(a_block, datatype=...): ... +def local_suffix_generator(a_block, datatype=...): ... +def active_suffix_generator(a_block, datatype=...): ... + +class SuffixDataType(IntEnum): + INT = 0 + FLOAT = 4 + +class SuffixDirection(IntEnum): + LOCAL = 0 + EXPORT = 1 + IMPORT = 2 + IMPORT_EXPORT = 3 + +class Suffix(ComponentMap, ActiveComponent): + LOCAL: Incomplete + EXPORT: Incomplete + IMPORT: Incomplete + IMPORT_EXPORT: Incomplete + FLOAT: Incomplete + INT: Incomplete + def __new__(cls, *args, **kwargs): ... + def construct(self, data=None) -> None: ... + @property + def datatype(self): ... + @datatype.setter + def datatype(self, datatype) -> None: ... + @property + def direction(self): ... + @direction.setter + def direction(self, direction) -> None: ... + def export_enabled(self): ... + def import_enabled(self): ... + def update_values(self, data, expand: bool = True) -> None: ... + def set_value(self, component, value, expand: bool = True) -> None: ... + def set_all_values(self, value) -> None: ... + def clear_value(self, component, expand: bool = True) -> None: ... + def clear_all_values(self) -> None: ... + def set_datatype(self, datatype) -> None: ... + def get_datatype(self): ... + def set_direction(self, direction) -> None: ... + def get_direction(self): ... + def pprint(self, *args, **kwds): ... + +class AbstractSuffix(Suffix): ... + +class SuffixFinder: + name: Incomplete + default: Incomplete + all_suffixes: Incomplete + def __init__(self, name, default=None, context=None) -> None: ... + def find(self, component_data): ... diff --git a/stubs/pyomo/core/base/symbol_map.pyi b/stubs/pyomo/core/base/symbol_map.pyi new file mode 100644 index 000000000..3e0d5c381 --- /dev/null +++ b/stubs/pyomo/core/base/symbol_map.pyi @@ -0,0 +1,4 @@ +from pyomo.core.base.label import TextLabeler as TextLabeler +from pyomo.core.expr.symbol_map import SymbolMap as SymbolMap + +def symbol_map_from_instance(instance): ... diff --git a/stubs/pyomo/core/base/symbolic.pyi b/stubs/pyomo/core/base/symbolic.pyi new file mode 100644 index 000000000..54d5c93d0 --- /dev/null +++ b/stubs/pyomo/core/base/symbolic.pyi @@ -0,0 +1,7 @@ +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.errors import NondifferentiableError as NondifferentiableError +from pyomo.core.expr.calculus.diff_with_sympy import ( + differentiate_available as differentiate_available, +) + +def differentiate(expr, wrt=None, wrt_list=None): ... diff --git a/stubs/pyomo/core/base/transformation.pyi b/stubs/pyomo/core/base/transformation.pyi new file mode 100644 index 000000000..f60ad1b2e --- /dev/null +++ b/stubs/pyomo/core/base/transformation.pyi @@ -0,0 +1,40 @@ +import types + +from _typeshed import Incomplete +from pyomo.common import Factory as Factory +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.errors import MouseTrap as MouseTrap +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.timing import TransformationTimer as TransformationTimer + +class TransformationInfo: ... + +class TransformationData: + def __init__(self) -> None: ... + def __getitem__(self, name): ... + +class Transformation: + def __init__(self, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def apply(self, model, **kwds): ... + def apply_to(self, model, **kwds): ... + def create_using(self, model, **kwds): ... + +class ReverseTransformationToken: + def __init__(self, transformation, model, targets, reverse_dict) -> None: ... + @property + def transformation(self): ... + @property + def reverse_dict(self): ... + def check_token_valid(self, cls, model, targets) -> None: ... + +TransformationFactory: Incomplete + +def apply_transformation(*args, **kwds): ... diff --git a/stubs/pyomo/core/base/units_container.pyi b/stubs/pyomo/core/base/units_container.pyi new file mode 100644 index 000000000..849c2b64b --- /dev/null +++ b/stubs/pyomo/core/base/units_container.pyi @@ -0,0 +1,84 @@ +import pyomo.core.expr as EXPR +from _typeshed import Incomplete +from pyomo.common.dependencies import pint_available as pint_available +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.template_expr import IndexTemplate as IndexTemplate +from pyomo.core.expr.visitor import ExpressionValueVisitor as ExpressionValueVisitor + +logger: Incomplete + +class UnitsError(Exception): + msg: Incomplete + def __init__(self, msg) -> None: ... + +class InconsistentUnitsError(UnitsError): + def __init__(self, exp1, exp2, msg) -> None: ... + +class _PyomoUnit(NumericValue): + __autoslot_mappers__: Incomplete + def __init__(self, pint_unit, pint_registry) -> None: ... + def getname(self, fully_qualified: bool = False, name_buffer=None): ... + def is_constant(self): ... + def is_fixed(self): ... + def is_parameter_type(self): ... + def is_variable_type(self): ... + def is_potentially_variable(self): ... + def is_named_expression_type(self): ... + def is_expression_type(self, expression_system=None): ... + def is_component_type(self): ... + def is_indexed(self): ... + def __deepcopy__(self, memo): ... + def __eq__(self, other): ... + def to_string(self, verbose=None, labeler=None, smap=None, compute_values: bool = False): ... + def __call__(self, exception=...): ... + @property + def value(self): ... + def pprint(self, ostream=None, verbose: bool = False) -> None: ... + +class PintUnitExtractionVisitor(EXPR.StreamBasedExpressionVisitor): + def __init__( + self, pyomo_units_container, units_equivalence_tolerance: float = 1e-12 + ) -> None: ... + node_type_method_map: Incomplete + unary_function_method_map: Incomplete + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, data): ... + def finalizeResult(self, result): ... + +class PyomoUnitsContainer: + def __init__(self, pint_registry=...) -> None: ... + def load_definitions_from_file(self, definition_file) -> None: ... + def load_definitions_from_strings(self, definition_string_list) -> None: ... + def __getattr__(self, item): ... + def get_units(self, expr): ... + def convert_temp_K_to_C(self, value_in_K): ... + def convert_temp_C_to_K(self, value_in_C): ... + def convert_temp_R_to_F(self, value_in_R): ... + def convert_temp_F_to_R(self, value_in_F): ... + def convert(self, src, to_units=None): ... + def convert_value(self, num_value, from_units=None, to_units=None): ... + def set_pint_registry(self, pint_registry) -> None: ... + @property + def pint_registry(self): ... + +class _QuantityVisitor(ExpressionValueVisitor): + native_types: Incomplete + def __init__(self) -> None: ... + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + def finalize(self, val): ... + +def as_quantity(expr): ... + +class _DeferredUnitsSingleton(PyomoUnitsContainer): + def __init__(self) -> None: ... + __class__: Incomplete + def __getattribute__(self, attr): ... + +units: Incomplete diff --git a/stubs/pyomo/core/base/util.pyi b/stubs/pyomo/core/base/util.pyi new file mode 100644 index 000000000..640e0c03d --- /dev/null +++ b/stubs/pyomo/core/base/util.pyi @@ -0,0 +1,5 @@ +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.core.base.indexed_component import normalize_index as normalize_index + +def is_functor(obj): ... +def flatten_tuple(x): ... diff --git a/stubs/pyomo/core/base/var.pyi b/stubs/pyomo/core/base/var.pyi new file mode 100644 index 000000000..e53501e39 --- /dev/null +++ b/stubs/pyomo/core/base/var.pyi @@ -0,0 +1,200 @@ +from typing import Any as typingAny + +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.pyomo_typing import overload as overload +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ( + ComponentData as ComponentData, +) +from pyomo.core.base.component import ( + ModelComponentFactory as ModelComponentFactory, +) +from pyomo.core.base.disable_methods import disable_methods as disable_methods +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ( + IndexedComponent as IndexedComponent, +) +from pyomo.core.base.indexed_component import ( + IndexedComponent_NDArrayMixin as IndexedComponent_NDArrayMixin, +) +from pyomo.core.base.indexed_component import ( + UnindexedComponent_set as UnindexedComponent_set, +) +from pyomo.core.base.initializer import ( + BoundInitializer as BoundInitializer, +) +from pyomo.core.base.initializer import ( + DefaultInitializer as DefaultInitializer, +) +from pyomo.core.base.initializer import ( + Initializer as Initializer, +) +from pyomo.core.base.set import ( + Binary as Binary, +) +from pyomo.core.base.set import ( + Reals as Reals, +) +from pyomo.core.base.set import ( + Set as Set, +) +from pyomo.core.base.set import ( + SetInitializer as SetInitializer, +) +from pyomo.core.base.set import ( + integer_global_set_ids as integer_global_set_ids, +) +from pyomo.core.base.set import ( + real_global_set_ids as real_global_set_ids, +) +from pyomo.core.base.units_container import units as units +from pyomo.core.expr import GetItemExpression as GetItemExpression +from pyomo.core.expr.numeric_expr import ( + NPV_MaxExpression as NPV_MaxExpression, +) +from pyomo.core.expr.numeric_expr import ( + NPV_MinExpression as NPV_MinExpression, +) +from pyomo.core.expr.numvalue import ( + NumericValue as NumericValue, +) +from pyomo.core.expr.numvalue import ( + is_potentially_variable as is_potentially_variable, +) +from pyomo.core.expr.numvalue import ( + native_numeric_types as native_numeric_types, +) +from pyomo.core.expr.numvalue import ( + value as value, +) +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +logger: Incomplete + +class VarData(ComponentData, NumericValue): + __autoslot_mappers__: Incomplete + def __init__(self, component=None) -> None: ... + @classmethod + def copy(cls, src): ... + def set_value(self, val, skip_validation: bool = False) -> None: ... + @property + def value(self): ... + @value.setter + def value(self, val) -> None: ... + def __call__(self, exception=...): ... + @property + def domain(self): ... + @domain.setter + def domain(self, domain) -> None: ... + def has_lb(self): ... + def has_ub(self): ... + def setlb(self, val) -> None: ... + def setub(self, val) -> None: ... + @property + def bounds(self): ... + @bounds.setter + def bounds(self, val) -> None: ... + @property + def lb(self): ... + @lb.setter + def lb(self, val) -> None: ... + @property + def ub(self): ... + @ub.setter + def ub(self, val) -> None: ... + @property + def lower(self): ... + @lower.setter + def lower(self, val) -> None: ... + @property + def upper(self): ... + @upper.setter + def upper(self, val) -> None: ... + def get_units(self): ... + def fix(self, value=..., skip_validation: bool = False) -> None: ... + def unfix(self) -> None: ... + def free(self): ... + @property + def fixed(self): ... + @fixed.setter + def fixed(self, val) -> None: ... + @property + def stale(self): ... + @stale.setter + def stale(self, val) -> None: ... + def is_integer(self): ... + def is_binary(self): ... + def is_continuous(self): ... + def is_fixed(self): ... + def is_constant(self): ... + def is_variable_type(self): ... + def is_potentially_variable(self): ... + def clear(self) -> None: ... + +class _VarData(metaclass=RenamedClass): + __renamed__new_class__ = VarData + __renamed__version__: str + +class _GeneralVarData(metaclass=RenamedClass): + __renamed__new_class__ = VarData + __renamed__version__: str + +class Var(IndexedComponent, IndexedComponent_NDArrayMixin): + def flag_as_stale(self) -> None: ... + def get_values(self, include_fixed_values: bool = True): ... + extract_values = get_values + def set_values(self, new_values, skip_validation: bool = False) -> None: ... + def get_units(self): ... + def __getitem__(self, args) -> VarData: ... + def add(self, index): ... + def construct(self, data=None) -> None: ... + def __lt__(self, other: typingAny) -> typingAny: ... + def __le__(self, other: typingAny) -> typingAny: ... + def __eq__(self, other: typingAny) -> typingAny: ... + def __ne__(self, other: typingAny) -> typingAny: ... + def __gt__(self, other: typingAny) -> typingAny: ... + def __ge__(self, other: typingAny) -> typingAny: ... + def __neg__(self) -> typingAny: ... + def __pos__(self) -> typingAny: ... + def __abs__(self) -> typingAny: ... + def __add__(self, other: typingAny) -> typingAny: ... + def __sub__(self, other: typingAny) -> typingAny: ... + def __mul__(self, other: typingAny) -> typingAny: ... + def __div__(self, other: typingAny) -> typingAny: ... + def __truediv__(self, other: typingAny) -> typingAny: ... + def __pow__(self, other: typingAny) -> typingAny: ... + def __radd__(self, other: typingAny) -> typingAny: ... + def __rsub__(self, other: typingAny) -> typingAny: ... + def __rmul__(self, other: typingAny) -> typingAny: ... + def __rdiv__(self, other: typingAny) -> typingAny: ... + def __rtruediv__(self, other: typingAny) -> typingAny: ... + def __rpow__(self, other: typingAny) -> typingAny: ... + +class ScalarVar(VarData, Var): + def __init__(self, *args, **kwd) -> None: ... + +class AbstractScalarVar(ScalarVar): ... + +class SimpleVar(metaclass=RenamedClass): + __renamed__new_class__ = ScalarVar + __renamed__version__: str + +class IndexedVar(Var): + def setlb(self, val) -> None: ... + def setub(self, val) -> None: ... + def fix(self, value=..., skip_validation: bool = False) -> None: ... + def unfix(self) -> None: ... + def free(self): ... + @property + def domain(self) -> None: ... + @domain.setter + def domain(self, domain) -> None: ... + def __getitem__(self, args) -> VarData: ... + +class VarList(IndexedVar): + def __init__(self, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + def add(self): ... diff --git a/stubs/pyomo/core/beta/__init__.pyi b/stubs/pyomo/core/beta/__init__.pyi new file mode 100644 index 000000000..70bf92629 --- /dev/null +++ b/stubs/pyomo/core/beta/__init__.pyi @@ -0,0 +1,2 @@ +from pyomo.core.beta import dict_objects as dict_objects +from pyomo.core.beta import list_objects as list_objects diff --git a/stubs/pyomo/core/beta/dict_objects.pyi b/stubs/pyomo/core/beta/dict_objects.pyi new file mode 100644 index 000000000..f06736f4c --- /dev/null +++ b/stubs/pyomo/core/beta/dict_objects.pyi @@ -0,0 +1,38 @@ +from collections.abc import MutableMapping + +from _typeshed import Incomplete +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.constraint import IndexedConstraint as IndexedConstraint +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import IndexedExpression as IndexedExpression +from pyomo.core.base.objective import IndexedObjective as IndexedObjective +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.set_types import Any as Any +from pyomo.core.base.var import IndexedVar as IndexedVar +from pyomo.core.base.var import VarData as VarData + +logger: Incomplete + +class ComponentDict(MutableMapping): + def __init__(self, interface_datatype, *args) -> None: ... + def construct(self, data=None) -> None: ... + def __setitem__(self, key, val) -> None: ... + def __delitem__(self, key) -> None: ... + def __getitem__(self, key): ... + def __iter__(self): ... + def __len__(self) -> int: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + +class VarDict(ComponentDict, IndexedVar): + def __init__(self, *args, **kwds) -> None: ... + +class ConstraintDict(ComponentDict, IndexedConstraint): + def __init__(self, *args, **kwds) -> None: ... + +class ObjectiveDict(ComponentDict, IndexedObjective): + def __init__(self, *args, **kwds) -> None: ... + +class ExpressionDict(ComponentDict, IndexedExpression): + def __init__(self, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/core/beta/list_objects.pyi b/stubs/pyomo/core/beta/list_objects.pyi new file mode 100644 index 000000000..484ee6730 --- /dev/null +++ b/stubs/pyomo/core/beta/list_objects.pyi @@ -0,0 +1,46 @@ +from collections.abc import MutableSequence + +from _typeshed import Incomplete +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.constraint import IndexedConstraint as IndexedConstraint +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import IndexedExpression as IndexedExpression +from pyomo.core.base.objective import IndexedObjective as IndexedObjective +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.set_types import Any as Any +from pyomo.core.base.var import IndexedVar as IndexedVar +from pyomo.core.base.var import VarData as VarData + +logger: Incomplete + +class ComponentList(MutableSequence): + def __init__(self, interface_datatype, *args) -> None: ... + def construct(self, data=None) -> None: ... + def keys(self): ... + iterkeys = keys + def values(self): ... + itervalues = values + def items(self): ... + iteritems = items + def __setitem__(self, i, item) -> None: ... + def insert(self, i, item) -> None: ... + def __delitem__(self, i) -> None: ... + def __getitem__(self, i): ... + def __len__(self) -> int: ... + def __contains__(self, item) -> bool: ... + def index(self, item, start: int = 0, stop=None): ... + def count(self, item): ... + def reverse(self) -> None: ... + +class XVarList(ComponentList, IndexedVar): + def __init__(self, *args, **kwds) -> None: ... + +class XConstraintList(ComponentList, IndexedConstraint): + def __init__(self, *args, **kwds) -> None: ... + +class XObjectiveList(ComponentList, IndexedObjective): + def __init__(self, *args, **kwds) -> None: ... + +class XExpressionList(ComponentList, IndexedExpression): + def __init__(self, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/core/expr/__init__.pyi b/stubs/pyomo/core/expr/__init__.pyi new file mode 100644 index 000000000..8d04ec0a2 --- /dev/null +++ b/stubs/pyomo/core/expr/__init__.pyi @@ -0,0 +1,154 @@ +from pyomo.common.deprecation import moved_module as moved_module +from pyomo.common.errors import TemplateExpressionError as TemplateExpressionError +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.common.numeric_types import value as value + +from . import boolean_value as boolean_value +from . import logical_expr as logical_expr +from . import numeric_expr as numeric_expr +from . import numvalue as numvalue +from . import relational_expr as relational_expr +from . import visitor as visitor +from .base import ExpressionBase as ExpressionBase +from .calculus.derivatives import differentiate as differentiate +from .expr_common import ExpressionType as ExpressionType +from .expr_common import Mode as Mode +from .expr_common import OperatorAssociativity as OperatorAssociativity +from .logical_expr import AllDifferentExpression as AllDifferentExpression +from .logical_expr import AndExpression as AndExpression +from .logical_expr import AtLeastExpression as AtLeastExpression +from .logical_expr import AtMostExpression as AtMostExpression +from .logical_expr import BinaryBooleanExpression as BinaryBooleanExpression +from .logical_expr import BooleanConstant as BooleanConstant +from .logical_expr import BooleanExpression as BooleanExpression +from .logical_expr import BooleanExpressionBase as BooleanExpressionBase +from .logical_expr import BooleanValue as BooleanValue +from .logical_expr import CountIfExpression as CountIfExpression +from .logical_expr import EquivalenceExpression as EquivalenceExpression +from .logical_expr import ExactlyExpression as ExactlyExpression +from .logical_expr import ImplicationExpression as ImplicationExpression +from .logical_expr import NaryBooleanExpression as NaryBooleanExpression +from .logical_expr import NotExpression as NotExpression +from .logical_expr import OrExpression as OrExpression +from .logical_expr import UnaryBooleanExpression as UnaryBooleanExpression +from .logical_expr import XorExpression as XorExpression +from .logical_expr import all_different as all_different +from .logical_expr import atleast as atleast +from .logical_expr import atmost as atmost +from .logical_expr import count_if as count_if +from .logical_expr import equivalent as equivalent +from .logical_expr import exactly as exactly +from .logical_expr import implies as implies +from .logical_expr import land as land +from .logical_expr import lnot as lnot +from .logical_expr import lor as lor +from .logical_expr import native_logical_types as native_logical_types +from .logical_expr import special_boolean_atom_types as special_boolean_atom_types +from .logical_expr import xor as xor +from .numeric_expr import AbsExpression as AbsExpression +from .numeric_expr import DivisionExpression as DivisionExpression +from .numeric_expr import Expr_if as Expr_if +from .numeric_expr import Expr_ifExpression as Expr_ifExpression +from .numeric_expr import ExternalFunctionExpression as ExternalFunctionExpression +from .numeric_expr import LinearDecompositionError as LinearDecompositionError +from .numeric_expr import LinearExpression as LinearExpression +from .numeric_expr import MaxExpression as MaxExpression +from .numeric_expr import MinExpression as MinExpression +from .numeric_expr import MonomialTermExpression as MonomialTermExpression +from .numeric_expr import NegationExpression as NegationExpression +from .numeric_expr import NPV_AbsExpression as NPV_AbsExpression +from .numeric_expr import NPV_DivisionExpression as NPV_DivisionExpression +from .numeric_expr import NPV_Expr_ifExpression as NPV_Expr_ifExpression +from .numeric_expr import NPV_expression_types as NPV_expression_types +from .numeric_expr import NPV_ExternalFunctionExpression as NPV_ExternalFunctionExpression +from .numeric_expr import NPV_MaxExpression as NPV_MaxExpression +from .numeric_expr import NPV_MinExpression as NPV_MinExpression +from .numeric_expr import NPV_NegationExpression as NPV_NegationExpression +from .numeric_expr import NPV_PowExpression as NPV_PowExpression +from .numeric_expr import NPV_ProductExpression as NPV_ProductExpression +from .numeric_expr import NPV_SumExpression as NPV_SumExpression +from .numeric_expr import NPV_UnaryFunctionExpression as NPV_UnaryFunctionExpression +from .numeric_expr import NumericExpression as NumericExpression +from .numeric_expr import NumericValue as NumericValue +from .numeric_expr import PowExpression as PowExpression +from .numeric_expr import ProductExpression as ProductExpression +from .numeric_expr import SumExpression as SumExpression +from .numeric_expr import SumExpressionBase as SumExpressionBase +from .numeric_expr import UnaryFunctionExpression as UnaryFunctionExpression +from .numeric_expr import acos as acos +from .numeric_expr import acosh as acosh +from .numeric_expr import asin as asin +from .numeric_expr import asinh as asinh +from .numeric_expr import atan as atan +from .numeric_expr import atanh as atanh +from .numeric_expr import ceil as ceil +from .numeric_expr import cos as cos +from .numeric_expr import cosh as cosh +from .numeric_expr import decompose_term as decompose_term +from .numeric_expr import exp as exp +from .numeric_expr import floor as floor +from .numeric_expr import linear_expression as linear_expression +from .numeric_expr import log as log +from .numeric_expr import log10 as log10 +from .numeric_expr import mutable_expression as mutable_expression +from .numeric_expr import nonlinear_expression as nonlinear_expression +from .numeric_expr import sin as sin +from .numeric_expr import sinh as sinh +from .numeric_expr import sqrt as sqrt +from .numeric_expr import tan as tan +from .numeric_expr import tanh as tanh +from .numvalue import ZeroConstant as ZeroConstant +from .numvalue import as_numeric as as_numeric +from .numvalue import is_constant as is_constant +from .numvalue import is_fixed as is_fixed +from .numvalue import is_potentially_variable as is_potentially_variable +from .numvalue import is_variable_type as is_variable_type +from .relational_expr import EqualityExpression as EqualityExpression +from .relational_expr import InequalityExpression as InequalityExpression +from .relational_expr import NotEqualExpression as NotEqualExpression +from .relational_expr import RangedExpression as RangedExpression +from .relational_expr import RelationalExpression as RelationalExpression +from .relational_expr import inequality as inequality +from .symbol_map import SymbolMap as SymbolMap +from .taylor_series import taylor_series_expansion as taylor_series_expansion +from .template_expr import Boolean_GetAttrExpression as Boolean_GetAttrExpression +from .template_expr import Boolean_GetItemExpression as Boolean_GetItemExpression +from .template_expr import CallExpression as CallExpression +from .template_expr import GetAttrExpression as GetAttrExpression +from .template_expr import GetItemExpression as GetItemExpression +from .template_expr import IndexTemplate as IndexTemplate +from .template_expr import NPV_Boolean_GetAttrExpression as NPV_Boolean_GetAttrExpression +from .template_expr import NPV_Boolean_GetItemExpression as NPV_Boolean_GetItemExpression +from .template_expr import NPV_Numeric_GetAttrExpression as NPV_Numeric_GetAttrExpression +from .template_expr import NPV_Numeric_GetItemExpression as NPV_Numeric_GetItemExpression +from .template_expr import NPV_Structural_GetAttrExpression as NPV_Structural_GetAttrExpression +from .template_expr import NPV_Structural_GetItemExpression as NPV_Structural_GetItemExpression +from .template_expr import Numeric_GetAttrExpression as Numeric_GetAttrExpression +from .template_expr import Numeric_GetItemExpression as Numeric_GetItemExpression +from .template_expr import ReplaceTemplateExpression as ReplaceTemplateExpression +from .template_expr import Structural_GetAttrExpression as Structural_GetAttrExpression +from .template_expr import Structural_GetItemExpression as Structural_GetItemExpression +from .template_expr import TemplateSumExpression as TemplateSumExpression +from .template_expr import resolve_template as resolve_template +from .template_expr import substitute_getitem_with_param as substitute_getitem_with_param +from .template_expr import substitute_template_expression as substitute_template_expression +from .template_expr import substitute_template_with_value as substitute_template_with_value +from .template_expr import templatize_constraint as templatize_constraint +from .template_expr import templatize_rule as templatize_rule +from .visitor import ExpressionReplacementVisitor as ExpressionReplacementVisitor +from .visitor import ExpressionValueVisitor as ExpressionValueVisitor +from .visitor import FixedExpressionError as FixedExpressionError +from .visitor import NonConstantExpressionError as NonConstantExpressionError +from .visitor import SimpleExpressionVisitor as SimpleExpressionVisitor +from .visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from .visitor import clone_expression as clone_expression +from .visitor import evaluate_expression as evaluate_expression +from .visitor import expression_to_string as expression_to_string +from .visitor import identify_components as identify_components +from .visitor import identify_mutable_parameters as identify_mutable_parameters +from .visitor import identify_variables as identify_variables +from .visitor import polynomial_degree as polynomial_degree +from .visitor import replace_expressions as replace_expressions +from .visitor import sizeof_expression as sizeof_expression diff --git a/stubs/pyomo/core/expr/base.pyi b/stubs/pyomo/core/expr/base.pyi new file mode 100644 index 000000000..a6900ee7b --- /dev/null +++ b/stubs/pyomo/core/expr/base.pyi @@ -0,0 +1,62 @@ +from typing import Any + +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import native_types as native_types +from pyomo.core.expr.expr_common import OperatorAssociativity as OperatorAssociativity +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +visitor: Incomplete +_: Incomplete + +class ExpressionBase(PyomoObject): + PRECEDENCE: int + ASSOCIATIVITY: Incomplete + def nargs(self) -> None: ... + def arg(self, i): ... + @property + def args(self) -> None: ... + def __call__(self, exception=...): ... + def to_string(self, verbose=None, labeler=None, smap=None, compute_values: bool = False): ... + def getname(self, *args, **kwds) -> None: ... + def clone(self, substitute=None): ... + def create_node_with_local_data(self, args, classtype=None): ... + def is_constant(self): ... + def is_fixed(self): ... + def is_potentially_variable(self): ... + def is_named_expression_type(self): ... + def is_expression_type(self, expression_system=None): ... + def size(self): ... + def __lt__(self, other: Any) -> Any: ... + def __le__(self, other: Any) -> Any: ... + def __eq__(self, other: Any) -> Any: ... + def __ne__(self, other: Any) -> Any: ... + def __gt__(self, other: Any) -> Any: ... + def __ge__(self, other: Any) -> Any: ... + def __neg__(self) -> Any: ... + def __pos__(self) -> Any: ... + def __abs__(self) -> Any: ... + def __add__(self, other: Any) -> Any: ... + def __sub__(self, other: Any) -> Any: ... + def __mul__(self, other: Any) -> Any: ... + def __div__(self, other: Any) -> Any: ... + def __truediv__(self, other: Any) -> Any: ... + def __pow__(self, other: Any) -> Any: ... + def __radd__(self, other: Any) -> Any: ... + def __rsub__(self, other: Any) -> Any: ... + def __rmul__(self, other: Any) -> Any: ... + def __rdiv__(self, other: Any) -> Any: ... + def __rtruediv__(self, other: Any) -> Any: ... + def __rpow__(self, other: Any) -> Any: ... + +class NPV_Mixin: + def is_potentially_variable(self): ... + def create_node_with_local_data(self, args, classtype=None): ... + def potentially_variable_base_class(self): ... + +class ExpressionArgs_Mixin: + def __init__(self, args) -> None: ... + def nargs(self): ... + @property + def args(self): ... diff --git a/stubs/pyomo/core/expr/boolean_value.pyi b/stubs/pyomo/core/expr/boolean_value.pyi new file mode 100644 index 000000000..338e7cf5a --- /dev/null +++ b/stubs/pyomo/core/expr/boolean_value.pyi @@ -0,0 +1,52 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import native_logical_types as native_logical_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +logger: Incomplete +native_logical_values: Incomplete + +def as_boolean(obj): ... + +class BooleanValue(PyomoObject): + __hash__: Incomplete + def getname(self, fully_qualified: bool = False, name_buffer=None): ... + @property + def name(self): ... + @property + def local_name(self): ... + def is_constant(self): ... + def is_fixed(self): ... + def is_relational(self): ... + def is_indexed(self): ... + def is_numeric_type(self): ... + def is_logical_type(self): ... + def __invert__(self): ... + def equivalent_to(self, other): ... + def land(self, other): ... + def __and__(self, other): ... + def __rand__(self, other): ... + def __iand__(self, other): ... + def lor(self, other): ... + def __or__(self, other): ... + def __ror__(self, other): ... + def __ior__(self, other): ... + def xor(self, other): ... + def __xor__(self, other): ... + def __rxor__(self, other): ... + def __ixor__(self, other): ... + def implies(self, other): ... + def to_string(self, verbose=None, labeler=None, smap=None, compute_values: bool = False): ... + +class BooleanConstant(BooleanValue): + value: Incomplete + def __init__(self, value) -> None: ... + def is_constant(self): ... + def is_fixed(self): ... + def is_potentially_variable(self): ... + def __nonzero__(self): ... + def __bool__(self) -> bool: ... + def __call__(self, exception=...): ... + def pprint(self, ostream=None, verbose: bool = False) -> None: ... diff --git a/stubs/pyomo/core/expr/calculus/__init__.pyi b/stubs/pyomo/core/expr/calculus/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/core/expr/calculus/derivatives.pyi b/stubs/pyomo/core/expr/calculus/derivatives.pyi new file mode 100644 index 000000000..d9decd230 --- /dev/null +++ b/stubs/pyomo/core/expr/calculus/derivatives.pyi @@ -0,0 +1,11 @@ +import enum + +from .diff_with_pyomo import reverse_ad as reverse_ad +from .diff_with_pyomo import reverse_sd as reverse_sd + +class Modes(str, enum.Enum): + sympy = 'sympy' + reverse_symbolic = 'reverse_symbolic' + reverse_numeric = 'reverse_numeric' + +def differentiate(expr, wrt=None, wrt_list=None, mode=...): ... diff --git a/stubs/pyomo/core/expr/calculus/diff_with_pyomo.pyi b/stubs/pyomo/core/expr/calculus/diff_with_pyomo.pyi new file mode 100644 index 000000000..8f4018ee0 --- /dev/null +++ b/stubs/pyomo/core/expr/calculus/diff_with_pyomo.pyi @@ -0,0 +1,26 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.expr import cos as cos +from pyomo.core.expr import exp as exp +from pyomo.core.expr import log as log +from pyomo.core.expr import sin as sin +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import ExpressionValueVisitor as ExpressionValueVisitor +from pyomo.core.expr.visitor import nonpyomo_leaf_types as nonpyomo_leaf_types + +class DifferentiationException(Exception): ... + +class _LeafToRootVisitor(ExpressionValueVisitor): + val_dict: Incomplete + der_dict: Incomplete + expr_list: Incomplete + value_func: Incomplete + operation_func: Incomplete + def __init__(self, val_dict, der_dict, expr_list, numeric: bool = True) -> None: ... + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +def reverse_ad(expr): ... +def reverse_sd(expr): ... diff --git a/stubs/pyomo/core/expr/calculus/diff_with_sympy.pyi b/stubs/pyomo/core/expr/calculus/diff_with_sympy.pyi new file mode 100644 index 000000000..d641451a1 --- /dev/null +++ b/stubs/pyomo/core/expr/calculus/diff_with_sympy.pyi @@ -0,0 +1,7 @@ +from pyomo.core.expr.sympy_tools import sympy2pyomo_expression as sympy2pyomo_expression +from pyomo.core.expr.sympy_tools import sympy_available as sympy_available +from pyomo.core.expr.sympy_tools import sympyify_expression as sympyify_expression + +differentiate_available = sympy_available + +def differentiate(expr, wrt=None, wrt_list=None): ... diff --git a/stubs/pyomo/core/expr/cnf_walker.pyi b/stubs/pyomo/core/expr/cnf_walker.pyi new file mode 100644 index 000000000..c3361c0d3 --- /dev/null +++ b/stubs/pyomo/core/expr/cnf_walker.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.core.expr.logical_expr import special_boolean_atom_types as special_boolean_atom_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.sympy_tools import Pyomo2SympyVisitor as Pyomo2SympyVisitor +from pyomo.core.expr.sympy_tools import PyomoSympyBimap as PyomoSympyBimap +from pyomo.core.expr.sympy_tools import sympy as sympy +from pyomo.core.expr.sympy_tools import sympy2pyomo_expression as sympy2pyomo_expression + +class CNF_Pyomo2SympyVisitor(Pyomo2SympyVisitor): + boolean_variable_list: Incomplete + special_atom_map: Incomplete + def __init__(self, object_map, bool_varlist) -> None: ... + def beforeChild(self, node, child, child_idx): ... + +def to_cnf(expr, bool_varlist=None, bool_var_to_special_atoms=None): ... diff --git a/stubs/pyomo/core/expr/compare.pyi b/stubs/pyomo/core/expr/compare.pyi new file mode 100644 index 000000000..86c9da94d --- /dev/null +++ b/stubs/pyomo/core/expr/compare.pyi @@ -0,0 +1,57 @@ +import collections + +from _typeshed import Incomplete +from pyomo.common.collections import Sequence as Sequence +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.formatting import tostr as tostr +from pyomo.common.numeric_types import native_types as native_types +from pyomo.core.expr import AbsExpression as AbsExpression +from pyomo.core.expr import DivisionExpression as DivisionExpression +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.core.expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr import ExpressionBase as ExpressionBase +from pyomo.core.expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr import GetItemExpression as GetItemExpression +from pyomo.core.expr import InequalityExpression as InequalityExpression +from pyomo.core.expr import LinearExpression as LinearExpression +from pyomo.core.expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr import NegationExpression as NegationExpression +from pyomo.core.expr import NPV_AbsExpression as NPV_AbsExpression +from pyomo.core.expr import NPV_DivisionExpression as NPV_DivisionExpression +from pyomo.core.expr import NPV_ExternalFunctionExpression as NPV_ExternalFunctionExpression +from pyomo.core.expr import NPV_NegationExpression as NPV_NegationExpression +from pyomo.core.expr import NPV_PowExpression as NPV_PowExpression +from pyomo.core.expr import NPV_ProductExpression as NPV_ProductExpression +from pyomo.core.expr import NPV_SumExpression as NPV_SumExpression +from pyomo.core.expr import NPV_UnaryFunctionExpression as NPV_UnaryFunctionExpression +from pyomo.core.expr import NumericValue as NumericValue +from pyomo.core.expr import PowExpression as PowExpression +from pyomo.core.expr import ProductExpression as ProductExpression +from pyomo.core.expr import RangedExpression as RangedExpression +from pyomo.core.expr import SumExpression as SumExpression +from pyomo.core.expr import UnaryFunctionExpression as UnaryFunctionExpression + +from .numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from .visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor + +def handle_expression(node: ExpressionBase, pn: list): ... +def handle_named_expression(node, pn: list, include_named_exprs: bool = True): ... +def handle_unary_expression(node: UnaryFunctionExpression, pn: list): ... +def handle_external_function_expression(node: ExternalFunctionExpression, pn: list): ... +def handle_sequence(node: collections.abc.Sequence, pn: list): ... +def handle_inequality(node: collections.abc.Sequence, pn: list): ... + +handler: Incomplete + +class PrefixVisitor(StreamBasedExpressionVisitor): + def __init__(self, include_named_exprs: bool = True) -> None: ... + def initializeWalker(self, expr): ... + def enterNode(self, node): ... + def finalizeResult(self, result): ... + +def convert_expression_to_prefix_notation(expr, include_named_exprs: bool = True): ... +def compare_expressions(expr1, expr2, include_named_exprs: bool = True): ... +def assertExpressionsEqual(test, a, b, include_named_exprs: bool = True, places=None) -> None: ... +def assertExpressionsStructurallyEqual( + test, a, b, include_named_exprs: bool = True, places=None +) -> None: ... diff --git a/stubs/pyomo/core/expr/expr_common.pyi b/stubs/pyomo/core/expr/expr_common.pyi new file mode 100644 index 000000000..4fc59dbf7 --- /dev/null +++ b/stubs/pyomo/core/expr/expr_common.pyi @@ -0,0 +1,48 @@ +from contextlib import nullcontext + +from pyomo.common import enums as enums +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.modeling import NOTSET as NOTSET + +TO_STRING_VERBOSE: bool + +class Mode(enums.IntEnum): + coopr_trees = 1 + coopr3_trees = 3 + pyomo4_trees = 4 + pyomo5_trees = 5 + pyomo6_trees = 6 + CURRENT = pyomo6_trees + +class OperatorAssociativity(enums.IntEnum): + RIGHT_TO_LEFT = -1 + NON_ASSOCIATIVE = 0 + LEFT_TO_RIGHT = 1 + +class ExpressionType(enums.Enum): + NUMERIC = 0 + RELATIONAL = 1 + LOGICAL = 2 + +class NUMERIC_ARG_TYPE(enums.IntEnum): + MUTABLE = -2 + ASNUMERIC = -1 + INVALID = 0 + NATIVE = 1 + NPV = 2 + PARAM = 3 + VAR = 4 + MONOMIAL = 5 + LINEAR = 6 + SUM = 7 + OTHER = 8 + +class RELATIONAL_ARG_TYPE(enums.IntEnum, metaclass=enums.ExtendedEnumType): + __base_enum__ = NUMERIC_ARG_TYPE + INEQUALITY = 100 + INVALID_RELATIONAL = 101 + +class clone_counter(nullcontext): + def __init__(self) -> None: ... + @property + def count(self): ... diff --git a/stubs/pyomo/core/expr/expr_errors.pyi b/stubs/pyomo/core/expr/expr_errors.pyi new file mode 100644 index 000000000..21f55e254 --- /dev/null +++ b/stubs/pyomo/core/expr/expr_errors.pyi @@ -0,0 +1 @@ +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute diff --git a/stubs/pyomo/core/expr/logical_expr.pyi b/stubs/pyomo/core/expr/logical_expr.pyi new file mode 100644 index 000000000..ce6a055b8 --- /dev/null +++ b/stubs/pyomo/core/expr/logical_expr.pyi @@ -0,0 +1,103 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import PyomoException as PyomoException + +from .base import ExpressionBase as ExpressionBase +from .boolean_value import BooleanConstant as BooleanConstant +from .boolean_value import BooleanValue as BooleanValue +from .expr_common import ExpressionType as ExpressionType +from .numeric_expr import NumericExpression as NumericExpression +from .numvalue import as_numeric as as_numeric +from .numvalue import is_potentially_variable as is_potentially_variable +from .numvalue import native_logical_types as native_logical_types +from .numvalue import native_numeric_types as native_numeric_types +from .numvalue import native_types as native_types +from .numvalue import value as value + +logger: Incomplete + +class BooleanExpression(ExpressionBase, BooleanValue): + EXPRESSION_SYSTEM: Incomplete + PRECEDENCE: int + def __init__(self, args) -> None: ... + @property + def args(self): ... + +class BooleanExpressionBase(metaclass=RenamedClass): + __renamed__new_class__ = BooleanExpression + __renamed__version__: str + +def lnot(Y): ... +def equivalent(Y1, Y2): ... +def xor(Y1, Y2): ... +def implies(Y1, Y2): ... +def land(*args): ... +def lor(*args): ... +def exactly(n, *args): ... +def atmost(n, *args): ... +def atleast(n, *args): ... +def all_different(*args): ... +def count_if(*args): ... + +class UnaryBooleanExpression(BooleanExpression): + def nargs(self): ... + +class NotExpression(UnaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + +class BinaryBooleanExpression(BooleanExpression): + def nargs(self): ... + +class EquivalenceExpression(BinaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + +class XorExpression(BinaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + +class ImplicationExpression(BinaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + +class NaryBooleanExpression(BooleanExpression): + def __init__(self, args) -> None: ... + def nargs(self): ... + def getname(self, *arg, **kwd): ... + +class AndExpression(NaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + def add(self, new_arg): ... + +class OrExpression(NaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + def add(self, new_arg): ... + +class ExactlyExpression(NaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + +class AtMostExpression(NaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + +class AtLeastExpression(NaryBooleanExpression): + PRECEDENCE: int + def getname(self, *arg, **kwd): ... + +class AllDifferentExpression(NaryBooleanExpression): + PRECEDENCE: Incomplete + def getname(self, *arg, **kwd): ... + +class CountIfExpression(NumericExpression): + PRECEDENCE: Incomplete + def nargs(self): ... + def getname(self, *arg, **kwd): ... + +special_boolean_atom_types: Incomplete diff --git a/stubs/pyomo/core/expr/ndarray.pyi b/stubs/pyomo/core/expr/ndarray.pyi new file mode 100644 index 000000000..e3cb0f9a3 --- /dev/null +++ b/stubs/pyomo/core/expr/ndarray.pyi @@ -0,0 +1,4 @@ +from pyomo.common.dependencies import numpy_available as numpy_available + +class NumericNDArray: + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): ... diff --git a/stubs/pyomo/core/expr/numeric_expr.pyi b/stubs/pyomo/core/expr/numeric_expr.pyi new file mode 100644 index 000000000..a1d607bea --- /dev/null +++ b/stubs/pyomo/core/expr/numeric_expr.pyi @@ -0,0 +1,240 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.formatting import tostr as tostr +from pyomo.common.numeric_types import check_if_numeric_type as check_if_numeric_type +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.common.numeric_types import value as value +from pyomo.core.expr.base import ExpressionBase as ExpressionBase +from pyomo.core.expr.base import NPV_Mixin as NPV_Mixin +from pyomo.core.expr.base import visitor as visitor +from pyomo.core.expr.expr_common import ExpressionType as ExpressionType +from pyomo.core.expr.expr_common import OperatorAssociativity as OperatorAssociativity +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +logger: Incomplete +_: Incomplete + +def enable_expression_optimizations(zero=None, one=None) -> None: ... + +class mutable_expression: + e: Incomplete + def __enter__(self): ... + def __exit__(self, *args) -> None: ... + +class nonlinear_expression(mutable_expression): + e: Incomplete + def __enter__(self): ... + +class linear_expression(mutable_expression): ... + +class NumericValue(PyomoObject): + __hash__: Incomplete + def getname(self, *args, **kwargs): ... + @property + def name(self): ... + @property + def local_name(self): ... + def is_numeric_type(self): ... + def is_constant(self): ... + def is_fixed(self): ... + def is_potentially_variable(self): ... + def is_relational(self): ... + def is_indexed(self): ... + def polynomial_degree(self): ... + def __bool__(self) -> bool: ... + def __float__(self) -> float: ... + def __int__(self) -> int: ... + def __lt__(self, other): ... + def __gt__(self, other): ... + def __le__(self, other): ... + def __ge__(self, other): ... + def __eq__(self, other): ... + def __add__(self, other): ... + def __sub__(self, other): ... + def __mul__(self, other): ... + def __div__(self, other): ... + def __truediv__(self, other): ... + def __pow__(self, other): ... + def __radd__(self, other): ... + def __rsub__(self, other): ... + def __rmul__(self, other): ... + def __rdiv__(self, other): ... + def __rtruediv__(self, other): ... + def __rpow__(self, other): ... + def __iadd__(self, other): ... + def __isub__(self, other): ... + def __imul__(self, other): ... + def __idiv__(self, other): ... + def __itruediv__(self, other): ... + def __ipow__(self, other): ... + def __neg__(self): ... + def __pos__(self): ... + def __abs__(self): ... + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): ... + def to_string(self, verbose=None, labeler=None, smap=None, compute_values: bool = False): ... + +class NumericExpression(ExpressionBase, NumericValue): + EXPRESSION_SYSTEM: Incomplete + PRECEDENCE: int + def __init__(self, args) -> None: ... + def nargs(self): ... + @property + def args(self): ... + __class__: Incomplete + def create_potentially_variable_object(self): ... + def polynomial_degree(self): ... + +class Numeric_NPV_Mixin(NPV_Mixin): + def potentially_variable_base_class(self): ... + def __neg__(self): ... + def __abs__(self): ... + +class NegationExpression(NumericExpression): + PRECEDENCE: int + def nargs(self): ... + def getname(self, *args, **kwds): ... + def __neg__(self): ... + +class NPV_NegationExpression(Numeric_NPV_Mixin, NegationExpression): + def __neg__(self): ... + +class ExternalFunctionExpression(NumericExpression): + PRECEDENCE: Incomplete + def __init__(self, args, fcn=None) -> None: ... + def nargs(self): ... + def create_node_with_local_data(self, args, classtype=None): ... + def getname(self, *args, **kwds): ... + def get_arg_units(self): ... + def get_units(self): ... + +class NPV_ExternalFunctionExpression(Numeric_NPV_Mixin, ExternalFunctionExpression): ... + +class PowExpression(NumericExpression): + PRECEDENCE: int + ASSOCIATIVITY: Incomplete + def getname(self, *args, **kwds): ... + +class NPV_PowExpression(Numeric_NPV_Mixin, PowExpression): ... + +class MaxExpression(NumericExpression): + PRECEDENCE: Incomplete + def nargs(self): ... + def getname(self, *args, **kwds): ... + +class NPV_MaxExpression(Numeric_NPV_Mixin, MaxExpression): ... + +class MinExpression(NumericExpression): + PRECEDENCE: Incomplete + def nargs(self): ... + def getname(self, *args, **kwds): ... + +class NPV_MinExpression(Numeric_NPV_Mixin, MinExpression): ... + +class ProductExpression(NumericExpression): + PRECEDENCE: int + def getname(self, *args, **kwds): ... + +class NPV_ProductExpression(Numeric_NPV_Mixin, ProductExpression): ... + +class MonomialTermExpression(ProductExpression): + def getname(self, *args, **kwds): ... + def create_node_with_local_data(self, args, classtype=None): ... + +class DivisionExpression(NumericExpression): + PRECEDENCE: int + def getname(self, *args, **kwds): ... + +class NPV_DivisionExpression(Numeric_NPV_Mixin, DivisionExpression): ... + +class SumExpression(NumericExpression): + PRECEDENCE: int + def __init__(self, args) -> None: ... + def nargs(self): ... + @property + def args(self): ... + def getname(self, *args, **kwds): ... + def add(self, new_arg): ... + +SumExpressionBase = SumExpression + +class LinearExpression(SumExpression): + def __init__(self, args=None, constant=None, linear_coefs=None, linear_vars=None) -> None: ... + @property + def constant(self): ... + @property + def linear_coefs(self): ... + @property + def linear_vars(self): ... + def create_node_with_local_data(self, args, classtype=None): ... + +class NPV_SumExpression(Numeric_NPV_Mixin, LinearExpression): ... + +class _MutableSumExpression(SumExpression): + __class__: Incomplete + def make_immutable(self) -> None: ... + def __iadd__(self, other): ... + +class _MutableLinearExpression(_MutableSumExpression): + __class__: Incomplete + def make_immutable(self) -> None: ... + def __iadd__(self, other): ... + +class _MutableNPVSumExpression(_MutableLinearExpression): + __class__: Incomplete + def make_immutable(self) -> None: ... + def __iadd__(self, other): ... + +class Expr_ifExpression(NumericExpression): + PRECEDENCE: Incomplete + def nargs(self): ... + def getname(self, *args, **kwds): ... + +class NPV_Expr_ifExpression(Numeric_NPV_Mixin, Expr_ifExpression): ... + +class UnaryFunctionExpression(NumericExpression): + PRECEDENCE: Incomplete + def __init__(self, args, name=None, fcn=None) -> None: ... + def nargs(self): ... + def create_node_with_local_data(self, args, classtype=None): ... + def getname(self, *args, **kwds): ... + +class NPV_UnaryFunctionExpression(Numeric_NPV_Mixin, UnaryFunctionExpression): ... + +class AbsExpression(UnaryFunctionExpression): + def __init__(self, arg) -> None: ... + def create_node_with_local_data(self, args, classtype=None): ... + +class NPV_AbsExpression(Numeric_NPV_Mixin, AbsExpression): ... + +def decompose_term(expr): ... + +class LinearDecompositionError(Exception): ... + +def register_arg_type(arg_class, etype) -> None: ... +def ceil(arg): ... +def floor(arg): ... +def exp(arg): ... +def log(arg): ... +def log10(arg): ... +def sqrt(arg): ... +def sin(arg): ... +def cos(arg): ... +def tan(arg): ... +def sinh(arg): ... +def cosh(arg): ... +def tanh(arg): ... +def asin(arg): ... +def acos(arg): ... +def atan(arg): ... +def asinh(arg): ... +def acosh(arg): ... +def atanh(arg): ... +def Expr_if(IF_=None, THEN_=None, ELSE_=None, **kwargs): ... + +NPV_expression_types: Incomplete diff --git a/stubs/pyomo/core/expr/numvalue.pyi b/stubs/pyomo/core/expr/numvalue.pyi new file mode 100644 index 000000000..22e2f61a8 --- /dev/null +++ b/stubs/pyomo/core/expr/numvalue.pyi @@ -0,0 +1,43 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import check_if_numeric_type as check_if_numeric_type +from pyomo.common.numeric_types import native_integer_types as native_integer_types +from pyomo.common.numeric_types import native_logical_types as native_logical_types +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.common.numeric_types import value as value +from pyomo.core.expr.expr_common import ExpressionType as ExpressionType +from pyomo.core.expr.numeric_expr import NumericValue as NumericValue +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +logger: Incomplete + +class NonNumericValue(PyomoObject): + value: Incomplete + def __init__(self, value) -> None: ... + def __call__(self, exception=...): ... + def is_constant(self): ... + def is_fixed(self): ... + +def is_constant(obj): ... +def is_fixed(obj): ... +def is_variable_type(obj): ... +def is_potentially_variable(obj): ... +def is_numeric_data(obj): ... +def polynomial_degree(obj): ... +def as_numeric(obj): ... +def check_if_numeric_type_and_cache(obj): ... + +class NumericConstant(NumericValue): + value: Incomplete + def __init__(self, value) -> None: ... + def is_constant(self): ... + def is_fixed(self): ... + def __call__(self, exception=...): ... + def pprint(self, ostream=None, verbose: bool = False) -> None: ... + +ZeroConstant: Incomplete diff --git a/stubs/pyomo/core/expr/relational_expr.pyi b/stubs/pyomo/core/expr/relational_expr.pyi new file mode 100644 index 000000000..913761010 --- /dev/null +++ b/stubs/pyomo/core/expr/relational_expr.pyi @@ -0,0 +1,58 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.numeric_types import check_if_numeric_type as check_if_numeric_type +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import value as value +from pyomo.core.expr.base import ExpressionBase as ExpressionBase +from pyomo.core.expr.boolean_value import BooleanValue as BooleanValue +from pyomo.core.expr.expr_common import ExpressionType as ExpressionType +from pyomo.core.expr.visitor import polynomial_degree as polynomial_degree +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +class RelationalExpression(ExpressionBase, BooleanValue): + EXPRESSION_SYSTEM: Incomplete + def __init__(self, args) -> None: ... + def __bool__(self) -> bool: ... + @property + def args(self): ... + def is_relational(self): ... + def is_potentially_variable(self): ... + def polynomial_degree(self): ... + def __eq__(self, other): ... + def __lt__(self, other): ... + def __gt__(self, other): ... + def __le__(self, other): ... + def __ge__(self, other): ... + +class RangedExpression(RelationalExpression): + PRECEDENCE: int + STRICT: Incomplete + def __init__(self, args, strict) -> None: ... + def nargs(self): ... + def create_node_with_local_data(self, args): ... + @property + def strict(self): ... + +class InequalityExpression(RelationalExpression): + PRECEDENCE: int + def __init__(self, args, strict) -> None: ... + def nargs(self): ... + def create_node_with_local_data(self, args): ... + @property + def strict(self): ... + +def inequality(lower=None, body=None, upper=None, strict: bool = False): ... + +class EqualityExpression(RelationalExpression): + PRECEDENCE: int + def nargs(self): ... + def __bool__(self) -> bool: ... + +class NotEqualExpression(RelationalExpression): + def nargs(self): ... + def __bool__(self) -> bool: ... + +def tuple_to_relational_expr(args): ... diff --git a/stubs/pyomo/core/expr/symbol_map.pyi b/stubs/pyomo/core/expr/symbol_map.pyi new file mode 100644 index 000000000..5162a03df --- /dev/null +++ b/stubs/pyomo/core/expr/symbol_map.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete + +class SymbolMap: + byObject: Incomplete + bySymbol: Incomplete + aliases: Incomplete + default_labeler: Incomplete + def __init__(self, labeler=None) -> None: ... + class UnknownSymbol: ... + + def addSymbol(self, obj, symb) -> None: ... + def addSymbols(self, obj_symbol_tuples) -> None: ... + def createSymbol(self, obj, labeler=None, *args): ... + def createSymbols(self, objs, labeler=None, *args) -> None: ... + def getSymbol(self, obj, labeler=None, *args): ... + def alias(self, obj, name) -> None: ... + def getObject(self, symbol): ... + def removeSymbol(self, obj) -> None: ... diff --git a/stubs/pyomo/core/expr/sympy_tools.pyi b/stubs/pyomo/core/expr/sympy_tools.pyi new file mode 100644 index 000000000..6ef946ebb --- /dev/null +++ b/stubs/pyomo/core/expr/sympy_tools.pyi @@ -0,0 +1,39 @@ +import pyomo.core.expr as EXPR +from _typeshed import Incomplete +from pyomo.common import DeveloperError as DeveloperError +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import NondifferentiableError as NondifferentiableError +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import value as value + +sympy: Incomplete +sympy_available: Incomplete + +class PyomoSympyBimap: + pyomo2sympy: Incomplete + sympy2pyomo: Incomplete + i: int + def __init__(self) -> None: ... + def getPyomoSymbol(self, sympy_object, default=None): ... + def getSympySymbol(self, pyomo_object): ... + def sympyVars(self): ... + +class Pyomo2SympyVisitor(EXPR.StreamBasedExpressionVisitor): + object_map: Incomplete + keep_mutable_parameters: Incomplete + def __init__(self, object_map, keep_mutable_parameters: bool = False) -> None: ... + def initializeWalker(self, expr): ... + def exitNode(self, node, values): ... + def beforeChild(self, node, child, child_idx): ... + +class Sympy2PyomoVisitor(EXPR.StreamBasedExpressionVisitor): + object_map: Incomplete + def __init__(self, object_map) -> None: ... + def initializeWalker(self, expr): ... + def enterNode(self, node): ... + def exitNode(self, node, values): ... + def beforeChild(self, node, child, child_idx): ... + +def sympyify_expression(expr, keep_mutable_parameters: bool = False): ... +def sympy2pyomo_expression(expr, object_map): ... diff --git a/stubs/pyomo/core/expr/taylor_series.pyi b/stubs/pyomo/core/expr/taylor_series.pyi new file mode 100644 index 000000000..ba1b5fb97 --- /dev/null +++ b/stubs/pyomo/core/expr/taylor_series.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete +from pyomo.core.expr import differentiate as differentiate +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.expr import value as value + +logger: Incomplete + +def taylor_series_expansion(expr, diff_mode=..., order: int = 1): ... diff --git a/stubs/pyomo/core/expr/template_expr.pyi b/stubs/pyomo/core/expr/template_expr.pyi new file mode 100644 index 000000000..47abd5178 --- /dev/null +++ b/stubs/pyomo/core/expr/template_expr.pyi @@ -0,0 +1,206 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.collections import MutableMapping as MutableMapping +from pyomo.common.errors import TemplateExpressionError as TemplateExpressionError +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.core.expr.base import ExpressionArgs_Mixin as ExpressionArgs_Mixin +from pyomo.core.expr.base import ExpressionBase as ExpressionBase +from pyomo.core.expr.base import NPV_Mixin as NPV_Mixin +from pyomo.core.expr.logical_expr import BooleanExpression as BooleanExpression +from pyomo.core.expr.numeric_expr import ARG_TYPE as ARG_TYPE +from pyomo.core.expr.numeric_expr import Numeric_NPV_Mixin as Numeric_NPV_Mixin +from pyomo.core.expr.numeric_expr import NumericExpression as NumericExpression +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression +from pyomo.core.expr.numeric_expr import mutable_expression as mutable_expression +from pyomo.core.expr.numeric_expr import register_arg_type as register_arg_type +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import as_numeric as as_numeric +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.relational_expr import tuple_to_relational_expr as tuple_to_relational_expr +from pyomo.core.expr.visitor import ExpressionReplacementVisitor as ExpressionReplacementVisitor +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.expr.visitor import expression_to_string as expression_to_string + +logger: Incomplete + +class _NotSpecified: ... + +class GetItemExpression(ExpressionBase): + PRECEDENCE: int + def __new__(cls, args=()): ... + def __getattr__(self, attr): ... + def __iter__(self): ... + def __len__(self) -> int: ... + def getname(self, *args, **kwds): ... + def nargs(self): ... + +class Numeric_GetItemExpression(GetItemExpression, NumericExpression): + def nargs(self): ... + +class NPV_Numeric_GetItemExpression(Numeric_NPV_Mixin, Numeric_GetItemExpression): ... +class Boolean_GetItemExpression(GetItemExpression, BooleanExpression): ... +class NPV_Boolean_GetItemExpression(NPV_Mixin, Boolean_GetItemExpression): ... +class Structural_GetItemExpression(ExpressionArgs_Mixin, GetItemExpression): ... +class NPV_Structural_GetItemExpression(NPV_Mixin, Structural_GetItemExpression): ... + +class GetAttrExpression(ExpressionBase): + PRECEDENCE: int + def __new__(cls, args=()): ... + def __getattr__(self, attr): ... + def __getitem__(self, *idx): ... + def __iter__(self): ... + def __len__(self) -> int: ... + def __call__(self, *args, **kwargs): ... + def getname(self, *args, **kwds): ... + def nargs(self): ... + +class Numeric_GetAttrExpression(GetAttrExpression, NumericExpression): ... +class NPV_Numeric_GetAttrExpression(Numeric_NPV_Mixin, Numeric_GetAttrExpression): ... +class Boolean_GetAttrExpression(GetAttrExpression, BooleanExpression): ... +class NPV_Boolean_GetAttrExpression(NPV_Mixin, Boolean_GetAttrExpression): ... +class Structural_GetAttrExpression(ExpressionArgs_Mixin, GetAttrExpression): ... +class NPV_Structural_GetAttrExpression(NPV_Mixin, Structural_GetAttrExpression): ... + +class CallExpression(NumericExpression): + PRECEDENCE: Incomplete + def __init__(self, args, kwargs) -> None: ... + def nargs(self): ... + def __getattr__(self, attr): ... + def __getitem__(self, *idx): ... + def __iter__(self): ... + def __len__(self) -> int: ... + def getname(self, *args, **kwds): ... + +class _TemplateSumExpression_argList: + def __init__(self, TSE) -> None: ... + def __len__(self) -> int: ... + def __getitem__(self, i): ... + def __enter__(self) -> None: ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + +class TemplateSumExpression(NumericExpression): + PRECEDENCE: int + def __init__(self, args, _iters) -> None: ... + def nargs(self): ... + @property + def args(self): ... + def template_args(self): ... + def template_iters(self): ... + def create_node_with_local_data(self, args): ... + def getname(self, *args, **kwds): ... + def is_potentially_variable(self): ... + def to_string(self, verbose=None, smap=None): ... + +class IndexTemplate(NumericValue): + def __init__(self, _set, index: int = 0, _id=None, _group=None) -> None: ... + def __deepcopy__(self, memo): ... + def __call__(self, exception: bool = True): ... + def is_fixed(self): ... + def is_potentially_variable(self): ... + def getname(self, fully_qualified: bool = False, name_buffer=None, relative_to=None): ... + def set_value(self, values=..., lock=None) -> None: ... + def lock(self, lock): ... + def unlock(self, lock) -> None: ... + +class _TemplateResolver(StreamBasedExpressionVisitor): + def beforeChild(self, node, child, child_idx): ... + def exitNode(self, node, args): ... + def initializeWalker(self, expr): ... + +def resolve_template(expr): ... + +class _wildcard_info: + source: Incomplete + original_value: Incomplete + objects: Incomplete + def __init__(self, src, obj) -> None: ... + value: Incomplete + def advance(self) -> None: ... + iter: Incomplete + def reset(self) -> None: ... + def restore(self) -> None: ... + +class ReplaceTemplateExpression(ExpressionReplacementVisitor): + template_types: Incomplete + substituter: Incomplete + substituter_args: Incomplete + def __init__(self, substituter, *args, **kwargs) -> None: ... + def beforeChild(self, node, child, child_idx): ... + +def substitute_template_expression(expr, substituter, *args, **kwargs): ... + +class _GetItemIndexer: + def __init__(self, expr) -> None: ... + def nargs(self): ... + def arg(self, i): ... + @property + def base(self): ... + @property + def args(self): ... + def __hash__(self): ... + def __eq__(self, other): ... + +def substitute_getitem_with_param(expr, _map): ... +def substitute_template_with_value(expr): ... + +class _set_iterator_template_generator: + context: Incomplete + def __init__(self, _set, context) -> None: ... + def __iter__(self): ... + def __next__(self): ... + next = __next__ + +class _template_iter_context: + cache: Incomplete + def __init__(self) -> None: ... + def get_iter(self, _set): ... + def npop_cache(self, n): ... + def next_id(self): ... + def next_group(self): ... + def sum_template(self, generator): ... + +class _template_iter_manager: + class _iter_wrapper: + def __init__(self, cls, context) -> None: ... + def acquire(self) -> None: ... + def release(self) -> None: ... + + class _pause_template_iter_manager: + iter_manager: Incomplete + def __init__(self, iter_manager) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + + paused: bool + context: Incomplete + iters: Incomplete + builtin_sum: Incomplete + def __init__(self) -> None: ... + def init(self, context, *iter_fcns): ... + def acquire(self) -> None: ... + def release(self) -> None: ... + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + def pause(self): ... + +def templatize_rule(block, rule, index_set): ... +def templatize_constraint(con): ... diff --git a/stubs/pyomo/core/expr/visitor.pyi b/stubs/pyomo/core/expr/visitor.pyi new file mode 100644 index 000000000..c06bfdf89 --- /dev/null +++ b/stubs/pyomo/core/expr/visitor.pyi @@ -0,0 +1,146 @@ +import inspect +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import TemplateExpressionError as TemplateExpressionError +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.common.numeric_types import value as value +from pyomo.core.expr.symbol_map import SymbolMap as SymbolMap + +logger: Incomplete +currentframe: Incomplete +currentframe = inspect.currentframe + +def get_stack_depth(): ... + +RECURSION_LIMIT: int + +class RevertToNonrecursive(Exception): ... + +class StreamBasedExpressionVisitor: + client_methods: Incomplete + recursion_stack: Incomplete + def __init__(self, **kwds) -> None: ... + def walk_expression(self, expr): ... + def walk_expression_nonrecursive(self, expr): ... + +class SimpleExpressionVisitor: + def visit(self, node) -> None: ... + def finalize(self) -> None: ... + def xbfs(self, node): ... + def xbfs_yield_leaves(self, node) -> Generator[Incomplete]: ... + +class ExpressionValueVisitor: + def visit(self, node, values) -> None: ... + def visiting_potential_leaf(self, node) -> None: ... + def finalize(self, ans): ... + def dfs_postorder_stack(self, node): ... + +def replace_expressions( + expr, + substitution_map, + descend_into_named_expressions: bool = True, + remove_named_expressions: bool = True, +): ... + +class ExpressionReplacementVisitor(StreamBasedExpressionVisitor): + substitute: Incomplete + enter_named_expr: Incomplete + rm_named_expr: Incomplete + def __init__( + self, + substitute=None, + descend_into_named_expressions: bool = True, + remove_named_expressions: bool = True, + ) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def enterNode(self, node): ... + def acceptChildResult(self, node, data, child_result, child_idx): ... + def exitNode(self, node, data): ... + def dfs_postorder_stack(self, expr): ... + +def evaluate_fixed_subexpressions( + expr, descend_into_named_expressions: bool = True, remove_named_expressions: bool = True +): ... + +class EvaluateFixedSubexpressionVisitor(ExpressionReplacementVisitor): + def __init__( + self, descend_into_named_expressions: bool = False, remove_named_expressions: bool = False + ) -> None: ... + def beforeChild(self, node, child, child_idx): ... + +def clone_expression(expr, substitute=None): ... +def sizeof_expression(expr): ... + +class _EvaluationVisitor(ExpressionValueVisitor): + exception: Incomplete + def __init__(self, exception) -> None: ... + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +class FixedExpressionError(Exception): + def __init__(self, *args, **kwds) -> None: ... + +class NonConstantExpressionError(Exception): + def __init__(self, *args, **kwds) -> None: ... + +class _EvaluateConstantExpressionVisitor(ExpressionValueVisitor): + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +def evaluate_expression(exp, exception: bool = True, constant: bool = False): ... + +class _ComponentVisitor(StreamBasedExpressionVisitor): + def __init__(self, types) -> None: ... + def initializeWalker(self, expr): ... + def finalizeResult(self, result): ... + def exitNode(self, node, data) -> None: ... + +def identify_components(expr, component_types) -> Generator[Incomplete, Incomplete]: ... + +class IdentifyVariableVisitor(StreamBasedExpressionVisitor): + def __init__(self, include_fixed: bool = False, named_expression_cache=None) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, parent, child, index): ... + def exitNode(self, node, data) -> None: ... + def finalizeResult(self, result): ... + +def identify_variables( + expr, include_fixed: bool = True, named_expression_cache=None +) -> Generator[Incomplete, Incomplete]: ... + +class IdentifyMutableParamVisitor(IdentifyVariableVisitor): + def __init__(self) -> None: ... + def beforeChild(self, parent, child, index): ... + +def identify_mutable_parameters(expr) -> Generator[Incomplete, Incomplete]: ... + +class _PolynomialDegreeVisitor(ExpressionValueVisitor): + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +def polynomial_degree(node): ... + +class _IsFixedVisitor(ExpressionValueVisitor): + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +LEFT_TO_RIGHT: Incomplete +RIGHT_TO_LEFT: Incomplete + +class _ToStringVisitor(ExpressionValueVisitor): + verbose: Incomplete + smap: Incomplete + def __init__(self, verbose, smap) -> None: ... + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +def expression_to_string( + expr, verbose=None, labeler=None, smap=None, compute_values: bool = False +): ... diff --git a/stubs/pyomo/core/kernel/__init__.pyi b/stubs/pyomo/core/kernel/__init__.pyi new file mode 100644 index 000000000..499c58296 --- /dev/null +++ b/stubs/pyomo/core/kernel/__init__.pyi @@ -0,0 +1,64 @@ +from pyomo.common.deprecation import moved_module as moved_module +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.core.expr import Expr_if as Expr_if +from pyomo.core.expr import acos as acos +from pyomo.core.expr import acosh as acosh +from pyomo.core.expr import asin as asin +from pyomo.core.expr import asinh as asinh +from pyomo.core.expr import atan as atan +from pyomo.core.expr import atanh as atanh +from pyomo.core.expr import atleast as atleast +from pyomo.core.expr import atmost as atmost +from pyomo.core.expr import boolean_value as boolean_value +from pyomo.core.expr import ceil as ceil +from pyomo.core.expr import cos as cos +from pyomo.core.expr import cosh as cosh +from pyomo.core.expr import equivalent as equivalent +from pyomo.core.expr import exactly as exactly +from pyomo.core.expr import exp as exp +from pyomo.core.expr import floor as floor +from pyomo.core.expr import implies as implies +from pyomo.core.expr import inequality as inequality +from pyomo.core.expr import land as land +from pyomo.core.expr import linear_expression as linear_expression +from pyomo.core.expr import lnot as lnot +from pyomo.core.expr import log as log +from pyomo.core.expr import log10 as log10 +from pyomo.core.expr import logical_expr as logical_expr +from pyomo.core.expr import lor as lor +from pyomo.core.expr import nonlinear_expression as nonlinear_expression +from pyomo.core.expr import numeric_expr as numeric_expr +from pyomo.core.expr import numvalue as numvalue +from pyomo.core.expr import sin as sin +from pyomo.core.expr import sinh as sinh +from pyomo.core.expr import sqrt as sqrt +from pyomo.core.expr import tan as tan +from pyomo.core.expr import tanh as tanh +from pyomo.core.expr import xor as xor +from pyomo.core.expr.boolean_value import BooleanValue as BooleanValue +from pyomo.core.expr.calculus.derivatives import differentiate as differentiate +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.core.expr.numvalue import is_variable_type as is_variable_type +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import polynomial_degree as polynomial_degree +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.taylor_series import taylor_series_expansion as taylor_series_expansion +from pyomo.core.kernel import base as base +from pyomo.core.kernel import block as block +from pyomo.core.kernel import constraint as constraint +from pyomo.core.kernel import expression as expression +from pyomo.core.kernel import heterogeneous_container as heterogeneous_container +from pyomo.core.kernel import homogeneous_container as homogeneous_container +from pyomo.core.kernel import matrix_constraint as matrix_constraint +from pyomo.core.kernel import objective as objective +from pyomo.core.kernel import parameter as parameter +from pyomo.core.kernel import piecewise_library as piecewise_library +from pyomo.core.kernel import set_types as set_types +from pyomo.core.kernel import sos as sos +from pyomo.core.kernel import suffix as suffix +from pyomo.core.kernel import variable as variable diff --git a/stubs/pyomo/core/kernel/base.pyi b/stubs/pyomo/core/kernel/base.pyi new file mode 100644 index 000000000..cf93e8520 --- /dev/null +++ b/stubs/pyomo/core/kernel/base.pyi @@ -0,0 +1,35 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots + +class _no_ctype: ... + +class ICategorizedObject(AutoSlots.Mixin): + __autoslot_mappers__: Incomplete + @property + def ctype(self): ... + @property + def parent(self): ... + @property + def storage_key(self): ... + @property + def active(self): ... + @active.setter + def active(self, value) -> None: ... + def activate(self) -> None: ... + def deactivate(self) -> None: ... + def getname( + self, fully_qualified: bool = False, name_buffer={}, convert=..., relative_to=None + ): ... + @property + def name(self): ... + @property + def local_name(self): ... + def clone(self): ... + def __deepcopy__(self, memo): ... + +class ICategorizedObjectContainer(ICategorizedObject): + def activate(self, shallow: bool = True) -> None: ... + def deactivate(self, shallow: bool = True) -> None: ... + def child(self, *args, **kwds) -> None: ... + def children(self, *args, **kwds) -> None: ... + def components(self, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/core/kernel/block.pyi b/stubs/pyomo/core/kernel/block.pyi new file mode 100644 index 000000000..0a2814ac5 --- /dev/null +++ b/stubs/pyomo/core/kernel/block.pyi @@ -0,0 +1,35 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.core.expr.symbol_map import SymbolMap as SymbolMap +from pyomo.core.kernel.container_utils import define_simple_containers as define_simple_containers +from pyomo.core.kernel.heterogeneous_container import ( + IHeterogeneousContainer as IHeterogeneousContainer, +) +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +logger: Incomplete + +class IBlock(IHeterogeneousContainer): + def child(self, key): ... + +class block(IBlock): + def __init__(self) -> None: ... + def child_ctypes(self): ... + def children(self, ctype=...) -> Generator[Incomplete, Incomplete]: ... + def __setattr__(self, name, obj) -> None: ... + def __delattr__(self, name) -> None: ... + def write( + self, + filename, + format=None, + _solver_capability=None, + _called_by_solver: bool = False, + **kwds, + ): ... + def load_solution( + self, + solution, + allow_consistent_values_for_fixed_vars: bool = False, + comparison_tolerance_for_fixed_vars: float = 1e-05, + ) -> None: ... diff --git a/stubs/pyomo/core/kernel/conic.pyi b/stubs/pyomo/core/kernel/conic.pyi new file mode 100644 index 000000000..333fdd3e8 --- /dev/null +++ b/stubs/pyomo/core/kernel/conic.pyi @@ -0,0 +1,132 @@ +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.expr import exp as exp +from pyomo.core.expr import value as value +from pyomo.core.expr.numvalue import is_numeric_data as is_numeric_data +from pyomo.core.kernel.block import block as block +from pyomo.core.kernel.constraint import IConstraint as IConstraint +from pyomo.core.kernel.constraint import constraint as constraint +from pyomo.core.kernel.constraint import constraint_tuple as constraint_tuple +from pyomo.core.kernel.constraint import linear_constraint as linear_constraint +from pyomo.core.kernel.variable import IVariable as IVariable +from pyomo.core.kernel.variable import variable as variable +from pyomo.core.kernel.variable import variable_tuple as variable_tuple + +class _ConicBase(IConstraint): + def __init__(self) -> None: ... + @classmethod + def as_domain(cls, *args, **kwds) -> None: ... + def check_convexity_conditions(self, relax: bool = False) -> None: ... + @property + def body(self): ... + @property + def lower(self) -> None: ... + @property + def upper(self): ... + @property + def lb(self) -> None: ... + @property + def ub(self): ... + @property + def rhs(self) -> None: ... + @property + def equality(self): ... + def __call__(self, exception=...): ... + +class quadratic(_ConicBase): + def __init__(self, r, x) -> None: ... + @classmethod + def as_domain(cls, r, x): ... + @property + def r(self): ... + @property + def x(self): ... + def check_convexity_conditions(self, relax: bool = False): ... + +class rotated_quadratic(_ConicBase): + def __init__(self, r1, r2, x) -> None: ... + @classmethod + def as_domain(cls, r1, r2, x): ... + @property + def r1(self): ... + @property + def r2(self): ... + @property + def x(self): ... + def check_convexity_conditions(self, relax: bool = False): ... + +class primal_exponential(_ConicBase): + def __init__(self, r, x1, x2) -> None: ... + @classmethod + def as_domain(cls, r, x1, x2): ... + @property + def r(self): ... + @property + def x1(self): ... + @property + def x2(self): ... + def check_convexity_conditions(self, relax: bool = False): ... + +class primal_power(_ConicBase): + def __init__(self, r1, r2, x, alpha) -> None: ... + @classmethod + def as_domain(cls, r1, r2, x, alpha): ... + @property + def r1(self): ... + @property + def r2(self): ... + @property + def x(self): ... + @property + def alpha(self): ... + def check_convexity_conditions(self, relax: bool = False): ... + +class primal_geomean(_ConicBase): + def __init__(self, r, x) -> None: ... + @classmethod + def as_domain(cls, r, x): ... + @property + def r(self): ... + @property + def x(self): ... + +class dual_exponential(_ConicBase): + def __init__(self, r, x1, x2) -> None: ... + @classmethod + def as_domain(cls, r, x1, x2): ... + @property + def r(self): ... + @property + def x1(self): ... + @property + def x2(self): ... + def check_convexity_conditions(self, relax: bool = False): ... + +class dual_power(_ConicBase): + def __init__(self, r1, r2, x, alpha) -> None: ... + @classmethod + def as_domain(cls, r1, r2, x, alpha): ... + @property + def r1(self): ... + @property + def r2(self): ... + @property + def x(self): ... + @property + def alpha(self): ... + def check_convexity_conditions(self, relax: bool = False): ... + +class dual_geomean(_ConicBase): + def __init__(self, r, x) -> None: ... + @classmethod + def as_domain(cls, r, x): ... + @property + def r(self): ... + @property + def x(self): ... + +class svec_psdcone(_ConicBase): + def __init__(self, x) -> None: ... + @classmethod + def as_domain(cls, x): ... + @property + def x(self): ... diff --git a/stubs/pyomo/core/kernel/constraint.pyi b/stubs/pyomo/core/kernel/constraint.pyi new file mode 100644 index 000000000..f0a1a5089 --- /dev/null +++ b/stubs/pyomo/core/kernel/constraint.pyi @@ -0,0 +1,96 @@ +from _typeshed import Incomplete +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.expr.expr_common import ExpressionType as ExpressionType +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.expr.numvalue import as_numeric as as_numeric +from pyomo.core.expr.numvalue import is_numeric_data as is_numeric_data +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.relational_expr import EqualityExpression as EqualityExpression +from pyomo.core.expr.relational_expr import InequalityExpression as InequalityExpression +from pyomo.core.expr.relational_expr import RangedExpression as RangedExpression +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject +from pyomo.core.kernel.container_utils import define_simple_containers as define_simple_containers + +class IConstraint(ICategorizedObject): + body: Incomplete + lower: Incomplete + upper: Incomplete + lb: Incomplete + ub: Incomplete + rhs: Incomplete + equality: Incomplete + def __call__(self, exception=...): ... + @property + def lslack(self): ... + @property + def uslack(self): ... + @property + def slack(self): ... + @property + def expr(self): ... + @property + def bounds(self): ... + def has_lb(self): ... + def has_ub(self): ... + def to_bounded_expression(self, evaluate_bounds: bool = False): ... + +class _MutableBoundsConstraintMixin: + @property + def lower(self): ... + @lower.setter + def lower(self, lb) -> None: ... + @property + def upper(self): ... + @upper.setter + def upper(self, ub) -> None: ... + @property + def lb(self): ... + @lb.setter + def lb(self, lb) -> None: ... + @property + def ub(self): ... + @ub.setter + def ub(self, ub) -> None: ... + @property + def rhs(self): ... + @rhs.setter + def rhs(self, rhs) -> None: ... + @property + def bounds(self): ... + @bounds.setter + def bounds(self, bounds_tuple) -> None: ... + @property + def equality(self): ... + @equality.setter + def equality(self, equality) -> None: ... + +class constraint(_MutableBoundsConstraintMixin, IConstraint): + lb: Incomplete + ub: Incomplete + rhs: Incomplete + def __init__(self, expr=None, body=None, lb=None, ub=None, rhs=None) -> None: ... + @property + def body(self): ... + @body.setter + def body(self, body) -> None: ... + @property + def expr(self): ... + @expr.setter + def expr(self, expr) -> None: ... + +class linear_constraint(_MutableBoundsConstraintMixin, IConstraint): + lb: Incomplete + ub: Incomplete + rhs: Incomplete + def __init__( + self, variables=None, coefficients=None, terms=None, lb=None, ub=None, rhs=None + ) -> None: ... + @property + def terms(self): ... + @terms.setter + def terms(self, terms) -> None: ... + def __call__(self, exception=...): ... + @property + def body(self): ... + def canonical_form(self, compute_values: bool = True): ... diff --git a/stubs/pyomo/core/kernel/container_utils.pyi b/stubs/pyomo/core/kernel/container_utils.pyi new file mode 100644 index 000000000..b0c60515d --- /dev/null +++ b/stubs/pyomo/core/kernel/container_utils.pyi @@ -0,0 +1,8 @@ +from pyomo.core.kernel.dict_container import DictContainer as DictContainer +from pyomo.core.kernel.list_container import ListContainer as ListContainer +from pyomo.core.kernel.tuple_container import TupleContainer as TupleContainer + +def define_homogeneous_container_type( + namespace, name, container_class, ctype, doc=None, use_slots: bool = True +) -> None: ... +def define_simple_containers(namespace, prefix, ctype, use_slots: bool = True) -> None: ... diff --git a/stubs/pyomo/core/kernel/dict_container.pyi b/stubs/pyomo/core/kernel/dict_container.pyi new file mode 100644 index 000000000..b5e047cdb --- /dev/null +++ b/stubs/pyomo/core/kernel/dict_container.pyi @@ -0,0 +1,19 @@ +import collections.abc + +from _typeshed import Incomplete +from pyomo.core.kernel.homogeneous_container import IHomogeneousContainer as IHomogeneousContainer + +logger: Incomplete + +class DictContainer(IHomogeneousContainer, collections.abc.MutableMapping): + def __init__(self, *args, **kwds) -> None: ... + def child(self, key): ... + def children(self): ... + def __setitem__(self, key, item) -> None: ... + def __delitem__(self, key) -> None: ... + def __getitem__(self, key): ... + def __iter__(self): ... + def __len__(self) -> int: ... + def __contains__(self, key) -> bool: ... + def __eq__(self, other): ... + def __ne__(self, other): ... diff --git a/stubs/pyomo/core/kernel/expression.pyi b/stubs/pyomo/core/kernel/expression.pyi new file mode 100644 index 000000000..6550f6488 --- /dev/null +++ b/stubs/pyomo/core/kernel/expression.pyi @@ -0,0 +1,56 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import is_numeric_data as is_numeric_data +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject +from pyomo.core.kernel.container_utils import define_simple_containers as define_simple_containers + +class IIdentityExpression(NumericValue): + PRECEDENCE: int + ASSOCIATIVITY: Incomplete + @property + def expr(self): ... + def __call__(self, exception=...): ... + def is_fixed(self): ... + def is_parameter_type(self): ... + def is_variable_type(self): ... + def is_named_expression_type(self): ... + def is_expression_type(self, expression_system=None): ... + @property + def args(self): ... + def nargs(self): ... + def arg(self, i): ... + def polynomial_degree(self): ... + def to_string(self, verbose=None, labeler=None, smap=None, compute_values: bool = False): ... + def create_node_with_local_data(self, values): ... + def is_constant(self) -> None: ... + def is_potentially_variable(self) -> None: ... + def clone(self) -> None: ... + +def noclone(expr): ... + +class IExpression(ICategorizedObject, IIdentityExpression): + expr: Incomplete + def is_constant(self): ... + def is_potentially_variable(self): ... + def clone(self): ... + +class expression(IExpression): + def __init__(self, expr=None) -> None: ... + @property + def expr(self): ... + @expr.setter + def expr(self, expr) -> None: ... + +class data_expression(expression): + def is_potentially_variable(self): ... + def polynomial_degree(self): ... + @property + def expr(self): ... + @expr.setter + def expr(self, expr) -> None: ... diff --git a/stubs/pyomo/core/kernel/heterogeneous_container.pyi b/stubs/pyomo/core/kernel/heterogeneous_container.pyi new file mode 100644 index 000000000..507094d23 --- /dev/null +++ b/stubs/pyomo/core/kernel/heterogeneous_container.pyi @@ -0,0 +1,15 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.core.kernel.base import ICategorizedObjectContainer as ICategorizedObjectContainer + +def heterogeneous_containers( + node, ctype=..., active: bool = True, descend_into: bool = True +) -> Generator[Incomplete, Incomplete]: ... + +class IHeterogeneousContainer(ICategorizedObjectContainer): + def collect_ctypes(self, active: bool = True, descend_into: bool = True): ... + def child_ctypes(self, *args, **kwds) -> None: ... + def components( + self, ctype=..., active: bool = True, descend_into: bool = True + ) -> Generator[Incomplete, Incomplete]: ... diff --git a/stubs/pyomo/core/kernel/homogeneous_container.pyi b/stubs/pyomo/core/kernel/homogeneous_container.pyi new file mode 100644 index 000000000..099922625 --- /dev/null +++ b/stubs/pyomo/core/kernel/homogeneous_container.pyi @@ -0,0 +1,7 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.core.kernel.base import ICategorizedObjectContainer as ICategorizedObjectContainer + +class IHomogeneousContainer(ICategorizedObjectContainer): + def components(self, active: bool = True) -> Generator[Incomplete]: ... diff --git a/stubs/pyomo/core/kernel/list_container.pyi b/stubs/pyomo/core/kernel/list_container.pyi new file mode 100644 index 000000000..7601a3c5b --- /dev/null +++ b/stubs/pyomo/core/kernel/list_container.pyi @@ -0,0 +1,13 @@ +import collections.abc + +from _typeshed import Incomplete +from pyomo.core.kernel.tuple_container import TupleContainer as TupleContainer + +logger: Incomplete + +class ListContainer(TupleContainer, collections.abc.MutableSequence): + def __init__(self, *args) -> None: ... + def __setitem__(self, i, item) -> None: ... + def insert(self, i, item) -> None: ... + def __delitem__(self, i) -> None: ... + def reverse(self) -> None: ... diff --git a/stubs/pyomo/core/kernel/matrix_constraint.pyi b/stubs/pyomo/core/kernel/matrix_constraint.pyi new file mode 100644 index 000000000..68968efe8 --- /dev/null +++ b/stubs/pyomo/core/kernel/matrix_constraint.pyi @@ -0,0 +1,83 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.dependencies import numpy as numpy +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.constraint import IConstraint as IConstraint +from pyomo.core.kernel.constraint import constraint_tuple as constraint_tuple + +class _MatrixConstraintData(IConstraint): + def __init__(self, index) -> None: ... + @property + def index(self): ... + @property + def terms(self) -> Generator[Incomplete]: ... + def __call__(self, exception=...): ... + @property + def body(self): ... + @property + def lower(self): ... + @lower.setter + def lower(self, lb) -> None: ... + @property + def upper(self): ... + @upper.setter + def upper(self, ub) -> None: ... + @property + def lb(self): ... + @lb.setter + def lb(self, lb) -> None: ... + @property + def ub(self): ... + @ub.setter + def ub(self, ub) -> None: ... + @property + def rhs(self): ... + @rhs.setter + def rhs(self, rhs) -> None: ... + @property + def bounds(self): ... + @bounds.setter + def bounds(self, bounds_tuple) -> None: ... + @property + def equality(self): ... + @equality.setter + def equality(self, equality) -> None: ... + def canonical_form(self, compute_values: bool = True): ... + +class matrix_constraint(constraint_tuple): + def __init__(self, A, lb=None, ub=None, rhs=None, x=None, sparse: bool = True) -> None: ... + @property + def sparse(self): ... + @property + def A(self): ... + @property + def x(self): ... + @x.setter + def x(self, x) -> None: ... + @property + def lb(self): ... + @lb.setter + def lb(self, lb) -> None: ... + @property + def ub(self): ... + @ub.setter + def ub(self, ub) -> None: ... + @property + def rhs(self): ... + @rhs.setter + def rhs(self, rhs) -> None: ... + @property + def equality(self): ... + @equality.setter + def equality(self, equality) -> None: ... + def __call__(self, exception=...): ... + @property + def lslack(self, body=...): ... + @property + def uslack(self, body=...): ... + @property + def slack(self): ... diff --git a/stubs/pyomo/core/kernel/objective.pyi b/stubs/pyomo/core/kernel/objective.pyi new file mode 100644 index 000000000..771d987b3 --- /dev/null +++ b/stubs/pyomo/core/kernel/objective.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.common.enums import ObjectiveSense as ObjectiveSense +from pyomo.common.enums import maximize as maximize +from pyomo.common.enums import minimize as minimize +from pyomo.core.expr.numvalue import as_numeric as as_numeric +from pyomo.core.kernel.container_utils import define_simple_containers as define_simple_containers +from pyomo.core.kernel.expression import IExpression as IExpression + +class IObjective(IExpression): + sense: Incomplete + def is_minimizing(self): ... + +class objective(IObjective): + def __init__(self, expr=None, sense=...) -> None: ... + @property + def expr(self): ... + @expr.setter + def expr(self, expr) -> None: ... + @property + def sense(self): ... + @sense.setter + def sense(self, sense) -> None: ... diff --git a/stubs/pyomo/core/kernel/parameter.pyi b/stubs/pyomo/core/kernel/parameter.pyi new file mode 100644 index 000000000..0faac7572 --- /dev/null +++ b/stubs/pyomo/core/kernel/parameter.pyi @@ -0,0 +1,30 @@ +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import is_numeric_data as is_numeric_data +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject +from pyomo.core.kernel.container_utils import define_simple_containers as define_simple_containers + +class IParameter(ICategorizedObject, NumericValue): + def __call__(self, exception=...) -> None: ... + def is_constant(self): ... + def is_parameter_type(self): ... + def is_variable_type(self): ... + def is_fixed(self): ... + def is_potentially_variable(self): ... + def polynomial_degree(self): ... + +class parameter(IParameter): + def __init__(self, value=None) -> None: ... + def __call__(self, exception=...): ... + @property + def value(self): ... + @value.setter + def value(self, value) -> None: ... + +class functional_value(IParameter): + def __init__(self, fn=None) -> None: ... + def __call__(self, exception=...): ... + @property + def fn(self): ... + @fn.setter + def fn(self, fn) -> None: ... diff --git a/stubs/pyomo/core/kernel/piecewise_library/__init__.pyi b/stubs/pyomo/core/kernel/piecewise_library/__init__.pyi new file mode 100644 index 000000000..0bc382934 --- /dev/null +++ b/stubs/pyomo/core/kernel/piecewise_library/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.core.kernel.piecewise_library import transforms as transforms +from pyomo.core.kernel.piecewise_library import transforms_nd as transforms_nd +from pyomo.core.kernel.piecewise_library import util as util diff --git a/stubs/pyomo/core/kernel/piecewise_library/transforms.pyi b/stubs/pyomo/core/kernel/piecewise_library/transforms.pyi new file mode 100644 index 000000000..7439e0b6e --- /dev/null +++ b/stubs/pyomo/core/kernel/piecewise_library/transforms.pyi @@ -0,0 +1,124 @@ +from _typeshed import Incomplete +from pyomo.core.kernel.block import block as block +from pyomo.core.kernel.constraint import constraint_list as constraint_list +from pyomo.core.kernel.constraint import constraint_tuple as constraint_tuple +from pyomo.core.kernel.constraint import linear_constraint as linear_constraint +from pyomo.core.kernel.expression import expression as expression +from pyomo.core.kernel.expression import expression_tuple as expression_tuple +from pyomo.core.kernel.piecewise_library.util import ( + PiecewiseValidationError as PiecewiseValidationError, +) +from pyomo.core.kernel.piecewise_library.util import characterize_function as characterize_function +from pyomo.core.kernel.piecewise_library.util import generate_gray_code as generate_gray_code +from pyomo.core.kernel.piecewise_library.util import is_nondecreasing as is_nondecreasing +from pyomo.core.kernel.piecewise_library.util import ( + is_positive_power_of_two as is_positive_power_of_two, +) +from pyomo.core.kernel.piecewise_library.util import log2floor as log2floor +from pyomo.core.kernel.set_types import IntegerSet as IntegerSet +from pyomo.core.kernel.sos import sos2 as sos2 +from pyomo.core.kernel.variable import IVariable as IVariable +from pyomo.core.kernel.variable import variable as variable +from pyomo.core.kernel.variable import variable_dict as variable_dict +from pyomo.core.kernel.variable import variable_list as variable_list +from pyomo.core.kernel.variable import variable_tuple as variable_tuple + +logger: Incomplete +registered_transforms: Incomplete + +class _shadow_list: + def __init__(self, x) -> None: ... + def __len__(self) -> int: ... + def __getitem__(self, i): ... + +def piecewise( + breakpoints, + values, + input=None, + output=None, + bound: str = 'eq', + repn: str = 'sos2', + validate: bool = True, + simplify: bool = True, + equal_slopes_tolerance: float = 1e-06, + require_bounded_input_variable: bool = True, + require_variable_domain_coverage: bool = True, +): ... + +class PiecewiseLinearFunction: + def __init__(self, breakpoints, values, validate: bool = True, **kwds) -> None: ... + def validate(self, equal_slopes_tolerance: float = 1e-06): ... + @property + def breakpoints(self): ... + @property + def values(self): ... + def __call__(self, x): ... + +class TransformedPiecewiseLinearFunction(block): + def __init__( + self, f, input=None, output=None, bound: str = 'eq', validate: bool = True, **kwds + ) -> None: ... + @property + def input(self): ... + @property + def output(self): ... + @property + def bound(self): ... + def validate( + self, + equal_slopes_tolerance: float = 1e-06, + require_bounded_input_variable: bool = True, + require_variable_domain_coverage: bool = True, + ): ... + @property + def breakpoints(self): ... + @property + def values(self): ... + def __call__(self, x): ... + +class piecewise_convex(TransformedPiecewiseLinearFunction): + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... + +class piecewise_sos2(TransformedPiecewiseLinearFunction): + c: Incomplete + s: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... + +class piecewise_dcc(TransformedPiecewiseLinearFunction): + v: Incomplete + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... + +class piecewise_cc(TransformedPiecewiseLinearFunction): + v: Incomplete + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... + +class piecewise_mc(TransformedPiecewiseLinearFunction): + v: Incomplete + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... + +class piecewise_inc(TransformedPiecewiseLinearFunction): + v: Incomplete + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... + +class piecewise_dlog(TransformedPiecewiseLinearFunction): + v: Incomplete + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... + +class piecewise_log(TransformedPiecewiseLinearFunction): + v: Incomplete + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def validate(self, **kwds): ... diff --git a/stubs/pyomo/core/kernel/piecewise_library/transforms_nd.pyi b/stubs/pyomo/core/kernel/piecewise_library/transforms_nd.pyi new file mode 100644 index 000000000..49d2a5421 --- /dev/null +++ b/stubs/pyomo/core/kernel/piecewise_library/transforms_nd.pyi @@ -0,0 +1,43 @@ +from _typeshed import Incomplete +from pyomo.core.kernel.block import block as block +from pyomo.core.kernel.constraint import constraint_list as constraint_list +from pyomo.core.kernel.constraint import constraint_tuple as constraint_tuple +from pyomo.core.kernel.constraint import linear_constraint as linear_constraint +from pyomo.core.kernel.expression import expression as expression +from pyomo.core.kernel.expression import expression_tuple as expression_tuple +from pyomo.core.kernel.set_types import IntegerSet as IntegerSet +from pyomo.core.kernel.variable import variable as variable +from pyomo.core.kernel.variable import variable_dict as variable_dict +from pyomo.core.kernel.variable import variable_tuple as variable_tuple + +logger: Incomplete +registered_transforms: Incomplete + +def piecewise_nd(tri, values, input=None, output=None, bound: str = 'eq', repn: str = 'cc'): ... + +class PiecewiseLinearFunctionND: + def __init__(self, tri, values, validate: bool = True, **kwds) -> None: ... + @property + def triangulation(self): ... + @property + def values(self): ... + def __call__(self, x): ... + +class TransformedPiecewiseLinearFunctionND(block): + def __init__(self, f, input=None, output=None, bound: str = 'eq') -> None: ... + @property + def input(self): ... + @property + def output(self): ... + @property + def bound(self): ... + @property + def triangulation(self): ... + @property + def values(self): ... + def __call__(self, x): ... + +class piecewise_nd_cc(TransformedPiecewiseLinearFunctionND): + v: Incomplete + c: Incomplete + def __init__(self, *args, **kwds) -> None: ... diff --git a/stubs/pyomo/core/kernel/piecewise_library/util.pyi b/stubs/pyomo/core/kernel/piecewise_library/util.pyi new file mode 100644 index 000000000..0c38f323c --- /dev/null +++ b/stubs/pyomo/core/kernel/piecewise_library/util.pyi @@ -0,0 +1,15 @@ +from pyomo.common.dependencies import numpy as numpy +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.dependencies import scipy_available as scipy_available + +class PiecewiseValidationError(Exception): ... + +def is_constant(vals): ... +def is_nondecreasing(vals): ... +def is_nonincreasing(vals): ... +def is_positive_power_of_two(x): ... +def log2floor(n): ... +def generate_gray_code(nbits): ... +def characterize_function(breakpoints, values): ... +def generate_delaunay(variables, num: int = 10, **kwds): ... diff --git a/stubs/pyomo/core/kernel/set_types.pyi b/stubs/pyomo/core/kernel/set_types.pyi new file mode 100644 index 000000000..88fcf147d --- /dev/null +++ b/stubs/pyomo/core/kernel/set_types.pyi @@ -0,0 +1,35 @@ +from _typeshed import Incomplete + +logger: Incomplete + +class RealSet: + @staticmethod + def get_interval(): ... + @staticmethod + def is_continuous(): ... + @staticmethod + def is_integer(): ... + @staticmethod + def is_binary(): ... + +class IntegerSet: + @staticmethod + def get_interval(): ... + @staticmethod + def is_continuous(): ... + @staticmethod + def is_integer(): ... + @staticmethod + def is_binary(): ... + +class BinarySet: + @staticmethod + def get_interval(): ... + @staticmethod + def is_continuous(): ... + @staticmethod + def is_integer(): ... + @staticmethod + def is_binary(): ... + +BooleanSet = BinarySet diff --git a/stubs/pyomo/core/kernel/sos.pyi b/stubs/pyomo/core/kernel/sos.pyi new file mode 100644 index 000000000..25fd49b7d --- /dev/null +++ b/stubs/pyomo/core/kernel/sos.pyi @@ -0,0 +1,24 @@ +from _typeshed import Incomplete +from pyomo.core.expr.numvalue import is_numeric_data as is_numeric_data +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject +from pyomo.core.kernel.container_utils import define_simple_containers as define_simple_containers + +class ISOS(ICategorizedObject): + variables: Incomplete + weights: Incomplete + level: Incomplete + def items(self): ... + def __contains__(self, v) -> bool: ... + def __len__(self) -> int: ... + +class sos(ISOS): + def __init__(self, variables, weights=None, level: int = 1) -> None: ... + @property + def variables(self): ... + @property + def weights(self): ... + @property + def level(self): ... + +def sos1(variables, weights=None): ... +def sos2(variables, weights=None): ... diff --git a/stubs/pyomo/core/kernel/suffix.pyi b/stubs/pyomo/core/kernel/suffix.pyi new file mode 100644 index 000000000..84fc60020 --- /dev/null +++ b/stubs/pyomo/core/kernel/suffix.pyi @@ -0,0 +1,55 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject +from pyomo.core.kernel.container_utils import ( + define_homogeneous_container_type as define_homogeneous_container_type, +) +from pyomo.core.kernel.dict_container import DictContainer as DictContainer + +logger: Incomplete + +class ISuffix(ComponentMap, ICategorizedObject): + direction: Incomplete + datatype: Incomplete + +class suffix(ISuffix): + LOCAL: int + EXPORT: int + IMPORT: int + IMPORT_EXPORT: int + FLOAT: int + INT: int + def __init__(self, *args, **kwds) -> None: ... + def export_enabled(self): ... + def import_enabled(self): ... + @property + def datatype(self): ... + @datatype.setter + def datatype(self, datatype) -> None: ... + @property + def direction(self): ... + @direction.setter + def direction(self, direction) -> None: ... + def set_all_values(self, value) -> None: ... + def clear_value(self, component) -> None: ... + def clear_all_values(self) -> None: ... + def get_datatype(self): ... + def set_datatype(self, datatype) -> None: ... + def get_direction(self): ... + def set_direction(self, direction) -> None: ... + +def export_suffix_generator( + blk, datatype=..., active: bool = True, descend_into: bool = True +) -> Generator[Incomplete, None, Incomplete]: ... +def import_suffix_generator( + blk, datatype=..., active: bool = True, descend_into: bool = True +) -> Generator[Incomplete, None, Incomplete]: ... +def local_suffix_generator( + blk, datatype=..., active: bool = True, descend_into: bool = True +) -> Generator[Incomplete, None, Incomplete]: ... +def suffix_generator( + blk, datatype=..., active: bool = True, descend_into: bool = True +) -> Generator[Incomplete, None, Incomplete]: ... diff --git a/stubs/pyomo/core/kernel/tuple_container.pyi b/stubs/pyomo/core/kernel/tuple_container.pyi new file mode 100644 index 000000000..d869f5a68 --- /dev/null +++ b/stubs/pyomo/core/kernel/tuple_container.pyi @@ -0,0 +1,16 @@ +import collections.abc + +from pyomo.core.kernel.homogeneous_container import IHomogeneousContainer as IHomogeneousContainer + +class TupleContainer(IHomogeneousContainer, collections.abc.Sequence): + def __init__(self, *args) -> None: ... + def child(self, key): ... + def children(self): ... + def __getitem__(self, i): ... + def __len__(self) -> int: ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __iter__(self): ... + def __contains__(self, item) -> bool: ... + def index(self, item, start: int = 0, stop=None): ... + def count(self, item): ... diff --git a/stubs/pyomo/core/kernel/variable.pyi b/stubs/pyomo/core/kernel/variable.pyi new file mode 100644 index 000000000..9ed0cd830 --- /dev/null +++ b/stubs/pyomo/core/kernel/variable.pyi @@ -0,0 +1,85 @@ +from _typeshed import Incomplete +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import is_numeric_data as is_numeric_data +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject +from pyomo.core.kernel.container_utils import define_simple_containers as define_simple_containers +from pyomo.core.kernel.set_types import IntegerSet as IntegerSet +from pyomo.core.kernel.set_types import RealSet as RealSet +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager + +class IVariable(ICategorizedObject, NumericValue): + domain_type: Incomplete + lb: Incomplete + ub: Incomplete + value: Incomplete + fixed: Incomplete + stale: Incomplete + @property + def bounds(self): ... + @bounds.setter + def bounds(self, bounds_tuple) -> None: ... + @property + def lb(self): ... + lower: Incomplete + @lb.setter + def lb(self, val) -> None: ... + @property + def ub(self): ... + upper: Incomplete + @ub.setter + def ub(self, val) -> None: ... + def fix(self, value=...) -> None: ... + def unfix(self) -> None: ... + free = unfix + def has_lb(self): ... + def has_ub(self): ... + @property + def lslack(self): ... + @property + def uslack(self): ... + @property + def slack(self): ... + def is_continuous(self): ... + def is_discrete(self): ... + def is_integer(self): ... + def is_binary(self): ... + def is_fixed(self): ... + def is_constant(self): ... + def is_parameter_type(self): ... + def is_variable_type(self): ... + def is_potentially_variable(self): ... + def polynomial_degree(self): ... + def __call__(self, exception=...): ... + +class variable(IVariable): + def __init__( + self, domain_type=None, domain=None, lb=None, ub=None, value=None, fixed: bool = False + ) -> None: ... + @property + def lower(self): ... + @lower.setter + def lower(self, lb) -> None: ... + @property + def upper(self): ... + @upper.setter + def upper(self, ub) -> None: ... + @property + def value(self): ... + @value.setter + def value(self, value) -> None: ... + def set_value(self, value, skip_validation: bool = True) -> None: ... + @property + def fixed(self): ... + @fixed.setter + def fixed(self, fixed) -> None: ... + @property + def stale(self): ... + @stale.setter + def stale(self, stale) -> None: ... + @property + def domain_type(self): ... + @domain_type.setter + def domain_type(self, domain_type) -> None: ... + domain: Incomplete diff --git a/stubs/pyomo/core/plugins/__init__.pyi b/stubs/pyomo/core/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/core/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/__init__.pyi b/stubs/pyomo/core/plugins/transform/__init__.pyi new file mode 100644 index 000000000..e6b24df05 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/__init__.pyi @@ -0,0 +1,9 @@ +from pyomo.core.plugins.transform import add_slack_vars as add_slack_vars +from pyomo.core.plugins.transform import discrete_vars as discrete_vars +from pyomo.core.plugins.transform import expand_connectors as expand_connectors +from pyomo.core.plugins.transform import logical_to_linear as logical_to_linear +from pyomo.core.plugins.transform import lp_dual as lp_dual +from pyomo.core.plugins.transform import nonnegative_transform as nonnegative_transform +from pyomo.core.plugins.transform import radix_linearization as radix_linearization +from pyomo.core.plugins.transform import relax_integrality as relax_integrality +from pyomo.core.plugins.transform import scaling as scaling diff --git a/stubs/pyomo/core/plugins/transform/add_slack_vars.pyi b/stubs/pyomo/core/plugins/transform/add_slack_vars.pyi new file mode 100644 index 000000000..62e24558a --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/add_slack_vars.pyi @@ -0,0 +1,24 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import Objective as Objective +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.base import ComponentUID as ComponentUID +from pyomo.core.plugins.transform.hierarchy import ( + NonIsomorphicTransformation as NonIsomorphicTransformation, +) + +def target_list(x): ... + +logger: Incomplete + +class AddSlackVariables(NonIsomorphicTransformation): + CONFIG: Incomplete + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/discrete_vars.pyi b/stubs/pyomo/core/plugins/transform/discrete_vars.pyi new file mode 100644 index 000000000..3deec7f55 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/discrete_vars.pyi @@ -0,0 +1,21 @@ +from _typeshed import Incomplete +from pyomo.common import deprecated as deprecated +from pyomo.core.base import Reals as Reals +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import Var as Var + +logger: Incomplete + +class RelaxIntegerVars(Transformation): + def __init__(self) -> None: ... + +class RelaxDiscreteVars(RelaxIntegerVars): + def __init__(self, **kwds) -> None: ... + +class FixIntegerVars(Transformation): + def __init__(self) -> None: ... + +class FixDiscreteVars(FixIntegerVars): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/eliminate_fixed_vars.pyi b/stubs/pyomo/core/plugins/transform/eliminate_fixed_vars.pyi new file mode 100644 index 000000000..e0c2ce979 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/eliminate_fixed_vars.pyi @@ -0,0 +1,14 @@ +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core.base.var import Var as Var +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr import ExpressionBase as ExpressionBase +from pyomo.core.expr import as_numeric as as_numeric +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.core.util import sequence as sequence + +class EliminateFixedVars(IsomorphicTransformation): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/equality_transform.pyi b/stubs/pyomo/core/plugins/transform/equality_transform.pyi new file mode 100644 index 000000000..797ebabf6 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/equality_transform.pyi @@ -0,0 +1,11 @@ +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core.base.misc import create_name as create_name +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.core.plugins.transform.util import collectAbstractComponents as collectAbstractComponents + +class EqualityTransform(IsomorphicTransformation): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/expand_connectors.pyi b/stubs/pyomo/core/plugins/transform/expand_connectors.pyi new file mode 100644 index 000000000..92dc64dc1 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/expand_connectors.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.core.base import Connector as Connector +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import ConstraintList as ConstraintList +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import Var as Var +from pyomo.core.base.connector import ConnectorData as ConnectorData +from pyomo.core.base.connector import ScalarConnector as ScalarConnector + +logger: Incomplete + +class ExpandConnectors(Transformation): ... diff --git a/stubs/pyomo/core/plugins/transform/hierarchy.pyi b/stubs/pyomo/core/plugins/transform/hierarchy.pyi new file mode 100644 index 000000000..8a3ca5a84 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/hierarchy.pyi @@ -0,0 +1,19 @@ +from pyomo.core.base import Transformation as Transformation + +class AbstractTransformation(Transformation): + def __init__(self, **kwds) -> None: ... + +class ConcreteTransformation(Transformation): + def __init__(self, **kwds) -> None: ... + +class IsomorphicTransformation(Transformation): + def __init__(self, **kwds) -> None: ... + +class LinearTransformation(Transformation): + def __init__(self, **kwds) -> None: ... + +class NonIsomorphicTransformation(Transformation): + def __init__(self, **kwds) -> None: ... + +class NonlinearTransformation(Transformation): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/logical_to_linear.pyi b/stubs/pyomo/core/plugins/transform/logical_to_linear.pyi new file mode 100644 index 000000000..e982fd3a8 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/logical_to_linear.pyi @@ -0,0 +1,49 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import MouseTrap as MouseTrap +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import BooleanVarList as BooleanVarList +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import LogicalConstraint as LogicalConstraint +from pyomo.core import SortComponents as SortComponents +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import VarList as VarList +from pyomo.core import native_types as native_types +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.expr import AndExpression as AndExpression +from pyomo.core.expr import AtLeastExpression as AtLeastExpression +from pyomo.core.expr import AtMostExpression as AtMostExpression +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.core.expr import ExactlyExpression as ExactlyExpression +from pyomo.core.expr import InequalityExpression as InequalityExpression +from pyomo.core.expr import NotExpression as NotExpression +from pyomo.core.expr import OrExpression as OrExpression +from pyomo.core.expr import RangedExpression as RangedExpression +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.expr import special_boolean_atom_types as special_boolean_atom_types +from pyomo.core.expr.cnf_walker import to_cnf as to_cnf +from pyomo.core.expr.numvalue import native_logical_types as native_logical_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.core.util import target_list as target_list + +class LogicalToLinear(IsomorphicTransformation): + CONFIG: Incomplete + +def update_boolean_vars_from_binary(model, integer_tolerance: float = 1e-05) -> None: ... + +class CnfToLinearVisitor(StreamBasedExpressionVisitor): + def __init__(self, indicator_var, binary_varlist) -> None: ... + def exitNode(self, node, values): ... + def beforeChild(self, node, child, child_idx): ... + def finalizeResult(self, result): ... diff --git a/stubs/pyomo/core/plugins/transform/lp_dual.pyi b/stubs/pyomo/core/plugins/transform/lp_dual.pyi new file mode 100644 index 000000000..879862f6c --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/lp_dual.pyi @@ -0,0 +1,36 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.dependencies import scipy as scipy +from pyomo.core import Block as Block +from pyomo.core import ConcreteModel as ConcreteModel +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import NonPositiveReals as NonPositiveReals +from pyomo.core import Objective as Objective +from pyomo.core import Reals as Reals +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import maximize as maximize +from pyomo.core import minimize as minimize +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.repn.standard_repn import isclose_const as isclose_const +from pyomo.util.config_domains import ComponentDataSet as ComponentDataSet + +class _LPDualData(AutoSlots.Mixin): + primal_var: Incomplete + dual_var: Incomplete + primal_constraint: Incomplete + dual_constraint: Incomplete + def __init__(self) -> None: ... + +class LinearProgrammingDual: + CONFIG: Incomplete + def apply_to(self, model, **options) -> None: ... + def create_using(self, model, ostream=None, **kwds): ... + def get_primal_constraint(self, model, dual_var): ... + def get_dual_constraint(self, model, primal_var): ... + def get_primal_var(self, model, dual_constraint): ... + def get_dual_var(self, model, primal_constraint): ... diff --git a/stubs/pyomo/core/plugins/transform/model.pyi b/stubs/pyomo/core/plugins/transform/model.pyi new file mode 100644 index 000000000..1c26b512f --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/model.pyi @@ -0,0 +1,5 @@ +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective + +def to_standard_form(self): ... diff --git a/stubs/pyomo/core/plugins/transform/nonnegative_transform.pyi b/stubs/pyomo/core/plugins/transform/nonnegative_transform.pyi new file mode 100644 index 000000000..72c7dc80d --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/nonnegative_transform.pyi @@ -0,0 +1,51 @@ +import pyomo.core.expr as EXPR +from _typeshed import Incomplete +from pyomo.core import Binary as Binary +from pyomo.core import Constraint as Constraint +from pyomo.core import Integers as Integers +from pyomo.core import IntegerSet as IntegerSet +from pyomo.core import NegativeIntegers as NegativeIntegers +from pyomo.core import NegativeReals as NegativeReals +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import NonPositiveIntegers as NonPositiveIntegers +from pyomo.core import NonPositiveReals as NonPositiveReals +from pyomo.core import Objective as Objective +from pyomo.core import PercentFraction as PercentFraction +from pyomo.core import PositiveIntegers as PositiveIntegers +from pyomo.core import PositiveReals as PositiveReals +from pyomo.core import Reals as Reals +from pyomo.core import RealSet as RealSet +from pyomo.core import Set as Set +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core import value as value +from pyomo.core.base.misc import create_name as create_name +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.core.plugins.transform.util import collectAbstractComponents as collectAbstractComponents +from pyomo.core.plugins.transform.util import partial as partial + +logger: Incomplete + +class VarmapVisitor(EXPR.ExpressionReplacementVisitor): + varmap: Incomplete + def __init__(self, varmap) -> None: ... + def visiting_potential_leaf(self, node): ... + +class NonNegativeTransformation(IsomorphicTransformation): + realSets: Incomplete + discreteSets: Incomplete + def __init__(self, **kwds) -> None: ... + @staticmethod + def boundsConstraintRule(lb, ub, attr, vars, model): ... + @staticmethod + def noConstraint(*args) -> None: ... + @staticmethod + def sumRule(attr, vars, model): ... + @staticmethod + def exprMapRule(ruleMap, model, ndx=None): ... + @staticmethod + def delayedExprMapRule(ruleMap, model, ndx=None): ... diff --git a/stubs/pyomo/core/plugins/transform/radix_linearization.pyi b/stubs/pyomo/core/plugins/transform/radix_linearization.pyi new file mode 100644 index 000000000..7f17aee97 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/radix_linearization.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.core import Binary as Binary +from pyomo.core import value as value +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import ConstraintList as ConstraintList +from pyomo.core.base import RangeSet as RangeSet +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import Var as Var +from pyomo.core.base.var import VarData as VarData +from pyomo.core.expr import PowExpression as PowExpression +from pyomo.core.expr import ProductExpression as ProductExpression +from pyomo.core.expr.numvalue import as_numeric as as_numeric + +logger: Incomplete + +class RadixLinearization(Transformation): ... diff --git a/stubs/pyomo/core/plugins/transform/relax_integrality.pyi b/stubs/pyomo/core/plugins/transform/relax_integrality.pyi new file mode 100644 index 000000000..19ba2f9b6 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/relax_integrality.pyi @@ -0,0 +1,6 @@ +from pyomo.common import deprecated as deprecated +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.plugins.transform.discrete_vars import RelaxIntegerVars as RelaxIntegerVars + +class RelaxIntegrality(RelaxIntegerVars): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/scaling.pyi b/stubs/pyomo/core/plugins/transform/scaling.pyi new file mode 100644 index 000000000..1efe9e8ed --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/scaling.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import Var as Var +from pyomo.core.base import value as value +from pyomo.core.base.suffix import SuffixFinder as SuffixFinder +from pyomo.core.expr import replace_expressions as replace_expressions +from pyomo.core.plugins.transform.hierarchy import Transformation as Transformation +from pyomo.util.components import rename_components as rename_components + +logger: Incomplete + +class ScaleModel(Transformation): + def __init__(self, **kwds) -> None: ... + def propagate_solution(self, scaled_model, original_model) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/standard_form.pyi b/stubs/pyomo/core/plugins/transform/standard_form.pyi new file mode 100644 index 000000000..ae989f922 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/standard_form.pyi @@ -0,0 +1,11 @@ +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core.plugins.transform.equality_transform import EqualityTransform as EqualityTransform +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.core.plugins.transform.nonnegative_transform import ( + NonNegativeTransformation as NonNegativeTransformation, +) + +class StandardForm(IsomorphicTransformation): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/core/plugins/transform/util.pyi b/stubs/pyomo/core/plugins/transform/util.pyi new file mode 100644 index 000000000..f8b5eec34 --- /dev/null +++ b/stubs/pyomo/core/plugins/transform/util.pyi @@ -0,0 +1,9 @@ +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import Param as Param +from pyomo.core import Set as Set +from pyomo.core import Var as Var + +def collectAbstractComponents(model): ... +def partial(*args, **kwargs): ... +def process_canonical_repn(expr): ... diff --git a/stubs/pyomo/core/pyomoobject.pyi b/stubs/pyomo/core/pyomoobject.pyi new file mode 100644 index 000000000..37f02cb45 --- /dev/null +++ b/stubs/pyomo/core/pyomoobject.pyi @@ -0,0 +1,11 @@ +from pyomo.common.autoslots import AutoSlots as AutoSlots + +class PyomoObject(AutoSlots.Mixin): + def is_component_type(self): ... + def is_numeric_type(self): ... + def is_parameter_type(self): ... + def is_variable_type(self): ... + def is_expression_type(self, expression_system=None): ... + def is_named_expression_type(self): ... + def is_logical_type(self): ... + def is_reference(self): ... diff --git a/stubs/pyomo/core/staleflag.pyi b/stubs/pyomo/core/staleflag.pyi new file mode 100644 index 000000000..46b9f1a11 --- /dev/null +++ b/stubs/pyomo/core/staleflag.pyi @@ -0,0 +1,9 @@ +from _typeshed import Incomplete + +class _StaleFlagManager: + def __init__(self) -> None: ... + def stale_mapper(self, encode, value): ... + def is_stale(self, val): ... + def mark_all_as_stale(self, delayed: bool = False) -> None: ... + +StaleFlagManager: Incomplete diff --git a/stubs/pyomo/core/util.pyi b/stubs/pyomo/core/util.pyi new file mode 100644 index 000000000..07e7ec711 --- /dev/null +++ b/stubs/pyomo/core/util.pyi @@ -0,0 +1,20 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.core.base.component import ComponentBase as ComponentBase +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.numeric_expr import NPV_SumExpression as NPV_SumExpression +from pyomo.core.expr.numeric_expr import mutable_expression as mutable_expression +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types + +logger: Incomplete + +def prod(terms): ... +def quicksum(args, start: int = 0, linear=None): ... +def sum_product(*args, **kwds): ... + +dot_product = sum_product +summation = sum_product + +def sequence(*args): ... +def target_list(x): ... diff --git a/stubs/pyomo/dae/__init__.pyi b/stubs/pyomo/dae/__init__.pyi new file mode 100644 index 000000000..acfc869e1 --- /dev/null +++ b/stubs/pyomo/dae/__init__.pyi @@ -0,0 +1,5 @@ +from pyomo.dae.contset import ContinuousSet as ContinuousSet +from pyomo.dae.diffvar import DAE_Error as DAE_Error +from pyomo.dae.diffvar import DerivativeVar as DerivativeVar +from pyomo.dae.integral import Integral as Integral +from pyomo.dae.simulator import Simulator as Simulator diff --git a/stubs/pyomo/dae/contset.pyi b/stubs/pyomo/dae/contset.pyi new file mode 100644 index 000000000..04cf49b83 --- /dev/null +++ b/stubs/pyomo/dae/contset.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.set import SortedScalarSet as SortedScalarSet + +logger: Incomplete + +class ContinuousSet(SortedScalarSet): + def __init__(self, *args, **kwds) -> None: ... + def get_finite_elements(self): ... + def get_discretization_info(self): ... + def get_changed(self): ... + def set_changed(self, newvalue) -> None: ... + def get_upper_element_boundary(self, point): ... + def get_lower_element_boundary(self, point): ... + def construct(self, values=None) -> None: ... + def find_nearest_index(self, target, tolerance=None): ... diff --git a/stubs/pyomo/dae/diffvar.pyi b/stubs/pyomo/dae/diffvar.pyi new file mode 100644 index 000000000..68aaefcff --- /dev/null +++ b/stubs/pyomo/dae/diffvar.pyi @@ -0,0 +1,17 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.set import UnknownSetDimen as UnknownSetDimen +from pyomo.core.base.var import Var as Var +from pyomo.dae.contset import ContinuousSet as ContinuousSet + +def create_access_function(var): ... + +class DAE_Error(Exception): ... + +class DerivativeVar(Var): + def __init__(self, sVar, **kwds) -> None: ... + def get_continuousset_list(self): ... + def is_fully_discretized(self): ... + def get_state_var(self): ... + def get_derivative_expression(self): ... + def set_derivative_expression(self, expr) -> None: ... diff --git a/stubs/pyomo/dae/flatten.pyi b/stubs/pyomo/dae/flatten.pyi new file mode 100644 index 000000000..26c3f60a8 --- /dev/null +++ b/stubs/pyomo/dae/flatten.pyi @@ -0,0 +1,26 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.base import Block as Block +from pyomo.core.base import Reference as Reference +from pyomo.core.base.block import SubclassOf as SubclassOf +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.indexed_component import normalize_index as normalize_index +from pyomo.core.base.indexed_component_slice import IndexedComponent_slice as IndexedComponent_slice +from pyomo.core.base.set import SetProduct as SetProduct + +def get_slice_for_set(s): ... + +class _NotAnIndex: ... + +def slice_component_along_sets( + component, sets, context_slice=None, normalize=None +) -> Generator[Incomplete]: ... +def generate_sliced_components( + b, index_stack, slice_, sets, ctype, index_map, active=None +) -> Generator[Incomplete]: ... +def flatten_components_along_sets(m, sets, ctype, indices=None, active=None): ... +def flatten_dae_components(model, time, ctype, indices=None, active=None): ... diff --git a/stubs/pyomo/dae/initialization.pyi b/stubs/pyomo/dae/initialization.pyi new file mode 100644 index 000000000..c7d833250 --- /dev/null +++ b/stubs/pyomo/dae/initialization.pyi @@ -0,0 +1,21 @@ +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import value as value +from pyomo.dae.set_utils import deactivate_model_at as deactivate_model_at +from pyomo.dae.set_utils import get_index_set_except as get_index_set_except +from pyomo.dae.set_utils import index_warning as index_warning +from pyomo.dae.set_utils import is_explicitly_indexed_by as is_explicitly_indexed_by +from pyomo.dae.set_utils import is_in_block_indexed_by as is_in_block_indexed_by + +def get_inconsistent_initial_conditions( + model, + time, + tol: float = 1e-08, + t0=None, + allow_skip: bool = True, + suppress_warnings: bool = False, +): ... +def solve_consistent_initial_conditions( + model, time, solver, tee: bool = False, allow_skip: bool = True, suppress_warnings: bool = False +): ... diff --git a/stubs/pyomo/dae/integral.pyi b/stubs/pyomo/dae/integral.pyi new file mode 100644 index 000000000..fe4b13a4c --- /dev/null +++ b/stubs/pyomo/dae/integral.pyi @@ -0,0 +1,28 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import IndexedExpression as IndexedExpression +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.indexed_component import rule_wrapper as rule_wrapper +from pyomo.dae.contset import ContinuousSet as ContinuousSet +from pyomo.dae.diffvar import DAE_Error as DAE_Error + +class Integral(Expression): + def __new__(cls, *args, **kwds): ... + loc: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def get_continuousset(self): ... + +class ScalarIntegral(ScalarExpression, Integral): + def __init__(self, *args, **kwds) -> None: ... + def clear(self) -> None: ... + def is_fully_discretized(self): ... + +class SimpleIntegral(metaclass=RenamedClass): + __renamed__new_class__ = ScalarIntegral + __renamed__version__: str + +class IndexedIntegral(IndexedExpression, Integral): + def is_fully_discretized(self): ... diff --git a/stubs/pyomo/dae/misc.pyi b/stubs/pyomo/dae/misc.pyi new file mode 100644 index 000000000..f072da91b --- /dev/null +++ b/stubs/pyomo/dae/misc.pyi @@ -0,0 +1,29 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.formatting import tostr as tostr +from pyomo.common.log import LoggingIntercept as LoggingIntercept +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import Param as Param +from pyomo.core import Piecewise as Piecewise +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core.base.block import IndexedBlock as IndexedBlock +from pyomo.core.base.block import SortComponents as SortComponents +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DAE_Error as DAE_Error + +logger: Incomplete + +def generate_finite_elements(ds, nfe) -> None: ... +def generate_colloc_points(ds, tau) -> None: ... +def expand_components(block) -> None: ... +def update_contset_indexed_component(comp, expansion_map) -> None: ... +def create_access_function(var): ... +def create_partial_expression(scheme, expr, ind, loc): ... +def add_discretization_equations(block, d): ... +def add_continuity_equations(block, d, i, loc): ... +def block_fully_discretized(b): ... +def get_index_information(var, ds): ... diff --git a/stubs/pyomo/dae/plugins/__init__.pyi b/stubs/pyomo/dae/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/dae/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/dae/plugins/colloc.pyi b/stubs/pyomo/dae/plugins/colloc.pyi new file mode 100644 index 000000000..ba5cffa55 --- /dev/null +++ b/stubs/pyomo/dae/plugins/colloc.pyi @@ -0,0 +1,39 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import PositiveInt as PositiveInt +from pyomo.common.dependencies import numpy as numpy +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import Expression as Expression +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.dae import Integral as Integral +from pyomo.dae.diffvar import DAE_Error as DAE_Error +from pyomo.dae.misc import add_continuity_equations as add_continuity_equations +from pyomo.dae.misc import add_discretization_equations as add_discretization_equations +from pyomo.dae.misc import block_fully_discretized as block_fully_discretized +from pyomo.dae.misc import create_partial_expression as create_partial_expression +from pyomo.dae.misc import expand_components as expand_components +from pyomo.dae.misc import generate_colloc_points as generate_colloc_points +from pyomo.dae.misc import generate_finite_elements as generate_finite_elements +from pyomo.dae.misc import get_index_information as get_index_information + +logger: Incomplete + +def conv(a, b): ... +def calc_cp(alpha, beta, k): ... +def calc_adot(cp, order: int = 1): ... +def calc_afinal(cp): ... + +class Collocation_Discretization_Transformation(Transformation): + CONFIG: Incomplete + all_schemes: Incomplete + def __init__(self) -> None: ... + def reduce_collocation_points(self, instance, var=None, ncp=None, contset=None): ... diff --git a/stubs/pyomo/dae/plugins/finitedifference.pyi b/stubs/pyomo/dae/plugins/finitedifference.pyi new file mode 100644 index 000000000..7e1190ee3 --- /dev/null +++ b/stubs/pyomo/dae/plugins/finitedifference.pyi @@ -0,0 +1,27 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import PositiveInt as PositiveInt +from pyomo.core import Expression as Expression +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.dae import Integral as Integral +from pyomo.dae.diffvar import DAE_Error as DAE_Error +from pyomo.dae.misc import add_discretization_equations as add_discretization_equations +from pyomo.dae.misc import block_fully_discretized as block_fully_discretized +from pyomo.dae.misc import create_partial_expression as create_partial_expression +from pyomo.dae.misc import expand_components as expand_components +from pyomo.dae.misc import generate_finite_elements as generate_finite_elements + +logger: Incomplete + +class Finite_Difference_Transformation(Transformation): + CONFIG: Incomplete + all_schemes: Incomplete + def __init__(self) -> None: ... diff --git a/stubs/pyomo/dae/set_utils.pyi b/stubs/pyomo/dae/set_utils.pyi new file mode 100644 index 000000000..99d70fa53 --- /dev/null +++ b/stubs/pyomo/dae/set_utils.pyi @@ -0,0 +1,11 @@ +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base.set import SetProduct as SetProduct + +def index_warning(name, index): ... +def is_explicitly_indexed_by(comp, *sets, **kwargs): ... +def is_in_block_indexed_by(comp, s, stop_at=None): ... +def get_indices_of_projection(index_set, *sets): ... +def get_index_set_except(comp, *sets): ... +def deactivate_model_at(b, cset, pts, allow_skip: bool = True, suppress_warnings: bool = False): ... diff --git a/stubs/pyomo/dae/simulator.pyi b/stubs/pyomo/dae/simulator.pyi new file mode 100644 index 000000000..8b6b06a3e --- /dev/null +++ b/stubs/pyomo/dae/simulator.pyi @@ -0,0 +1,55 @@ +import pyomo.core.expr as EXPR +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.dependencies import scipy_available as scipy_available +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Param as Param +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import value as value +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.template_expr import IndexTemplate as IndexTemplate +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.dae.diffvar import DAE_Error as DAE_Error + +logger: Incomplete +casadi_intrinsic: Incomplete +casadi: Incomplete +casadi_available: Incomplete + +class Pyomo2Scipy_Visitor(EXPR.ExpressionReplacementVisitor): + templatemap: Incomplete + def __init__(self, templatemap) -> None: ... + def beforeChild(self, node, child, child_idx): ... + +def convert_pyomo2scipy(expr, templatemap): ... + +class Substitute_Pyomo2Casadi_Visitor(EXPR.ExpressionReplacementVisitor): + templatemap: Incomplete + def __init__(self, templatemap) -> None: ... + def exitNode(self, node, data): ... + def beforeChild(self, node, child, child_idx): ... + +class Convert_Pyomo2Casadi_Visitor(EXPR.ExpressionValueVisitor): + def visit(self, node, values): ... + def visiting_potential_leaf(self, node): ... + +def substitute_pyomo2casadi(expr, templatemap): ... +def convert_pyomo2casadi(expr): ... + +class Simulator: + def __init__(self, m, package: str = 'scipy') -> None: ... + def get_variable_order(self, vartype=None): ... + def simulate( + self, + numpoints=None, + tstep=None, + integrator=None, + varying_inputs=None, + initcon=None, + integrator_options=None, + ): ... + def initialize_model(self) -> None: ... diff --git a/stubs/pyomo/dae/utilities.pyi b/stubs/pyomo/dae/utilities.pyi new file mode 100644 index 000000000..4d6031d03 --- /dev/null +++ b/stubs/pyomo/dae/utilities.pyi @@ -0,0 +1,9 @@ +from _typeshed import Incomplete + +radau_tau_dict: Incomplete +radau_adot_dict: Incomplete +radau_adotdot_dict: Incomplete +legendre_tau_dict: Incomplete +legendre_adot_dict: Incomplete +legendre_adotdot_dict: Incomplete +legendre_afinal_dict: Incomplete diff --git a/stubs/pyomo/dataportal/DataPortal.pyi b/stubs/pyomo/dataportal/DataPortal.pyi new file mode 100644 index 000000000..ffa71dba2 --- /dev/null +++ b/stubs/pyomo/dataportal/DataPortal.pyi @@ -0,0 +1,22 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory +from pyomo.dataportal.factory import UnknownDataManager as UnknownDataManager + +logger: Incomplete + +class DataPortal: + def __init__(self, *args, **kwds) -> None: ... + def connect(self, **kwds) -> None: ... + def disconnect(self) -> None: ... + def load(self, **kwds) -> None: ... + def store(self, **kwds) -> None: ... + def data(self, name=None, namespace=None): ... + def __getitem__(self, *args): ... + def __setitem__(self, name, value) -> None: ... + def namespaces(self) -> Generator[Incomplete]: ... + def keys(self, namespace=None) -> Generator[Incomplete]: ... + def values(self, namespace=None) -> Generator[Incomplete]: ... + def items(self, namespace=None) -> Generator[Incomplete]: ... diff --git a/stubs/pyomo/dataportal/TableData.pyi b/stubs/pyomo/dataportal/TableData.pyi new file mode 100644 index 000000000..2145522ca --- /dev/null +++ b/stubs/pyomo/dataportal/TableData.pyi @@ -0,0 +1,16 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch + +class TableData: + options: Incomplete + def __init__(self) -> None: ... + def available(self): ... + filename: Incomplete + def initialize(self, **kwds) -> None: ... + def add_options(self, **kwds) -> None: ... + def open(self) -> None: ... + def read(self): ... + def write(self, data): ... + def close(self) -> None: ... + def process(self, model, data, default): ... + def clear(self) -> None: ... diff --git a/stubs/pyomo/dataportal/__init__.pyi b/stubs/pyomo/dataportal/__init__.pyi new file mode 100644 index 000000000..2cbbb1be9 --- /dev/null +++ b/stubs/pyomo/dataportal/__init__.pyi @@ -0,0 +1,5 @@ +from pyomo.dataportal import parse_datacmds as parse_datacmds +from pyomo.dataportal.DataPortal import DataPortal as DataPortal +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory +from pyomo.dataportal.factory import UnknownDataManager as UnknownDataManager +from pyomo.dataportal.TableData import TableData as TableData diff --git a/stubs/pyomo/dataportal/factory.pyi b/stubs/pyomo/dataportal/factory.pyi new file mode 100644 index 000000000..468ce3bc3 --- /dev/null +++ b/stubs/pyomo/dataportal/factory.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common import Factory as Factory +from pyomo.common.plugin_base import PluginError as PluginError + +logger: Incomplete + +class UnknownDataManager: + type: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def available(self): ... + +class DataManagerFactoryClass(Factory): + def __call__(self, _name=None, args=[], **kwds): ... + +DataManagerFactory: Incomplete diff --git a/stubs/pyomo/dataportal/parse_datacmds.pyi b/stubs/pyomo/dataportal/parse_datacmds.pyi new file mode 100644 index 000000000..da3b8287d --- /dev/null +++ b/stubs/pyomo/dataportal/parse_datacmds.pyi @@ -0,0 +1,48 @@ +from _typeshed import Incomplete +from pyomo.common.fileutils import this_file as this_file +from pyomo.core.base.util import flatten_tuple as flatten_tuple + +states: Incomplete +reserved: Incomplete +tokens: Incomplete +t_ignore: str +t_COMMA: str +t_LBRACKET: str +t_RBRACKET: str +t_LBRACE: str +t_RBRACE: str +t_COLON: str +t_EQ: str +t_TR: str +t_LPAREN: str +t_RPAREN: str +t_ASTERISK: str + +def t_newline(t) -> None: ... +def t_COMMENT(t) -> None: ... +def t_COLONEQ(t): ... +def t_SEMICOLON(t): ... +def t_NUM_VAL(t): ... +def t_WORDWITHLBRACKET(t): ... +def t_WORD(t): ... +def t_STRING(t): ... +def t_data_BRACKETEDSTRING(t): ... +def t_QUOTEDSTRING(t): ... +def t_error(t) -> None: ... +def p_expr(p) -> None: ... +def p_statements(p) -> None: ... +def p_statement(p) -> None: ... +def p_datastar(p) -> None: ... +def p_data(p) -> None: ... +def p_args(p) -> None: ... +def p_arg(p) -> None: ... +def p_itemstar(p) -> None: ... +def p_items(p) -> None: ... +def p_error(p) -> None: ... + +tabmodule: str +dat_lexer: Incomplete +dat_yaccer: Incomplete +dat_yaccer_tabfile: Incomplete + +def parse_data_commands(data=None, filename=None, debug: int = 0, outputdir=None): ... diff --git a/stubs/pyomo/dataportal/plugins/__init__.pyi b/stubs/pyomo/dataportal/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/dataportal/plugins/csv_table.pyi b/stubs/pyomo/dataportal/plugins/csv_table.pyi new file mode 100644 index 000000000..2060dfc46 --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/csv_table.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.dataportal import TableData as TableData +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory + +class CSVTable(TableData): + def __init__(self) -> None: ... + def open(self) -> None: ... + def close(self) -> None: ... + FILE: Incomplete + def read(self) -> None: ... + def write(self, data) -> None: ... diff --git a/stubs/pyomo/dataportal/plugins/datacommands.pyi b/stubs/pyomo/dataportal/plugins/datacommands.pyi new file mode 100644 index 000000000..b89db26d2 --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/datacommands.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory + +class PyomoDataCommands: + options: Incomplete + def __init__(self) -> None: ... + def available(self): ... + filename: Incomplete + def initialize(self, **kwds) -> None: ... + def add_options(self, **kwds) -> None: ... + def open(self) -> None: ... + def close(self) -> None: ... + def read(self) -> None: ... + def write(self, data) -> None: ... + def process(self, model, data, default) -> None: ... + def clear(self) -> None: ... diff --git a/stubs/pyomo/dataportal/plugins/db_table.pyi b/stubs/pyomo/dataportal/plugins/db_table.pyi new file mode 100644 index 000000000..71a070615 --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/db_table.pyi @@ -0,0 +1,74 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.dataportal import TableData as TableData +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory + +pyodbc: Incomplete +pyodbc_available: Incomplete +pypyodbc: Incomplete +pypyodbc_available: Incomplete +sqlite3: Incomplete +sqlite3_available: Incomplete +pymysql: Incomplete +pymysql_available: Incomplete + +class db_Table(TableData): + using: Incomplete + def __init__(self) -> None: ... + filename: Incomplete + db: Incomplete + def open(self) -> None: ... + def read(self) -> None: ... + def close(self) -> None: ... + def connect(self, connection, options, kwds={}): ... + +class pyodbc_db_Table(db_Table): + using: str + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... + def connect(self, connection, options): ... + def create_dsn_dict(self, argstr, existing_config): ... + def create_connection_string(self, ctype, connection, options): ... + +class ODBCError(Exception): + parameter: Incomplete + def __init__(self, value) -> None: ... + +class ODBCConfig: + ODBC_DS_KEY: str + ODBC_INFO_KEY: str + file: Incomplete + sources: Incomplete + source_specs: Incomplete + odbc_info: Incomplete + def __init__(self, filename=None, data=None) -> None: ... + def load(self, filename=None, data=None) -> None: ... + def __eq__(self, other): ... + def odbc_repr(self): ... + def write(self, filename) -> None: ... + def add_source(self, name, driver) -> None: ... + def del_source(self, name) -> None: ... + def add_source_spec(self, name, spec) -> None: ... + def del_source_spec(self, name) -> None: ... + def set_odbc_info(self, key, value) -> None: ... + +class pypyodbc_db_Table(pyodbc_db_Table): + using: str + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... + def connect(self, connection, options): ... + +class sqlite3_db_Table(db_Table): + using: str + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... + def connect(self, connection, options): ... + +class pymysql_db_Table(db_Table): + using: str + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... diff --git a/stubs/pyomo/dataportal/plugins/json_dict.pyi b/stubs/pyomo/dataportal/plugins/json_dict.pyi new file mode 100644 index 000000000..40a38124b --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/json_dict.pyi @@ -0,0 +1,38 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import yaml as yaml +from pyomo.common.dependencies import yaml_available as yaml_available +from pyomo.common.dependencies import yaml_load_args as yaml_load_args +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory + +def detuplize(d, sort: bool = False): ... +def tuplize(d): ... + +class JSONDictionary: + options: Incomplete + def __init__(self) -> None: ... + def available(self): ... + filename: Incomplete + def initialize(self, **kwds) -> None: ... + def add_options(self, **kwds) -> None: ... + def open(self) -> None: ... + def close(self) -> None: ... + def read(self) -> None: ... + def write(self, data) -> None: ... + def process(self, model, data, default) -> None: ... + def clear(self) -> None: ... + +class YamlDictionary: + options: Incomplete + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... + filename: Incomplete + def initialize(self, **kwds) -> None: ... + def add_options(self, **kwds) -> None: ... + def open(self) -> None: ... + def close(self) -> None: ... + def read(self) -> None: ... + def write(self, data) -> None: ... + def process(self, model, data, default) -> None: ... + def clear(self) -> None: ... diff --git a/stubs/pyomo/dataportal/plugins/sheet.pyi b/stubs/pyomo/dataportal/plugins/sheet.pyi new file mode 100644 index 000000000..21ec488fa --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/sheet.pyi @@ -0,0 +1,33 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.dependencies import importlib as importlib +from pyomo.common.dependencies import pyutilib as pyutilib +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.dataportal import TableData as TableData +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory + +spreadsheet: Incomplete +spreadsheet_available: Incomplete + +class SheetTable(TableData): + ctype: Incomplete + def __init__(self, ctype=None) -> None: ... + sheet: Incomplete + def open(self) -> None: ... + def read(self) -> None: ... + def close(self) -> None: ... + +class SheetTable_xls(SheetTable): + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... + +class SheetTable_xlsx(SheetTable): + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... + +class SheetTable_xlsm(SheetTable): + def __init__(self) -> None: ... + def available(self): ... + def requirements(self): ... diff --git a/stubs/pyomo/dataportal/plugins/text.pyi b/stubs/pyomo/dataportal/plugins/text.pyi new file mode 100644 index 000000000..260c94d8e --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/text.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.dataportal import TableData as TableData +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory + +class TextTable(TableData): + FILE: Incomplete + def __init__(self) -> None: ... + def open(self) -> None: ... + def close(self) -> None: ... + def read(self) -> None: ... + def write(self, data) -> None: ... diff --git a/stubs/pyomo/dataportal/plugins/xml_table.pyi b/stubs/pyomo/dataportal/plugins/xml_table.pyi new file mode 100644 index 000000000..785f62e27 --- /dev/null +++ b/stubs/pyomo/dataportal/plugins/xml_table.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.dataportal import TableData as TableData +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory + +ET: Incomplete +ET_available: Incomplete + +class XMLTable(TableData): + def __init__(self) -> None: ... + def open(self) -> None: ... + def close(self) -> None: ... + def read(self) -> None: ... + def write(self, data) -> None: ... diff --git a/stubs/pyomo/dataportal/process_data.pyi b/stubs/pyomo/dataportal/process_data.pyi new file mode 100644 index 000000000..06187fddc --- /dev/null +++ b/stubs/pyomo/dataportal/process_data.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import OrderedDict as OrderedDict +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.core.base.set import UnknownSetDimen as UnknownSetDimen +from pyomo.core.base.util import flatten_tuple as flatten_tuple +from pyomo.dataportal.factory import DataManagerFactory as DataManagerFactory +from pyomo.dataportal.factory import UnknownDataManager as UnknownDataManager +from pyomo.dataportal.parse_datacmds import parse_data_commands as parse_data_commands + +numlist: Incomplete +logger: Incomplete diff --git a/stubs/pyomo/duality/__init__.pyi b/stubs/pyomo/duality/__init__.pyi new file mode 100644 index 000000000..1346defbb --- /dev/null +++ b/stubs/pyomo/duality/__init__.pyi @@ -0,0 +1 @@ +from pyomo.duality import collect as collect diff --git a/stubs/pyomo/duality/collect.pyi b/stubs/pyomo/duality/collect.pyi new file mode 100644 index 000000000..ac4bc5ac9 --- /dev/null +++ b/stubs/pyomo/duality/collect.pyi @@ -0,0 +1,9 @@ +from pyomo.common.collections import Bunch as Bunch +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Var as Var +from pyomo.core.base import maximize as maximize +from pyomo.core.base import minimize as minimize +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +def collect_linear_terms(block, unfixed): ... diff --git a/stubs/pyomo/duality/lagrangian_dual.pyi b/stubs/pyomo/duality/lagrangian_dual.pyi new file mode 100644 index 000000000..45207df32 --- /dev/null +++ b/stubs/pyomo/duality/lagrangian_dual.pyi @@ -0,0 +1,22 @@ +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.core import AbstractModel as AbstractModel +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import Set as Set +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import maximize as maximize +from pyomo.core.plugins.transform.hierarchy import ( + IsomorphicTransformation as IsomorphicTransformation, +) +from pyomo.core.plugins.transform.standard_form import StandardForm as StandardForm +from pyomo.core.plugins.transform.util import partial as partial +from pyomo.core.plugins.transform.util import process_canonical_repn as process_canonical_repn +from pyomo.repn import generate_standard_repn as generate_standard_repn + +class DualTransformation(IsomorphicTransformation): + def __init__(self, **kwds) -> None: ... + +class _sparse(dict): + def __init__(self, default, *args, **kwds) -> None: ... + def __getitem__(self, ndx): ... diff --git a/stubs/pyomo/duality/plugins.pyi b/stubs/pyomo/duality/plugins.pyi new file mode 100644 index 000000000..4d9888bbc --- /dev/null +++ b/stubs/pyomo/duality/plugins.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.core.base import Block as Block +from pyomo.core.base import ConcreteModel as ConcreteModel +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Model as Model +from pyomo.core.base import NonNegativeReals as NonNegativeReals +from pyomo.core.base import NonPositiveReals as NonPositiveReals +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Reals as Reals +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import Var as Var +from pyomo.core.base import minimize as minimize +from pyomo.duality.collect import collect_linear_terms as collect_linear_terms + +def load() -> None: ... + +logger: Incomplete + +class LinearDual_PyomoTransformation(Transformation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/environ/__init__.pyi b/stubs/pyomo/environ/__init__.pyi new file mode 100644 index 000000000..4eebab6b7 --- /dev/null +++ b/stubs/pyomo/environ/__init__.pyi @@ -0,0 +1,173 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.core import AbstractModel as AbstractModel +from pyomo.core import AlphaNumericTextLabeler as AlphaNumericTextLabeler +from pyomo.core import Any as Any +from pyomo.core import AnyWithNone as AnyWithNone +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import Boolean as Boolean +from pyomo.core import BooleanConstant as BooleanConstant +from pyomo.core import BooleanSet as BooleanSet +from pyomo.core import BooleanValue as BooleanValue +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import BooleanVarList as BooleanVarList +from pyomo.core import BuildAction as BuildAction +from pyomo.core import BuildCheck as BuildCheck +from pyomo.core import CNameLabeler as CNameLabeler +from pyomo.core import Component as Component +from pyomo.core import ComponentUID as ComponentUID +from pyomo.core import ConcreteModel as ConcreteModel +from pyomo.core import Connector as Connector +from pyomo.core import Constraint as Constraint +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import CounterLabeler as CounterLabeler +from pyomo.core import CuidLabeler as CuidLabeler +from pyomo.core import EmptySet as EmptySet +from pyomo.core import Expr_if as Expr_if +from pyomo.core import Expression as Expression +from pyomo.core import ExternalFunction as ExternalFunction +from pyomo.core import IntegerInterval as IntegerInterval +from pyomo.core import Integers as Integers +from pyomo.core import IntegerSet as IntegerSet +from pyomo.core import LogicalConstraint as LogicalConstraint +from pyomo.core import LogicalConstraintList as LogicalConstraintList +from pyomo.core import Model as Model +from pyomo.core import ModelComponentFactory as ModelComponentFactory +from pyomo.core import NameLabeler as NameLabeler +from pyomo.core import NegativeIntegers as NegativeIntegers +from pyomo.core import NegativeReals as NegativeReals +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import NonPositiveIntegers as NonPositiveIntegers +from pyomo.core import NonPositiveReals as NonPositiveReals +from pyomo.core import NumericLabeler as NumericLabeler +from pyomo.core import NumericValue as NumericValue +from pyomo.core import Objective as Objective +from pyomo.core import ObjectiveList as ObjectiveList +from pyomo.core import Param as Param +from pyomo.core import PercentFraction as PercentFraction +from pyomo.core import Piecewise as Piecewise +from pyomo.core import PositiveIntegers as PositiveIntegers +from pyomo.core import PositiveReals as PositiveReals +from pyomo.core import PyomoObject as PyomoObject +from pyomo.core import PyomoOptions as PyomoOptions +from pyomo.core import RangeSet as RangeSet +from pyomo.core import RealInterval as RealInterval +from pyomo.core import Reals as Reals +from pyomo.core import RealSet as RealSet +from pyomo.core import Reference as Reference +from pyomo.core import ScalarBlock as ScalarBlock +from pyomo.core import ScalarBooleanVar as ScalarBooleanVar +from pyomo.core import ScalarVar as ScalarVar +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import ShortNameLabeler as ShortNameLabeler +from pyomo.core import SortComponents as SortComponents +from pyomo.core import SOSConstraint as SOSConstraint +from pyomo.core import Suffix as Suffix +from pyomo.core import SymbolMap as SymbolMap +from pyomo.core import TextLabeler as TextLabeler +from pyomo.core import Transformation as Transformation +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import TraversalStrategy as TraversalStrategy +from pyomo.core import UnitInterval as UnitInterval +from pyomo.core import Var as Var +from pyomo.core import VarList as VarList +from pyomo.core import ZeroConstant as ZeroConstant +from pyomo.core import acos as acos +from pyomo.core import acosh as acosh +from pyomo.core import active_components as active_components +from pyomo.core import active_components_data as active_components_data +from pyomo.core import active_export_suffix_generator as active_export_suffix_generator +from pyomo.core import active_import_suffix_generator as active_import_suffix_generator +from pyomo.core import all_different as all_different +from pyomo.core import as_boolean as as_boolean +from pyomo.core import asin as asin +from pyomo.core import asinh as asinh +from pyomo.core import atan as atan +from pyomo.core import atanh as atanh +from pyomo.core import atleast as atleast +from pyomo.core import atmost as atmost +from pyomo.core import base as base +from pyomo.core import boolean_value as boolean_value +from pyomo.core import calculus as calculus +from pyomo.core import ceil as ceil +from pyomo.core import components as components +from pyomo.core import components_data as components_data +from pyomo.core import cos as cos +from pyomo.core import cosh as cosh +from pyomo.core import count_if as count_if +from pyomo.core import differentiate as differentiate +from pyomo.core import display as display +from pyomo.core import dot_product as dot_product +from pyomo.core import equivalent as equivalent +from pyomo.core import exactly as exactly +from pyomo.core import exp as exp +from pyomo.core import expr as expr +from pyomo.core import expr_common as expr_common +from pyomo.core import expr_errors as expr_errors +from pyomo.core import floor as floor +from pyomo.core import global_option as global_option +from pyomo.core import implies as implies +from pyomo.core import inequality as inequality +from pyomo.core import instance2dat as instance2dat +from pyomo.core import is_constant as is_constant +from pyomo.core import is_fixed as is_fixed +from pyomo.core import is_potentially_variable as is_potentially_variable +from pyomo.core import is_variable_type as is_variable_type +from pyomo.core import kernel as kernel +from pyomo.core import land as land +from pyomo.core import linear_expression as linear_expression +from pyomo.core import lnot as lnot +from pyomo.core import log as log +from pyomo.core import log10 as log10 +from pyomo.core import logical_expr as logical_expr +from pyomo.core import lor as lor +from pyomo.core import maximize as maximize +from pyomo.core import minimize as minimize +from pyomo.core import name as name +from pyomo.core import native_logical_values as native_logical_values +from pyomo.core import native_numeric_types as native_numeric_types +from pyomo.core import native_types as native_types +from pyomo.core import nonlinear_expression as nonlinear_expression +from pyomo.core import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core import numeric_expr as numeric_expr +from pyomo.core import numvalue as numvalue +from pyomo.core import plugins as plugins +from pyomo.core import polynomial_degree as polynomial_degree +from pyomo.core import prod as prod +from pyomo.core import quicksum as quicksum +from pyomo.core import sequence as sequence +from pyomo.core import set_options as set_options +from pyomo.core import simple_constraint_rule as simple_constraint_rule +from pyomo.core import simple_constraintlist_rule as simple_constraintlist_rule +from pyomo.core import simple_objective_rule as simple_objective_rule +from pyomo.core import simple_objectivelist_rule as simple_objectivelist_rule +from pyomo.core import simple_set_rule as simple_set_rule +from pyomo.core import sin as sin +from pyomo.core import sinh as sinh +from pyomo.core import sqrt as sqrt +from pyomo.core import sum_product as sum_product +from pyomo.core import summation as summation +from pyomo.core import symbol_map as symbol_map +from pyomo.core import symbol_map_from_instance as symbol_map_from_instance +from pyomo.core import sympy_tools as sympy_tools +from pyomo.core import tan as tan +from pyomo.core import tanh as tanh +from pyomo.core import taylor_series as taylor_series +from pyomo.core import taylor_series_expansion as taylor_series_expansion +from pyomo.core import value as value +from pyomo.core import visitor as visitor +from pyomo.core import xor as xor +from pyomo.core.base import util as util +from pyomo.core.base.units_container import as_quantity as as_quantity +from pyomo.core.base.units_container import units as units +from pyomo.dataportal import DataPortal as DataPortal +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverManagerFactory as SolverManagerFactory +from pyomo.opt import SolverStatus as SolverStatus +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.opt import UnknownSolver as UnknownSolver +from pyomo.opt import assert_optimal_termination as assert_optimal_termination +from pyomo.opt import check_optimal_termination as check_optimal_termination diff --git a/stubs/pyomo/future.pyi b/stubs/pyomo/future.pyi new file mode 100644 index 000000000..99cb12eef --- /dev/null +++ b/stubs/pyomo/future.pyi @@ -0,0 +1,4 @@ +__doc__: str + +def __getattr__(name): ... +def solver_factory(version=None): ... diff --git a/stubs/pyomo/gdp/__init__.pyi b/stubs/pyomo/gdp/__init__.pyi new file mode 100644 index 000000000..57ecaa0a6 --- /dev/null +++ b/stubs/pyomo/gdp/__init__.pyi @@ -0,0 +1,5 @@ +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.gdp.disjunct import DisjunctData as DisjunctData +from pyomo.gdp.disjunct import Disjunction as Disjunction +from pyomo.gdp.disjunct import DisjunctionData as DisjunctionData +from pyomo.gdp.disjunct import GDP_Error as GDP_Error diff --git a/stubs/pyomo/gdp/basic_step.pyi b/stubs/pyomo/gdp/basic_step.pyi new file mode 100644 index 000000000..a1363e6ca --- /dev/null +++ b/stubs/pyomo/gdp/basic_step.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import Set as Set +from pyomo.core.base import Reference as Reference +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.gdp.disjunct import Disjunction as Disjunction + +logger: Incomplete + +def apply_basic_step(disjunctions_or_constraints): ... diff --git a/stubs/pyomo/gdp/disjunct.pyi b/stubs/pyomo/gdp/disjunct.pyi new file mode 100644 index 000000000..e2613e235 --- /dev/null +++ b/stubs/pyomo/gdp/disjunct.pyi @@ -0,0 +1,171 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import PyomoException as PyomoException +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.numeric_types import native_logical_types as native_logical_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core import Any as Any +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import BooleanValue as BooleanValue +from pyomo.core import ConstraintList as ConstraintList +from pyomo.core import LogicalConstraintList as LogicalConstraintList +from pyomo.core import ModelComponentFactory as ModelComponentFactory +from pyomo.core import ScalarBooleanVar as ScalarBooleanVar +from pyomo.core import ScalarVar as ScalarVar +from pyomo.core import value as value +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.component import ComponentData as ComponentData +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.core.expr.expr_common import ExpressionType as ExpressionType + +logger: Incomplete + +class GDP_Error(PyomoException): ... + +class AutoLinkedBinaryVar(ScalarVar): + INTEGER_TOLERANCE: float + __autoslot_mappers__: Incomplete + def __init__(self, boolean_var=None) -> None: ... + def get_associated_boolean(self): ... + def set_value( + self, val, skip_validation: bool = False, _propagate_value: bool = True + ) -> None: ... + def fix(self, value=..., skip_validation: bool = False) -> None: ... + def unfix(self) -> None: ... + +class AutoLinkedBooleanVar(ScalarBooleanVar): + def as_numeric(self): ... + def as_binary(self): ... + def set_value( + self, val, skip_validation: bool = False, _propagate_value: bool = True + ) -> None: ... + def fix(self, value=..., skip_validation: bool = False) -> None: ... + def unfix(self) -> None: ... + @property + def bounds(self): ... + @bounds.setter + def bounds(self, value) -> None: ... + @property + def lb(self): ... + @lb.setter + def lb(self, value) -> None: ... + @property + def ub(self): ... + @ub.setter + def ub(self, value) -> None: ... + def __abs__(self): ... + def __float__(self) -> float: ... + def __int__(self) -> int: ... + def __neg__(self): ... + def __bool__(self) -> bool: ... + def __pos__(self): ... + def get_units(self): ... + def has_lb(self): ... + def has_ub(self): ... + def is_binary(self): ... + def is_continuous(self): ... + def is_integer(self): ... + def polynomial_degree(self): ... + def __le__(self, arg): ... + def __lt__(self, arg): ... + def __ge__(self, arg): ... + def __gt__(self, arg): ... + def __eq__(self, arg): ... + def __ne__(self, arg): ... + def __add__(self, arg): ... + def __div__(self, arg): ... + def __mul__(self, arg): ... + def __pow__(self, arg): ... + def __sub__(self, arg): ... + def __truediv__(self, arg): ... + def __iadd__(self, arg): ... + def __idiv__(self, arg): ... + def __imul__(self, arg): ... + def __ipow__(self, arg): ... + def __isub__(self, arg): ... + def __itruediv__(self, arg): ... + def __radd__(self, arg): ... + def __rdiv__(self, arg): ... + def __rmul__(self, arg): ... + def __rpow__(self, arg): ... + def __rsub__(self, arg): ... + def __rtruediv__(self, arg): ... + def setlb(self, arg): ... + def setub(self, arg): ... + +class _Initializer: + value: int + deferred_value: int + function: int + dict_like: int + @staticmethod + def process(arg): ... + +class DisjunctData(BlockData): + __autoslot_mappers__: Incomplete + @property + def transformation_block(self): ... + indicator_var: Incomplete + binary_indicator_var: Incomplete + def __init__(self, component) -> None: ... + def activate(self) -> None: ... + def deactivate(self) -> None: ... + +class _DisjunctData(metaclass=RenamedClass): + __renamed__new_class__ = DisjunctData + __renamed__version__: str + +class Disjunct(Block): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwargs) -> None: ... + +class ScalarDisjunct(DisjunctData, Disjunct): + def __init__(self, *args, **kwds) -> None: ... + +class SimpleDisjunct(metaclass=RenamedClass): + __renamed__new_class__ = ScalarDisjunct + __renamed__version__: str + +class IndexedDisjunct(Disjunct): + @property + def active(self): ... + +class DisjunctionData(ActiveComponentData): + __autoslot_mappers__: Incomplete + @property + def algebraic_constraint(self): ... + disjuncts: Incomplete + xor: bool + def __init__(self, component=None) -> None: ... + def set_value(self, expr) -> None: ... + +class _DisjunctionData(metaclass=RenamedClass): + __renamed__new_class__ = DisjunctionData + __renamed__version__: str + +class Disjunction(ActiveIndexedComponent): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwargs) -> None: ... + def construct(self, data=None) -> None: ... + +class ScalarDisjunction(DisjunctionData, Disjunction): + def __init__(self, *args, **kwds) -> None: ... + def set_value(self, expr): ... + +class SimpleDisjunction(metaclass=RenamedClass): + __renamed__new_class__ = ScalarDisjunction + __renamed__version__: str + +class IndexedDisjunction(Disjunction): + @property + def active(self): ... diff --git a/stubs/pyomo/gdp/plugins/__init__.pyi b/stubs/pyomo/gdp/plugins/__init__.pyi new file mode 100644 index 000000000..8f6326a14 --- /dev/null +++ b/stubs/pyomo/gdp/plugins/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.common.deprecation import moved_module as moved_module + +def load() -> None: ... diff --git a/stubs/pyomo/gdp/plugins/between_steps.pyi b/stubs/pyomo/gdp/plugins/between_steps.pyi new file mode 100644 index 000000000..e19f6d2fb --- /dev/null +++ b/stubs/pyomo/gdp/plugins/between_steps.pyi @@ -0,0 +1,5 @@ +from pyomo.core import Transformation as Transformation +from pyomo.core import TransformationFactory as TransformationFactory + +class BetweenSteps_Transformation(Transformation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/gdp/plugins/bigm.pyi b/stubs/pyomo/gdp/plugins/bigm.pyi new file mode 100644 index 000000000..b5dd46f2e --- /dev/null +++ b/stubs/pyomo/gdp/plugins/bigm.pyi @@ -0,0 +1,59 @@ +from weakref import ReferenceType as ReferenceType + +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.cp.transform.logical_to_disjunctive_program import ( + LogicalToDisjunctive as LogicalToDisjunctive, +) +from pyomo.core import Any as Any +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import Connector as Connector +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Param as Param +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import SortComponents as SortComponents +from pyomo.core import TraversalStrategy as TraversalStrategy +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.base import Reference as Reference +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.plugins.bigm_mixin import _BigM_MixIn +from pyomo.gdp.plugins.gdp_to_mip_transformation import ( + GDP_to_MIP_Transformation as GDP_to_MIP_Transformation, +) +from pyomo.gdp.util import is_child_of as is_child_of +from pyomo.network import Port as Port +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class _BigMData(AutoSlots.Mixin): + bigm_src: Incomplete + def __init__(self) -> None: ... + +class BigM_Transformation(GDP_to_MIP_Transformation, _BigM_MixIn): + CONFIG: Incomplete + transformation_name: str + def __init__(self) -> None: ... + def get_m_value_src(self, constraint): ... + def get_M_value_src(self, constraint): ... + def get_M_value(self, constraint): ... + def get_all_M_values_by_constraint(self, model): ... + def get_largest_M_value(self, model): ... diff --git a/stubs/pyomo/gdp/plugins/bigm_mixin.pyi b/stubs/pyomo/gdp/plugins/bigm_mixin.pyi new file mode 100644 index 000000000..5b7fa7cab --- /dev/null +++ b/stubs/pyomo/gdp/plugins/bigm_mixin.pyi @@ -0,0 +1,8 @@ +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.contrib.fbbt.expression_bounds_walker import ( + ExpressionBoundsVisitor as ExpressionBoundsVisitor, +) +from pyomo.core import Suffix as Suffix +from pyomo.gdp import GDP_Error as GDP_Error + +class _BigM_MixIn: ... diff --git a/stubs/pyomo/gdp/plugins/bilinear.pyi b/stubs/pyomo/gdp/plugins/bilinear.pyi new file mode 100644 index 000000000..480525f2d --- /dev/null +++ b/stubs/pyomo/gdp/plugins/bilinear.pyi @@ -0,0 +1,17 @@ +from _typeshed import Incomplete +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Objective as Objective +from pyomo.core import Set as Set +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Transformation as Transformation +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import VarList as VarList +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class Bilinear_Transformation(Transformation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/gdp/plugins/binary_multiplication.pyi b/stubs/pyomo/gdp/plugins/binary_multiplication.pyi new file mode 100644 index 000000000..0c7858cb8 --- /dev/null +++ b/stubs/pyomo/gdp/plugins/binary_multiplication.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunction as Disjunction + +from .gdp_to_mip_transformation import GDP_to_MIP_Transformation as GDP_to_MIP_Transformation + +logger: Incomplete + +class GDPBinaryMultiplicationTransformation(GDP_to_MIP_Transformation): + CONFIG: Incomplete + transformation_name: str + def __init__(self) -> None: ... diff --git a/stubs/pyomo/gdp/plugins/bound_pretransformation.pyi b/stubs/pyomo/gdp/plugins/bound_pretransformation.pyi new file mode 100644 index 000000000..742d34535 --- /dev/null +++ b/stubs/pyomo/gdp/plugins/bound_pretransformation.pyi @@ -0,0 +1,32 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Any as Any +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.util import get_gdp_tree as get_gdp_tree +from pyomo.gdp.util import is_child_of as is_child_of +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class BoundPretransformation(Transformation): + CONFIG: Incomplete + transformation_name: str + logger: Incomplete + def __init__(self) -> None: ... + def get_transformed_constraints(self, v, disjunction): ... diff --git a/stubs/pyomo/gdp/plugins/cuttingplane.pyi b/stubs/pyomo/gdp/plugins/cuttingplane.pyi new file mode 100644 index 000000000..7deeeed6b --- /dev/null +++ b/stubs/pyomo/gdp/plugins/cuttingplane.pyi @@ -0,0 +1,77 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import In as In +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.common.config import PositiveInt as PositiveInt +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.fme.fourier_motzkin_elimination import ( + Fourier_Motzkin_Elimination_Transformation as Fourier_Motzkin_Elimination_Transformation, +) +from pyomo.core import Any as Any +from pyomo.core import Block as Block +from pyomo.core import ComponentMap as ComponentMap +from pyomo.core import Constraint as Constraint +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import NonNegativeReals as NonNegativeReals +from pyomo.core import Objective as Objective +from pyomo.core import Param as Param +from pyomo.core import Reals as Reals +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Suffix as Suffix +from pyomo.core import Transformation as Transformation +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.expr import differentiate as differentiate +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.util import NORMAL as NORMAL +from pyomo.gdp.util import ( + clone_without_expression_components as clone_without_expression_components, +) +from pyomo.gdp.util import verify_successful_solve as verify_successful_solve +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +def do_not_tighten(m): ... +def create_cuts_fme( + transBlock_rHull, + var_info, + hull_to_bigm_map, + rBigM_linear_constraints, + rHull_vars, + disaggregated_vars, + norm, + cut_threshold, + zero_tolerance, + integer_arithmetic, + constraint_tolerance, +): ... +def create_cuts_normal_vector( + transBlock_rHull, + var_info, + hull_to_bigm_map, + rBigM_linear_constraints, + rHull_vars, + disaggregated_vars, + norm, + cut_threshold, + zero_tolerance, + integer_arithmetic, + constraint_tolerance, +): ... +def back_off_constraint_with_calculated_cut_violation( + cut, transBlock_rHull, bigm_to_hull_map, opt, stream_solver, TOL +) -> None: ... +def back_off_constraint_by_fixed_tolerance( + cut, transBlock_rHull, bigm_to_hull_map, opt, stream_solver, TOL +) -> None: ... + +class CuttingPlane_Transformation(Transformation): + CONFIG: Incomplete + def __init__(self) -> None: ... diff --git a/stubs/pyomo/gdp/plugins/fix_disjuncts.pyi b/stubs/pyomo/gdp/plugins/fix_disjuncts.pyi new file mode 100644 index 000000000..636631a8f --- /dev/null +++ b/stubs/pyomo/gdp/plugins/fix_disjuncts.pyi @@ -0,0 +1,23 @@ +from math import fabs as fabs + +from _typeshed import Incomplete +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import NonNegativeFloat as NonNegativeFloat +from pyomo.contrib.cp.transform.logical_to_disjunctive_program import ( + LogicalToDisjunctive as LogicalToDisjunctive, +) +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base.block import Block as Block +from pyomo.core.expr.numvalue import value as value +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.gdp.disjunct import Disjunction as Disjunction +from pyomo.gdp.plugins.bigm import BigM_Transformation as BigM_Transformation + +logger: Incomplete + +class GDP_Disjunct_Fixer(Transformation): + def __init__(self, **kwargs) -> None: ... + CONFIG: Incomplete diff --git a/stubs/pyomo/gdp/plugins/gdp_to_mip_transformation.pyi b/stubs/pyomo/gdp/plugins/gdp_to_mip_transformation.pyi new file mode 100644 index 000000000..389efb19e --- /dev/null +++ b/stubs/pyomo/gdp/plugins/gdp_to_mip_transformation.pyi @@ -0,0 +1,46 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import DefaultComponentMap as DefaultComponentMap +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Any as Any +from pyomo.core import Block as Block +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import Connector as Connector +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Param as Param +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Reference as Reference +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base.external import ExternalFunction as ExternalFunction +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.util import get_gdp_tree as get_gdp_tree +from pyomo.gdp.util import get_src_constraint as get_src_constraint +from pyomo.gdp.util import get_src_disjunct as get_src_disjunct +from pyomo.gdp.util import get_src_disjunction as get_src_disjunction +from pyomo.gdp.util import get_transformed_constraints as get_transformed_constraints +from pyomo.network import Port as Port + +class _GDPTransformationData(AutoSlots.Mixin): + src_constraint: Incomplete + transformed_constraints: Incomplete + def __init__(self) -> None: ... + +class GDP_to_MIP_Transformation(Transformation): + logger: Incomplete + handlers: Incomplete + def __init__(self, logger) -> None: ... + def get_src_disjunct(self, transBlock): ... + def get_src_disjunction(self, xor_constraint): ... + def get_src_constraint(self, transformedConstraint): ... + def get_transformed_constraints(self, srcConstraint): ... diff --git a/stubs/pyomo/gdp/plugins/gdp_var_mover.pyi b/stubs/pyomo/gdp/plugins/gdp_var_mover.pyi new file mode 100644 index 000000000..1df0733e9 --- /dev/null +++ b/stubs/pyomo/gdp/plugins/gdp_var_mover.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import TraversalStrategy as TraversalStrategy +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error + +logger: Incomplete + +class HACK_GDP_Disjunct_Reclassifier(Transformation): ... diff --git a/stubs/pyomo/gdp/plugins/hull.pyi b/stubs/pyomo/gdp/plugins/hull.pyi new file mode 100644 index 000000000..b05f2e87c --- /dev/null +++ b/stubs/pyomo/gdp/plugins/hull.pyi @@ -0,0 +1,64 @@ +from _typeshed import Incomplete +from pyomo.common import deprecated as deprecated +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.collections import DefaultComponentMap as DefaultComponentMap +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Any as Any +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import Connector as Connector +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Param as Param +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Reals as Reals +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Suffix as Suffix +from pyomo.core import TraversalStrategy as TraversalStrategy +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.disjunct import DisjunctData as DisjunctData +from pyomo.gdp.plugins.gdp_to_mip_transformation import ( + GDP_to_MIP_Transformation as GDP_to_MIP_Transformation, +) +from pyomo.gdp.util import ( + clone_without_expression_components as clone_without_expression_components, +) +from pyomo.gdp.util import is_child_of as is_child_of +from pyomo.util.vars_from_expressions import get_vars_from_components as get_vars_from_components + +logger: Incomplete + +class _HullTransformationData(AutoSlots.Mixin): + disaggregated_var_map: Incomplete + original_var_map: Incomplete + bigm_constraint_map: Incomplete + disaggregation_constraint_map: Incomplete + def __init__(self) -> None: ... + +class Hull_Reformulation(GDP_to_MIP_Transformation): + CONFIG: Incomplete + transformation_name: str + def __init__(self) -> None: ... + def get_disaggregated_var(self, v, disjunct, raise_exception: bool = True): ... + def get_src_var(self, disaggregated_var): ... + def get_disaggregation_constraint( + self, original_var, disjunction, raise_exception: bool = True + ): ... + def get_var_bounds_constraint(self, v, disjunct=None): ... + def get_transformed_constraints(self, cons): ... + +class _Deprecated_Name_Hull(Hull_Reformulation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/gdp/plugins/multiple_bigm.pyi b/stubs/pyomo/gdp/plugins/multiple_bigm.pyi new file mode 100644 index 000000000..67ebebda2 --- /dev/null +++ b/stubs/pyomo/gdp/plugins/multiple_bigm.pyi @@ -0,0 +1,52 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core import Any as Any +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import Connector as Connector +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import ExternalFunction as ExternalFunction +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Objective as Objective +from pyomo.core import Param as Param +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Suffix as Suffix +from pyomo.core import Var as Var +from pyomo.core import maximize as maximize +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.base import Reference as Reference +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.plugins.bigm_mixin import _BigM_MixIn +from pyomo.gdp.plugins.gdp_to_mip_transformation import ( + GDP_to_MIP_Transformation as GDP_to_MIP_Transformation, +) +from pyomo.gdp.util import get_gdp_tree as get_gdp_tree +from pyomo.network import Port as Port +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +def Solver(val): ... + +class MultipleBigMTransformation(GDP_to_MIP_Transformation, _BigM_MixIn): + CONFIG: Incomplete + transformation_name: str + def __init__(self) -> None: ... + def get_src_constraints(self, transformedConstraint): ... + def get_all_M_values(self, model): ... diff --git a/stubs/pyomo/gdp/plugins/partition_disjuncts.pyi b/stubs/pyomo/gdp/plugins/partition_disjuncts.pyi new file mode 100644 index 000000000..bc84d3929 --- /dev/null +++ b/stubs/pyomo/gdp/plugins/partition_disjuncts.pyi @@ -0,0 +1,61 @@ +from math import floor as floor + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.core import Binary as Binary +from pyomo.core import Block as Block +from pyomo.core import BooleanVar as BooleanVar +from pyomo.core import ComponentMap as ComponentMap +from pyomo.core import ConcreteModel as ConcreteModel +from pyomo.core import Connector as Connector +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import LogicalConstraint as LogicalConstraint +from pyomo.core import LogicalConstraintList as LogicalConstraintList +from pyomo.core import NonNegativeIntegers as NonNegativeIntegers +from pyomo.core import Objective as Objective +from pyomo.core import Param as Param +from pyomo.core import RangeSet as RangeSet +from pyomo.core import Reference as Reference +from pyomo.core import Set as Set +from pyomo.core import SetOf as SetOf +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Suffix as Suffix +from pyomo.core import Transformation as Transformation +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import TraversalStrategy as TraversalStrategy +from pyomo.core import Var as Var +from pyomo.core import maximize as maximize +from pyomo.core import value as value +from pyomo.core.base.external import ExternalFunction as ExternalFunction +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.util import NORMAL as NORMAL +from pyomo.gdp.util import ( + clone_without_expression_components as clone_without_expression_components, +) +from pyomo.gdp.util import get_gdp_tree as get_gdp_tree +from pyomo.gdp.util import is_child_of as is_child_of +from pyomo.gdp.util import verify_successful_solve as verify_successful_solve +from pyomo.network import Port as Port +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.util.vars_from_expressions import get_vars_from_components as get_vars_from_components + +logger: Incomplete + +def arbitrary_partition(disjunction, P): ... +def compute_optimal_bounds(expr, global_constraints, opt): ... +def compute_fbbt_bounds(expr, global_constraints, opt): ... + +class PartitionDisjuncts_Transformation(Transformation): + CONFIG: Incomplete + handlers: Incomplete + def __init__(self) -> None: ... diff --git a/stubs/pyomo/gdp/plugins/transform_current_disjunctive_state.pyi b/stubs/pyomo/gdp/plugins/transform_current_disjunctive_state.pyi new file mode 100644 index 000000000..70b875c86 --- /dev/null +++ b/stubs/pyomo/gdp/plugins/transform_current_disjunctive_state.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core.base import Any as Any +from pyomo.core.base import Block as Block +from pyomo.core.base import ReverseTransformationToken as ReverseTransformationToken +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.util import target_list as target_list +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp.util import GDP_Error as GDP_Error + +class TransformCurrentDisjunctiveState(Transformation): + CONFIG: Incomplete diff --git a/stubs/pyomo/gdp/transformed_disjunct.pyi b/stubs/pyomo/gdp/transformed_disjunct.pyi new file mode 100644 index 000000000..80d5f32b5 --- /dev/null +++ b/stubs/pyomo/gdp/transformed_disjunct.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.block import IndexedBlock as IndexedBlock +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.global_set import UnindexedComponent_set as UnindexedComponent_set + +class _TransformedDisjunctData(BlockData): + __autoslot_mappers__: Incomplete + @property + def src_disjunct(self): ... + def __init__(self, component) -> None: ... + +class _TransformedDisjunct(IndexedBlock): ... diff --git a/stubs/pyomo/gdp/util.pyi b/stubs/pyomo/gdp/util.pyi new file mode 100644 index 000000000..a386dad20 --- /dev/null +++ b/stubs/pyomo/gdp/util.pyi @@ -0,0 +1,58 @@ +from collections import defaultdict as defaultdict +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.collections import OrderedSet as OrderedSet +from pyomo.core import Block as Block +from pyomo.core import LogicalConstraint as LogicalConstraint +from pyomo.core import SortComponents as SortComponents +from pyomo.core import Suffix as Suffix +from pyomo.core import TraversalStrategy as TraversalStrategy +from pyomo.core import value as value +from pyomo.core.base.block import BlockData as BlockData +from pyomo.gdp import Disjunction as Disjunction +from pyomo.gdp import GDP_Error as GDP_Error +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.gdp.disjunct import DisjunctData as DisjunctData +from pyomo.opt import SolverStatus as SolverStatus +from pyomo.opt import TerminationCondition as TerminationCondition + +logger: Incomplete + +class NORMAL: ... +class INFEASIBLE: ... +class NONOPTIMAL: ... + +def verify_successful_solve(results): ... +def clone_without_expression_components(expr, substitute=None): ... + +class GDPTree: + def __init__(self) -> None: ... + @property + def vertices(self): ... + def add_node(self, u) -> None: ... + def parent(self, u): ... + def children(self, u): ... + def parent_disjunct(self, u): ... + def root_disjunct(self, u): ... + def add_node(self, u) -> None: ... + def add_edge(self, u, v) -> None: ... + def topological_sort(self): ... + def reverse_topological_sort(self): ... + def in_degree(self, u): ... + def is_leaf(self, u): ... + @property + def leaves(self) -> Generator[Incomplete]: ... + @property + def disjunct_nodes(self) -> Generator[Incomplete]: ... + +def get_gdp_tree(targets, instance, knownBlocks=None): ... +def preprocess_targets(targets, instance, knownBlocks, gdp_tree=None): ... +def is_child_of(parent, child, knownBlocks=None): ... +def get_src_disjunction(xor_constraint): ... +def get_src_disjunct(transBlock): ... +def get_src_constraint(transformedConstraint): ... +def get_transformed_constraints(srcConstraint): ... +def check_model_algebraic(instance): ... diff --git a/stubs/pyomo/kernel/__init__.pyi b/stubs/pyomo/kernel/__init__.pyi new file mode 100644 index 000000000..8874f6d18 --- /dev/null +++ b/stubs/pyomo/kernel/__init__.pyi @@ -0,0 +1,137 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.expr import Expr_if as Expr_if +from pyomo.core.expr import acos as acos +from pyomo.core.expr import acosh as acosh +from pyomo.core.expr import asin as asin +from pyomo.core.expr import asinh as asinh +from pyomo.core.expr import atan as atan +from pyomo.core.expr import atanh as atanh +from pyomo.core.expr import atleast as atleast +from pyomo.core.expr import atmost as atmost +from pyomo.core.expr import boolean_value as boolean_value +from pyomo.core.expr import calculus as calculus +from pyomo.core.expr import ceil as ceil +from pyomo.core.expr import cnf_walker as cnf_walker +from pyomo.core.expr import cos as cos +from pyomo.core.expr import cosh as cosh +from pyomo.core.expr import equivalent as equivalent +from pyomo.core.expr import exactly as exactly +from pyomo.core.expr import exp as exp +from pyomo.core.expr import expr_common as expr_common +from pyomo.core.expr import expr_errors as expr_errors +from pyomo.core.expr import floor as floor +from pyomo.core.expr import implies as implies +from pyomo.core.expr import inequality as inequality +from pyomo.core.expr import land as land +from pyomo.core.expr import linear_expression as linear_expression +from pyomo.core.expr import lnot as lnot +from pyomo.core.expr import log as log +from pyomo.core.expr import log10 as log10 +from pyomo.core.expr import logical_expr as logical_expr +from pyomo.core.expr import lor as lor +from pyomo.core.expr import nonlinear_expression as nonlinear_expression +from pyomo.core.expr import numeric_expr as numeric_expr +from pyomo.core.expr import numvalue as numvalue +from pyomo.core.expr import sin as sin +from pyomo.core.expr import sinh as sinh +from pyomo.core.expr import sqrt as sqrt +from pyomo.core.expr import symbol_map as symbol_map +from pyomo.core.expr import sympy_tools as sympy_tools +from pyomo.core.expr import tan as tan +from pyomo.core.expr import tanh as tanh +from pyomo.core.expr import taylor_series as taylor_series +from pyomo.core.expr import template_expr as template_expr +from pyomo.core.expr import visitor as visitor +from pyomo.core.expr import xor as xor +from pyomo.core.expr.boolean_value import BooleanValue as BooleanValue +from pyomo.core.expr.calculus.derivatives import differentiate as differentiate +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.expr.numvalue import is_constant as is_constant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import is_potentially_variable as is_potentially_variable +from pyomo.core.expr.numvalue import is_variable_type as is_variable_type +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import polynomial_degree as polynomial_degree +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.taylor_series import taylor_series_expansion as taylor_series_expansion +from pyomo.core.kernel.block import block as block +from pyomo.core.kernel.block import block_dict as block_dict +from pyomo.core.kernel.block import block_list as block_list +from pyomo.core.kernel.block import block_tuple as block_tuple +from pyomo.core.kernel.constraint import constraint as constraint +from pyomo.core.kernel.constraint import constraint_dict as constraint_dict +from pyomo.core.kernel.constraint import constraint_list as constraint_list +from pyomo.core.kernel.constraint import constraint_tuple as constraint_tuple +from pyomo.core.kernel.constraint import linear_constraint as linear_constraint +from pyomo.core.kernel.expression import data_expression as data_expression +from pyomo.core.kernel.expression import expression as expression +from pyomo.core.kernel.expression import expression_dict as expression_dict +from pyomo.core.kernel.expression import expression_list as expression_list +from pyomo.core.kernel.expression import expression_tuple as expression_tuple +from pyomo.core.kernel.expression import noclone as noclone +from pyomo.core.kernel.heterogeneous_container import ( + IHeterogeneousContainer as IHeterogeneousContainer, +) +from pyomo.core.kernel.heterogeneous_container import ( + heterogeneous_containers as heterogeneous_containers, +) +from pyomo.core.kernel.matrix_constraint import matrix_constraint as matrix_constraint +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.kernel.objective import objective as objective +from pyomo.core.kernel.objective import objective_dict as objective_dict +from pyomo.core.kernel.objective import objective_list as objective_list +from pyomo.core.kernel.objective import objective_tuple as objective_tuple +from pyomo.core.kernel.parameter import functional_value as functional_value +from pyomo.core.kernel.parameter import parameter as parameter +from pyomo.core.kernel.parameter import parameter_dict as parameter_dict +from pyomo.core.kernel.parameter import parameter_list as parameter_list +from pyomo.core.kernel.parameter import parameter_tuple as parameter_tuple +from pyomo.core.kernel.piecewise_library.transforms import piecewise as piecewise +from pyomo.core.kernel.piecewise_library.transforms_nd import piecewise_nd as piecewise_nd +from pyomo.core.kernel.set_types import BooleanSet as BooleanSet +from pyomo.core.kernel.set_types import IntegerSet as IntegerSet +from pyomo.core.kernel.set_types import RealSet as RealSet +from pyomo.core.kernel.sos import sos as sos +from pyomo.core.kernel.sos import sos1 as sos1 +from pyomo.core.kernel.sos import sos2 as sos2 +from pyomo.core.kernel.sos import sos_dict as sos_dict +from pyomo.core.kernel.sos import sos_list as sos_list +from pyomo.core.kernel.sos import sos_tuple as sos_tuple +from pyomo.core.kernel.suffix import export_suffix_generator as export_suffix_generator +from pyomo.core.kernel.suffix import import_suffix_generator as import_suffix_generator +from pyomo.core.kernel.suffix import local_suffix_generator as local_suffix_generator +from pyomo.core.kernel.suffix import suffix as suffix +from pyomo.core.kernel.suffix import suffix_dict as suffix_dict +from pyomo.core.kernel.suffix import suffix_generator as suffix_generator +from pyomo.core.kernel.variable import variable as variable +from pyomo.core.kernel.variable import variable_dict as variable_dict +from pyomo.core.kernel.variable import variable_list as variable_list +from pyomo.core.kernel.variable import variable_tuple as variable_tuple +from pyomo.environ import Binary as Binary +from pyomo.environ import Boolean as Boolean +from pyomo.environ import IntegerInterval as IntegerInterval +from pyomo.environ import Integers as Integers +from pyomo.environ import NegativeIntegers as NegativeIntegers +from pyomo.environ import NegativeReals as NegativeReals +from pyomo.environ import NonNegativeIntegers as NonNegativeIntegers +from pyomo.environ import NonNegativeReals as NonNegativeReals +from pyomo.environ import NonPositiveIntegers as NonPositiveIntegers +from pyomo.environ import NonPositiveReals as NonPositiveReals +from pyomo.environ import PercentFraction as PercentFraction +from pyomo.environ import PositiveIntegers as PositiveIntegers +from pyomo.environ import PositiveReals as PositiveReals +from pyomo.environ import RealInterval as RealInterval +from pyomo.environ import Reals as Reals +from pyomo.environ import UnitInterval as UnitInterval +from pyomo.kernel.util import generate_names as generate_names +from pyomo.kernel.util import pprint as pprint +from pyomo.kernel.util import preorder_traversal as preorder_traversal +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverStatus as SolverStatus +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.version import __version__ as __version__ +from pyomo.version import version_info as version_info diff --git a/stubs/pyomo/kernel/util.pyi b/stubs/pyomo/kernel/util.pyi new file mode 100644 index 000000000..ae0c78ee9 --- /dev/null +++ b/stubs/pyomo/kernel/util.pyi @@ -0,0 +1,12 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject + +def preorder_traversal( + node, ctype=..., active: bool = True, descend: bool = True +) -> Generator[Incomplete, None, Incomplete]: ... +def generate_names(node, convert=..., prefix: str = '', **kwds): ... +def pprint(obj, indent: int = 0, stream=...) -> None: ... diff --git a/stubs/pyomo/mpec/__init__.pyi b/stubs/pyomo/mpec/__init__.pyi new file mode 100644 index 000000000..5527fd29f --- /dev/null +++ b/stubs/pyomo/mpec/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.mpec.complementarity import Complementarity as Complementarity +from pyomo.mpec.complementarity import ComplementarityList as ComplementarityList +from pyomo.mpec.complementarity import complements as complements diff --git a/stubs/pyomo/mpec/complementarity.pyi b/stubs/pyomo/mpec/complementarity.pyi new file mode 100644 index 000000000..7dafec668 --- /dev/null +++ b/stubs/pyomo/mpec/complementarity.pyi @@ -0,0 +1,65 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Set as Set +from pyomo.core import Var as Var +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.disable_methods import disable_methods as disable_methods +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.initializer import CountedCallInitializer as CountedCallInitializer +from pyomo.core.base.initializer import IndexedCallInitializer as IndexedCallInitializer +from pyomo.core.base.initializer import Initializer as Initializer +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.expr.numvalue import as_numeric as as_numeric +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types + +logger: Incomplete + +class ComplementarityTuple(NamedTuple): + arg0: Incomplete + arg1: Incomplete + +def complements(a, b): ... + +class ComplementarityData(BlockData): + c: Incomplete + v: Incomplete + ve: Incomplete + def to_standard_form(self) -> None: ... + def set_value(self, cc): ... + +class _ComplementarityData(metaclass=RenamedClass): + __renamed__new_class__ = ComplementarityData + __renamed__version__: str + +class Complementarity(Block): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwargs) -> None: ... + def add(self, index, cc): ... + +class ScalarComplementarity(ComplementarityData, Complementarity): + def __init__(self, *args, **kwds) -> None: ... + +class SimpleComplementarity(metaclass=RenamedClass): + __renamed__new_class__ = ScalarComplementarity + __renamed__version__: str + +class AbstractScalarComplementarity(ScalarComplementarity): ... + +class AbstractSimpleComplementarity(metaclass=RenamedClass): + __renamed__new_class__ = AbstractScalarComplementarity + __renamed__version__: str + +class IndexedComplementarity(Complementarity): ... + +class ComplementarityList(IndexedComplementarity): + End: Incomplete + def __init__(self, **kwargs) -> None: ... + def add(self, expr): ... + def construct(self, data=None) -> None: ... diff --git a/stubs/pyomo/mpec/plugins/__init__.pyi b/stubs/pyomo/mpec/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/mpec/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/mpec/plugins/mpec1.pyi b/stubs/pyomo/mpec/plugins/mpec1.pyi new file mode 100644 index 000000000..796ad5a88 --- /dev/null +++ b/stubs/pyomo/mpec/plugins/mpec1.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.core.base import Block as Block +from pyomo.core.base import ComponentUID as ComponentUID +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Param as Param +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.gdp import Disjunct as Disjunct +from pyomo.mpec.complementarity import Complementarity as Complementarity + +logger: Incomplete + +class MPEC1_Transformation(Transformation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/mpec/plugins/mpec2.pyi b/stubs/pyomo/mpec/plugins/mpec2.pyi new file mode 100644 index 000000000..227cb2f85 --- /dev/null +++ b/stubs/pyomo/mpec/plugins/mpec2.pyi @@ -0,0 +1,16 @@ +from _typeshed import Incomplete +from pyomo.core.base import Block as Block +from pyomo.core.base import ComponentUID as ComponentUID +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.expr import inequality as inequality +from pyomo.gdp.disjunct import Disjunct as Disjunct +from pyomo.gdp.disjunct import Disjunction as Disjunction +from pyomo.mpec.complementarity import Complementarity as Complementarity + +logger: Incomplete + +class MPEC2_Transformation(Transformation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/mpec/plugins/mpec3.pyi b/stubs/pyomo/mpec/plugins/mpec3.pyi new file mode 100644 index 000000000..b962802fd --- /dev/null +++ b/stubs/pyomo/mpec/plugins/mpec3.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from pyomo.core.base import Block as Block +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.gdp import Disjunct as Disjunct +from pyomo.mpec.complementarity import Complementarity as Complementarity + +logger: Incomplete + +class MPEC3_Transformation(Transformation): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/mpec/plugins/mpec4.pyi b/stubs/pyomo/mpec/plugins/mpec4.pyi new file mode 100644 index 000000000..89728c615 --- /dev/null +++ b/stubs/pyomo/mpec/plugins/mpec4.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.core.base import Block as Block +from pyomo.core.base import ComponentUID as ComponentUID +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base import Var as Var +from pyomo.core.base import value as value +from pyomo.gdp import Disjunct as Disjunct +from pyomo.mpec.complementarity import Complementarity as Complementarity + +logger: Incomplete + +class MPEC4_Transformation(Transformation): + def __init__(self) -> None: ... + def print_nl_form(self, instance) -> None: ... + def to_common_form(self, cdata, free_vars) -> None: ... diff --git a/stubs/pyomo/mpec/plugins/pathampl.pyi b/stubs/pyomo/mpec/plugins/pathampl.pyi new file mode 100644 index 000000000..a90673cec --- /dev/null +++ b/stubs/pyomo/mpec/plugins/pathampl.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.solvers.plugins.solvers.ASL import ASL as ASL + +logger: Incomplete + +class PATHAMPL(ASL): + def __init__(self, **kwds) -> None: ... + def create_command_line(self, executable, problem_files): ... diff --git a/stubs/pyomo/mpec/plugins/solver1.pyi b/stubs/pyomo/mpec/plugins/solver1.pyi new file mode 100644 index 000000000..d9e486756 --- /dev/null +++ b/stubs/pyomo/mpec/plugins/solver1.pyi @@ -0,0 +1,7 @@ +import pyomo.opt +from pyomo.common.collections import Bunch as Bunch +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.opt import SolverFactory as SolverFactory + +class MPEC_Solver1(pyomo.opt.OptSolver): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/mpec/plugins/solver2.pyi b/stubs/pyomo/mpec/plugins/solver2.pyi new file mode 100644 index 000000000..d32a96874 --- /dev/null +++ b/stubs/pyomo/mpec/plugins/solver2.pyi @@ -0,0 +1,7 @@ +import pyomo.opt +from pyomo.common.collections import Bunch as Bunch +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.opt import SolverFactory as SolverFactory + +class MPEC_Solver2(pyomo.opt.OptSolver): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/neos/__init__.pyi b/stubs/pyomo/neos/__init__.pyi new file mode 100644 index 000000000..dbe6feb74 --- /dev/null +++ b/stubs/pyomo/neos/__init__.pyi @@ -0,0 +1,3 @@ +from _typeshed import Incomplete + +doc: Incomplete diff --git a/stubs/pyomo/neos/kestrel.pyi b/stubs/pyomo/neos/kestrel.pyi new file mode 100644 index 000000000..a9ffc0b5b --- /dev/null +++ b/stubs/pyomo/neos/kestrel.pyi @@ -0,0 +1,31 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import + +xmlrpclib: Incomplete +gzip: Incomplete +logger: Incomplete + +class NEOS: + scheme: str + host: str + port: str + +def ProxiedTransport(): ... + +class kestrelAMPL: + def __init__(self) -> None: ... + def __del__(self) -> None: ... + transport: Incomplete + neos: Incomplete + def setup_connection(self) -> None: ... + def tempfile(self): ... + def kill(self, jobNumber, password) -> None: ... + def solvers(self): ... + def retrieve(self, stub, jobNumber, password) -> None: ... + def submit(self, xml): ... + def getEmailAddress(self): ... + def getJobAndPassword(self): ... + def getAvailableSolvers(self): ... + options: Incomplete + def getSolverName(self): ... + def formXML(self, stub): ... diff --git a/stubs/pyomo/neos/plugins/NEOS.pyi b/stubs/pyomo/neos/plugins/NEOS.pyi new file mode 100644 index 000000000..6e76f783c --- /dev/null +++ b/stubs/pyomo/neos/plugins/NEOS.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver + +logger: Incomplete + +class NEOSRemoteSolver(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def create_command_line(self, executable, problem_files): ... diff --git a/stubs/pyomo/neos/plugins/__init__.pyi b/stubs/pyomo/neos/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/neos/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/neos/plugins/kestrel_plugin.pyi b/stubs/pyomo/neos/plugins/kestrel_plugin.pyi new file mode 100644 index 000000000..339ab29f2 --- /dev/null +++ b/stubs/pyomo/neos/plugins/kestrel_plugin.pyi @@ -0,0 +1,24 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.core.base import Block as Block +from pyomo.opt import OptSolver as OptSolver +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverManagerFactory as SolverManagerFactory +from pyomo.opt.parallel.async_solver import AsynchronousSolverManager as AsynchronousSolverManager +from pyomo.opt.parallel.manager import ActionManagerError as ActionManagerError +from pyomo.opt.parallel.manager import ActionStatus as ActionStatus + +xmlrpc_client: Incomplete +logger: Incomplete + +class SolverManager_NEOS(AsynchronousSolverManager): + kestrel: Incomplete + def clear(self) -> None: ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... diff --git a/stubs/pyomo/network/__init__.pyi b/stubs/pyomo/network/__init__.pyi new file mode 100644 index 000000000..a5fd92fb0 --- /dev/null +++ b/stubs/pyomo/network/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.network.arc import Arc as Arc +from pyomo.network.decomposition import SequentialDecomposition as SequentialDecomposition +from pyomo.network.port import Port as Port diff --git a/stubs/pyomo/network/arc.pyi b/stubs/pyomo/network/arc.pyi new file mode 100644 index 000000000..9b6464b26 --- /dev/null +++ b/stubs/pyomo/network/arc.pyi @@ -0,0 +1,54 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ActiveComponentData as ActiveComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import ActiveIndexedComponent as ActiveIndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.network.port import Port as Port + +logger: Incomplete + +class ArcData(ActiveComponentData): + def __init__(self, component=None, **kwds) -> None: ... + def __getattr__(self, name): ... + @property + def source(self): ... + src = source + @property + def destination(self): ... + dest = destination + @property + def ports(self): ... + @property + def directed(self): ... + @property + def expanded_block(self): ... + def set_value(self, vals) -> None: ... + +class _ArcData(metaclass=RenamedClass): + __renamed__new_class__ = ArcData + __renamed__version__: str + +class Arc(ActiveIndexedComponent): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwds) -> None: ... + def construct(self, data=None) -> None: ... + +class ScalarArc(ArcData, Arc): + index: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def set_value(self, vals) -> None: ... + +class SimpleArc(metaclass=RenamedClass): + __renamed__new_class__ = ScalarArc + __renamed__version__: str + +class IndexedArc(Arc): + def __init__(self, *args, **kwds) -> None: ... + @property + def expanded_block(self): ... diff --git a/stubs/pyomo/network/decomposition.pyi b/stubs/pyomo/network/decomposition.pyi new file mode 100644 index 000000000..da66166ed --- /dev/null +++ b/stubs/pyomo/network/decomposition.pyi @@ -0,0 +1,60 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import networkx_available as networkx_available +from pyomo.common.dependencies import numpy as numpy +from pyomo.common.dependencies import numpy_available as numpy_available +from pyomo.core import Binary as Binary +from pyomo.core import ConcreteModel as ConcreteModel +from pyomo.core import Constraint as Constraint +from pyomo.core import Expression as Expression +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.network import Arc as Arc +from pyomo.network import Port as Port +from pyomo.network.foqus_graph import FOQUSGraph as FOQUSGraph +from pyomo.repn import generate_standard_repn as generate_standard_repn + +imports_available: Incomplete +logger: Incomplete + +class SequentialDecomposition(FOQUSGraph): + cache: Incomplete + def __init__(self, **kwds) -> None: ... + def set_guesses_for(self, port, guesses) -> None: ... + def set_tear_set(self, tset) -> None: ... + def tear_set_arcs(self, G, method: str = 'mip', **kwds): ... + def indexes_to_arcs(self, G, lst): ... + def run(self, model, function): ... + def run_order(self, G, order, function, ignore=None, use_guesses: bool = False) -> None: ... + def pass_values(self, arc, fixed_inputs) -> None: ... + def pass_single_value(self, port, name, member, val, fixed) -> None: ... + def load_guesses(self, guesses, port, fixed) -> None: ... + def load_values(self, port, default, fixed, use_guesses) -> None: ... + def check_value_fix( + self, port, var, default, fixed, use_guesses, extensive: bool = False + ) -> None: ... + def combine_and_fix(self, port, name, obj, evars, fixed) -> None: ... + def source_dest_peer(self, arc, name, index=None): ... + def create_graph(self, model): ... + def select_tear_mip_model(self, G): ... + def select_tear_mip(self, G, solver, solver_io=None, solver_options={}): ... + def compute_err(self, svals, dvals, tol_type): ... + def tear_diff_direct(self, G, tears): ... + def pass_edges(self, G, edges) -> None: ... + def pass_tear_direct(self, G, tears) -> None: ... + def pass_tear_wegstein(self, G, tears, x) -> None: ... + def generate_gofx(self, G, tears): ... + def generate_first_x(self, G, tears): ... + def cacher(self, key, fcn, *args): ... + def tear_set(self, G): ... + def arc_to_edge(self, G): ... + def fixed_inputs(self): ... + def idx_to_node(self, G): ... + def node_to_idx(self, G): ... + def idx_to_edge(self, G): ... + def edge_to_idx(self, G): ... diff --git a/stubs/pyomo/network/foqus_graph.pyi b/stubs/pyomo/network/foqus_graph.pyi new file mode 100644 index 000000000..87edb1ec8 --- /dev/null +++ b/stubs/pyomo/network/foqus_graph.pyi @@ -0,0 +1,34 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import numpy as numpy + +logger: Incomplete + +class FOQUSGraph: + def solve_tear_direct( + self, G, order, function, tears, outEdges, iterLim, tol, tol_type, report_diffs + ): ... + def solve_tear_wegstein( + self, + G, + order, + function, + tears, + outEdges, + iterLim, + tol, + tol_type, + report_diffs, + accel_min, + accel_max, + ): ... + def scc_collect(self, G, excludeEdges=None): ... + def scc_calculation_order(self, sccNodes, ie, oe): ... + def calculation_order(self, G, roots=None, nodes=None): ... + def tree_order(self, adj, adjR, roots=None): ... + def check_tear_set(self, G, tset): ... + def select_tear_heuristic(self, G): ... + def tear_upper_bound(self, G): ... + def sub_graph_edges(self, G, nodes): ... + def cycle_edge_matrix(self, G): ... + def all_cycles(self, G): ... + def adj_lists(self, G, excludeEdges=None, nodes=None, multi: bool = False): ... diff --git a/stubs/pyomo/network/plugins/__init__.pyi b/stubs/pyomo/network/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/network/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/network/plugins/expand_arcs.pyi b/stubs/pyomo/network/plugins/expand_arcs.pyi new file mode 100644 index 000000000..4604a9714 --- /dev/null +++ b/stubs/pyomo/network/plugins/expand_arcs.pyi @@ -0,0 +1,18 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core.base import Block as Block +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Transformation as Transformation +from pyomo.core.base import TransformationFactory as TransformationFactory +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.gdp import Disjunct as Disjunct +from pyomo.network import Arc as Arc +from pyomo.network.util import replicate_var as replicate_var + +logger: Incomplete +obj_iter_kwds: Incomplete + +class ExpandArcs(Transformation): ... diff --git a/stubs/pyomo/network/port.pyi b/stubs/pyomo/network/port.pyi new file mode 100644 index 000000000..dbf3a6c80 --- /dev/null +++ b/stubs/pyomo/network/port.pyi @@ -0,0 +1,81 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.autoslots import AutoSlots as AutoSlots +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.deprecation import RenamedClass as RenamedClass +from pyomo.common.formatting import tabular_writer as tabular_writer +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.common.numeric_types import value as value +from pyomo.common.timing import ConstructionTimer as ConstructionTimer +from pyomo.core.base.component import ComponentData as ComponentData +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.global_set import UnindexedComponent_index as UnindexedComponent_index +from pyomo.core.base.indexed_component import IndexedComponent as IndexedComponent +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.label import alphanum_label_from_name as alphanum_label_from_name +from pyomo.core.base.misc import apply_indexed_rule as apply_indexed_rule +from pyomo.core.base.var import Var as Var +from pyomo.core.expr import identify_variables as identify_variables +from pyomo.core.expr.numvalue import as_numeric as as_numeric +from pyomo.network.util import create_var as create_var +from pyomo.network.util import tighten_var_domain as tighten_var_domain + +logger: Incomplete + +class PortData(ComponentData): + __autoslot_mappers__: Incomplete + vars: Incomplete + def __init__(self, component=None) -> None: ... + def __getattr__(self, name): ... + def arcs(self, active=None): ... + def sources(self, active=None): ... + def dests(self, active=None): ... + def set_value(self, value) -> None: ... + def polynomial_degree(self): ... + def is_fixed(self): ... + def is_potentially_variable(self): ... + def is_binary(self): ... + def is_integer(self): ... + def is_continuous(self): ... + def add(self, var, name=None, rule=None, **kwds) -> None: ... + def remove(self, name) -> None: ... + def rule_for(self, name): ... + def is_equality(self, name): ... + def is_extensive(self, name): ... + def fix(self) -> None: ... + def unfix(self) -> None: ... + free = unfix + def iter_vars( + self, expr_vars: bool = False, fixed=None, names: bool = False + ) -> Generator[Incomplete]: ... + def set_split_fraction(self, arc, val, fix: bool = True) -> None: ... + def get_split_fraction(self, arc): ... + +class _PortData(metaclass=RenamedClass): + __renamed__new_class__ = PortData + __renamed__version__: str + +class Port(IndexedComponent): + def __new__(cls, *args, **kwds): ... + def __init__(self, *args, **kwd) -> None: ... + def construct(self, data=None) -> None: ... + def display(self, prefix: str = '', ostream=None) -> None: ... + @staticmethod + def Equality(port, name, index_set) -> None: ... + @staticmethod + def Extensive( + port, name, index_set, include_splitfrac=None, write_var_sum: bool = True + ) -> None: ... + +class ScalarPort(Port, PortData): + def __init__(self, *args, **kwd) -> None: ... + +class SimplePort(metaclass=RenamedClass): + __renamed__new_class__ = ScalarPort + __renamed__version__: str + +class IndexedPort(Port): ... diff --git a/stubs/pyomo/network/util.pyi b/stubs/pyomo/network/util.pyi new file mode 100644 index 000000000..61774e05a --- /dev/null +++ b/stubs/pyomo/network/util.pyi @@ -0,0 +1,6 @@ +from pyomo.core import Var as Var +from pyomo.core.base.indexed_component import UnindexedComponent_set as UnindexedComponent_set + +def create_var(comp, name, block, index_set=None): ... +def tighten_var_domain(comp, new_var, index_set=None): ... +def replicate_var(comp, name, block, index_set=None): ... diff --git a/stubs/pyomo/opt/__init__.pyi b/stubs/pyomo/opt/__init__.pyi new file mode 100644 index 000000000..833ed3f05 --- /dev/null +++ b/stubs/pyomo/opt/__init__.pyi @@ -0,0 +1,44 @@ +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.opt.base import AbstractProblemWriter as AbstractProblemWriter +from pyomo.opt.base import AbstractResultsReader as AbstractResultsReader +from pyomo.opt.base import BranchDirection as BranchDirection +from pyomo.opt.base import ConverterError as ConverterError +from pyomo.opt.base import OptSolver as OptSolver +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ReaderFactory as ReaderFactory +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.opt.base import UnknownSolver as UnknownSolver +from pyomo.opt.base import WriterFactory as WriterFactory +from pyomo.opt.base import check_available_solvers as check_available_solvers +from pyomo.opt.base import convert as convert +from pyomo.opt.base import convert_problem as convert_problem +from pyomo.opt.base import error as error +from pyomo.opt.base import formats as formats +from pyomo.opt.base import guess_format as guess_format +from pyomo.opt.base import opt_config as opt_config +from pyomo.opt.base import solvers as solvers +from pyomo.opt.parallel import AsynchronousSolverManager as AsynchronousSolverManager +from pyomo.opt.parallel import SolverManagerFactory as SolverManagerFactory +from pyomo.opt.parallel import async_solver as async_solver +from pyomo.opt.parallel import local as local +from pyomo.opt.parallel import manager as manager +from pyomo.opt.problem import AmplModel as AmplModel +from pyomo.opt.problem import ampl as ampl +from pyomo.opt.results import ListContainer as ListContainer +from pyomo.opt.results import MapContainer as MapContainer +from pyomo.opt.results import ProblemSense as ProblemSense +from pyomo.opt.results import Solution as Solution +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition +from pyomo.opt.results import UndefinedData as UndefinedData +from pyomo.opt.results import assert_optimal_termination as assert_optimal_termination +from pyomo.opt.results import check_optimal_termination as check_optimal_termination +from pyomo.opt.results import container as container +from pyomo.opt.results import ignore as ignore +from pyomo.opt.results import problem as problem +from pyomo.opt.results import results_ as results_ +from pyomo.opt.results import solution as solution +from pyomo.opt.results import undefined as undefined diff --git a/stubs/pyomo/opt/base/__init__.pyi b/stubs/pyomo/opt/base/__init__.pyi new file mode 100644 index 000000000..6ea26a9ce --- /dev/null +++ b/stubs/pyomo/opt/base/__init__.pyi @@ -0,0 +1,14 @@ +from pyomo.opt.base.convert import convert_problem as convert_problem +from pyomo.opt.base.error import ConverterError as ConverterError +from pyomo.opt.base.formats import ProblemFormat as ProblemFormat +from pyomo.opt.base.formats import ResultsFormat as ResultsFormat +from pyomo.opt.base.formats import guess_format as guess_format +from pyomo.opt.base.problem import AbstractProblemWriter as AbstractProblemWriter +from pyomo.opt.base.problem import BranchDirection as BranchDirection +from pyomo.opt.base.problem import WriterFactory as WriterFactory +from pyomo.opt.base.results import AbstractResultsReader as AbstractResultsReader +from pyomo.opt.base.results import ReaderFactory as ReaderFactory +from pyomo.opt.base.solvers import OptSolver as OptSolver +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.base.solvers import UnknownSolver as UnknownSolver +from pyomo.opt.base.solvers import check_available_solvers as check_available_solvers diff --git a/stubs/pyomo/opt/base/convert.pyi b/stubs/pyomo/opt/base/convert.pyi new file mode 100644 index 000000000..47924e2d9 --- /dev/null +++ b/stubs/pyomo/opt/base/convert.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete +from pyomo.common import Factory as Factory +from pyomo.opt.base.error import ConverterError as ConverterError +from pyomo.opt.base.formats import guess_format as guess_format + +ProblemConverterFactory: Incomplete + +def convert_problem(args, target_problem_type, valid_problem_types, has_capability=..., **kwds): ... diff --git a/stubs/pyomo/opt/base/error.pyi b/stubs/pyomo/opt/base/error.pyi new file mode 100644 index 000000000..b61d9fdc0 --- /dev/null +++ b/stubs/pyomo/opt/base/error.pyi @@ -0,0 +1,2 @@ +class ConverterError(Exception): + def __init__(self, *args, **kargs) -> None: ... diff --git a/stubs/pyomo/opt/base/formats.pyi b/stubs/pyomo/opt/base/formats.pyi new file mode 100644 index 000000000..e5c9194e7 --- /dev/null +++ b/stubs/pyomo/opt/base/formats.pyi @@ -0,0 +1,22 @@ +import enum + +class ProblemFormat(str, enum.Enum): + pyomo = 'pyomo' + cpxlp = 'cpxlp' + nl = 'nl' + mps = 'mps' + mod = 'mod' + lpxlp = 'lpxlp' + osil = 'osil' + bar = 'bar' + gams = 'gams' + +class ResultsFormat(str, enum.Enum): + osrl = 'osrl' + results = 'results' + sol = 'sol' + soln = 'soln' + yaml = 'yaml' + json = 'json' + +def guess_format(filename): ... diff --git a/stubs/pyomo/opt/base/opt_config.pyi b/stubs/pyomo/opt/base/opt_config.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/opt/base/problem.pyi b/stubs/pyomo/opt/base/problem.pyi new file mode 100644 index 000000000..e45c7014d --- /dev/null +++ b/stubs/pyomo/opt/base/problem.pyi @@ -0,0 +1,24 @@ +import types + +from _typeshed import Incomplete +from pyomo.common import Factory as Factory + +WriterFactory: Incomplete + +class AbstractProblemWriter: + format: Incomplete + def __init__(self, problem_format) -> None: ... + def __call__(self, model, filename, solver_capability, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +class BranchDirection: + default: int + down: int + up: int + ALL: Incomplete diff --git a/stubs/pyomo/opt/base/results.pyi b/stubs/pyomo/opt/base/results.pyi new file mode 100644 index 000000000..87cc9b852 --- /dev/null +++ b/stubs/pyomo/opt/base/results.pyi @@ -0,0 +1,18 @@ +import types + +from _typeshed import Incomplete +from pyomo.common import Factory as Factory + +ReaderFactory: Incomplete + +class AbstractResultsReader: + format: Incomplete + def __init__(self, results_format) -> None: ... + def __call__(self, filename, res=None, suffixes=[]) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... diff --git a/stubs/pyomo/opt/base/solvers.pyi b/stubs/pyomo/opt/base/solvers.pyi new file mode 100644 index 000000000..b581377a7 --- /dev/null +++ b/stubs/pyomo/opt/base/solvers.pyi @@ -0,0 +1,99 @@ +import types + +from _typeshed import Incomplete +from pyomo.common import Factory as Factory +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.opt.base.convert import convert_problem as convert_problem +from pyomo.opt.base.formats import ResultsFormat as ResultsFormat + +logger: Incomplete + +class UnknownSolver: + type: Incomplete + options: Incomplete + def __init__(self, *args, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def warm_start_capable(self): ... + def solve(self, *args, **kwds) -> None: ... + def reset(self) -> None: ... + def set_options(self, istr) -> None: ... + def __bool__(self) -> bool: ... + def __getattr__(self, attr) -> None: ... + +class SolverFactoryClass(Factory): + def __call__(self, _name=None, **kwds): ... + +LegacySolverFactory: Incomplete +SolverFactory: Incomplete + +def check_available_solvers(*args): ... + +class OptSolver: + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + @property + def tee(self) -> None: ... + @tee.setter + def tee(self, val) -> None: ... + @property + def suffixes(self) -> None: ... + @suffixes.setter + def suffixes(self, val) -> None: ... + @property + def keepfiles(self) -> None: ... + @keepfiles.setter + def keepfiles(self, val) -> None: ... + @property + def soln_file(self) -> None: ... + @soln_file.setter + def soln_file(self, val) -> None: ... + @property + def log_file(self) -> None: ... + @log_file.setter + def log_file(self, val) -> None: ... + @property + def symbolic_solver_labels(self) -> None: ... + @symbolic_solver_labels.setter + def symbolic_solver_labels(self, val) -> None: ... + @property + def warm_start_solve(self) -> None: ... + @warm_start_solve.setter + def warm_start_solve(self, val) -> None: ... + @property + def warm_start_file_name(self) -> None: ... + @warm_start_file_name.setter + def warm_start_file_name(self, val) -> None: ... + type: Incomplete + name: Incomplete + options: Incomplete + def __init__(self, **kwds) -> None: ... + def default_variable_value(self): ... + def __bool__(self) -> bool: ... + def version(self): ... + def problem_format(self): ... + def set_problem_format(self, format) -> None: ... + def results_format(self): ... + def set_results_format(self, format) -> None: ... + def has_capability(self, cap): ... + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def warm_start_capable(self): ... + def solve(self, *args, **kwds): ... + def reset(self) -> None: ... + def set_options(self, istr) -> None: ... + def set_callback(self, name, callback_fn=None) -> None: ... + def config_block(self, init: bool = False): ... diff --git a/stubs/pyomo/opt/parallel/__init__.pyi b/stubs/pyomo/opt/parallel/__init__.pyi new file mode 100644 index 000000000..9131c3aa5 --- /dev/null +++ b/stubs/pyomo/opt/parallel/__init__.pyi @@ -0,0 +1,6 @@ +from pyomo.opt.parallel import local as local +from pyomo.opt.parallel import manager as manager +from pyomo.opt.parallel.async_solver import AsynchronousActionManager as AsynchronousActionManager +from pyomo.opt.parallel.async_solver import AsynchronousSolverManager as AsynchronousSolverManager +from pyomo.opt.parallel.async_solver import Factory as Factory +from pyomo.opt.parallel.async_solver import SolverManagerFactory as SolverManagerFactory diff --git a/stubs/pyomo/opt/parallel/async_solver.pyi b/stubs/pyomo/opt/parallel/async_solver.pyi new file mode 100644 index 000000000..99857d690 --- /dev/null +++ b/stubs/pyomo/opt/parallel/async_solver.pyi @@ -0,0 +1,19 @@ +import types + +from _typeshed import Incomplete +from pyomo.common import Factory as Factory +from pyomo.opt.parallel.manager import AsynchronousActionManager as AsynchronousActionManager + +SolverManagerFactory: Incomplete + +class AsynchronousSolverManager(AsynchronousActionManager): + def __init__(self, **kwds) -> None: ... + def solve(self, *args, **kwds): ... + def solve_all(self, solver, instances, **kwds) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... diff --git a/stubs/pyomo/opt/parallel/local.pyi b/stubs/pyomo/opt/parallel/local.pyi new file mode 100644 index 000000000..28cd4c95b --- /dev/null +++ b/stubs/pyomo/opt/parallel/local.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.common.collections import OrderedDict as OrderedDict +from pyomo.opt.parallel.async_solver import AsynchronousSolverManager as AsynchronousSolverManager +from pyomo.opt.parallel.async_solver import SolverManagerFactory as SolverManagerFactory +from pyomo.opt.parallel.manager import ActionHandle as ActionHandle +from pyomo.opt.parallel.manager import ActionManagerError as ActionManagerError +from pyomo.opt.parallel.manager import ActionStatus as ActionStatus + +class SolverManager_Serial(AsynchronousSolverManager): + results: Incomplete + def clear(self) -> None: ... diff --git a/stubs/pyomo/opt/parallel/manager.pyi b/stubs/pyomo/opt/parallel/manager.pyi new file mode 100644 index 000000000..ba3f1663b --- /dev/null +++ b/stubs/pyomo/opt/parallel/manager.pyi @@ -0,0 +1,44 @@ +import enum + +from _typeshed import Incomplete + +class ActionStatus(str, enum.Enum): + done = 'done' + error = 'error' + queued = 'queued' + executing = 'executing' + unknown = 'unknown' + +def solve_all_instances(solver_manager, solver, instances, **kwds) -> None: ... + +class ActionManagerError(Exception): + def __init__(self, *args, **kargs) -> None: ... + +class ActionHandle: + id_counter: int + id: int + status: Incomplete + explanation: Incomplete + def __init__(self, error: bool = False, explanation: str = '') -> None: ... + def update(self, ah) -> None: ... + def __lt__(self, other): ... + def __hash__(self): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + +FailedActionHandle: Incomplete + +class AsynchronousActionManager: + def __init__(self) -> None: ... + event_handle: Incomplete + results: Incomplete + queued_action_counter: int + def clear(self) -> None: ... + def execute(self, *args, **kwds): ... + def queue(self, *args, **kwds): ... + def wait_all(self, *args) -> None: ... + def wait_any(self, *args): ... + def wait_for(self, ah): ... + def num_queued(self): ... + def get_status(self, ah): ... + def get_results(self, ah): ... diff --git a/stubs/pyomo/opt/plugins/__init__.pyi b/stubs/pyomo/opt/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/opt/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/opt/plugins/driver.pyi b/stubs/pyomo/opt/plugins/driver.pyi new file mode 100644 index 000000000..a2ddb884e --- /dev/null +++ b/stubs/pyomo/opt/plugins/driver.pyi @@ -0,0 +1,6 @@ +from _typeshed import Incomplete + +logger: Incomplete + +def setup_test_parser(parser) -> None: ... +def test_exec(options) -> None: ... diff --git a/stubs/pyomo/opt/plugins/res.pyi b/stubs/pyomo/opt/plugins/res.pyi new file mode 100644 index 000000000..4c1307fcb --- /dev/null +++ b/stubs/pyomo/opt/plugins/res.pyi @@ -0,0 +1,11 @@ +from pyomo.opt import SolverResults as SolverResults +from pyomo.opt.base import results as results +from pyomo.opt.base.formats import ResultsFormat as ResultsFormat + +class ResultsReader_yaml(results.AbstractResultsReader): + def __init__(self) -> None: ... + def __call__(self, filename, res=None, soln=None, suffixes=[]): ... + +class ResultsReader_json(results.AbstractResultsReader): + def __init__(self) -> None: ... + def __call__(self, filename, res=None, soln=None, suffixes=[]): ... diff --git a/stubs/pyomo/opt/plugins/sol.pyi b/stubs/pyomo/opt/plugins/sol.pyi new file mode 100644 index 000000000..9fda05639 --- /dev/null +++ b/stubs/pyomo/opt/plugins/sol.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from pyomo.opt import SolutionStatus as SolutionStatus +from pyomo.opt import SolverResults as SolverResults +from pyomo.opt import SolverStatus as SolverStatus +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.opt.base import results as results +from pyomo.opt.base.formats import ResultsFormat as ResultsFormat + +class ResultsReader_sol(results.AbstractResultsReader): + name: Incomplete + def __init__(self, name=None) -> None: ... + def __call__(self, filename, res=None, soln=None, suffixes=[]): ... diff --git a/stubs/pyomo/opt/problem/__init__.pyi b/stubs/pyomo/opt/problem/__init__.pyi new file mode 100644 index 000000000..c6b1a9980 --- /dev/null +++ b/stubs/pyomo/opt/problem/__init__.pyi @@ -0,0 +1,4 @@ +from pyomo.opt.problem.ampl import AmplModel as AmplModel +from pyomo.opt.problem.ampl import ProblemFormat as ProblemFormat +from pyomo.opt.problem.ampl import convert_problem as convert_problem +from pyomo.opt.problem.ampl import guess_format as guess_format diff --git a/stubs/pyomo/opt/problem/ampl.pyi b/stubs/pyomo/opt/problem/ampl.pyi new file mode 100644 index 000000000..ad2c7ef76 --- /dev/null +++ b/stubs/pyomo/opt/problem/ampl.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import convert_problem as convert_problem +from pyomo.opt.base import guess_format as guess_format + +class AmplModel: + modfile: Incomplete + datfile: Incomplete + def __init__(self, modfile, datfile=None) -> None: ... + def valid_problem_types(self): ... + def write(self, filename, format=None, solver_capability=None): ... diff --git a/stubs/pyomo/opt/results.pyi b/stubs/pyomo/opt/results.pyi new file mode 100644 index 000000000..69adf6db9 --- /dev/null +++ b/stubs/pyomo/opt/results.pyi @@ -0,0 +1,44 @@ +""" +Type stubs for Pyomo optimization module. + +This module provides minimal type definitions for Pyomo solver-related classes +to avoid requiring Pyomo as a dependency during type checking. +""" + +from enum import Enum +from typing import Any + +class SolverResults: + """Stub for Pyomo SolverResults class.""" + + def __init__(self, solver: Any = None) -> None: ... + def __getitem__(self, key: str) -> Any: ... + @property + def solver(self) -> Any: ... + +class SolverStatus(Enum): + """Stub for Pyomo SolverStatus enum.""" + + OK = 'ok' + WARNING = 'warning' + ERROR = 'error' + ABORTED = 'aborted' + UNKNOWN = 'unknown' + +class TerminationCondition(Enum): + """Stub for Pyomo TerminationCondition enum.""" + + convergenceCriteriaSatisfied = 0 + maxTimeLimit = 1 + iterationLimit = 2 + objectiveLimit = 3 + minStepLength = 4 + unbounded = 5 + provenInfeasible = 6 + locallyInfeasible = 7 + infeasibleOrUnbounded = 8 + error = 9 + interrupted = 10 + licensingProblems = 11 + emptyModel = 12 + unknown = 42 diff --git a/stubs/pyomo/opt/results/__init__.pyi b/stubs/pyomo/opt/results/__init__.pyi new file mode 100644 index 000000000..b42450891 --- /dev/null +++ b/stubs/pyomo/opt/results/__init__.pyi @@ -0,0 +1,14 @@ +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.opt.results.container import ListContainer as ListContainer +from pyomo.opt.results.container import MapContainer as MapContainer +from pyomo.opt.results.container import UndefinedData as UndefinedData +from pyomo.opt.results.container import ignore as ignore +from pyomo.opt.results.container import undefined as undefined +from pyomo.opt.results.problem import ProblemSense as ProblemSense +from pyomo.opt.results.results_ import SolverResults as SolverResults +from pyomo.opt.results.solution import Solution as Solution +from pyomo.opt.results.solution import SolutionStatus as SolutionStatus +from pyomo.opt.results.solver import SolverStatus as SolverStatus +from pyomo.opt.results.solver import TerminationCondition as TerminationCondition +from pyomo.opt.results.solver import assert_optimal_termination as assert_optimal_termination +from pyomo.opt.results.solver import check_optimal_termination as check_optimal_termination diff --git a/stubs/pyomo/opt/results/container.pyi b/stubs/pyomo/opt/results/container.pyi new file mode 100644 index 000000000..3d897760b --- /dev/null +++ b/stubs/pyomo/opt/results/container.pyi @@ -0,0 +1,73 @@ +import enum + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch + +class ScalarType(str, enum.Enum): + int = 'int' + time = 'time' + string = 'string' + float = 'float' + enum = 'enum' + undefined = 'undefined' + +default_print_options: Incomplete +strict: bool + +class UndefinedData: ... + +undefined: Incomplete +ignore: Incomplete + +class ScalarData: + value: Incomplete + description: Incomplete + units: Incomplete + scalar_description: Incomplete + scalar_type: Incomplete + def __init__( + self, + value=..., + description=None, + units=None, + scalar_description=None, + type=..., + required: bool = False, + ) -> None: ... + def get_value(self): ... + def pprint(self, ostream, option, prefix: str = '', repn=None): ... + def yaml_fix(self, val): ... + def load(self, repn) -> None: ... + +class ListContainer: + def __init__(self, cls) -> None: ... + def __len__(self) -> int: ... + def __getitem__(self, i): ... + def clear(self) -> None: ... + def delete(self, i) -> None: ... + def __call__(self, i: int = 0): ... + def __getattr__(self, name): ... + __class__: Incomplete + def __setattr__(self, name, val) -> None: ... + def insert(self, obj) -> None: ... + def add(self): ... + def pprint(self, ostream, option, prefix: str = '', repn=None): ... + def load(self, repn) -> None: ... + +class MapContainer(dict): + def __getnewargs_ex__(self): ... + def __getnewargs__(self): ... + def __new__(cls, *args, **kwargs): ... + def __init__(self, ordered: bool = False) -> None: ... + def keys(self): ... + def __getattr__(self, name): ... + __class__: Incomplete + def __setattr__(self, name, val) -> None: ... + def __setitem__(self, name, val) -> None: ... + def __getitem__(self, name): ... + def declare(self, name, **kwds) -> None: ... + def pprint( + self, ostream, option, from_list: bool = False, prefix: str = '', repn=None + ) -> None: ... + def load(self, repn) -> None: ... + def __getnewargs__(self): ... diff --git a/stubs/pyomo/opt/results/problem.pyi b/stubs/pyomo/opt/results/problem.pyi new file mode 100644 index 000000000..3028288f6 --- /dev/null +++ b/stubs/pyomo/opt/results/problem.pyi @@ -0,0 +1,11 @@ +from pyomo.common.enums import ExtendedEnumType as ExtendedEnumType +from pyomo.common.enums import IntEnum as IntEnum +from pyomo.common.enums import ObjectiveSense as ObjectiveSense +from pyomo.opt.results.container import MapContainer as MapContainer + +class ProblemSense(IntEnum, metaclass=ExtendedEnumType): + __base_enum__ = ObjectiveSense + unknown = 0 + +class ProblemInformation(MapContainer): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/opt/results/results_.pyi b/stubs/pyomo/opt/results/results_.pyi new file mode 100644 index 000000000..50a9f92ba --- /dev/null +++ b/stubs/pyomo/opt/results/results_.pyi @@ -0,0 +1,21 @@ +from _typeshed import Incomplete +from pyomo.common.dependencies import yaml as yaml +from pyomo.common.dependencies import yaml_load_args as yaml_load_args +from pyomo.opt.results.container import ListContainer as ListContainer +from pyomo.opt.results.container import MapContainer as MapContainer +from pyomo.opt.results.container import ignore as ignore +from pyomo.opt.results.container import undefined as undefined +from pyomo.opt.results.solution import default_print_options as dpo + +logger: Incomplete + +class SolverResults(MapContainer): + undefined = undefined + default_print_options = dpo + def __init__(self) -> None: ... + def add(self, name, value, active, description) -> None: ... + def json_repn(self, options=None): ... + def write(self, **kwds) -> None: ... + def write_json(self, **kwds) -> None: ... + def write_yaml(self, **kwds) -> None: ... + def read(self, **kwds) -> None: ... diff --git a/stubs/pyomo/opt/results/solution.pyi b/stubs/pyomo/opt/results/solution.pyi new file mode 100644 index 000000000..628dc165d --- /dev/null +++ b/stubs/pyomo/opt/results/solution.pyi @@ -0,0 +1,43 @@ +import enum + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import OrderedDict as OrderedDict +from pyomo.opt.results.container import ListContainer as ListContainer +from pyomo.opt.results.container import MapContainer as MapContainer +from pyomo.opt.results.container import ignore as ignore + +default_print_options: Incomplete + +class SolutionStatus(str, enum.Enum): + bestSoFar = 'bestSoFar' + error = 'error' + feasible = 'feasible' + globallyOptimal = 'globallyOptimal' + infeasible = 'infeasible' + locallyOptimal = 'locallyOptimal' + optimal = 'optimal' + other = 'other' + stoppedByLimit = 'stoppedByLimit' + unbounded = 'unbounded' + unknown = 'unknown' + unsure = 'unsure' + +intlist: Incomplete +numlist: Incomplete + +class Solution(MapContainer): + def __init__(self) -> None: ... + variable: Incomplete + constraint: Incomplete + problem: Incomplete + objective: Incomplete + def load(self, repn) -> None: ... + def pprint(self, ostream, option, from_list: bool = False, prefix: str = '', repn=None): ... + +class SolutionSet(ListContainer): + def __init__(self) -> None: ... + def __len__(self) -> int: ... + def __call__(self, i: int = 1): ... + def pprint(self, ostream, option, prefix: str = '', repn=None): ... + def load(self, repn) -> None: ... diff --git a/stubs/pyomo/opt/results/solver.pyi b/stubs/pyomo/opt/results/solver.pyi new file mode 100644 index 000000000..eb8c38b9b --- /dev/null +++ b/stubs/pyomo/opt/results/solver.pyi @@ -0,0 +1,53 @@ +import enum + +from pyomo.opt.results.container import MapContainer as MapContainer +from pyomo.opt.results.container import ScalarType as ScalarType + +class SolverStatus(str, enum.Enum): + ok = 'ok' + warning = 'warning' + error = 'error' + aborted = 'aborted' + unknown = 'unknown' + +class TerminationCondition(str, enum.Enum): + unknown = 'unknown' + maxTimeLimit = 'maxTimeLimit' + maxIterations = 'maxIterations' + minFunctionValue = 'minFunctionValue' + minStepLength = 'minStepLength' + globallyOptimal = 'globallyOptimal' + locallyOptimal = 'locallyOptimal' + feasible = 'feasible' + optimal = 'optimal' + maxEvaluations = 'maxEvaluations' + other = 'other' + unbounded = 'unbounded' + infeasible = 'infeasible' + infeasibleOrUnbounded = 'infeasibleOrUnbounded' + invalidProblem = 'invalidProblem' + intermediateNonInteger = 'intermediateNonInteger' + noSolution = 'noSolution' + solverFailure = 'solverFailure' + internalSolverError = 'internalSolverError' + error = 'error' + userInterrupt = 'userInterrupt' + resourceInterrupt = 'resourceInterrupt' + licensingProblems = 'licensingProblems' + @staticmethod + def to_solver_status(tc): ... + +def check_optimal_termination(results): ... +def assert_optimal_termination(results) -> None: ... + +class BranchAndBoundStats(MapContainer): + def __init__(self) -> None: ... + +class BlackBoxStats(MapContainer): + def __init__(self) -> None: ... + +class SolverStatistics(MapContainer): + def __init__(self) -> None: ... + +class SolverInformation(MapContainer): + def __init__(self) -> None: ... diff --git a/stubs/pyomo/opt/solver/__init__.pyi b/stubs/pyomo/opt/solver/__init__.pyi new file mode 100644 index 000000000..d8ef6d90e --- /dev/null +++ b/stubs/pyomo/opt/solver/__init__.pyi @@ -0,0 +1,6 @@ +from pyomo.opt.solver.ilmcmd import ILMLicensedSystemCallSolver as ILMLicensedSystemCallSolver +from pyomo.opt.solver.shellcmd import OptSolver as OptSolver +from pyomo.opt.solver.shellcmd import ResultsFormat as ResultsFormat +from pyomo.opt.solver.shellcmd import SolverResults as SolverResults +from pyomo.opt.solver.shellcmd import SolverStatus as SolverStatus +from pyomo.opt.solver.shellcmd import SystemCallSolver as SystemCallSolver diff --git a/stubs/pyomo/opt/solver/ilmcmd.pyi b/stubs/pyomo/opt/solver/ilmcmd.pyi new file mode 100644 index 000000000..2bd45407e --- /dev/null +++ b/stubs/pyomo/opt/solver/ilmcmd.pyi @@ -0,0 +1,6 @@ +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.opt.solver.shellcmd import SystemCallSolver as SystemCallSolver + +class ILMLicensedSystemCallSolver(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = False): ... diff --git a/stubs/pyomo/opt/solver/shellcmd.pyi b/stubs/pyomo/opt/solver/shellcmd.pyi new file mode 100644 index 000000000..3f57e9f57 --- /dev/null +++ b/stubs/pyomo/opt/solver/shellcmd.pyi @@ -0,0 +1,25 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.log import LoggingIntercept as LoggingIntercept +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import OptSolver as OptSolver +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus + +logger: Incomplete +SUBPROCESS_TIMEOUT_ABS_ADJUST: int +SUBPROCESS_TIMEOUT_REL_ADJUST: float + +class SystemCallSolver(OptSolver): + def __init__(self, **kwargs) -> None: ... + def set_executable(self, name=None, validate: bool = True) -> None: ... + def available(self, exception_flag: bool = False): ... + def create_command_line(self, executable, problem_files) -> None: ... + def process_logfile(self): ... + def process_soln_file(self, results): ... + def executable(self): ... + def process_output(self, rc): ... diff --git a/stubs/pyomo/repn/__init__.pyi b/stubs/pyomo/repn/__init__.pyi new file mode 100644 index 000000000..b14113122 --- /dev/null +++ b/stubs/pyomo/repn/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.repn.standard_aux import compute_standard_repn as compute_standard_repn +from pyomo.repn.standard_repn import StandardRepn as StandardRepn +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn diff --git a/stubs/pyomo/repn/ampl.pyi b/stubs/pyomo/repn/ampl.pyi new file mode 100644 index 000000000..5b717ae93 --- /dev/null +++ b/stubs/pyomo/repn/ampl.pyi @@ -0,0 +1,136 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.common.errors import MouseTrap as MouseTrap +from pyomo.common.numeric_types import native_complex_types as native_complex_types +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import value as value +from pyomo.core.base import Expression as Expression +from pyomo.core.expr import AbsExpression as AbsExpression +from pyomo.core.expr import DivisionExpression as DivisionExpression +from pyomo.core.expr import EqualityExpression as EqualityExpression +from pyomo.core.expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr import InequalityExpression as InequalityExpression +from pyomo.core.expr import LinearExpression as LinearExpression +from pyomo.core.expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr import NegationExpression as NegationExpression +from pyomo.core.expr import PowExpression as PowExpression +from pyomo.core.expr import ProductExpression as ProductExpression +from pyomo.core.expr import RangedExpression as RangedExpression +from pyomo.core.expr import SumExpression as SumExpression +from pyomo.core.expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.repn.util import BeforeChildDispatcher as BeforeChildDispatcher +from pyomo.repn.util import ExitNodeDispatcher as ExitNodeDispatcher +from pyomo.repn.util import ExprType as ExprType +from pyomo.repn.util import InvalidNumber as InvalidNumber +from pyomo.repn.util import apply_node_operation as apply_node_operation +from pyomo.repn.util import complex_number_error as complex_number_error +from pyomo.repn.util import nan as nan +from pyomo.repn.util import sum_like_expression_types as sum_like_expression_types + +TOL: float + +class TextNLDebugTemplate: + unary: Incomplete + binary_sum: str + product: str + division: str + pow: str + abs: str + negation: str + nary_sum: str + exprif: str + and_expr: str + less_than: str + less_equal: str + equality: str + external_fcn: str + var: str + const: str + string: str + monomial: Incomplete + multiplier: Incomplete + +nl_operators: Incomplete + +class TextNLTemplate(TextNLDebugTemplate): ... + +class NLFragment: + def __init__(self, repn, node) -> None: ... + @property + def name(self): ... + +class AMPLRepn: + template = TextNLTemplate + nl: Incomplete + mult: int + const: Incomplete + linear: Incomplete + nonlinear: Incomplete + def __init__(self, const, linear, nonlinear) -> None: ... + def __eq__(self, other): ... + def __hash__(self): ... + def duplicate(self): ... + def compile_repn(self, prefix: str = '', args=None, named_exprs=None): ... + def compile_nonlinear_fragment(self) -> None: ... + named_exprs: Incomplete + def append(self, other) -> None: ... + def to_expr(self, var_map): ... + +class DebugAMPLRepn(AMPLRepn): + template = TextNLDebugTemplate + +def handle_negation_node(visitor, node, arg1): ... +def handle_product_node(visitor, node, arg1, arg2): ... +def handle_division_node(visitor, node, arg1, arg2): ... +def handle_pow_node(visitor, node, arg1, arg2): ... +def handle_abs_node(visitor, node, arg1): ... +def handle_unary_node(visitor, node, arg1): ... +def handle_exprif_node(visitor, node, arg1, arg2, arg3): ... +def handle_equality_node(visitor, node, arg1, arg2): ... +def handle_inequality_node(visitor, node, arg1, arg2): ... +def handle_ranged_inequality_node(visitor, node, arg1, arg2, arg3): ... +def handle_named_expression_node(visitor, node, arg1): ... +def handle_external_function_node(visitor, node, *args): ... + +class AMPLBeforeChildDispatcher(BeforeChildDispatcher): + def __init__(self) -> None: ... + +class AMPLRepnVisitor(StreamBasedExpressionVisitor): + subexpression_cache: Incomplete + external_functions: Incomplete + active_expression_source: Incomplete + var_map: Incomplete + used_named_expressions: Incomplete + symbolic_solver_labels: Incomplete + use_named_exprs: Incomplete + encountered_string_arguments: bool + fixed_vars: Incomplete + evaluate: Incomplete + sorter: Incomplete + Result: Incomplete + template: Incomplete + def __init__( + self, + subexpression_cache, + external_functions, + var_map, + used_named_expressions, + symbolic_solver_labels, + use_named_exprs, + sorter, + ) -> None: ... + def check_constant(self, ans, obj): ... + def cache_fixed_var(self, _id, child) -> None: ... + def node_result_to_amplrepn(self, data): ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def enterNode(self, node): ... + def exitNode(self, node, data): ... + def finalizeResult(self, result): ... + +def evaluate_ampl_nl_expression(nl, external_functions): ... diff --git a/stubs/pyomo/repn/beta/__init__.pyi b/stubs/pyomo/repn/beta/__init__.pyi new file mode 100644 index 000000000..257feb782 --- /dev/null +++ b/stubs/pyomo/repn/beta/__init__.pyi @@ -0,0 +1 @@ +from pyomo.repn.beta import matrix as matrix diff --git a/stubs/pyomo/repn/beta/matrix.pyi b/stubs/pyomo/repn/beta/matrix.pyi new file mode 100644 index 000000000..0823d0dd9 --- /dev/null +++ b/stubs/pyomo/repn/beta/matrix.pyi @@ -0,0 +1,86 @@ +from collections.abc import Mapping + +from _typeshed import Incomplete +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.modeling import NOTSET as NOTSET +from pyomo.common.numeric_types import value as value +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Var as Var +from pyomo.core.base.component import ModelComponentFactory as ModelComponentFactory +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.constraint import IndexedConstraint as IndexedConstraint +from pyomo.core.base.constraint import ScalarConstraint as ScalarConstraint +from pyomo.core.base.set_types import Any as Any +from pyomo.core.expr.numvalue import ZeroConstant as ZeroConstant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +def compile_block_linear_constraints( + parent_block, + constraint_name, + skip_trivial_constraints: bool = False, + single_precision_storage: bool = False, + verbose: bool = False, + descend_into: bool = True, +): ... + +class _LinearConstraintData(ConstraintData): + def __init__(self, index, component=None) -> None: ... + +class _LinearMatrixConstraintData(_LinearConstraintData): + def __init__(self, index, component=None) -> None: ... + def __call__(self, exception=...): ... + def has_lb(self): ... + def has_ub(self): ... + def lslack(self) -> None: ... + def uslack(self) -> None: ... + def index(self): ... + @property + def variables(self): ... + @property + def coefficients(self): ... + linear = coefficients + @property + def constant(self): ... + def to_bounded_expression(self, evaluate_bounds: bool = False): ... + @property + def body(self): ... + @property + def lower(self): ... + @property + def upper(self): ... + @property + def lb(self): ... + @property + def ub(self): ... + @property + def equality(self): ... + @property + def strict_lower(self): ... + @property + def strict_upper(self): ... + def set_value(self, expr) -> None: ... + +class MatrixConstraint(Mapping, IndexedConstraint): + StrictUpperBound: int + UpperBound: int + Equality: int + LowerBound: int + StrictLowerBound: int + NoBound: int + def __init__( + self, nrows, ncols, nnz, prows, jcols, vals, ranges, range_types, varmap + ) -> None: ... + def construct(self, data=None) -> None: ... + def __getitem__(self, key): ... + def __len__(self) -> int: ... + def __iter__(self): ... + def add(self, index, expr) -> None: ... + def __delitem__(self) -> None: ... + def keys(self, sort=None): ... + def values(self, sort=None): ... + def items(self, sort=None): ... diff --git a/stubs/pyomo/repn/linear.pyi b/stubs/pyomo/repn/linear.pyi new file mode 100644 index 000000000..979bf4da5 --- /dev/null +++ b/stubs/pyomo/repn/linear.pyi @@ -0,0 +1,76 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.numeric_types import native_complex_types as native_complex_types +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.expr import is_fixed as is_fixed +from pyomo.core.expr import value as value +from pyomo.core.expr.numeric_expr import AbsExpression as AbsExpression +from pyomo.core.expr.numeric_expr import DivisionExpression as DivisionExpression +from pyomo.core.expr.numeric_expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr.numeric_expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression +from pyomo.core.expr.numeric_expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr.numeric_expr import NegationExpression as NegationExpression +from pyomo.core.expr.numeric_expr import PowExpression as PowExpression +from pyomo.core.expr.numeric_expr import ProductExpression as ProductExpression +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression +from pyomo.core.expr.numeric_expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.numeric_expr import mutable_expression as mutable_expression +from pyomo.core.expr.relational_expr import EqualityExpression as EqualityExpression +from pyomo.core.expr.relational_expr import InequalityExpression as InequalityExpression +from pyomo.core.expr.relational_expr import RangedExpression as RangedExpression +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.repn.util import BeforeChildDispatcher as BeforeChildDispatcher +from pyomo.repn.util import ExitNodeDispatcher as ExitNodeDispatcher +from pyomo.repn.util import ExprType as ExprType +from pyomo.repn.util import FileDeterminism as FileDeterminism +from pyomo.repn.util import FileDeterminism_to_SortComponents as FileDeterminism_to_SortComponents +from pyomo.repn.util import InvalidNumber as InvalidNumber +from pyomo.repn.util import OrderedVarRecorder as OrderedVarRecorder +from pyomo.repn.util import VarRecorder as VarRecorder +from pyomo.repn.util import apply_node_operation as apply_node_operation +from pyomo.repn.util import complex_number_error as complex_number_error +from pyomo.repn.util import initialize_exit_node_dispatcher as initialize_exit_node_dispatcher +from pyomo.repn.util import nan as nan +from pyomo.repn.util import sum_like_expression_types as sum_like_expression_types + +logger: Incomplete + +class LinearRepn: + multiplier: int + constant: int + linear: Incomplete + nonlinear: Incomplete + def __init__(self) -> None: ... + def walker_exitNode(self): ... + def duplicate(self): ... + def to_expression(self, visitor): ... + def append(self, other) -> None: ... + +def to_expression(visitor, arg): ... +def define_exit_node_handlers(_exit_node_handlers=None): ... + +class LinearBeforeChildDispatcher(BeforeChildDispatcher): + def __init__(self) -> None: ... + +class LinearRepnVisitor(StreamBasedExpressionVisitor): + Result = LinearRepn + before_child_dispatcher: Incomplete + exit_node_dispatcher: Incomplete + expand_nonlinear_products: bool + max_exponential_expansion: int + subexpression_cache: Incomplete + var_recorder: Incomplete + var_map: Incomplete + evaluate: Incomplete + def __init__( + self, subexpression_cache, var_map=None, var_order=None, sorter=None, var_recorder=None + ) -> None: ... + def check_constant(self, ans, obj): ... + def initializeWalker(self, expr): ... + def beforeChild(self, node, child, child_idx): ... + def enterNode(self, node): ... + def exitNode(self, node, data): ... + def finalizeResult(self, result): ... diff --git a/stubs/pyomo/repn/linear_template.pyi b/stubs/pyomo/repn/linear_template.pyi new file mode 100644 index 000000000..896be8ba8 --- /dev/null +++ b/stubs/pyomo/repn/linear_template.pyi @@ -0,0 +1,48 @@ +from itertools import chain as chain + +import pyomo.repn.linear as linear +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.errors import MouseTrap as MouseTrap +from pyomo.common.numeric_types import native_types as native_types +from pyomo.core.expr import ExpressionType as ExpressionType +from pyomo.repn.linear import LinearRepn as LinearRepn + +code_type: Incomplete + +class LinearTemplateRepn(LinearRepn): + linear_sum: Incomplete + def __init__(self) -> None: ... + def walker_exitNode(self): ... + def duplicate(self): ... + def append(self, other) -> None: ... + def compile( + self, + env, + smap, + expr_cache, + args, + remove_fixed_vars: bool = False, + check_duplicates: bool = False, + ): ... + +class LinearTemplateBeforeChildDispatcher(linear.LinearBeforeChildDispatcher): ... + +def define_exit_node_handlers(_exit_node_handlers=None): ... + +class LinearTemplateRepnVisitor(linear.LinearRepnVisitor): + Result = LinearTemplateRepn + before_child_dispatcher: Incomplete + exit_node_dispatcher: Incomplete + indexed_vars: Incomplete + indexed_params: Incomplete + expr_cache: Incomplete + env: Incomplete + symbolmap: Incomplete + expanded_templates: Incomplete + remove_fixed_vars: Incomplete + def __init__( + self, subexpression_cache, var_recorder, remove_fixed_vars: bool = False + ) -> None: ... + def enterNode(self, node): ... + def expand_expression(self, obj, template_info): ... diff --git a/stubs/pyomo/repn/parameterized_linear.pyi b/stubs/pyomo/repn/parameterized_linear.pyi new file mode 100644 index 000000000..18357b977 --- /dev/null +++ b/stubs/pyomo/repn/parameterized_linear.pyi @@ -0,0 +1,49 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.core import Var as Var +from pyomo.core.expr.numeric_expr import AbsExpression as AbsExpression +from pyomo.core.expr.numeric_expr import DivisionExpression as DivisionExpression +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression +from pyomo.core.expr.numeric_expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr.numeric_expr import NegationExpression as NegationExpression +from pyomo.core.expr.numeric_expr import PowExpression as PowExpression +from pyomo.core.expr.numeric_expr import ProductExpression as ProductExpression +from pyomo.core.expr.numeric_expr import SumExpression as SumExpression +from pyomo.core.expr.numeric_expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.numeric_expr import mutable_expression as mutable_expression +from pyomo.repn.linear import ExitNodeDispatcher as ExitNodeDispatcher +from pyomo.repn.linear import LinearBeforeChildDispatcher as LinearBeforeChildDispatcher +from pyomo.repn.linear import LinearRepn as LinearRepn +from pyomo.repn.linear import LinearRepnVisitor as LinearRepnVisitor +from pyomo.repn.linear import initialize_exit_node_dispatcher as initialize_exit_node_dispatcher +from pyomo.repn.util import ExprType as ExprType + +def to_expression(visitor, arg): ... + +class ParameterizedLinearRepn(LinearRepn): + def walker_exitNode(self): ... + def to_expression(self, visitor): ... + nonlinear: Incomplete + def append(self, other) -> None: ... + +class ParameterizedLinearBeforeChildDispatcher(LinearBeforeChildDispatcher): + def __init__(self) -> None: ... + +def define_exit_node_handlers(exit_node_handlers=None): ... + +class ParameterizedLinearRepnVisitor(LinearRepnVisitor): + Result = ParameterizedLinearRepn + exit_node_dispatcher: Incomplete + wrt: Incomplete + def __init__( + self, + subexpression_cache, + var_map=None, + var_order=None, + sorter=None, + wrt=None, + var_recorder=None, + ) -> None: ... + def beforeChild(self, node, child, child_idx): ... + def finalizeResult(self, result): ... diff --git a/stubs/pyomo/repn/parameterized_quadratic.pyi b/stubs/pyomo/repn/parameterized_quadratic.pyi new file mode 100644 index 000000000..21e54ca49 --- /dev/null +++ b/stubs/pyomo/repn/parameterized_quadratic.pyi @@ -0,0 +1,34 @@ +from _typeshed import Incomplete +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.core.expr.numeric_expr import DivisionExpression as DivisionExpression +from pyomo.core.expr.numeric_expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr.numeric_expr import PowExpression as PowExpression +from pyomo.core.expr.numeric_expr import ProductExpression as ProductExpression +from pyomo.core.expr.numeric_expr import mutable_expression as mutable_expression +from pyomo.repn.linear import ExitNodeDispatcher as ExitNodeDispatcher +from pyomo.repn.linear import initialize_exit_node_dispatcher as initialize_exit_node_dispatcher +from pyomo.repn.parameterized_linear import ( + ParameterizedLinearRepnVisitor as ParameterizedLinearRepnVisitor, +) +from pyomo.repn.parameterized_linear import to_expression as to_expression +from pyomo.repn.quadratic import QuadraticRepn as QuadraticRepn +from pyomo.repn.util import ExprType as ExprType + +class ParameterizedQuadraticRepn(QuadraticRepn): + def walker_exitNode(self): ... + def to_expression(self, visitor): ... + quadratic: Incomplete + nonlinear: Incomplete + def append(self, other) -> None: ... + +def is_zero(obj): ... +def is_zero_product(e1, e2): ... +def is_equal_to(obj, val): ... +def define_exit_node_handlers(exit_node_handlers=None): ... + +class ParameterizedQuadraticRepnVisitor(ParameterizedLinearRepnVisitor): + Result = ParameterizedQuadraticRepn + exit_node_dispatcher: Incomplete + max_exponential_expansion: int + expand_nonlinear_products: bool + def finalizeResult(self, result): ... diff --git a/stubs/pyomo/repn/plugins/__init__.pyi b/stubs/pyomo/repn/plugins/__init__.pyi new file mode 100644 index 000000000..e586f1817 --- /dev/null +++ b/stubs/pyomo/repn/plugins/__init__.pyi @@ -0,0 +1,3 @@ +def load() -> None: ... +def activate_writer_version(name, ver) -> None: ... +def active_writer_version(name): ... diff --git a/stubs/pyomo/repn/plugins/ampl/__init__.pyi b/stubs/pyomo/repn/plugins/ampl/__init__.pyi new file mode 100644 index 000000000..1afdf3b6f --- /dev/null +++ b/stubs/pyomo/repn/plugins/ampl/__init__.pyi @@ -0,0 +1 @@ +from pyomo.repn.plugins.ampl.ampl_ import ProblemWriter_nl as ProblemWriter_nl diff --git a/stubs/pyomo/repn/plugins/ampl/ampl_.pyi b/stubs/pyomo/repn/plugins/ampl/ampl_.pyi new file mode 100644 index 000000000..133c2037f --- /dev/null +++ b/stubs/pyomo/repn/plugins/ampl/ampl_.pyi @@ -0,0 +1,69 @@ +from _typeshed import Incomplete +from pyomo.common.fileutils import find_library as find_library +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.core.base import ComponentMap as ComponentMap +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import ExternalFunction as ExternalFunction +from pyomo.core.base import NamedExpressionData as NamedExpressionData +from pyomo.core.base import NameLabeler as NameLabeler +from pyomo.core.base import Objective as Objective +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import SOSConstraint as SOSConstraint +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import Var as Var +from pyomo.core.base import param as param +from pyomo.core.base import var as var +from pyomo.core.expr.numvalue import NumericConstant as NumericConstant +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.core.kernel.expression import IIdentityExpression as IIdentityExpression +from pyomo.core.kernel.variable import IVariable as IVariable +from pyomo.opt import AbstractProblemWriter as AbstractProblemWriter +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.repn.standard_repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +def set_pyomo_amplfunc_env(external_libs) -> None: ... + +class StopWatch: + start: Incomplete + def __init__(self) -> None: ... + def report(self, msg) -> None: ... + def reset(self) -> None: ... + +class _Counter: + def __init__(self, start) -> None: ... + def __call__(self, obj): ... + +class ModelSOS: + class AmplSuffix: + name: Incomplete + ids: Incomplete + vals: Incomplete + def __init__(self, name) -> None: ... + def add(self, idx, val) -> None: ... + def genfilelines(self): ... + def is_empty(self): ... + + ampl_var_id: Incomplete + sosno: Incomplete + ref: Incomplete + block_cntr: int + varID_map: Incomplete + def __init__(self, ampl_var_id, varID_map) -> None: ... + def count_constraint(self, soscondata) -> None: ... + +class RepnWrapper: + repn: Incomplete + linear_vars: Incomplete + nonlinear_vars: Incomplete + def __init__(self, repn, linear, nonlinear) -> None: ... + +class ProblemWriter_nl(AbstractProblemWriter): + def __init__(self) -> None: ... + def __call__(self, model, filename, solver_capability, io_options): ... diff --git a/stubs/pyomo/repn/plugins/baron_writer.pyi b/stubs/pyomo/repn/plugins/baron_writer.pyi new file mode 100644 index 000000000..988952a4d --- /dev/null +++ b/stubs/pyomo/repn/plugins/baron_writer.pyi @@ -0,0 +1,35 @@ +from _typeshed import Incomplete +from pyomo.common.collections import OrderedSet as OrderedSet +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import ShortNameLabeler as ShortNameLabeler +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import _ToStringVisitor +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt.base import AbstractProblemWriter as AbstractProblemWriter +from pyomo.opt.base import WriterFactory as WriterFactory +from pyomo.repn.util import ftoa as ftoa +from pyomo.repn.util import valid_active_ctypes_minlp as valid_active_ctypes_minlp +from pyomo.repn.util import valid_expr_ctypes_minlp as valid_expr_ctypes_minlp + +logger: Incomplete + +class ToBaronVisitor(_ToStringVisitor): + variables: Incomplete + def __init__(self, variables, smap) -> None: ... + def visiting_potential_leaf(self, node): ... + +def expression_to_string(expr, variables, smap): ... + +class ProblemWriter_bar(AbstractProblemWriter): + def __init__(self) -> None: ... + def __call__(self, model, output_filename, solver_capability, io_options): ... diff --git a/stubs/pyomo/repn/plugins/cpxlp.pyi b/stubs/pyomo/repn/plugins/cpxlp.pyi new file mode 100644 index 000000000..5fbe6e894 --- /dev/null +++ b/stubs/pyomo/repn/plugins/cpxlp.pyi @@ -0,0 +1,33 @@ +from _typeshed import Incomplete +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.core.base import ComponentMap as ComponentMap +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import Objective as Objective +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import SOSConstraint as SOSConstraint +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base import Var as Var +from pyomo.core.base import is_fixed as is_fixed +from pyomo.core.base import value as value +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt.base import AbstractProblemWriter as AbstractProblemWriter +from pyomo.opt.base import WriterFactory as WriterFactory +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class ProblemWriter_cpxlp(AbstractProblemWriter): + linear_coef_string_template: Incomplete + quad_coef_string_template: Incomplete + obj_string_template: Incomplete + sos_template_string: Incomplete + eq_string_template: Incomplete + geq_string_template: Incomplete + leq_string_template: Incomplete + lb_string_template: Incomplete + ub_string_template: Incomplete + def __init__(self) -> None: ... + def __call__(self, model, output_filename, solver_capability, io_options): ... + def printSOS(self, symbol_map, labeler, variable_symbol_map, soscondata, output) -> None: ... diff --git a/stubs/pyomo/repn/plugins/gams_writer.pyi b/stubs/pyomo/repn/plugins/gams_writer.pyi new file mode 100644 index 000000000..cbf7033fa --- /dev/null +++ b/stubs/pyomo/repn/plugins/gams_writer.pyi @@ -0,0 +1,67 @@ +from _typeshed import Incomplete +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import Objective as Objective +from pyomo.core.base import ShortNameLabeler as ShortNameLabeler +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import Var as Var +from pyomo.core.base import minimize as minimize +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.expr.numvalue import as_numeric as as_numeric +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.numvalue import nonpyomo_leaf_types as nonpyomo_leaf_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import _ToStringVisitor +from pyomo.core.kernel.base import ICategorizedObject as ICategorizedObject +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt.base import AbstractProblemWriter as AbstractProblemWriter +from pyomo.opt.base import WriterFactory as WriterFactory +from pyomo.repn.util import ftoa as ftoa +from pyomo.repn.util import valid_active_ctypes_minlp as valid_active_ctypes_minlp +from pyomo.repn.util import valid_expr_ctypes_minlp as valid_expr_ctypes_minlp + +logger: Incomplete + +class ToGamsVisitor(_ToStringVisitor): + treechecker: Incomplete + is_discontinuous: bool + output_fixed_variables: Incomplete + def __init__(self, smap, treechecker, output_fixed_variables: bool = False) -> None: ... + def visiting_potential_leaf(self, node): ... + +def expression_to_string(expr, treechecker, smap=None, output_fixed_variables: bool = False): ... + +class Categorizer: + binary: Incomplete + ints: Incomplete + positive: Incomplete + reals: Incomplete + fixed: Incomplete + def __init__(self, var_list, symbol_map) -> None: ... + def __iter__(self): ... + +class StorageTreeChecker: + tree: Incomplete + model: Incomplete + def __init__(self, model) -> None: ... + def __call__(self, comp, exception_flag: bool = True): ... + def parent_block(self, comp): ... + def raise_error(self, comp) -> None: ... + +def split_long_line(line): ... + +class GAMSSymbolMap(SymbolMap): + var_labeler: Incomplete + var_list: Incomplete + def __init__(self, var_labeler, var_list) -> None: ... + def var_label(self, obj): ... + def var_recorder(self, obj): ... + +class ProblemWriter_gams(AbstractProblemWriter): + def __init__(self) -> None: ... + def __call__(self, model, output_filename, solver_capability, io_options): ... + +valid_solvers: Incomplete diff --git a/stubs/pyomo/repn/plugins/lp_writer.pyi b/stubs/pyomo/repn/plugins/lp_writer.pyi new file mode 100644 index 000000000..0497aebda --- /dev/null +++ b/stubs/pyomo/repn/plugins/lp_writer.pyi @@ -0,0 +1,64 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import InEnum as InEnum +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.timing import TicTocTimer as TicTocTimer +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Expression as Expression +from pyomo.core.base import ExternalFunction as ExternalFunction +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import RangeSet as RangeSet +from pyomo.core.base import Set as Set +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import SOSConstraint as SOSConstraint +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import Var as Var +from pyomo.core.base import minimize as minimize +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.base.label import LPFileLabeler as LPFileLabeler +from pyomo.core.base.label import NumericLabeler as NumericLabeler +from pyomo.network import Port as Port +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.repn.linear import LinearRepnVisitor as LinearRepnVisitor +from pyomo.repn.quadratic import QuadraticRepnVisitor as QuadraticRepnVisitor +from pyomo.repn.util import FileDeterminism as FileDeterminism +from pyomo.repn.util import FileDeterminism_to_SortComponents as FileDeterminism_to_SortComponents +from pyomo.repn.util import OrderedVarRecorder as OrderedVarRecorder +from pyomo.repn.util import categorize_valid_components as categorize_valid_components +from pyomo.repn.util import ( + initialize_var_map_from_column_order as initialize_var_map_from_column_order, +) +from pyomo.repn.util import int_float as int_float +from pyomo.repn.util import ordered_active_constraints as ordered_active_constraints + +logger: Incomplete +inf: Incomplete +neg_inf: Incomplete + +class LPWriterInfo: + symbol_map: Incomplete + def __init__(self, symbol_map) -> None: ... + +class LPWriter: + CONFIG: Incomplete + config: Incomplete + def __init__(self) -> None: ... + def __call__(self, model, filename, solver_capability, io_options): ... + def write(self, model, ostream, **options): ... + +class _LPWriter_impl: + ostream: Incomplete + config: Incomplete + symbol_map: Incomplete + def __init__(self, ostream, config) -> None: ... + sorter: Incomplete + var_map: Incomplete + var_order: Incomplete + var_recorder: Incomplete + def write(self, model): ... + def write_expression(self, ostream, expr, is_objective): ... diff --git a/stubs/pyomo/repn/plugins/mps.pyi b/stubs/pyomo/repn/plugins/mps.pyi new file mode 100644 index 000000000..31db44451 --- /dev/null +++ b/stubs/pyomo/repn/plugins/mps.pyi @@ -0,0 +1,23 @@ +from _typeshed import Incomplete +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.core.base import ComponentMap as ComponentMap +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import Objective as Objective +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import SOSConstraint as SOSConstraint +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base import Var as Var +from pyomo.core.base import is_fixed as is_fixed +from pyomo.core.base import value as value +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt.base import AbstractProblemWriter as AbstractProblemWriter +from pyomo.opt.base import WriterFactory as WriterFactory +from pyomo.repn import generate_standard_repn as generate_standard_repn + +logger: Incomplete + +class ProblemWriter_mps(AbstractProblemWriter): + def __init__(self, int_marker: bool = False) -> None: ... + def __call__(self, model, output_filename, solver_capability, io_options): ... diff --git a/stubs/pyomo/repn/plugins/nl_writer.pyi b/stubs/pyomo/repn/plugins/nl_writer.pyi new file mode 100644 index 000000000..3ccecba3b --- /dev/null +++ b/stubs/pyomo/repn/plugins/nl_writer.pyi @@ -0,0 +1,134 @@ +import types +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.config import ConfigDict as ConfigDict +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import InEnum as InEnum +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import InfeasibleConstraintException as InfeasibleConstraintException +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.timing import TicTocTimer as TicTocTimer +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Expression as Expression +from pyomo.core.base import ExternalFunction as ExternalFunction +from pyomo.core.base import NameLabeler as NameLabeler +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import RangeSet as RangeSet +from pyomo.core.base import Set as Set +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import SOSConstraint as SOSConstraint +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import Var as Var +from pyomo.core.base import minimize as minimize +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.base.constraint import ConstraintData as ConstraintData +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.objective import ScalarObjective as ScalarObjective +from pyomo.core.base.suffix import SuffixFinder as SuffixFinder +from pyomo.core.base.var import VarData as VarData +from pyomo.core.pyomoobject import PyomoObject as PyomoObject +from pyomo.network import Port as Port +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.repn.ampl import TOL as TOL +from pyomo.repn.ampl import AMPLRepnVisitor as AMPLRepnVisitor +from pyomo.repn.ampl import evaluate_ampl_nl_expression as evaluate_ampl_nl_expression +from pyomo.repn.plugins.ampl.ampl_ import set_pyomo_amplfunc_env as set_pyomo_amplfunc_env +from pyomo.repn.util import FileDeterminism as FileDeterminism +from pyomo.repn.util import FileDeterminism_to_SortComponents as FileDeterminism_to_SortComponents +from pyomo.repn.util import categorize_valid_components as categorize_valid_components +from pyomo.repn.util import ( + initialize_var_map_from_column_order as initialize_var_map_from_column_order, +) +from pyomo.repn.util import int_float as int_float +from pyomo.repn.util import ordered_active_constraints as ordered_active_constraints + +logger: Incomplete +inf: Incomplete +minus_inf: Incomplete +allowable_binary_var_bounds: Incomplete + +class ScalingFactors(NamedTuple): + variables: Incomplete + constraints: Incomplete + objectives: Incomplete + +class NLWriterInfo: + variables: Incomplete + constraints: Incomplete + objectives: Incomplete + external_function_libraries: Incomplete + row_labels: Incomplete + column_labels: Incomplete + eliminated_vars: Incomplete + scaling: Incomplete + def __init__( + self, var, con, obj, external_libs, row_labels, col_labels, eliminated_vars, scaling + ) -> None: ... + +class NLWriter: + CONFIG: Incomplete + config: Incomplete + def __init__(self) -> None: ... + def __call__(self, model, filename, solver_capability, io_options): ... + def write(self, model, ostream, rowstream=None, colstream=None, **options) -> NLWriterInfo: ... + +class _SuffixData: + name: Incomplete + obj: Incomplete + con: Incomplete + var: Incomplete + prob: Incomplete + datatype: Incomplete + values: Incomplete + def __init__(self, name) -> None: ... + def update(self, suffix) -> None: ... + def store(self, obj, val) -> None: ... + def compile(self, column_order, row_order, obj_order, model_id) -> None: ... + +class CachingNumericSuffixFinder(SuffixFinder): + scale: bool + suffix_cache: Incomplete + def __init__(self, name, default=None, context=None) -> None: ... + def __call__(self, obj): ... + +class _NoScalingFactor: + scale: bool + def __call__(self, obj): ... + +class _NLWriter_impl: + ostream: Incomplete + rowstream: Incomplete + colstream: Incomplete + config: Incomplete + symbolic_solver_labels: Incomplete + subexpression_cache: Incomplete + subexpression_order: Incomplete + external_functions: Incomplete + used_named_expressions: Incomplete + var_map: Incomplete + var_id_to_nl_map: Incomplete + sorter: Incomplete + visitor: Incomplete + next_V_line_id: int + pause_gc: Incomplete + template: Incomplete + def __init__(self, ostream, rowstream, colstream, config) -> None: ... + def __enter__(self): ... + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + column_order: Incomplete + def write(self, model): ... diff --git a/stubs/pyomo/repn/plugins/parameterized_standard_form.pyi b/stubs/pyomo/repn/plugins/parameterized_standard_form.pyi new file mode 100644 index 000000000..67b12c9cd --- /dev/null +++ b/stubs/pyomo/repn/plugins/parameterized_standard_form.pyi @@ -0,0 +1,43 @@ +from _typeshed import Incomplete +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.core import Var as Var +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.repn.parameterized_linear import ( + ParameterizedLinearRepnVisitor as ParameterizedLinearRepnVisitor, +) +from pyomo.repn.plugins.standard_form import ( + LinearStandardFormCompiler as LinearStandardFormCompiler, +) +from pyomo.repn.plugins.standard_form import LinearStandardFormInfo as LinearStandardFormInfo +from pyomo.repn.plugins.standard_form import _LinearStandardFormCompiler_impl +from pyomo.util.config_domains import ComponentDataSet as ComponentDataSet + +class ParameterizedLinearStandardFormCompiler(LinearStandardFormCompiler): + CONFIG: Incomplete + def write(self, model, ostream=None, **options): ... + +class _SparseMatrixBase: + data: Incomplete + indices: Incomplete + indptr: Incomplete + shape: Incomplete + def __init__(self, matrix_data, shape) -> None: ... + def __eq__(self, other): ... + +class _CSRMatrix(_SparseMatrixBase): + def __init__(self, matrix_data, shape) -> None: ... + def tocsc(self): ... + def todense(self): ... + +class _CSCMatrix(_SparseMatrixBase): + def __init__(self, matrix_data, shape) -> None: ... + def todense(self): ... + data: Incomplete + row_index: Incomplete + def sum_duplicates(self) -> None: ... + def eliminate_zeros(self) -> None: ... + +class _ParameterizedLinearStandardFormCompiler_impl(_LinearStandardFormCompiler_impl): ... diff --git a/stubs/pyomo/repn/plugins/standard_form.pyi b/stubs/pyomo/repn/plugins/standard_form.pyi new file mode 100644 index 000000000..1aedbfc77 --- /dev/null +++ b/stubs/pyomo/repn/plugins/standard_form.pyi @@ -0,0 +1,70 @@ +from typing import NamedTuple + +from _typeshed import Incomplete +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigValue as ConfigValue +from pyomo.common.config import InEnum as InEnum +from pyomo.common.config import document_kwargs_from_configdict as document_kwargs_from_configdict +from pyomo.common.dependencies import scipy as scipy +from pyomo.common.enums import ObjectiveSense as ObjectiveSense +from pyomo.common.gc_manager import PauseGC as PauseGC +from pyomo.common.numeric_types import native_types as native_types +from pyomo.common.numeric_types import value as value +from pyomo.common.timing import TicTocTimer as TicTocTimer +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Expression as Expression +from pyomo.core.base import ExternalFunction as ExternalFunction +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import RangeSet as RangeSet +from pyomo.core.base import Set as Set +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import Var as Var +from pyomo.network import Port as Port +from pyomo.opt import WriterFactory as WriterFactory +from pyomo.repn.linear import LinearRepnVisitor as LinearRepnVisitor +from pyomo.repn.linear_template import LinearTemplateRepnVisitor as LinearTemplateRepnVisitor +from pyomo.repn.util import FileDeterminism as FileDeterminism +from pyomo.repn.util import FileDeterminism_to_SortComponents as FileDeterminism_to_SortComponents +from pyomo.repn.util import TemplateVarRecorder as TemplateVarRecorder +from pyomo.repn.util import categorize_valid_components as categorize_valid_components +from pyomo.repn.util import ( + initialize_var_map_from_column_order as initialize_var_map_from_column_order, +) +from pyomo.repn.util import ordered_active_constraints as ordered_active_constraints + +logger: Incomplete + +class RowEntry(NamedTuple): + constraint: Incomplete + bound_type: Incomplete + +class LinearStandardFormInfo: + c: Incomplete + c_offset: Incomplete + A: Incomplete + rhs: Incomplete + rows: Incomplete + columns: Incomplete + objectives: Incomplete + eliminated_vars: Incomplete + def __init__(self, c, c_offset, A, rhs, rows, columns, objectives, eliminated_vars) -> None: ... + @property + def x(self): ... + @property + def b(self): ... + +class LinearStandardFormCompiler: + CONFIG: Incomplete + config: Incomplete + def __init__(self) -> None: ... + def write(self, model, ostream=None, **options): ... + +class _LinearStandardFormCompiler_impl: + config: Incomplete + def __init__(self, config) -> None: ... + var_map: Incomplete + def write(self, model): ... diff --git a/stubs/pyomo/repn/quadratic.pyi b/stubs/pyomo/repn/quadratic.pyi new file mode 100644 index 000000000..d5e1d9363 --- /dev/null +++ b/stubs/pyomo/repn/quadratic.pyi @@ -0,0 +1,38 @@ +from _typeshed import Incomplete +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.expr.numeric_expr import AbsExpression as AbsExpression +from pyomo.core.expr.numeric_expr import DivisionExpression as DivisionExpression +from pyomo.core.expr.numeric_expr import Expr_ifExpression as Expr_ifExpression +from pyomo.core.expr.numeric_expr import LinearExpression as LinearExpression +from pyomo.core.expr.numeric_expr import MonomialTermExpression as MonomialTermExpression +from pyomo.core.expr.numeric_expr import NegationExpression as NegationExpression +from pyomo.core.expr.numeric_expr import PowExpression as PowExpression +from pyomo.core.expr.numeric_expr import ProductExpression as ProductExpression +from pyomo.core.expr.numeric_expr import UnaryFunctionExpression as UnaryFunctionExpression +from pyomo.core.expr.numeric_expr import mutable_expression as mutable_expression +from pyomo.core.expr.relational_expr import EqualityExpression as EqualityExpression +from pyomo.core.expr.relational_expr import InequalityExpression as InequalityExpression +from pyomo.core.expr.relational_expr import RangedExpression as RangedExpression + +from . import linear as linear +from . import util as util +from .linear import to_expression as to_expression + +class QuadraticRepn: + multiplier: int + constant: int + linear: Incomplete + quadratic: Incomplete + nonlinear: Incomplete + def __init__(self) -> None: ... + def walker_exitNode(self): ... + def duplicate(self): ... + def to_expression(self, visitor): ... + def append(self, other) -> None: ... + +def define_exit_node_handlers(_exit_node_handlers=None): ... + +class QuadraticRepnVisitor(linear.LinearRepnVisitor): + Result = QuadraticRepn + exit_node_dispatcher: Incomplete + max_exponential_expansion: int diff --git a/stubs/pyomo/repn/standard_aux.pyi b/stubs/pyomo/repn/standard_aux.pyi new file mode 100644 index 000000000..65afea0d1 --- /dev/null +++ b/stubs/pyomo/repn/standard_aux.pyi @@ -0,0 +1,4 @@ +from pyomo.repn.standard_repn import preprocess_block_constraints as preprocess_block_constraints +from pyomo.repn.standard_repn import preprocess_block_objectives as preprocess_block_objectives + +def compute_standard_repn(data, model=None) -> None: ... diff --git a/stubs/pyomo/repn/standard_repn.pyi b/stubs/pyomo/repn/standard_repn.pyi new file mode 100644 index 000000000..0959fb4cb --- /dev/null +++ b/stubs/pyomo/repn/standard_repn.pyi @@ -0,0 +1,76 @@ +from _typeshed import Incomplete +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.core.base import ComponentMap as ComponentMap +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Expression as Expression +from pyomo.core.base import Objective as Objective +from pyomo.core.base.expression import ExpressionData as ExpressionData +from pyomo.core.base.expression import NamedExpressionData as NamedExpressionData +from pyomo.core.base.expression import ScalarExpression as ScalarExpression +from pyomo.core.base.objective import ObjectiveData as ObjectiveData +from pyomo.core.base.objective import ScalarObjective as ScalarObjective +from pyomo.core.base.param import ParamData as ParamData +from pyomo.core.base.param import ScalarParam as ScalarParam +from pyomo.core.base.var import ScalarVar as ScalarVar +from pyomo.core.base.var import Var as Var +from pyomo.core.base.var import VarData as VarData +from pyomo.core.base.var import value as value +from pyomo.core.expr.numvalue import NumericConstant as NumericConstant +from pyomo.core.kernel.expression import expression as expression +from pyomo.core.kernel.expression import noclone as noclone +from pyomo.core.kernel.objective import objective as objective +from pyomo.core.kernel.variable import IVariable as IVariable +from pyomo.core.kernel.variable import variable as variable + +logger: Incomplete + +def isclose_const(a, b, rel_tol: float = 1e-09, abs_tol: float = 0.0): ... + +class StandardRepn: + constant: int + linear_vars: Incomplete + linear_coefs: Incomplete + quadratic_vars: Incomplete + quadratic_coefs: Incomplete + nonlinear_expr: Incomplete + nonlinear_vars: Incomplete + def __init__(self) -> None: ... + def is_fixed(self): ... + def polynomial_degree(self): ... + def is_constant(self): ... + def is_linear(self): ... + def is_quadratic(self): ... + def is_nonlinear(self): ... + def to_expression(self, sort: bool = True): ... + +def generate_standard_repn( + expr, + idMap=None, + compute_values: bool = True, + verbose: bool = False, + quadratic: bool = True, + repn=None, +): ... + +class ResultsWithQuadratics: + __slot__: Incomplete + constant: Incomplete + nonl: Incomplete + linear: Incomplete + quadratic: Incomplete + def __init__(self, constant: int = 0, nonl: int = 0, linear=None, quadratic=None) -> None: ... + +class ResultsWithoutQuadratics: + __slot__: Incomplete + constant: Incomplete + nonl: Incomplete + linear: Incomplete + def __init__(self, constant: int = 0, nonl: int = 0, linear=None) -> None: ... + +Results = ResultsWithQuadratics + +def preprocess_block_objectives(block, idMap=None) -> None: ... +def preprocess_block_constraints(block, idMap=None) -> None: ... +def preprocess_constraint(block, constraint, idMap=None, block_repn=None) -> None: ... +def preprocess_constraint_data(block, constraint_data, idMap=None, block_repn=None) -> None: ... diff --git a/stubs/pyomo/repn/util.pyi b/stubs/pyomo/repn/util.pyi new file mode 100644 index 000000000..3789dbc39 --- /dev/null +++ b/stubs/pyomo/repn/util.pyi @@ -0,0 +1,121 @@ +import collections + +from _typeshed import Incomplete +from pyomo.common import enums as enums +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.collections import Sequence as Sequence +from pyomo.common.deprecation import deprecation_warning as deprecation_warning +from pyomo.common.errors import DeveloperError as DeveloperError +from pyomo.common.errors import InvalidValueError as InvalidValueError +from pyomo.common.numeric_types import check_if_numeric_type as check_if_numeric_type +from pyomo.common.numeric_types import native_complex_types as native_complex_types +from pyomo.common.numeric_types import native_logical_types as native_logical_types +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import native_types as native_types +from pyomo.core.base import Block as Block +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Expression as Expression +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import SortComponents as SortComponents +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import Var as Var +from pyomo.core.base.component import ActiveComponent as ActiveComponent +from pyomo.core.base.expression import NamedExpressionData as NamedExpressionData +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import value as value +from pyomo.core.pyomoobject import PyomoObject as PyomoObject + +logger: Incomplete +valid_expr_ctypes_minlp: Incomplete +valid_active_ctypes_minlp: Incomplete +sum_like_expression_types: Incomplete +HALT_ON_EVALUATION_ERROR: bool +nan: Incomplete +int_float: Incomplete + +class ExprType(enums.IntEnum): + CONSTANT = 0 + FIXED = 3 + VARIABLE = 5 + MONOMIAL = 10 + LINEAR = 20 + QUADRATIC = 30 + GENERAL = 40 + +class FileDeterminism(enums.IntEnum): + NONE = 0 + ORDERED = 10 + SORT_INDICES = 20 + SORT_SYMBOLS = 30 + def __format__(self, spec) -> str: ... + +class InvalidNumber(PyomoObject): + value: Incomplete + causes: Incomplete + def __init__(self, value, cause: str = '') -> None: ... + @staticmethod + def parse_args(*args): ... + def __eq__(self, other): ... + def __lt__(self, other): ... + def __gt__(self, other): ... + def __le__(self, other): ... + def __ge__(self, other): ... + def __format__(self, format_spec) -> str: ... + def __float__(self) -> float: ... + def __neg__(self): ... + def __abs__(self): ... + def __add__(self, other): ... + def __sub__(self, other): ... + def __mul__(self, other): ... + def __truediv__(self, other): ... + def __pow__(self, other): ... + def __radd__(self, other): ... + def __rsub__(self, other): ... + def __rmul__(self, other): ... + def __rtruediv__(self, other): ... + def __rpow__(self, other): ... + +class BeforeChildDispatcher(collections.defaultdict): + def __missing__(self, key): ... + def register_dispatcher(self, visitor, child): ... + +class ExitNodeDispatcher(collections.defaultdict): + def __init__(self, *args, **kwargs) -> None: ... + def __missing__(self, key): ... + def unexpected_expression_type(self, visitor, node, *args) -> None: ... + +def initialize_exit_node_dispatcher(exit_handlers): ... +def apply_node_operation(node, args): ... +def complex_number_error(value, visitor, expr, node: str = ''): ... +def categorize_valid_components(model, active: bool = True, sort=None, valid=..., targets=...): ... +def FileDeterminism_to_SortComponents(file_determinism): ... +def initialize_var_map_from_column_order(model, config, var_map): ... +def ordered_active_constraints(model, config): ... + +class VarRecorder: + var_map: Incomplete + sorter: Incomplete + def __init__(self, var_map, sorter) -> None: ... + def add(self, var) -> None: ... + +class OrderedVarRecorder: + var_map: Incomplete + var_order: Incomplete + sorter: Incomplete + def __init__(self, var_map, var_order, sorter) -> None: ... + def add(self, var) -> None: ... + +class TemplateVarRecorder: + var_map: Incomplete + sorter: Incomplete + env: Incomplete + symbolmap: Incomplete + def __init__(self, var_map, var_order, sorter) -> None: ... + @property + def var_order(self): ... + def add(self, var) -> None: ... + +def ftoa(val, parenthesize_negative_values: bool = False): ... diff --git a/stubs/pyomo/scripting/__init__.pyi b/stubs/pyomo/scripting/__init__.pyi new file mode 100644 index 000000000..e91cc075c --- /dev/null +++ b/stubs/pyomo/scripting/__init__.pyi @@ -0,0 +1,2 @@ +from pyomo.scripting import pyomo_command as pyomo_command +from pyomo.scripting import util as util diff --git a/stubs/pyomo/scripting/commands.pyi b/stubs/pyomo/scripting/commands.pyi new file mode 100644 index 000000000..023b0dd50 --- /dev/null +++ b/stubs/pyomo/scripting/commands.pyi @@ -0,0 +1,4 @@ +from pyomo.common._command import pyomo_command as pyomo_command +from pyomo.common.deprecation import deprecated as deprecated + +def pyomo_python(args=None) -> None: ... diff --git a/stubs/pyomo/scripting/convert.pyi b/stubs/pyomo/scripting/convert.pyi new file mode 100644 index 000000000..f000c782b --- /dev/null +++ b/stubs/pyomo/scripting/convert.pyi @@ -0,0 +1,14 @@ +from pyomo.common.collections import Bunch as Bunch +from pyomo.core.base import ConcreteModel as ConcreteModel +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Var as Var +from pyomo.core.base import value as value +from pyomo.opt import ProblemFormat as ProblemFormat + +def convert(options=..., parser=None, model_format=None): ... +def convert_dakota(options=..., parser=None): ... +def pyomo2lp(args=None): ... +def pyomo2nl(args=None): ... +def pyomo2bar(args=None): ... +def pyomo2dakota(args=None): ... diff --git a/stubs/pyomo/scripting/driver_help.pyi b/stubs/pyomo/scripting/driver_help.pyi new file mode 100644 index 000000000..778fcbd35 --- /dev/null +++ b/stubs/pyomo/scripting/driver_help.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.tee import capture_output as capture_output + +logger: Incomplete + +def setup_command_parser(parser) -> None: ... +def command_exec(options): ... +def help_commands(): ... +def help_writers() -> None: ... +def help_datamanagers(options) -> None: ... +def help_environment() -> None: ... +def help_transformations() -> None: ... +def help_solvers(): ... +def print_components(data) -> None: ... +def help_exec(options) -> None: ... +def setup_help_parser(parser): ... + +help_parser: Incomplete diff --git a/stubs/pyomo/scripting/interface.pyi b/stubs/pyomo/scripting/interface.pyi new file mode 100644 index 000000000..c65fd1e48 --- /dev/null +++ b/stubs/pyomo/scripting/interface.pyi @@ -0,0 +1,53 @@ +from _typeshed import Incomplete +from pyomo.common.plugin_base import DeprecatedInterface as DeprecatedInterface +from pyomo.common.plugin_base import ExtensionPoint as ExtensionPoint +from pyomo.common.plugin_base import Interface as Interface +from pyomo.common.plugin_base import Plugin as Plugin +from pyomo.common.plugin_base import SingletonPlugin as SingletonPlugin +from pyomo.common.plugin_base import alias as alias +from pyomo.common.plugin_base import implements as implements + +registered_callback: Incomplete + +def pyomo_callback(name): ... + +class IPyomoScriptPreprocess(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptCreateModel(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptModifyInstance(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptCreateDataPortal(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptPrintModel(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptPrintInstance(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptSaveInstance(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptPrintResults(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptSaveResults(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoScriptPostprocess(Interface): + def apply(self, **kwds) -> None: ... + +class IPyomoPresolver(Interface): + def get_actions(self) -> None: ... + def activate_action(self, action) -> None: ... + def deactivate_action(self, action) -> None: ... + def set_actions(self, actions) -> None: ... + def presolve(self, instance) -> None: ... + +class IPyomoPresolveAction(Interface): + def presolve(self, instance) -> None: ... + def rank(self) -> None: ... diff --git a/stubs/pyomo/scripting/plugins/__init__.pyi b/stubs/pyomo/scripting/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/scripting/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/scripting/plugins/build_ext.pyi b/stubs/pyomo/scripting/plugins/build_ext.pyi new file mode 100644 index 000000000..afb26bbd5 --- /dev/null +++ b/stubs/pyomo/scripting/plugins/build_ext.pyi @@ -0,0 +1,6 @@ +from pyomo.common.extensions import ExtensionBuilderFactory as ExtensionBuilderFactory +from pyomo.scripting.pyomo_parser import add_subparser as add_subparser + +class ExtensionBuilder: + def create_parser(self, parser): ... + def call(self, args, unparsed): ... diff --git a/stubs/pyomo/scripting/plugins/convert.pyi b/stubs/pyomo/scripting/plugins/convert.pyi new file mode 100644 index 000000000..704222c31 --- /dev/null +++ b/stubs/pyomo/scripting/plugins/convert.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.opt import guess_format as guess_format +from pyomo.scripting.pyomo_parser import CustomHelpFormatter as CustomHelpFormatter +from pyomo.scripting.pyomo_parser import add_subparser as add_subparser +from pyomo.scripting.solve_config import Default_Config as Default_Config + +def create_parser(parser=None): ... +def run_convert(options=..., parser=None): ... +def convert_exec(args, unparsed): ... + +convert_parser: Incomplete + +def create_temporary_parser(output: bool = False, generate: bool = False): ... diff --git a/stubs/pyomo/scripting/plugins/download.pyi b/stubs/pyomo/scripting/plugins/download.pyi new file mode 100644 index 000000000..a569d2760 --- /dev/null +++ b/stubs/pyomo/scripting/plugins/download.pyi @@ -0,0 +1,10 @@ +from _typeshed import Incomplete +from pyomo.common.download import DownloadFactory as DownloadFactory +from pyomo.common.download import FileDownloader as FileDownloader +from pyomo.scripting.pyomo_parser import add_subparser as add_subparser + +class GroupDownloader: + downloader: Incomplete + def __init__(self) -> None: ... + def create_parser(self, parser): ... + def call(self, args, unparsed): ... diff --git a/stubs/pyomo/scripting/plugins/extras.pyi b/stubs/pyomo/scripting/plugins/extras.pyi new file mode 100644 index 000000000..c1fb98a6a --- /dev/null +++ b/stubs/pyomo/scripting/plugins/extras.pyi @@ -0,0 +1,7 @@ +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.scripting.pyomo_parser import CustomHelpFormatter as CustomHelpFormatter +from pyomo.scripting.pyomo_parser import add_subparser as add_subparser + +def get_packages(): ... +def install_extras(args=[], quiet: bool = False) -> None: ... +def pyomo_subcommand(options): ... diff --git a/stubs/pyomo/scripting/plugins/solve.pyi b/stubs/pyomo/scripting/plugins/solve.pyi new file mode 100644 index 000000000..8a81343e6 --- /dev/null +++ b/stubs/pyomo/scripting/plugins/solve.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import UnknownSolver as UnknownSolver +from pyomo.scripting.pyomo_parser import CustomHelpFormatter as CustomHelpFormatter +from pyomo.scripting.pyomo_parser import add_subparser as add_subparser + +def create_parser(parser=None): ... +def create_temporary_parser(solver: bool = False, generate: bool = False): ... +def solve_exec(args, unparsed): ... + +solve_parser: Incomplete diff --git a/stubs/pyomo/scripting/pyomo_command.pyi b/stubs/pyomo/scripting/pyomo_command.pyi new file mode 100644 index 000000000..c4acd442f --- /dev/null +++ b/stubs/pyomo/scripting/pyomo_command.pyi @@ -0,0 +1,5 @@ +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import pympler_available as pympler_available +from pyomo.core import ConcreteModel as ConcreteModel + +def run_pyomo(options=..., parser=None): ... diff --git a/stubs/pyomo/scripting/pyomo_main.pyi b/stubs/pyomo/scripting/pyomo_main.pyi new file mode 100644 index 000000000..f62aeb1e9 --- /dev/null +++ b/stubs/pyomo/scripting/pyomo_main.pyi @@ -0,0 +1,12 @@ +from _typeshed import Incomplete +from pyomo.common.deprecation import deprecation_warning as deprecation_warning + +pyomo_commands: Incomplete +plugin_class: Incomplete +exctype: Incomplete +err: Incomplete +tb: Incomplete +msg: Incomplete + +def main(args=None): ... +def main_console_script(): ... diff --git a/stubs/pyomo/scripting/pyomo_parser.pyi b/stubs/pyomo/scripting/pyomo_parser.pyi new file mode 100644 index 000000000..1e3a67217 --- /dev/null +++ b/stubs/pyomo/scripting/pyomo_parser.pyi @@ -0,0 +1,14 @@ +import argparse + +from _typeshed import Incomplete + +class CustomHelpFormatter(argparse.RawDescriptionHelpFormatter): ... + +def get_version(): ... + +doc: str +epilog: str +subparsers: Incomplete + +def add_subparser(name, **args): ... +def get_parser(): ... diff --git a/stubs/pyomo/scripting/solve_config.pyi b/stubs/pyomo/scripting/solve_config.pyi new file mode 100644 index 000000000..6e789191b --- /dev/null +++ b/stubs/pyomo/scripting/solve_config.pyi @@ -0,0 +1,9 @@ +from pyomo.common.config import ConfigBlock as ConfigBlock +from pyomo.common.config import ConfigList as ConfigList +from pyomo.common.config import ConfigValue as ConfigValue + +class Default_Config: + def config_block(self, init: bool = False): ... + +def minlp_config_block(init: bool = False): ... +def default_config_block(solver, init: bool = False): ... diff --git a/stubs/pyomo/scripting/util.pyi b/stubs/pyomo/scripting/util.pyi new file mode 100644 index 000000000..2f35dc7fa --- /dev/null +++ b/stubs/pyomo/scripting/util.pyi @@ -0,0 +1,72 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import pympler as pympler +from pyomo.common.dependencies import pympler_available as pympler_available +from pyomo.common.dependencies import yaml as yaml +from pyomo.common.dependencies import yaml_available as yaml_available +from pyomo.common.dependencies import yaml_load_args as yaml_load_args +from pyomo.common.deprecation import deprecated as deprecated +from pyomo.common.fileutils import import_file as import_file +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core import Model as Model +from pyomo.core import Suffix as Suffix +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core import display as display +from pyomo.dataportal import DataPortal as DataPortal +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.opt.parallel import SolverManagerFactory as SolverManagerFactory +from pyomo.scripting.interface import ExtensionPoint as ExtensionPoint +from pyomo.scripting.interface import IPyomoScriptCreateDataPortal as IPyomoScriptCreateDataPortal +from pyomo.scripting.interface import IPyomoScriptCreateModel as IPyomoScriptCreateModel +from pyomo.scripting.interface import IPyomoScriptModifyInstance as IPyomoScriptModifyInstance +from pyomo.scripting.interface import IPyomoScriptPostprocess as IPyomoScriptPostprocess +from pyomo.scripting.interface import IPyomoScriptPreprocess as IPyomoScriptPreprocess +from pyomo.scripting.interface import IPyomoScriptPrintInstance as IPyomoScriptPrintInstance +from pyomo.scripting.interface import IPyomoScriptPrintModel as IPyomoScriptPrintModel +from pyomo.scripting.interface import IPyomoScriptPrintResults as IPyomoScriptPrintResults +from pyomo.scripting.interface import IPyomoScriptSaveInstance as IPyomoScriptSaveInstance +from pyomo.scripting.interface import IPyomoScriptSaveResults as IPyomoScriptSaveResults +from pyomo.scripting.interface import Plugin as Plugin +from pyomo.scripting.interface import implements as implements +from pyomo.scripting.interface import registered_callback as registered_callback + +memory_data: Incomplete +IPython_available: Incomplete +filter_excepthook: bool +modelapi: Incomplete +logger: Incomplete +start_time: float + +def setup_environment(data) -> None: ... +def apply_preprocessing(data, parser=None): ... +def create_model(data): ... +def apply_optimizer(data, instance=None): ... +def process_results(data, instance=None, results=None, opt=None) -> None: ... +def apply_postprocessing(data, instance=None, results=None) -> None: ... +def finalize(data, model=None, instance=None, results=None) -> None: ... +def configure_loggers(options=None, shutdown: bool = False) -> None: ... + +class PyomoCommandLogContext: + options: Incomplete + fileLogger: Incomplete + original: Incomplete + def __init__(self, options) -> None: ... + capture: Incomplete + def __enter__(self): ... + def __exit__( + self, + et: type[BaseException] | None, + ev: BaseException | None, + tb: types.TracebackType | None, + ) -> None: ... + +def run_command( + command=None, parser=None, args=None, name: str = 'unknown', data=None, options=None +): ... +def cleanup() -> None: ... +def get_config_values(filename): ... diff --git a/stubs/pyomo/solvers/__init__.pyi b/stubs/pyomo/solvers/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/solvers/amplfunc_merge.pyi b/stubs/pyomo/solvers/amplfunc_merge.pyi new file mode 100644 index 000000000..a597f0e01 --- /dev/null +++ b/stubs/pyomo/solvers/amplfunc_merge.pyi @@ -0,0 +1,4 @@ +from pyomo.common.deprecation import relocated_module_attribute as relocated_module_attribute + +def unique_paths(*paths): ... +def amplfunc_merge(env, *funcs): ... diff --git a/stubs/pyomo/solvers/mockmip.pyi b/stubs/pyomo/solvers/mockmip.pyi new file mode 100644 index 000000000..719cdac03 --- /dev/null +++ b/stubs/pyomo/solvers/mockmip.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete + +class MockMIP: + mock_subdir: Incomplete + def __init__(self, mockdir) -> None: ... + def create_command_line(self, executable, problem_files) -> None: ... + executable: Incomplete + def version(self): ... diff --git a/stubs/pyomo/solvers/plugins/__init__.pyi b/stubs/pyomo/solvers/plugins/__init__.pyi new file mode 100644 index 000000000..08b671c7f --- /dev/null +++ b/stubs/pyomo/solvers/plugins/__init__.pyi @@ -0,0 +1 @@ +def load() -> None: ... diff --git a/stubs/pyomo/solvers/plugins/converter/__init__.pyi b/stubs/pyomo/solvers/plugins/converter/__init__.pyi new file mode 100644 index 000000000..e0ebeace6 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/converter/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.solvers.plugins.converter import ampl as ampl +from pyomo.solvers.plugins.converter import glpsol as glpsol +from pyomo.solvers.plugins.converter import model as model diff --git a/stubs/pyomo/solvers/plugins/converter/ampl.pyi b/stubs/pyomo/solvers/plugins/converter/ampl.pyi new file mode 100644 index 000000000..671404a16 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/converter/ampl.pyi @@ -0,0 +1,9 @@ +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ConverterError as ConverterError +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base.convert import ProblemConverterFactory as ProblemConverterFactory + +class AmplMIPConverter: + def can_convert(self, from_type, to_type): ... + def apply(self, *args, **kwargs): ... diff --git a/stubs/pyomo/solvers/plugins/converter/glpsol.pyi b/stubs/pyomo/solvers/plugins/converter/glpsol.pyi new file mode 100644 index 000000000..b4551abd6 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/converter/glpsol.pyi @@ -0,0 +1,9 @@ +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ConverterError as ConverterError +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base.convert import ProblemConverterFactory as ProblemConverterFactory + +class GlpsolMIPConverter: + def can_convert(self, from_type, to_type): ... + def apply(self, *args, **kwargs): ... diff --git a/stubs/pyomo/solvers/plugins/converter/model.pyi b/stubs/pyomo/solvers/plugins/converter/model.pyi new file mode 100644 index 000000000..adaca063d --- /dev/null +++ b/stubs/pyomo/solvers/plugins/converter/model.pyi @@ -0,0 +1,11 @@ +from _typeshed import Incomplete +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base.convert import ProblemConverterFactory as ProblemConverterFactory +from pyomo.solvers.plugins.converter.pico import PicoMIPConverter as PicoMIPConverter + +class PyomoMIPConverter: + pico_converter: Incomplete + def can_convert(self, from_type, to_type): ... + def apply(self, *args, **kwds): ... diff --git a/stubs/pyomo/solvers/plugins/converter/pico.pyi b/stubs/pyomo/solvers/plugins/converter/pico.pyi new file mode 100644 index 000000000..0ee576c5f --- /dev/null +++ b/stubs/pyomo/solvers/plugins/converter/pico.pyi @@ -0,0 +1,9 @@ +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ConverterError as ConverterError +from pyomo.opt.base import ProblemFormat as ProblemFormat + +class PicoMIPConverter: + def can_convert(self, from_type, to_type): ... + def available(self): ... + def apply(self, *args, **kwargs): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/ASL.pyi b/stubs/pyomo/solvers/plugins/solvers/ASL.pyi new file mode 100644 index 000000000..0cb13af4b --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/ASL.pyi @@ -0,0 +1,26 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core import TransformationFactory as TransformationFactory +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver +from pyomo.solvers.amplfunc_merge import amplfunc_merge as amplfunc_merge +from pyomo.solvers.mockmip import MockMIP as MockMIP + +logger: Incomplete + +class ASL(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def create_command_line(self, executable, problem_files): ... + +class MockASL(ASL, MockMIP): + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def create_command_line(self, executable, problem_files): ... + def executable(self): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/BARON.pyi b/stubs/pyomo/solvers/plugins/solvers/BARON.pyi new file mode 100644 index 000000000..30fb8a5c0 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/BARON.pyi @@ -0,0 +1,24 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import OptSolver as OptSolver +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import Solution as Solution +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver + +logger: Incomplete + +class BARONSHELL(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def license_is_valid(self): ... + def create_command_line(self, executable, problem_files): ... + def warm_start_capable(self): ... + def process_logfile(self): ... + def process_soln_file(self, results) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/CBCplugin.pyi b/stubs/pyomo/solvers/plugins/solvers/CBCplugin.pyi new file mode 100644 index 000000000..49d55b974 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/CBCplugin.pyi @@ -0,0 +1,39 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.enums import maximize as maximize +from pyomo.common.enums import minimize as minimize +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core import Var as Var +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.opt.base import OptSolver as OptSolver +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import Solution as Solution +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver +from pyomo.solvers.mockmip import MockMIP as MockMIP + +logger: Incomplete + +class CBC(OptSolver): + def __new__(cls, *args, **kwds): ... + +class CBCSHELL(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def set_problem_format(self, format) -> None: ... + def warm_start_capable(self): ... + def create_command_line(self, executable, problem_files): ... + def process_logfile(self): ... + def process_soln_file(self, results) -> None: ... + +class MockCBC(CBCSHELL, MockMIP): + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def create_command_line(self, executable, problem_files): ... + def executable(self): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/CONOPT.pyi b/stubs/pyomo/solvers/plugins/solvers/CONOPT.pyi new file mode 100644 index 000000000..7521d22ee --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/CONOPT.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver + +logger: Incomplete + +class CONOPT(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def create_command_line(self, executable, problem_files): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/CPLEX.pyi b/stubs/pyomo/solvers/plugins/solvers/CPLEX.pyi new file mode 100644 index 000000000..394619c77 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/CPLEX.pyi @@ -0,0 +1,51 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.enums import maximize as maximize +from pyomo.common.enums import minimize as minimize +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import Var as Var +from pyomo.core.base import active_export_suffix_generator as active_export_suffix_generator +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.core.kernel.suffix import export_suffix_generator as export_suffix_generator +from pyomo.opt.base import BranchDirection as BranchDirection +from pyomo.opt.base import OptSolver as OptSolver +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import Solution as Solution +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition +from pyomo.opt.solver import ILMLicensedSystemCallSolver as ILMLicensedSystemCallSolver +from pyomo.solvers.mockmip import MockMIP as MockMIP +from pyomo.util.components import iter_component as iter_component + +logger: Incomplete + +class CPLEX(OptSolver): + def __new__(cls, *args, **kwds): ... + +class ORDFileSchema: + HEADER: str + FOOTER: str + @classmethod + def ROW(cls, name, priority, branch_direction=None): ... + +class CPLEXSHELL(ILMLicensedSystemCallSolver): + def __init__(self, **kwds) -> None: ... + def warm_start_capable(self): ... + SUFFIX_PRIORITY_NAME: str + SUFFIX_DIRECTION_NAME: str + def create_command_line(self, executable, problem_files): ... + def process_logfile(self): ... + def process_soln_file(self, results) -> None: ... + +class MockCPLEX(CPLEXSHELL, MockMIP): + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def create_command_line(self, executable, problem_files): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/GAMS.pyi b/stubs/pyomo/solvers/plugins/solvers/GAMS.pyi new file mode 100644 index 000000000..adf5b2ca8 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/GAMS.pyi @@ -0,0 +1,67 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Var as Var +from pyomo.core.base import value as value +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.core.kernel.objective import IObjective as IObjective +from pyomo.core.kernel.variable import IVariable as IVariable +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt.results import Solution as Solution +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition + +gdxcc: Incomplete +gdxcc_available: Incomplete +logger: Incomplete + +class _GAMSSolver: + options: Incomplete + def __init__(self, **kwds) -> None: ... + def version(self): ... + def warm_start_capable(self): ... + def default_variable_value(self): ... + def set_options(self, istr) -> None: ... + def __enter__(self): ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + +class GAMSSolver(_GAMSSolver): + def __new__(cls, *args, **kwds): ... + +class GAMSDirect(_GAMSSolver): + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def solve(self, *args, **kwds): ... + +class GAMSShell(_GAMSSolver): + def available(self, exception_flag: bool = True): ... + def license_is_valid(self): ... + def executable(self): ... + def solve(self, *args, **kwds): ... + +class OutputStream: + tee: Incomplete + logfile: Incomplete + logfile_buffer: Incomplete + def __init__(self, tee: bool = False, logfile=None) -> None: ... + def __enter__(self): ... + def __exit__(self, *args, **kwargs) -> None: ... + def write(self, message) -> None: ... + def flush(self) -> None: ... + +def check_expr_evaluation(model, symbolMap, solver_io) -> None: ... +def check_expr(expr, name, solver_io) -> None: ... +def file_removal_gams_direct(tmpdir, newdir) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/GLPK.pyi b/stubs/pyomo/solvers/plugins/solvers/GLPK.pyi new file mode 100644 index 000000000..03726f0a6 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/GLPK.pyi @@ -0,0 +1,44 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.enums import maximize as maximize +from pyomo.common.enums import minimize as minimize +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt import OptSolver as OptSolver +from pyomo.opt import ProblemFormat as ProblemFormat +from pyomo.opt import ResultsFormat as ResultsFormat +from pyomo.opt import SolutionStatus as SolutionStatus +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt import SolverResults as SolverResults +from pyomo.opt import TerminationCondition as TerminationCondition +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver +from pyomo.solvers.mockmip import MockMIP as MockMIP + +logger: Incomplete +GLP_BS: int +GLP_NL: int +GLP_NU: int +GLP_NF: int +GLP_NS: int +GLP_UNDEF: str +GLP_FEAS: str +GLP_INFEAS: str +GLP_NOFEAS: str +GLP_OPT: str + +class GLPK(OptSolver): + def __new__(cls, *args, **kwds): ... + +class GLPKSHELL(SystemCallSolver): + def __init__(self, **kwargs) -> None: ... + def create_command_line(self, executable, problem_files): ... + def process_logfile(self): ... + is_integer: Incomplete + def process_soln_file(self, results) -> None: ... + +class MockGLPK(GLPKSHELL, MockMIP): + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def create_command_line(self, executable, problem_files): ... + def executable(self): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/GUROBI.pyi b/stubs/pyomo/solvers/plugins/solvers/GUROBI.pyi new file mode 100644 index 000000000..2725f941d --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/GUROBI.pyi @@ -0,0 +1,50 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.enums import maximize as maximize +from pyomo.common.enums import minimize as minimize +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.fileutils import this_file_dir as this_file_dir +from pyomo.common.log import is_debug_set as is_debug_set +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core import ConcreteModel as ConcreteModel +from pyomo.core import Objective as Objective +from pyomo.core import Var as Var +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.opt.base import OptSolver as OptSolver +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import Solution as Solution +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition +from pyomo.opt.solver import ILMLicensedSystemCallSolver as ILMLicensedSystemCallSolver +from pyomo.solvers.plugins.solvers.ASL import ASL as ASL +from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy as gurobipy +from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy_available as gurobipy_available + +logger: Incomplete +GUROBI_RUN: Incomplete + +class GUROBI(OptSolver): + def __new__(cls, *args, **kwds): ... + +class GUROBINL(ASL): + def license_is_valid(self): ... + +class GUROBISHELL(ILMLicensedSystemCallSolver): + def __init__(self, **kwds) -> None: ... + def license_is_valid(self): ... + def warm_start_capable(self): ... + def create_command_line(self, executable, problem_files): ... + def process_soln_file(self, results) -> None: ... + +class GUROBIFILE(GUROBISHELL): + def available(self, exception_flag: bool = False): ... + def license_is_valid(self): ... + def create_command_line(self, executable, problem_files): ... + def process_soln_file(self, results) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/GUROBI_RUN.pyi b/stubs/pyomo/solvers/plugins/solvers/GUROBI_RUN.pyi new file mode 100644 index 000000000..91698fb7c --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/GUROBI_RUN.pyi @@ -0,0 +1,6 @@ +from _typeshed import Incomplete + +GUROBI_VERSION: Incomplete + +def gurobi_run(model_file, warmstart_file, mipgap, options, suffixes): ... +def write_result(result, soln_file) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/IPOPT.pyi b/stubs/pyomo/solvers/plugins/solvers/IPOPT.pyi new file mode 100644 index 000000000..996c485d3 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/IPOPT.pyi @@ -0,0 +1,22 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver +from pyomo.solvers.amplfunc_merge import amplfunc_merge as amplfunc_merge + +logger: Incomplete + +class IPOPT(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def create_command_line(self, executable, problem_files): ... + def process_output(self, rc): ... + def has_linear_solver(self, linear_solver): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/KNITROAMPL.pyi b/stubs/pyomo/solvers/plugins/solvers/KNITROAMPL.pyi new file mode 100644 index 000000000..566068c44 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/KNITROAMPL.pyi @@ -0,0 +1,14 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import pathlib as pathlib +from pyomo.opt.base.formats import ProblemFormat as ProblemFormat +from pyomo.opt.base.formats import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import OptSolver as OptSolver +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.solvers.plugins.solvers.ASL import ASL as ASL + +logger: Incomplete + +class KNITROAMPL(ASL): + def __init__(self, **kwds) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/SAS.pyi b/stubs/pyomo/solvers/plugins/solvers/SAS.pyi new file mode 100644 index 000000000..a5f72ca85 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/SAS.pyi @@ -0,0 +1,48 @@ +import abc +from abc import ABC + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.log import LogStream as LogStream +from pyomo.common.tee import TeeStream as TeeStream +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core.base import Var as Var +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.opt.base import OptSolver as OptSolver +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import ProblemSense as ProblemSense +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverResults as SolverResults +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition + +uuid: Incomplete +uuid_available: Incomplete +logger: Incomplete +STATUS_TO_SOLVERSTATUS: Incomplete +SOLSTATUS_TO_TERMINATIONCOND: Incomplete +SOLSTATUS_TO_MESSAGE: Incomplete + +class SAS(OptSolver): + def __new__(cls, *args, **kwds): ... + +class SASAbc(ABC, OptSolver, metaclass=abc.ABCMeta): + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = False): ... + def warm_start_capable(self): ... + +class SAS94(SASAbc): + def __init__(self, **kwds) -> None: ... + def __del__(self) -> None: ... + def sas_version(self): ... + def start_sas_session(self): ... + +class SASCAS(SASAbc): + def __init__(self, **kwds) -> None: ... + def __del__(self) -> None: ... + def start_sas_session(self): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/SCIPAMPL.pyi b/stubs/pyomo/solvers/plugins/solvers/SCIPAMPL.pyi new file mode 100644 index 000000000..501d089fc --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/SCIPAMPL.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete +from pyomo.common import Executable as Executable +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.opt.base import ProblemFormat as ProblemFormat +from pyomo.opt.base import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import SolverFactory as SolverFactory +from pyomo.opt.results import SolutionStatus as SolutionStatus +from pyomo.opt.results import SolverStatus as SolverStatus +from pyomo.opt.results import TerminationCondition as TerminationCondition +from pyomo.opt.solver import SystemCallSolver as SystemCallSolver + +logger: Incomplete + +class SCIPAMPL(SystemCallSolver): + def __init__(self, **kwds) -> None: ... + def create_command_line(self, executable, problem_files): ... + @staticmethod + def read_scip_log(filename: str): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/XPRESS.pyi b/stubs/pyomo/solvers/plugins/solvers/XPRESS.pyi new file mode 100644 index 000000000..775c53d9d --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/XPRESS.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete +from pyomo.opt.base import OptSolver as OptSolver +from pyomo.opt.base.solvers import SolverFactory as SolverFactory + +logger: Incomplete + +class XPRESS(OptSolver): + def __new__(cls, *args, **kwds): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/__init__.pyi b/stubs/pyomo/solvers/plugins/solvers/__init__.pyi new file mode 100644 index 000000000..8c00cadfc --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/__init__.pyi @@ -0,0 +1,22 @@ +from pyomo.solvers.plugins.solvers import ASL as ASL +from pyomo.solvers.plugins.solvers import BARON as BARON +from pyomo.solvers.plugins.solvers import CONOPT as CONOPT +from pyomo.solvers.plugins.solvers import CPLEX as CPLEX +from pyomo.solvers.plugins.solvers import GAMS as GAMS +from pyomo.solvers.plugins.solvers import GLPK as GLPK +from pyomo.solvers.plugins.solvers import GUROBI as GUROBI +from pyomo.solvers.plugins.solvers import IPOPT as IPOPT +from pyomo.solvers.plugins.solvers import KNITROAMPL as KNITROAMPL +from pyomo.solvers.plugins.solvers import SAS as SAS +from pyomo.solvers.plugins.solvers import SCIPAMPL as SCIPAMPL +from pyomo.solvers.plugins.solvers import XPRESS as XPRESS +from pyomo.solvers.plugins.solvers import CBCplugin as CBCplugin +from pyomo.solvers.plugins.solvers import cplex_direct as cplex_direct +from pyomo.solvers.plugins.solvers import cplex_persistent as cplex_persistent +from pyomo.solvers.plugins.solvers import gurobi_direct as gurobi_direct +from pyomo.solvers.plugins.solvers import gurobi_persistent as gurobi_persistent +from pyomo.solvers.plugins.solvers import mosek_direct as mosek_direct +from pyomo.solvers.plugins.solvers import mosek_persistent as mosek_persistent +from pyomo.solvers.plugins.solvers import pywrapper as pywrapper +from pyomo.solvers.plugins.solvers import xpress_direct as xpress_direct +from pyomo.solvers.plugins.solvers import xpress_persistent as xpress_persistent diff --git a/stubs/pyomo/solvers/plugins/solvers/cplex_direct.pyi b/stubs/pyomo/solvers/plugins/solvers/cplex_direct.pyi new file mode 100644 index 000000000..74f4879f0 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/cplex_direct.pyi @@ -0,0 +1,73 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Objective as Objective +from pyomo.core.base import SOSConstraint as SOSConstraint +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import Var as Var +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.opt.results.results_ import SolverResults as SolverResults +from pyomo.opt.results.solution import Solution as Solution +from pyomo.opt.results.solution import SolutionStatus as SolutionStatus +from pyomo.opt.results.solver import SolverStatus as SolverStatus +from pyomo.opt.results.solver import TerminationCondition as TerminationCondition +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( + DirectOrPersistentSolver as DirectOrPersistentSolver, +) +from pyomo.solvers.plugins.solvers.direct_solver import DirectSolver as DirectSolver + +logger: Incomplete + +class DegreeError(ValueError): ... + +class _CplexExpr: + variables: Incomplete + coefficients: Incomplete + offset: Incomplete + q_variables1: Incomplete + q_variables2: Incomplete + q_coefficients: Incomplete + def __init__( + self, + variables, + coefficients, + offset=None, + q_variables1=None, + q_variables2=None, + q_coefficients=None, + ) -> None: ... + +class _VariableData: + lb: Incomplete + ub: Incomplete + types: Incomplete + names: Incomplete + def __init__(self, solver_model) -> None: ... + def add(self, lb, ub, type_, name) -> None: ... + def store_in_cplex(self) -> None: ... + +class _LinearConstraintData: + lin_expr: Incomplete + senses: Incomplete + rhs: Incomplete + range_values: Incomplete + names: Incomplete + def __init__(self, solver_model) -> None: ... + def add(self, cplex_expr, sense, rhs, range_values, name) -> None: ... + def store_in_cplex(self) -> None: ... + +class CPLEXDirect(DirectSolver): + def __init__(self, **kwds) -> None: ... + def warm_start_capable(self): ... + def load_duals(self, cons_to_load=None) -> None: ... + def load_rc(self, vars_to_load) -> None: ... + def load_slacks(self, cons_to_load=None) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/cplex_persistent.pyi b/stubs/pyomo/solvers/plugins/solvers/cplex_persistent.pyi new file mode 100644 index 000000000..59ffdae45 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/cplex_persistent.pyi @@ -0,0 +1,9 @@ +from pyomo.core.expr.numvalue import value as value +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.solvers.plugins.solvers.cplex_direct import CPLEXDirect as CPLEXDirect +from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver as PersistentSolver + +class CPLEXPersistent(PersistentSolver, CPLEXDirect): + def __init__(self, **kwds) -> None: ... + def update_var(self, var) -> None: ... + def write(self, filename, filetype: str = '') -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.pyi b/stubs/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.pyi new file mode 100644 index 000000000..89d2196b6 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/direct_or_persistent_solver.pyi @@ -0,0 +1,23 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core.base import NumericLabeler as NumericLabeler +from pyomo.core.base import SymbolMap as SymbolMap +from pyomo.core.base import TextLabeler as TextLabeler +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.PyomoModel import Model as Model +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.opt.base.formats import ResultsFormat as ResultsFormat +from pyomo.opt.base.solvers import OptSolver as OptSolver + +class DirectOrPersistentSolver(OptSolver): + results: Incomplete + def __init__(self, **kwds) -> None: ... + def load_vars(self, vars_to_load=None) -> None: ... + def warm_start_capable(self) -> None: ... + def available(self, exception_flag: bool = True): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/direct_solver.pyi b/stubs/pyomo/solvers/plugins/solvers/direct_solver.pyi new file mode 100644 index 000000000..5ed6c02e2 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/direct_solver.pyi @@ -0,0 +1,16 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.suffix import active_import_suffix_generator as active_import_suffix_generator +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.core.kernel.suffix import import_suffix_generator as import_suffix_generator +from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( + DirectOrPersistentSolver as DirectOrPersistentSolver, +) + +logger: Incomplete + +class DirectSolver(DirectOrPersistentSolver): + options: Incomplete + def solve(self, *args, **kwds): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/gurobi_direct.pyi b/stubs/pyomo/solvers/plugins/solvers/gurobi_direct.pyi new file mode 100644 index 000000000..d6e38832d --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/gurobi_direct.pyi @@ -0,0 +1,50 @@ +import types + +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.opt.results.results_ import SolverResults as SolverResults +from pyomo.opt.results.solution import Solution as Solution +from pyomo.opt.results.solution import SolutionStatus as SolutionStatus +from pyomo.opt.results.solver import SolverStatus as SolverStatus +from pyomo.opt.results.solver import TerminationCondition as TerminationCondition +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( + DirectOrPersistentSolver as DirectOrPersistentSolver, +) +from pyomo.solvers.plugins.solvers.direct_solver import DirectSolver as DirectSolver + +logger: Incomplete + +class DegreeError(ValueError): ... + +gurobipy: Incomplete +gurobipy_available: Incomplete + +class GurobiDirect(DirectSolver): + def __init__(self, manage_env: bool = False, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def close_global(self) -> None: ... + def close(self) -> None: ... + def __exit__( + self, + t: type[BaseException] | None, + v: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: ... + def warm_start_capable(self): ... + def load_duals(self, cons_to_load=None) -> None: ... + def load_rc(self, vars_to_load) -> None: ... + def load_slacks(self, cons_to_load=None) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/gurobi_persistent.pyi b/stubs/pyomo/solvers/plugins/solvers/gurobi_persistent.pyi new file mode 100644 index 000000000..78e201b1e --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/gurobi_persistent.pyi @@ -0,0 +1,31 @@ +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import value as value +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.solvers.plugins.solvers.gurobi_direct import GurobiDirect as GurobiDirect +from pyomo.solvers.plugins.solvers.gurobi_direct import gurobipy as gurobipy +from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver as PersistentSolver + +class GurobiPersistent(PersistentSolver, GurobiDirect): + def __init__(self, **kwds) -> None: ... + def update_var(self, var) -> None: ... + def write(self, filename) -> None: ... + def update(self) -> None: ... + def set_linear_constraint_attr(self, con, attr, val) -> None: ... + def set_var_attr(self, var, attr, val) -> None: ... + def get_model_attr(self, attr): ... + def get_var_attr(self, var, attr): ... + def get_linear_constraint_attr(self, con, attr): ... + def get_sos_attr(self, con, attr): ... + def get_quadratic_constraint_attr(self, con, attr): ... + def set_gurobi_param(self, param, val) -> None: ... + def get_gurobi_param_info(self, param): ... + def set_callback(self, func=None) -> None: ... + def cbCut(self, con) -> None: ... + def cbGet(self, what): ... + def cbGetNodeRel(self, vars) -> None: ... + def cbGetSolution(self, vars) -> None: ... + def cbLazy(self, con) -> None: ... + def cbSetSolution(self, vars, solution) -> None: ... + def cbUseSolution(self): ... + def reset(self) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/mosek_direct.pyi b/stubs/pyomo/solvers/plugins/solvers/mosek_direct.pyi new file mode 100644 index 000000000..26364ab73 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/mosek_direct.pyi @@ -0,0 +1,52 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core import is_fixed as is_fixed +from pyomo.core import maximize as maximize +from pyomo.core import minimize as minimize +from pyomo.core import value as value +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.kernel.conic import dual_exponential as dual_exponential +from pyomo.core.kernel.conic import dual_geomean as dual_geomean +from pyomo.core.kernel.conic import dual_power as dual_power +from pyomo.core.kernel.conic import primal_exponential as primal_exponential +from pyomo.core.kernel.conic import primal_geomean as primal_geomean +from pyomo.core.kernel.conic import primal_power as primal_power +from pyomo.core.kernel.conic import quadratic as quadratic +from pyomo.core.kernel.conic import rotated_quadratic as rotated_quadratic +from pyomo.core.kernel.conic import svec_psdcone as svec_psdcone +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.opt import SolverFactory as SolverFactory +from pyomo.opt.base.solvers import OptSolver as OptSolver +from pyomo.opt.results.results_ import SolverResults as SolverResults +from pyomo.opt.results.solution import Solution as Solution +from pyomo.opt.results.solution import SolutionStatus as SolutionStatus +from pyomo.opt.results.solver import SolverStatus as SolverStatus +from pyomo.opt.results.solver import TerminationCondition as TerminationCondition +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( + DirectOrPersistentSolver as DirectOrPersistentSolver, +) +from pyomo.solvers.plugins.solvers.direct_solver import DirectSolver as DirectSolver + +logger: Incomplete +inf: Incomplete +mosek: Incomplete +mosek_available: Incomplete + +class DegreeError(ValueError): ... +class UnsupportedDomainError(TypeError): ... + +class MOSEK(OptSolver): + def __new__(cls, *args, **kwds): ... + +class MOSEKDirect(DirectSolver): + def __init__(self, **kwds) -> None: ... + def license_is_valid(self): ... + def warm_start_capable(self): ... + def load_duals(self, cons_to_load=None) -> None: ... + def load_rc(self, vars_to_load) -> None: ... + def load_slacks(self, cons_to_load=None) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/mosek_persistent.pyi b/stubs/pyomo/solvers/plugins/solvers/mosek_persistent.pyi new file mode 100644 index 000000000..9f47bcca0 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/mosek_persistent.pyi @@ -0,0 +1,25 @@ +from pyomo.core import is_fixed as is_fixed +from pyomo.core import value as value +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.PyomoModel import ConcreteModel as ConcreteModel +from pyomo.core.base.var import Var as Var +from pyomo.core.kernel.block import block as block +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( + DirectOrPersistentSolver as DirectOrPersistentSolver, +) +from pyomo.solvers.plugins.solvers.direct_solver import DirectSolver as DirectSolver +from pyomo.solvers.plugins.solvers.mosek_direct import MOSEKDirect as MOSEKDirect +from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver as PersistentSolver + +class MOSEKPersistent(PersistentSolver, MOSEKDirect): + def __init__(self, **kwds) -> None: ... + def add_vars(self, var_seq) -> None: ... + def add_constraints(self, con_seq) -> None: ... + def remove_var(self, solver_var) -> None: ... + def remove_vars(self, *solver_vars) -> None: ... + def remove_constraint(self, solver_con) -> None: ... + def remove_constraints(self, *solver_cons): ... + def update_var(self, solver_var) -> None: ... + def update_vars(self, *solver_vars) -> None: ... + def write(self, filename) -> None: ... diff --git a/stubs/pyomo/solvers/plugins/solvers/persistent_solver.pyi b/stubs/pyomo/solvers/plugins/solvers/persistent_solver.pyi new file mode 100644 index 000000000..cf419a75a --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/persistent_solver.pyi @@ -0,0 +1,35 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.sos import SOSConstraint as SOSConstraint +from pyomo.core.base.suffix import active_import_suffix_generator as active_import_suffix_generator +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.numvalue import native_numeric_types as native_numeric_types +from pyomo.core.expr.numvalue import value as value +from pyomo.core.expr.visitor import evaluate_expression as evaluate_expression +from pyomo.core.kernel.block import IBlock as IBlock +from pyomo.core.kernel.suffix import import_suffix_generator as import_suffix_generator +from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( + DirectOrPersistentSolver as DirectOrPersistentSolver, +) + +logger: Incomplete + +class PersistentSolver(DirectOrPersistentSolver): + def set_instance(self, model, **kwds): ... + def add_block(self, block) -> None: ... + def set_objective(self, obj): ... + def add_constraint(self, con) -> None: ... + def add_var(self, var) -> None: ... + def add_sos_constraint(self, con) -> None: ... + def add_column(self, model, var, obj_coef, constraints, coefficients) -> None: ... + def remove_block(self, block) -> None: ... + def remove_constraint(self, con) -> None: ... + def remove_sos_constraint(self, con) -> None: ... + def remove_var(self, var) -> None: ... + def update_var(self, var) -> None: ... + options: Incomplete + def solve(self, *args, **kwds): ... + def has_instance(self): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/pywrapper.pyi b/stubs/pyomo/solvers/plugins/solvers/pywrapper.pyi new file mode 100644 index 000000000..7023b2922 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/pywrapper.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete +from pyomo.opt import OptSolver as OptSolver +from pyomo.opt import SolverFactory as SolverFactory + +logger: Incomplete + +class pywrapper(OptSolver): + def __new__(cls, *args, **kwds): ... diff --git a/stubs/pyomo/solvers/plugins/solvers/xpress_direct.pyi b/stubs/pyomo/solvers/plugins/solvers/xpress_direct.pyi new file mode 100644 index 000000000..a0b583ce2 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/xpress_direct.pyi @@ -0,0 +1,46 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.dependencies import attempt_import as attempt_import +from pyomo.common.errors import ApplicationError as ApplicationError +from pyomo.common.tee import capture_output as capture_output +from pyomo.common.tempfiles import TempfileManager as TempfileManager +from pyomo.core.base.suffix import Suffix as Suffix +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import value as value +from pyomo.core.kernel.objective import maximize as maximize +from pyomo.core.kernel.objective import minimize as minimize +from pyomo.core.staleflag import StaleFlagManager as StaleFlagManager +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.opt.results.results_ import SolverResults as SolverResults +from pyomo.opt.results.solution import Solution as Solution +from pyomo.opt.results.solution import SolutionStatus as SolutionStatus +from pyomo.opt.results.solver import SolverStatus as SolverStatus +from pyomo.opt.results.solver import TerminationCondition as TerminationCondition +from pyomo.repn import generate_standard_repn as generate_standard_repn +from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( + DirectOrPersistentSolver as DirectOrPersistentSolver, +) +from pyomo.solvers.plugins.solvers.direct_solver import DirectSolver as DirectSolver + +logger: Incomplete + +class DegreeError(ValueError): ... + +class _xpress_importer_class: + import_message: str + def __init__(self) -> None: ... + def __call__(self): ... + +class XpressDirect(DirectSolver): + XpressException = RuntimeError + def __init__(self, **kwds) -> None: ... + def available(self, exception_flag: bool = True): ... + def warm_start_capable(self): ... + def load_duals(self, cons_to_load=None) -> None: ... + def load_rc(self, vars_to_load=None) -> None: ... + def load_slacks(self, cons_to_load=None) -> None: ... + +xpress: Incomplete +xpress_available: Incomplete diff --git a/stubs/pyomo/solvers/plugins/solvers/xpress_persistent.pyi b/stubs/pyomo/solvers/plugins/solvers/xpress_persistent.pyi new file mode 100644 index 000000000..70e77f0c6 --- /dev/null +++ b/stubs/pyomo/solvers/plugins/solvers/xpress_persistent.pyi @@ -0,0 +1,14 @@ +from pyomo.core.base.PyomoModel import ConcreteModel as ConcreteModel +from pyomo.core.expr.numvalue import is_fixed as is_fixed +from pyomo.core.expr.numvalue import value as value +from pyomo.opt.base import SolverFactory as SolverFactory +from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver as PersistentSolver +from pyomo.solvers.plugins.solvers.xpress_direct import XpressDirect as XpressDirect + +class XpressPersistent(PersistentSolver, XpressDirect): + def __init__(self, **kwds) -> None: ... + def update_var(self, var) -> None: ... + def get_xpress_attribute(self, *args): ... + def set_xpress_control(self, *args) -> None: ... + def get_xpress_control(self, *args): ... + def write(self, filename, flags: str = '') -> None: ... diff --git a/stubs/pyomo/solvers/wrappers.pyi b/stubs/pyomo/solvers/wrappers.pyi new file mode 100644 index 000000000..3122a872d --- /dev/null +++ b/stubs/pyomo/solvers/wrappers.pyi @@ -0,0 +1,2 @@ +class MIPSolverWrapper: + def add(self, constraint) -> None: ... diff --git a/stubs/pyomo/util/__init__.pyi b/stubs/pyomo/util/__init__.pyi new file mode 100644 index 000000000..e69de29bb diff --git a/stubs/pyomo/util/blockutil.pyi b/stubs/pyomo/util/blockutil.pyi new file mode 100644 index 000000000..502fbbb24 --- /dev/null +++ b/stubs/pyomo/util/blockutil.pyi @@ -0,0 +1,9 @@ +from _typeshed import Incomplete +from pyomo.core import Constraint as Constraint +from pyomo.core import TraversalStrategy as TraversalStrategy +from pyomo.core import Var as Var + +logger: Incomplete + +def has_discrete_variables(block): ... +def log_model_constraints(m, logger=..., active: bool = True) -> None: ... diff --git a/stubs/pyomo/util/calc_var_value.pyi b/stubs/pyomo/util/calc_var_value.pyi new file mode 100644 index 000000000..effe9b5e1 --- /dev/null +++ b/stubs/pyomo/util/calc_var_value.pyi @@ -0,0 +1,19 @@ +from _typeshed import Incomplete +from pyomo.common.errors import IterationLimitError as IterationLimitError +from pyomo.common.numeric_types import native_complex_types as native_complex_types +from pyomo.common.numeric_types import native_numeric_types as native_numeric_types +from pyomo.common.numeric_types import value as value +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.expr.calculus.derivatives import differentiate as differentiate + +logger: Incomplete + +def calculate_variable_from_constraint( + variable, + constraint, + eps: float = 1e-08, + iterlim: int = 1000, + linesearch: bool = True, + alpha_min: float = 1e-08, + diff_mode=None, +) -> None: ... diff --git a/stubs/pyomo/util/check_units.pyi b/stubs/pyomo/util/check_units.pyi new file mode 100644 index 000000000..75ae61d2f --- /dev/null +++ b/stubs/pyomo/util/check_units.pyi @@ -0,0 +1,36 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.base import Block as Block +from pyomo.core.base import BooleanVar as BooleanVar +from pyomo.core.base import BuildAction as BuildAction +from pyomo.core.base import BuildCheck as BuildCheck +from pyomo.core.base import Constraint as Constraint +from pyomo.core.base import Expression as Expression +from pyomo.core.base import ExternalFunction as ExternalFunction +from pyomo.core.base import Objective as Objective +from pyomo.core.base import Param as Param +from pyomo.core.base import RangeSet as RangeSet +from pyomo.core.base import Set as Set +from pyomo.core.base import SetOf as SetOf +from pyomo.core.base import Suffix as Suffix +from pyomo.core.base import Var as Var +from pyomo.core.base import value as value +from pyomo.core.base.units_container import UnitsError as UnitsError +from pyomo.core.base.units_container import units as units +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.template_expr import IndexTemplate as IndexTemplate +from pyomo.dae import ContinuousSet as ContinuousSet +from pyomo.dae import DerivativeVar as DerivativeVar +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction +from pyomo.mpec import Complementarity as Complementarity +from pyomo.network import Arc as Arc +from pyomo.network import Port as Port +from pyomo.util.components import iter_component as iter_component + +logger: Incomplete + +def check_units_equivalent(*args): ... +def assert_units_equivalent(*args) -> None: ... +def assert_units_consistent(obj) -> None: ... +def identify_inconsistent_units(block): ... diff --git a/stubs/pyomo/util/components.pyi b/stubs/pyomo/util/components.pyi new file mode 100644 index 000000000..a8315ffad --- /dev/null +++ b/stubs/pyomo/util/components.pyi @@ -0,0 +1,6 @@ +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core.base.reference import Reference as Reference + +def rename_components(model, component_list, prefix): ... +def iter_component(obj): ... diff --git a/stubs/pyomo/util/config_domains.pyi b/stubs/pyomo/util/config_domains.pyi new file mode 100644 index 000000000..e2498664d --- /dev/null +++ b/stubs/pyomo/util/config_domains.pyi @@ -0,0 +1,6 @@ +from pyomo.common.collections import ComponentSet as ComponentSet + +class ComponentDataSet: + def __init__(self, ctype) -> None: ... + def __call__(self, x): ... + def domain_name(self): ... diff --git a/stubs/pyomo/util/diagnostics.pyi b/stubs/pyomo/util/diagnostics.pyi new file mode 100644 index 000000000..de8e4a196 --- /dev/null +++ b/stubs/pyomo/util/diagnostics.pyi @@ -0,0 +1,8 @@ +from _typeshed import Incomplete +from pyomo.core.base.block import Block as Block +from pyomo.core.base.block import TraversalStrategy as TraversalStrategy +from pyomo.gdp import Disjunct as Disjunct + +logger: Incomplete + +def log_disjunct_values(m) -> None: ... diff --git a/stubs/pyomo/util/infeasible.pyi b/stubs/pyomo/util/infeasible.pyi new file mode 100644 index 000000000..1f17500a6 --- /dev/null +++ b/stubs/pyomo/util/infeasible.pyi @@ -0,0 +1,21 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common import deprecated as deprecated +from pyomo.core import Constraint as Constraint +from pyomo.core import Var as Var +from pyomo.core import value as value +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.util.blockutil import log_model_constraints as log_model_constraints + +logger: Incomplete + +def find_infeasible_constraints(m, tol: float = 1e-06) -> Generator[Incomplete]: ... +def log_infeasible_constraints( + m, tol: float = 1e-06, logger=..., log_expression: bool = False, log_variables: bool = False +) -> None: ... +def find_infeasible_bounds(m, tol: float = 1e-06) -> Generator[Incomplete]: ... +def log_infeasible_bounds(m, tol: float = 1e-06, logger=...) -> None: ... +def find_close_to_bounds(m, tol: float = 1e-06) -> Generator[Incomplete]: ... +def log_close_to_bounds(m, tol: float = 1e-06, logger=...) -> None: ... +def log_active_constraints(m, logger=...) -> None: ... diff --git a/stubs/pyomo/util/model_size.pyi b/stubs/pyomo/util/model_size.pyi new file mode 100644 index 000000000..74b2ed9e2 --- /dev/null +++ b/stubs/pyomo/util/model_size.pyi @@ -0,0 +1,15 @@ +from _typeshed import Incomplete +from pyomo.common.collections import Bunch as Bunch +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core import Block as Block +from pyomo.core import Constraint as Constraint +from pyomo.core import Var as Var +from pyomo.gdp import Disjunct as Disjunct +from pyomo.gdp import Disjunction as Disjunction + +default_logger: Incomplete + +class ModelSizeReport(Bunch): ... + +def build_model_size_report(model): ... +def log_model_size_report(model, logger=...) -> None: ... diff --git a/stubs/pyomo/util/report_scaling.pyi b/stubs/pyomo/util/report_scaling.pyi new file mode 100644 index 000000000..8c9379829 --- /dev/null +++ b/stubs/pyomo/util/report_scaling.pyi @@ -0,0 +1,10 @@ +from _typeshed import Incomplete +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.contrib.fbbt.fbbt import compute_bounds_on_expr as compute_bounds_on_expr +from pyomo.core.base.block import BlockData as BlockData +from pyomo.core.base.var import Var as Var +from pyomo.core.expr.calculus.diff_with_pyomo import reverse_sd as reverse_sd + +logger: Incomplete + +def report_scaling(m: BlockData, too_large: float = 50000.0, too_small: float = 1e-06) -> bool: ... diff --git a/stubs/pyomo/util/slices.pyi b/stubs/pyomo/util/slices.pyi new file mode 100644 index 000000000..f988f9724 --- /dev/null +++ b/stubs/pyomo/util/slices.pyi @@ -0,0 +1,9 @@ +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.core.base.global_set import UnindexedComponent_set as UnindexedComponent_set +from pyomo.core.base.indexed_component import normalize_index as normalize_index +from pyomo.core.base.indexed_component_slice import IndexedComponent_slice as IndexedComponent_slice + +def get_component_call_stack(comp, context=None): ... +def slice_component_along_sets(comp, sets, context=None): ... +def replace_indices(index, location_set_map, sets): ... +def get_location_set_map(index, index_set): ... diff --git a/stubs/pyomo/util/subsystems.pyi b/stubs/pyomo/util/subsystems.pyi new file mode 100644 index 000000000..c3ab68d29 --- /dev/null +++ b/stubs/pyomo/util/subsystems.pyi @@ -0,0 +1,66 @@ +import types +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.common.collections import ComponentMap as ComponentMap +from pyomo.common.collections import ComponentSet as ComponentSet +from pyomo.common.modeling import unique_component_name as unique_component_name +from pyomo.core.base.block import Block as Block +from pyomo.core.base.constraint import Constraint as Constraint +from pyomo.core.base.expression import Expression as Expression +from pyomo.core.base.external import ExternalFunction as ExternalFunction +from pyomo.core.base.objective import Objective as Objective +from pyomo.core.base.reference import Reference as Reference +from pyomo.core.expr.numeric_expr import ExternalFunctionExpression as ExternalFunctionExpression +from pyomo.core.expr.numvalue import NumericValue as NumericValue +from pyomo.core.expr.numvalue import native_types as native_types +from pyomo.core.expr.visitor import StreamBasedExpressionVisitor as StreamBasedExpressionVisitor +from pyomo.core.expr.visitor import identify_variables as identify_variables +from pyomo.util.vars_from_expressions import get_vars_from_components as get_vars_from_components + +class _ExternalFunctionVisitor(StreamBasedExpressionVisitor): + named_expressions: Incomplete + def __init__(self, descend_into_named_expressions: bool = True) -> None: ... + def initializeWalker(self, expr): ... + def beforeChild(self, parent, child, index): ... + def exitNode(self, node, data) -> None: ... + def finalizeResult(self, result): ... + +def identify_external_functions(expr) -> Generator[Incomplete, Incomplete]: ... +def add_local_external_functions(block): ... +def create_subsystem_block(constraints, variables=None, include_fixed: bool = False): ... +def generate_subsystem_blocks(subsystems, include_fixed: bool = False) -> Generator[Incomplete]: ... + +class TemporarySubsystemManager: + def __init__( + self, + to_fix=None, + to_deactivate=None, + to_reset=None, + to_unfix=None, + remove_bounds_on_fix: bool = False, + ) -> None: ... + def __enter__(self): ... + def __exit__( + self, + ex_type: type[BaseException] | None, + ex_val: BaseException | None, + ex_bt: types.TracebackType | None, + ) -> None: ... + +class ParamSweeper(TemporarySubsystemManager): + input_values: Incomplete + output_values: Incomplete + n_scenario: Incomplete + initial_state_values: Incomplete + def __init__( + self, + n_scenario, + input_values, + output_values=None, + to_fix=None, + to_deactivate=None, + to_reset=None, + ) -> None: ... + def __iter__(self): ... + def __next__(self): ... diff --git a/stubs/pyomo/util/vars_from_expressions.pyi b/stubs/pyomo/util/vars_from_expressions.pyi new file mode 100644 index 000000000..c5b99746d --- /dev/null +++ b/stubs/pyomo/util/vars_from_expressions.pyi @@ -0,0 +1,15 @@ +from collections.abc import Generator + +from _typeshed import Incomplete +from pyomo.core import Block as Block +from pyomo.core.expr.visitor import IdentifyVariableVisitor as IdentifyVariableVisitor + +def get_vars_from_components( + block, + ctype, + include_fixed: bool = True, + active=None, + sort: bool = False, + descend_into=..., + descent_order=None, +) -> Generator[Incomplete]: ... diff --git a/stubs/pyomo/version/__init__.pyi b/stubs/pyomo/version/__init__.pyi new file mode 100644 index 000000000..fc61a17ad --- /dev/null +++ b/stubs/pyomo/version/__init__.pyi @@ -0,0 +1,3 @@ +from pyomo.version.info import __version__ as __version__ +from pyomo.version.info import version as version +from pyomo.version.info import version_info as version_info diff --git a/stubs/pyomo/version/info.pyi b/stubs/pyomo/version/info.pyi new file mode 100644 index 000000000..d70384d20 --- /dev/null +++ b/stubs/pyomo/version/info.pyi @@ -0,0 +1,10 @@ +from _typeshed import Incomplete + +major: int +minor: int +micro: int +releaselevel: str +serial: int +version_info: Incomplete +__version__: Incomplete +version = __version__ diff --git a/temoa/__about__.py b/temoa/__about__.py new file mode 100644 index 000000000..28c809a05 --- /dev/null +++ b/temoa/__about__.py @@ -0,0 +1,24 @@ +import re + +__version__ = '4.0.0a2' + +# Parse the version string to get major and minor versions +# We use a regex to be robust against versions like "4.1a1" or "4.0.0.dev1" +_match = re.match(r'^(\d+)\.(\d+)', __version__) +if not _match: + raise ValueError( + f"Could not parse major/minor version from '{__version__}'. " + "Expected format 'X.Y...' where X and Y are integers." + ) + +TEMOA_MAJOR = int(_match.group(1)) +TEMOA_MINOR = int(_match.group(2)) + +# === REQUIREMENTS === +# python versions are tested internally for greater than these values +MIN_PYTHON_MAJOR = 3 +MIN_PYTHON_MINOR = 12 + +# db is tested for match on major and >= on minor +DB_MAJOR_VERSION = 4 +MIN_DB_MINOR_VERSION = 0 diff --git a/temoa/__init__.py b/temoa/__init__.py index e69de29bb..67ef283bd 100644 --- a/temoa/__init__.py +++ b/temoa/__init__.py @@ -0,0 +1,62 @@ +""" +Tools for Energy Model Optimization and Analysis (Temoa): +An open source framework for energy systems optimization modeling + +This module provides backward compatibility imports for the refactored TEMOA structure. +New code should import directly from temoa.core and temoa._internal as appropriate. +""" + +# Core API - public interface +# Internal modules - for backward compatibility +# Version information +from temoa.__about__ import TEMOA_MAJOR, TEMOA_MINOR, __version__ +from temoa._internal.data_brick import DataBrick, data_brick_factory +from temoa._internal.exchange_tech_cost_ledger import CostType, ExchangeTechCostLedger +from temoa._internal.run_actions import ( + build_instance, + handle_results, + save_lp, + solve_instance, +) +from temoa._internal.table_data_puller import ( + loan_costs, + poll_capacity_results, + poll_cost_results, + poll_emissions, + poll_flow_results, +) +from temoa._internal.table_writer import TableWriter +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.model import TemoaModel +from temoa.core.modes import TemoaMode +from temoa.data_io.hybrid_loader import HybridLoader + +# Maintain backward compatibility for common imports +__all__ = [ + # Core API + 'TemoaModel', + 'TemoaConfig', + 'TemoaMode', + # Internal modules (for backward compatibility) + 'DataBrick', + 'data_brick_factory', + 'CostType', + 'ExchangeTechCostLedger', + 'HybridLoader', + 'build_instance', + 'solve_instance', + 'handle_results', + 'save_lp', + 'loan_costs', + 'poll_capacity_results', + 'poll_emissions', + 'poll_flow_results', + 'poll_cost_results', + 'TableWriter', + 'TemoaSequencer', + # Version info + 'TEMOA_MAJOR', + 'TEMOA_MINOR', + '__version__', +] diff --git a/temoa/__main__.py b/temoa/__main__.py new file mode 100644 index 000000000..610b96473 --- /dev/null +++ b/temoa/__main__.py @@ -0,0 +1,4 @@ +from .cli import app + +if __name__ == '__main__': + app() diff --git a/temoa/_internal/__init__.py b/temoa/_internal/__init__.py new file mode 100644 index 000000000..eb59f677d --- /dev/null +++ b/temoa/_internal/__init__.py @@ -0,0 +1,8 @@ +""" +TEMOA Internal API + +This module contains internal implementation details for the TEMOA energy systems modeling library. +These modules are not part of the public API and may change without notice. +""" + +__all__: list[str] = [] diff --git a/temoa/temoa_model/data_brick.py b/temoa/_internal/data_brick.py similarity index 55% rename from temoa/temoa_model/data_brick.py rename to temoa/_internal/data_brick.py index 0b026834d..a83544bee 100644 --- a/temoa/temoa_model/data_brick.py +++ b/temoa/_internal/data_brick.py @@ -1,63 +1,41 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 12/5/24 - -Objective of this module is to build a lightweight container to hold a selection of model results from a -Worker process with the intent to send this back via multiprocessing queue in lieu of sending the entire -model back (which is giant and slow). It will probably be a "superset" of data elements required to report -for MC and MGA right now, and maybe others +Objective of this module is to build a lightweight container to hold a selection of model results +from a Worker process with the intent to send this back via multiprocessing queue in lieu of +sending the entire +model back (which is giant and slow). It will probably be a "superset" of data elements required +to report for MC and MGA right now, and maybe others """ -from temoa.temoa_model.table_data_puller import ( - EI, - CapData, +from temoa._internal.exchange_tech_cost_ledger import CostType +from temoa._internal.table_data_puller import ( + poll_capacity_results, poll_cost_results, - poll_flow_results, poll_emissions, + poll_flow_results, poll_objective, - poll_capacity_results, ) -from temoa.temoa_model.temoa_model import TemoaModel +from temoa.core.model import TemoaModel +from temoa.types.core_types import Period, Region, Technology, Vintage +from temoa.types.model_types import EI, FI, CapData, FlowType class DataBrick: """ - A utility container for bundling assorted data structures for solved models done by Worker objects. + A utility container for bundling assorted data structures for solved models done by Worker + objects. """ def __init__( self, - name, - emission_costs, - emission_flows, - capacity_data, - flow_data, - obj_data, - regular_costs, - exchange_costs, + name: str, + emission_costs: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], + emission_flows: dict[EI, float], + capacity_data: CapData, + flow_data: dict[FI, dict[FlowType, float]], + obj_data: list[tuple[str, float]], + regular_costs: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], + exchange_costs: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], ): self._name = name self._emission_costs = emission_costs @@ -81,23 +59,27 @@ def capacity_data(self) -> CapData: return self._capacity_data @property - def flow_data(self) -> dict: + def flow_data(self) -> dict[FI, dict[FlowType, float]]: return self._flow_data @property - def obj_data(self) -> list: + def obj_data(self) -> list[tuple[str, float]]: return self._obj_data @property - def cost_data(self): + def cost_data(self) -> dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]]: return self._regular_costs @property - def exchange_cost_data(self): + def exchange_cost_data( + self, + ) -> dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]]: return self._exchange_costs @property - def emission_cost_data(self): + def emission_cost_data( + self, + ) -> dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]]: return self._emission_costs diff --git a/temoa/temoa_model/exchange_tech_cost_ledger.py b/temoa/_internal/exchange_tech_cost_ledger.py similarity index 50% rename from temoa/temoa_model/exchange_tech_cost_ledger.py rename to temoa/_internal/exchange_tech_cost_ledger.py index 5faedb576..12b170f5f 100644 --- a/temoa/temoa_model/exchange_tech_cost_ledger.py +++ b/temoa/_internal/exchange_tech_cost_ledger.py @@ -1,42 +1,23 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/1/24 The purpose of this module is to provide a ledger for all costs for exchange techs. The main reason -for the need is that in many cases, the costs need to be apportioned by use ratio so it is helpful to -separately gather all of the costs and then use a usage ratio to generate entries when asked for +for the need is that in many cases, the costs need to be apportioned by use ratio so it is helpful +to separately gather all of the costs and then use a usage ratio to generate entries when asked for """ + +from __future__ import annotations + from collections import defaultdict -from enum import unique, Enum -from typing import Union +from enum import Enum, unique +from typing import TYPE_CHECKING, cast from pyomo.common.numeric_types import value -from temoa.temoa_model.temoa_model import TemoaModel -from tests.utilities.namespace_mock import Namespace +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types.core_types import Period, Region, Technology, Vintage + from tests.utilities.namespace_mock import Namespace @unique @@ -52,23 +33,35 @@ class CostType(Enum): class ExchangeTechCostLedger: - def __init__(self, M: Union[TemoaModel, 'Namespace']) -> None: - self.cost_records: dict[CostType, dict] = defaultdict(dict) + def __init__(self, model: TemoaModel | Namespace) -> None: + self.cost_records: dict[ + CostType, dict[tuple[Region, Region, Technology, Vintage, Period], float] + ] = defaultdict(dict) # could be a Namespace for testing purposes... See the related test - self.M = M - - def add_cost_record(self, link: str, period, tech, vintage, cost: float, cost_type: CostType): + self.model = model + + def add_cost_record( + self, + link: Region, + period: Period, + tech: Technology, + vintage: Vintage, + cost: float, + cost_type: CostType, + ) -> None: """ add a cost associated with an exchange tech :return: """ - r1, r2 = link.split('-') + r1, r2 = (cast('Region', r) for r in link.split('-')) if not r1 and r2: raise ValueError(f'problem splitting region-region: {link}') # add to the "seen" records for appropriate cost type self.cost_records[cost_type][r1, r2, tech, vintage, period] = cost - def get_use_ratio(self, exporter, importer, period, tech, vintage) -> float: + def get_use_ratio( + self, exporter: Region, importer: Region, period: Period, tech: Technology, vintage: Vintage + ) -> float: """ use flow to calculate the use ratio for these 2 entities for cost apportioning purposes :param exporter: @@ -78,50 +71,53 @@ def get_use_ratio(self, exporter, importer, period, tech, vintage) -> float: :param vintage: :return: the proportion to assign to the IMPORTER, or 0.5 if no usage """ - M = self.M + # Cast to TemoaModel for type checking - at runtime this will be either TemoaModel or + # Namespace + # Both have the same attributes, but mypy doesn't know about Namespace's dynamic attributes + model = cast('TemoaModel', self.model) # need to temporarily reconstitute the names - rr1 = '-'.join([exporter, importer]) - rr2 = '-'.join([importer, exporter]) + rr1 = cast('Region', '-'.join([exporter, importer])) + rr2 = cast('Region', '-'.join([importer, exporter])) if any( ( - period >= vintage + value(M.LifetimeProcess[rr1, tech, vintage]), - period >= vintage + value(M.LifetimeProcess[rr2, tech, vintage]), + period >= vintage + value(model.lifetime_process[rr1, tech, vintage]), + period >= vintage + value(model.lifetime_process[rr2, tech, vintage]), period < vintage, ) ): raise ValueError('received a bogus cost for an illegal period.') - if tech not in M.tech_annual: + if tech not in model.tech_annual: act_dir1 = value( sum( - M.V_FlowOut[rr1, period, s, d, S_i, tech, vintage, S_o] - for s in M.time_season - for d in M.time_of_day - for S_i in M.processInputs[rr1, period, tech, vintage] - for S_o in M.ProcessOutputsByInput[rr1, period, tech, vintage, S_i] + model.v_flow_out[rr1, period, s, d, s_i, tech, vintage, s_o] + for s in model.time_season + for d in model.time_of_day + for s_i in model.process_inputs[rr1, period, tech, vintage] + for s_o in model.process_outputs_by_input[rr1, period, tech, vintage, s_i] ) ) act_dir2 = value( sum( - M.V_FlowOut[rr2, period, s, d, S_i, tech, vintage, S_o] - for s in M.time_season - for d in M.time_of_day - for S_i in M.processInputs[rr2, period, tech, vintage] - for S_o in M.ProcessOutputsByInput[rr2, period, tech, vintage, S_i] + model.v_flow_out[rr2, period, s, d, s_i, tech, vintage, s_o] + for s in model.time_season + for d in model.time_of_day + for s_i in model.process_inputs[rr2, period, tech, vintage] + for s_o in model.process_outputs_by_input[rr2, period, tech, vintage, s_i] ) ) else: act_dir1 = value( sum( - M.V_FlowOutAnnual[rr1, period, S_i, tech, vintage, S_o] - for S_i in M.processInputs[rr1, period, tech, vintage] - for S_o in M.ProcessOutputsByInput[rr1, period, tech, vintage, S_i] + model.v_flow_out_annual[rr1, period, s_i, tech, vintage, s_o] + for s_i in model.process_inputs[rr1, period, tech, vintage] + for s_o in model.process_outputs_by_input[rr1, period, tech, vintage, s_i] ) ) act_dir2 = value( sum( - M.V_FlowOutAnnual[rr2, period, S_i, tech, vintage, S_o] - for S_i in M.processInputs[rr2, period, tech, vintage] - for S_o in M.ProcessOutputsByInput[rr2, period, tech, vintage, S_i] + model.v_flow_out_annual[rr2, period, s_i, tech, vintage, s_o] + for s_i in model.process_inputs[rr2, period, tech, vintage] + for s_o in model.process_outputs_by_input[rr2, period, tech, vintage, s_i] ) ) @@ -129,9 +125,14 @@ def get_use_ratio(self, exporter, importer, period, tech, vintage) -> float: return act_dir1 / (act_dir1 + act_dir2) return 0.5 - def get_entries(self) -> dict: - region_costs = defaultdict(dict) - # iterate through each region pairing, pull the cost records and decide if/how to split each one + def get_entries( + self, + ) -> dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]]: + region_costs: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]] = ( + defaultdict(dict) + ) + # iterate through each region pairing, pull the cost records and decide if/how to split + # each one for cost_type in self.cost_records: # make a copy, this will be destructive operation records = self.cost_records[cost_type].copy() diff --git a/temoa/_internal/run_actions.py b/temoa/_internal/run_actions.py new file mode 100644 index 000000000..9cd82447e --- /dev/null +++ b/temoa/_internal/run_actions.py @@ -0,0 +1,393 @@ +""" +Basic-level atomic functions that can be used by a sequencer, as needed +""" + +import sqlite3 +from collections.abc import Generator, Iterable +from contextlib import contextmanager +from logging import getLogger +from pathlib import Path +from sys import version_info +from time import perf_counter + +from pyomo.environ import ( + Constraint, + DataPortal, + SolverFactory, + Suffix, + UnknownSolver, + Var, + check_optimal_termination, + value, +) +from pyomo.opt import SolverResults + +from temoa._internal.table_writer import TableWriter +from temoa.core.config import TemoaConfig +from temoa.core.model import TemoaModel +from temoa.data_processing.db_to_excel import make_excel + +logger = getLogger(__name__) + + +@contextmanager +def task_timer(action_name: str, *, silent: bool = False) -> Generator[None, None, None]: + """ + Context manager to time blocks of code using the standard logger. + """ + if not silent: + logger.info('Started: %s', action_name) + + start_time = perf_counter() + + try: + yield + finally: + duration = perf_counter() - start_time + if not silent: + # sample output: [10:30:35] INFO Finished: Creating model instance (Time taken: 0.07s) + logger.info('Finished: %s (Time taken: %.2fs)', action_name, duration) + + +def check_python_version(min_major: int, min_minor: int) -> bool: + if (min_major, min_minor) >= version_info: + logger.error( + 'Model is being run with python %d.%d. Expecting version %d.%d or later. ', + version_info.major, + version_info.minor, + min_major, + min_minor, + ) + return False + return True + + +def check_database_version(config: TemoaConfig, db_major_reqd: int, min_db_minor: int) -> bool: + """ + check the db version + :param config: TemoaConfig instance + :param db_major_reqd: the required major version (equality test) + :param min_db_minor: the required minimum minor version (GTE test) + :return: T/F + """ + db_paths = [config.input_database] + if config.input_database != config.output_database: + db_paths.append(config.output_database) + + # check for correct version + all_good = True + + for name in db_paths: + con = sqlite3.connect(name) + try: + db_major_row = con.execute( + "SELECT value from metadata where element = 'DB_MAJOR'" + ).fetchone() + db_minor_row = con.execute( + "SELECT value from metadata where element = 'DB_MINOR'" + ).fetchone() + db_major = int(db_major_row[0]) if db_major_row else -1 + db_minor = int(db_minor_row[0]) if db_minor_row else -1 + except sqlite3.OperationalError: + logger.error( + 'Database %s does not appear to have metadata table. Is this v%d.%d+ compatible?' + 'If required, see docs on using the database migrator to move to v%d.%d.', + str(name), + db_major_reqd, + min_db_minor, + db_major_reqd, + min_db_minor, + ) + db_major, db_minor = -1, -1 + finally: + con.close() + + good_version = db_major == db_major_reqd and db_minor >= min_db_minor + if not good_version: + logger.error( + 'Database %s version %d.%d does not match the major version %d and have ' + 'at least minor version %d', + str(name), + db_major, + db_minor, + db_major_reqd, + min_db_minor, + ) + all_good &= good_version + + return all_good + + +def build_instance( + loaded_portal: DataPortal, + model_name: str | None = None, + silent: bool = False, + keep_lp_file: bool = False, + lp_path: Path | None = None, +) -> TemoaModel: + """ + Build a Temoa Instance from data + :param lp_path: the path to save the LP file to + :param keep_lp_file: True to keep the LP file + :param loaded_portal: a DataPortal instance + :param silent: Run silently + :param model_name: Optional name for this instance + :return: a built TemoaModel + """ + model = TemoaModel() + + model.dual = Suffix(direction=Suffix.IMPORT) + # self.model.rc = Suffix(direction=Suffix.IMPORT) + # self.model.slack = Suffix(direction=Suffix.IMPORT) + + with task_timer('Creating model instance', silent=silent): + instance = model.create_instance(loaded_portal, name=model_name) + + # save LP if requested + if keep_lp_file and lp_path is not None: + save_lp(instance, lp_path) + + # gather some stats... + c_count = sum(len(c) for c in instance.component_objects(ctype=Constraint)) + v_count = sum(len(v) for v in instance.component_objects(ctype=Var)) + + logger.info('Model built... Variables: %d, Constraints: %d', v_count, c_count) + return instance + + +def save_lp(instance: TemoaModel, lp_path: Path) -> None: + """ + quick utility to save the LP file to disc. + Note: if saving multiple LP's they need to be differentiated by path + """ + if not lp_path: + logger.warning('Requested "keep LP file", but no path is provided...skipped') + else: + lp_path.mkdir(parents=True, exist_ok=True) + filename = lp_path / 'model.lp' + instance.write(str(filename), format='lp', io_options={'symbolic_solver_labels': True}) + + +def solve_instance( + instance: TemoaModel, + solver_name: str, + silent: bool = False, + solver_suffixes: Iterable[str] | None = None, +) -> tuple[TemoaModel, SolverResults]: + """ + Solve the instance and return a loaded instance + :param solver_suffixes: iterable of string names for suffixes. See pyomo dox. right now, only + 'duals' is supported in the Temoa Framework. Some solvers may not support duals. + :param silent: Run silently + :param solver_name: The name of the solver to request from the SolverFactory + :param instance: the instance to solve + :return: loaded instance + """ + + # QA the solver name and get a handle on solver + if not solver_name: + logger.error('No solver specified in solve sequence') + raise TypeError('Error occurred during solve, see log') + + optimizer = SolverFactory(solver_name) + if isinstance(optimizer, UnknownSolver): + logger.error( + 'Failed to create a solver instance for name: %s. Check name and availability on ' + 'this system', + solver_name, + ) + raise TypeError('Failed to make Solver instance. See log.') + + if solver_name == 'neos': + raise NotImplementedError('Neos based solve is not currently supported') + + # Solver Configuration + if solver_name == 'cbc': + pass + + elif solver_name == 'cplex': + # Note: these parameter values match mip-dev / PyPSA + # (see: https://pypsa-eur.readthedocs.io/en/latest/configuration.html) + optimizer.options['lpmethod'] = 4 # barrier + optimizer.options['solutiontype'] = 2 # non basic solution, ie no crossover + optimizer.options['barrier convergetol'] = 1.0e-3 + optimizer.options['feasopt tolerance'] = 1.0e-4 + + elif solver_name == 'gurobi': + # Note: these parameter values match mip-dev / PyPSA (see: https://pypsa-eur.readthedocs.io/en/latest/configuration.html) + optimizer.options['Method'] = 2 # barrier + optimizer.options['Crossover'] = 0 # non basic solution, ie no crossover + optimizer.options['BarConvTol'] = 1.0e-3 + optimizer.options['FeasibilityTol'] = 1.0e-4 + optimizer.options['BarOrder'] = -1 # auto ordering; 2-4x faster than AMD on large models + + elif solver_name == 'appsi_highs': + pass + + # Suffix Handling + solver_suffixes_list: list[str] = [] + if solver_suffixes: + solver_suffixes_set = set(solver_suffixes) + legit_suffixes = {'dual', 'slack', 'rc'} + bad_apples = solver_suffixes_set - legit_suffixes + solver_suffixes_set &= legit_suffixes + if bad_apples: + logger.warning( + 'Solver suffix %s is not in pyomo standards (see pyomo dox). Removed', + bad_apples, + ) + solver_suffixes_list = list(solver_suffixes_set) + + result: SolverResults | None = None + + with task_timer(f'Solving model {instance.name}', silent=silent): + try: + # currently, the highs solver call will puke if the suffixes are passed + if solver_name == 'appsi_highs': + result = optimizer.solve(instance) + else: + result = optimizer.solve(instance, suffixes=solver_suffixes_list) + except RuntimeError as error: + logger.exception('Solver failed to solve and returned an error: %s', error) + logger.error( + 'This may be due to asking for suffixes (duals) for an incompatible solver. ' + "Try de-selecting 'save_duals' in the config. (see note in run_actions.py code)" + ) + if result: + try: + _ok, status_msg = check_solve_status(result) + except Exception: + status_msg = '' + logger.error( + 'Solver reported termination/status (if any): %s', + status_msg, + ) + raise RuntimeError('Solver failure. See log file.') from error + + if check_optimal_termination(result): + if solver_suffixes_list: + # Needed to capture the duals/suffixes from the Solutions obj + instance.solutions.store_to(result) + + logger.debug('Solver results: \n %s', result.solver) + + return instance, result + + +def check_solve_status(result: SolverResults) -> tuple[bool, str]: + """ + Check the status of the solve in a solver-agnostic way. + Handles both legacy solver results and APPSI solver results. + + :param result: the results object returned by the solver + :return: tuple of status boolean (True='optimal', others False), and string message if not + optimal + """ + # Use check_optimal_termination for solver-agnostic checking + is_optimal = check_optimal_termination(result) + + # Safely extract termination condition for logging + termination_condition = 'unknown' + if hasattr(result, 'solver') and hasattr(result.solver, 'termination_condition'): + termination_condition = result.solver.termination_condition + elif hasattr(result, 'termination_condition'): + # Some APPSI solvers expose this directly + termination_condition = result.termination_condition + + logger.info( + 'Solver termination condition: %s (optimal: %s)', + termination_condition, + is_optimal, + ) + + if is_optimal: + return True, '' + + # Safely extract status for error message + status_msg = 'unknown status' + + # Try legacy solver result format first + if hasattr(result, '__getitem__'): + try: + soln = result.get('Solution') if hasattr(result, 'get') else result['Solution'] + if soln and hasattr(soln, 'Status'): + status_msg = str(soln.Status) + except (KeyError, TypeError, AttributeError): + pass + + # Try APPSI result format + if status_msg == 'unknown status': + for attr in ['status', 'problem_status', 'solver_status']: + if hasattr(result, attr): + status_msg = str(getattr(result, attr)) + break + + # Final fallback + if status_msg == 'unknown status': + status_msg = str(result) if result else 'no solution returned' + + return False, f'{status_msg} was returned from solve' + + +def handle_results( + instance: TemoaModel, + results: SolverResults, + config: TemoaConfig, + append: bool = False, + iteration: int | None = None, +) -> None: + with task_timer('Processing results', silent=config.silent): + table_writer = TableWriter(config=config) + table_writer.write_results( + model=instance, + results_with_duals=results if config.save_duals else None, + save_storage_levels=config.save_storage_levels, + append=append, + iteration=iteration, + ) + + if config.save_excel: + scenario_name = ( + f'{config.scenario}-{iteration}' if iteration is not None else config.scenario + ) + temp_scenario = {scenario_name} + excel_filename = config.output_path / scenario_name + make_excel(str(config.output_database), excel_filename, temp_scenario) + + # normal (non-MGA) run will have a total_cost as the OBJ: + if hasattr(instance, 'total_cost'): + logger.info('Total Cost value: %0.2f', value(instance.total_cost)) + + if config.graphviz_output: + try: + from temoa.utilities.graphviz_generator import GraphvizDiagramGenerator + + logger.info('Generating Graphviz plots...') + # Determine output directory (same as other outputs) + out_dir = str(config.output_path) + + # Initialize generator + graph_gen = GraphvizDiagramGenerator( + db_file=str(config.output_database), + scenario=config.scenario, + out_dir=out_dir, + verbose=0, # Less verbose for integrated run + ) + graph_gen.connect() + + try: + # Get periods from the model instance + periods = sorted(instance.time_optimize) + + for period in periods: + # Generate main results diagram for the period + # We pass None for region to generate for all/default + graph_gen.create_main_results_diagram(period=period, region=None) + except Exception as e: + logger.error('Failed to generate Graphviz plots: %s', e, exc_info=True) + finally: + graph_gen.close() + logger.info('Graphviz plots generated in %s', graph_gen.out_dir) + + except Exception as e: + logger.error('Failed to generate Graphviz plots: %s', e, exc_info=True) diff --git a/temoa/_internal/table_data_puller.py b/temoa/_internal/table_data_puller.py new file mode 100644 index 000000000..832d3557c --- /dev/null +++ b/temoa/_internal/table_data_puller.py @@ -0,0 +1,783 @@ +""" +A companion module to the table writer to hold some data-pulling functions and small utilities and +separate them from the writing process for organization and to isolate the DB access in the writer +such that these functions can be called on a model instance without any DB interactions. (Intended +to support use by Workers who shouldn't interact with DB). Dev Note: In future, if transition +away from sqlite, this could all be refactored to perform tasks within workers, but concurrent +access to sqlite is a no-go +""" + +from __future__ import annotations + +import functools +import logging +from collections import defaultdict +from typing import TYPE_CHECKING, cast + +from pyomo.common.numeric_types import value +from pyomo.core import Objective + +from temoa._internal.exchange_tech_cost_ledger import CostType, ExchangeTechCostLedger +from temoa.components import costs +from temoa.components.utils import get_variable_efficiency +from temoa.types.model_types import EI, FI, SLI, CapData, FlowType + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types.core_types import Commodity, Period, Region, Technology, Vintage + +logger = logging.getLogger(__name__) + + +def _marks(num: int) -> str: + """convenience to make a sequence of question marks for query""" + qs = ','.join('?' for _ in range(num)) + marks = '(' + qs + ')' + return marks + + +def ritvo(fi: FI) -> tuple[Region, Commodity | None, Technology, Vintage, Commodity | None]: + """convert FI to ritvo index""" + return fi.r, fi.i, fi.t, fi.v, fi.o + + +def rpetv(fi: FI, e: Commodity) -> tuple[Region, Period, Commodity | None, Technology, Vintage]: + """convert FI and emission to rpetv index""" + return fi.r, fi.p, e, fi.t, fi.v + + +def poll_capacity_results(model: TemoaModel, epsilon: float = 1e-5) -> CapData: + """ + Poll a solved model for capacity results. + :param M: Solved Model + :param epsilon: epsilon (default 1e-5) + :return: a CapData object + """ + # Built Capacity + built = [] + for r, t, v in model.v_new_capacity.keys(): + if v in model.time_optimize: + val = value(model.v_new_capacity[r, t, v]) + if val < -epsilon: + logger.warning( + 'Negative built capacity for %s, %s, %s: %s. ' + 'This should not be possible. Could be a result of ' + 'numerical instability or a code problem.', + r, + t, + v, + val, + ) + continue + if val < epsilon: + continue + new_cap = (r, t, v, val) + built.append(new_cap) + + # NetCapacity + net = [] + for r, p, t, v in model.v_capacity.keys(): + val = value(model.v_capacity[r, p, t, v]) + if val < -epsilon: + logger.warning( + 'Negative net capacity for %s, %s, %s, %s: %s. ' + 'This should not be possible. Could be a result of ' + 'numerical instability or a code problem.', + r, + p, + t, + v, + val, + ) + continue + if val < epsilon: + continue + new_net_cap = (r, p, t, v, val) + net.append(new_net_cap) + + # Retired Capacity + ret = [] + for r, t, v in model.retirement_periods: + lifetime = value(model.lifetime_process[r, t, v]) + for p in model.retirement_periods[r, t, v]: + # We want to output period retirement, not annual retirement, so multiply by + # PeriodLength + eol = value(model.period_length[p]) * value(model.v_annual_retirement[r, p, t, v]) + early = 0 + if t in model.tech_retirement and v < p <= v + lifetime - value(model.period_length[p]): + early = value(model.v_retired_capacity[r, p, t, v]) + eol -= early + if early < -epsilon or eol < -epsilon: + logger.warning( + 'Negative retirement components for %s, %s, %s, %s: cap_eol=%s, cap_early=%s', + r, + p, + t, + v, + eol, + early, + ) + continue + early = 0 if early < epsilon else early + eol = 0 if eol < epsilon else eol + if early == 0 and eol == 0: + continue + new_retired_cap = (r, p, t, v, eol, early) + ret.append(new_retired_cap) + + return CapData(built=built, net=net, retired=ret) + + +def poll_flow_results(model: TemoaModel, epsilon: float = 1e-5) -> dict[FI, dict[FlowType, float]]: + """ + Poll a solved model for flow results. + :param M: A solved Model + :param epsilon: epsilon (default 1e-5) + :return: nested dictionary of FlowIndex, FlowType : value + """ + dd: functools.partial[dict[FlowType, float]] = functools.partial(defaultdict, float) + res: dict[FI, dict[FlowType, float]] = defaultdict(dd) + + # ---- NON-annual ---- + + # Storage, which has a unique v_flow_in (non-storage techs do not have this variable) + for key in model.v_flow_in.keys(): + fi = FI(*key) + flow = value(model.v_flow_in[fi]) + if abs(flow) < epsilon: + continue + res[fi][FlowType.IN] = flow + res[fi][FlowType.LOST] = (1 - get_variable_efficiency(model, *key)) * flow + + # regular flows + for key in model.v_flow_out.keys(): + fi = FI(*key) + flow = value(model.v_flow_out[fi]) + if abs(flow) < epsilon: + continue + res[fi][FlowType.OUT] = flow + + if fi.t not in model.tech_storage: # we can get the flow in by out/eff... + flow = value(model.v_flow_out[fi]) / get_variable_efficiency(model, *key) + res[fi][FlowType.IN] = flow + res[fi][FlowType.LOST] = (1 - get_variable_efficiency(model, *key)) * flow + + # curtailment flows + for key in model.v_curtailment.keys(): + fi = FI(*key) + val = value(model.v_curtailment[fi]) + if abs(val) < epsilon: + continue + res[fi][FlowType.CURTAIL] = val + + # flex techs. This will subtract the flex from their output flow IOT make OUT the "net" + for key in model.v_flex.keys(): + fi = FI(*key) + flow = value(model.v_flex[fi]) + if abs(flow) < epsilon: + continue + res[fi][FlowType.FLEX] = flow + res[fi][FlowType.OUT] -= flow + + # ---- annual ---- + + # basic annual flows + for r, p, i, t, v, o in model.v_flow_out_annual.keys(): + # Make sure this isn't just a non-annual demand tech + if t not in model.tech_annual: + continue + for s in model.time_season: + for d in model.time_of_day: + if o in model.commodity_demand: + distribution = value(model.demand_specific_distribution[r, p, s, d, o]) + else: + distribution = value(model.segment_fraction[s, d]) + fi = FI(r, p, s, d, i, t, v, o) + flow = value(model.v_flow_out_annual[r, p, i, t, v, o]) * distribution + if abs(flow) < epsilon: + continue + res[fi][FlowType.OUT] = flow + res[fi][FlowType.IN] = flow / value(model.efficiency[ritvo(fi)]) + res[fi][FlowType.LOST] = (1 - value(model.efficiency[ritvo(fi)])) * res[fi][ + FlowType.IN + ] + + # flex annual + for r, p, i, t, v, o in model.v_flex_annual.keys(): + for s in model.time_season: + for d in model.time_of_day: + fi = FI(r, p, s, d, i, t, v, o) + flow = value(model.v_flex_annual[r, p, i, t, v, o]) * value( + model.segment_fraction[s, d] + ) + if abs(flow) < epsilon: + continue + res[fi][FlowType.FLEX] = flow + res[fi][FlowType.OUT] -= flow + + # construction flows + for r, i, t, v in model.construction_input.sparse_keys(): + annual = ( + value(model.construction_input[r, i, t, v]) + * value(model.v_new_capacity[r, t, v]) + / value(model.period_length[v]) + ) + for s in model.time_season: + for d in model.time_of_day: + fi = FI(r, v, s, d, i, t, v, cast('Commodity', None)) + flow = annual * value(model.segment_fraction[s, d]) + if abs(flow) < epsilon: + continue + res[fi][FlowType.IN] = flow + + # end of life flows + for r, t, v, o in model.end_of_life_output.sparse_keys(): + if (r, t, v) not in model.retirement_periods: + continue + for p in model.retirement_periods[r, t, v]: + annual = value(model.end_of_life_output[r, t, v, o]) * value( + model.v_annual_retirement[r, p, t, v] + ) + for s in model.time_season: + for d in model.time_of_day: + fi = FI(r, p, s, d, cast('Commodity', None), t, v, o) + flow = annual * value(model.segment_fraction[s, d]) + if abs(flow) < epsilon: + continue + res[fi][FlowType.OUT] = flow + + return res + + +def poll_storage_level_results(model: TemoaModel, epsilon: float = 1e-5) -> dict[SLI, float]: + """ + Poll a solved model for flow results. + :param M: A solved Model + :param epsilon: epsilon (default 1e-5) + :return: dictionary of storage level index, storage level + """ + res: dict[SLI, float] = defaultdict(float) + + # Storage level, the state variable for all but last time slice of each season + for r, p, s, d, t, v in model.storage_level_rpsdtv: + if t in model.tech_seasonal_storage: + continue + state = value(model.v_storage_level[r, p, s, d, t, v]) / ( + value(model.segment_fraction_per_season[s]) * value(model.days_per_period) + ) + sli = SLI(r, p, s, d, t, v) + if abs(state) < epsilon: + state = 0 # still want to know but decimals are ugly + res[sli] = state + + for r, p, s_seq, t, v in model.seasonal_storage_level_rpstv: + s = model.sequential_to_season[s_seq] + # Ratio of days in virtual storage season to days in actual season + # Flows and StorageLevel are normalised to the number of days in the ACTUAL season, so must + # be adjusted to the number of days in the virtual storage season + days_adjust = value(model.segment_fraction_per_sequential_season[s_seq]) / value( + model.segment_fraction_per_season[s] + ) + for d in model.time_of_day: + state = ( + value(model.v_seasonal_storage_level[r, p, s_seq, t, v]) + + value(model.v_storage_level[r, p, s, d, t, v]) * days_adjust + ) + sli = SLI(r, p, s_seq, d, t, v) + if abs(state) < epsilon: + state = 0 # still want to know but decimals are ugly + res[sli] = state + + return res + + +def poll_objective(model: TemoaModel) -> list[tuple[str, float]]: + """gather objective name, value tuples for all active objectives""" + objs: list[Objective] = list(model.component_data_objects(Objective)) + active_objs = [obj for obj in objs if obj.active] + if len(active_objs) > 1: + logger.warning('Multiple active objectives found. All will be logged in db') + res = [] + for obj in active_objs: + obj_name, obj_value = obj.getname(fully_qualified=True), float(value(obj)) + res.append((obj_name, obj_value)) + return res + + +def poll_cost_results( + model: TemoaModel, p_0: Period | None, epsilon: float = 1e-5 +) -> tuple[ + dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], + dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], +]: + """ + Poll a solved model for all cost results + :param M: Solved Model + :param p_0: a base year for discounting of loans, typically only used in MYOPIC. If none, + first optimization year used + :param epsilon: epsilon (default 1e-5) + :return: tuple of cost_dict, exchange_cost_dict (for exchange techs) + """ + p_0_true: Period + if p_0 is None: + p_0_true = min(model.time_optimize) + else: + p_0_true = p_0 + + p_e = model.time_future.last() + + # conveniences... + global_discount_rate = value(model.global_discount_rate) + # MPL = M.ModelProcessLife + loan_lifetime_process = model.loan_lifetime_process + + exchange_costs = ExchangeTechCostLedger(model) + entries: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]] = defaultdict( + dict + ) + for r, t, v in model.cost_invest.sparse_keys(): # Returns only non-zero values + # gather details... + cap = value(model.v_new_capacity[r, t, v]) + if abs(cap) < epsilon: + continue + loan_life = value(loan_lifetime_process[r, t, v]) + loan_rate = value(model.loan_rate[r, t, v]) + + if model.is_survival_curve_process[r, t, v]: + model_loan_cost, undiscounted_cost = loan_costs_survival_curve( + model=model, + r=r, + t=t, + v=v, + loan_rate=loan_rate, + loan_life=loan_life, + capacity=cap, + invest_cost=value(model.cost_invest[r, t, v]), + p_0=p_0_true, + p_e=p_e, + global_discount_rate=global_discount_rate, + vintage=v, + ) + else: + model_loan_cost, undiscounted_cost = loan_costs( + loan_rate=loan_rate, + loan_life=loan_life, + capacity=cap, + invest_cost=value(model.cost_invest[r, t, v]), + process_life=value(model.lifetime_process[r, t, v]), + p_0=p_0_true, + p_e=p_e, + global_discount_rate=global_discount_rate, + vintage=v, + ) + # screen for linked region... + if '-' in r: + exchange_costs.add_cost_record( + r, + period=v, + tech=t, + vintage=v, + cost=model_loan_cost, + cost_type=CostType.D_INVEST, + ) + exchange_costs.add_cost_record( + r, + period=v, + tech=t, + vintage=v, + cost=undiscounted_cost, + cost_type=CostType.INVEST, + ) + else: + # The period `p` for an investment cost is its vintage `v`. + key = (cast('Region', r), cast('Period', v), cast('Technology', t), cast('Vintage', v)) + entries[key].update( + {CostType.D_INVEST: model_loan_cost, CostType.INVEST: undiscounted_cost} + ) + + for r, p, t, v in model.cost_fixed.sparse_keys(): + cap = value(model.v_capacity[r, p, t, v]) + if abs(cap) < epsilon: + continue + + fixed_cost = value(model.cost_fixed[r, p, t, v]) + undiscounted_fixed_cost = cap * fixed_cost * value(model.period_length[p]) + + model_fixed_cost = costs.fixed_or_variable_cost( + cap, + value(fixed_cost), + value(model.period_length[p]), + global_discount_rate=global_discount_rate, + p_0=float(p_0_true), + p=p, + ) + if '-' in r: + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=v, + cost=float(value(model_fixed_cost)), + cost_type=CostType.D_FIXED, + ) + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=v, + cost=float(value(undiscounted_fixed_cost)), + cost_type=CostType.FIXED, + ) + else: + entries[r, p, t, v].update( + { + CostType.D_FIXED: float(value(model_fixed_cost)), + CostType.FIXED: float(value(undiscounted_fixed_cost)), + } + ) + + for r, p, t, v in model.cost_variable.sparse_keys(): + if t not in model.tech_annual: + activity = sum( + value(model.v_flow_out[r, p, S_s, S_d, S_i, t, v, S_o]) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + for S_s in model.time_season + for S_d in model.time_of_day + ) + else: + activity = sum( + value(model.v_flow_out_annual[r, p, S_i, t, v, S_o]) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + if abs(activity) < epsilon: + continue + + var_cost = value(model.cost_variable[r, p, t, v]) + undiscounted_var_cost = activity * var_cost * value(model.period_length[p]) + + model_var_cost = costs.fixed_or_variable_cost( + activity, + var_cost, + value(model.period_length[p]), + global_discount_rate=global_discount_rate, + p_0=float(p_0_true), + p=p, + ) + if '-' in r: + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=v, + cost=float(value(model_var_cost)), + cost_type=CostType.D_VARIABLE, + ) + exchange_costs.add_cost_record( + r, + period=p, + tech=t, + vintage=v, + cost=float(value(undiscounted_var_cost)), + cost_type=CostType.VARIABLE, + ) + else: + entries[r, p, t, v].update( + { + CostType.D_VARIABLE: float(value(model_var_cost)), + CostType.VARIABLE: float(value(undiscounted_var_cost)), + } + ) + exchange_entries = exchange_costs.get_entries() + return entries, exchange_entries + + +def loan_costs( + loan_rate: float, # this is referred to as loan_rate in parameters + loan_life: float, + capacity: float, + invest_cost: float, + process_life: int, + p_0: int, + p_e: int, + global_discount_rate: float, + vintage: int, + **kwargs: object, +) -> tuple[float, float]: + """ + Calculate Loan costs by calling the loan annualize and loan cost functions in temoa_rules + :return: tuple of [model-view discounted cost, un-discounted annuity] + """ + # dev note: this is a passthrough function. Sole intent is to use the EXACT formula the + # model uses for these costs + loan_ar = costs.pv_to_annuity(rate=loan_rate, periods=int(loan_life)) + model_ic = costs.loan_cost( + capacity, + invest_cost, + loan_annualize=float(value(loan_ar)), + lifetime_loan_process=loan_life, + lifetime_process=process_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=global_discount_rate, + vintage=vintage, + ) + # Override the GDR to get the undiscounted value + global_discount_rate = 0 + undiscounted_cost = costs.loan_cost( + capacity, + invest_cost, + loan_annualize=float(value(loan_ar)), + lifetime_loan_process=loan_life, + lifetime_process=process_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=global_discount_rate, + vintage=vintage, + ) + return float(value(model_ic)), float(value(undiscounted_cost)) + + +def loan_costs_survival_curve( + model: TemoaModel, + r: Region, + t: Technology, + v: Vintage, + loan_rate: float, # this is referred to as loan_rate in parameters + loan_life: float, + capacity: float, + invest_cost: float, + p_0: Period, + p_e: Period, + global_discount_rate: float, + vintage: Vintage, + **kwargs: object, +) -> tuple[float, float]: + """ + Calculate Loan costs by calling the loan annualize and loan cost functions in temoa_rules + :return: tuple of [model-view discounted cost, un-discounted annuity] + """ + # dev note: this is a passthrough function. Sole intent is to use the EXACT formula the + # model uses for these costs + loan_ar = costs.pv_to_annuity(rate=loan_rate, periods=int(loan_life)) + model_ic = costs.loan_cost_survival_curve( + model, + r, + t, + v, + capacity, + invest_cost, + loan_annualize=float(value(loan_ar)), + lifetime_loan_process=loan_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=global_discount_rate, + ) + # Override the GDR to get the undiscounted value + global_discount_rate = 0 + undiscounted_cost = costs.loan_cost_survival_curve( + model, + r, + t, + v, + capacity, + invest_cost, + loan_annualize=float(value(loan_ar)), + lifetime_loan_process=loan_life, + p_0=p_0, + p_e=p_e, + global_discount_rate=global_discount_rate, + ) + return float(value(model_ic)), float(value(undiscounted_cost)) + + +def poll_emissions( + model: TemoaModel, p_0: Period | None = None, epsilon: float = 1e-5 +) -> tuple[ + dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], dict[EI, float] +]: + """ + Gather all emission flows, cost them and provide a tuple of costs and flows + :param M: the model + :param p_0: the first period, if other than min(time_optimize), as in MYOPIC + :param epsilon: a minimal epsilon for ignored values + :return: cost_dict, flow_dict + """ + + # UPDATE: older versions brought forward had some accounting errors here for flex/curtailed + # emissions + # see the note on emissions in the Cost function in temoa_rules + p_0_true: Period + if p_0 is None: + p_0_true = min(model.time_optimize) + else: + p_0_true = p_0 + + global_discount_rate = value(model.global_discount_rate) + + ########################### + # Process Emissions + ########################### + + base = [ + (r, p, e, i, t, v, o) + for (r, e, i, t, v, o) in model.emission_activity.sparse_keys() + for p in model.time_optimize + if (r, p, t, v) in model.process_inputs + ] + + # The "base set" can be expanded now to cover normal/annual indexing sets + normal = [ + (r, p, e, s, d, i, t, v, o) + for (r, p, e, i, t, v, o) in base + for s in model.time_season + for d in model.time_of_day + if t not in model.tech_annual + ] + annual = [(r, p, e, i, t, v, o) for (r, p, e, i, t, v, o) in base if t in model.tech_annual] + + flows: dict[EI, float] = defaultdict(float) + # iterate through the normal and annual and accumulate flow values + for r, p, e, s, d, i, t, v, o in normal: + flows[EI(r, p, t, v, e)] += ( + value(model.v_flow_out[r, p, s, d, i, t, v, o]) + * model.emission_activity[r, e, i, t, v, o] + ) + + for r, p, e, i, t, v, o in annual: + flows[EI(r, p, t, v, e)] += ( + value(model.v_flow_out_annual[r, p, i, t, v, o]) + * model.emission_activity[r, e, i, t, v, o] + ) + + # gather costs + ud_costs: dict[tuple[Region, Period, Technology, Vintage], float] = defaultdict(float) + d_costs: dict[tuple[Region, Period, Technology, Vintage], float] = defaultdict(float) + for ei in flows: + # zero out tiny flows + if abs(flows[ei]) < epsilon: + flows[ei] = 0.0 + continue + # screen to see if there is an associated cost + cost_index = (ei.r, ei.p, ei.e) + if cost_index not in model.cost_emission: + continue + undiscounted_emiss_cost = ( + flows[ei] * model.cost_emission[ei.r, ei.p, ei.e] * model.period_length[ei.p] + ) + discounted_emiss_cost = costs.fixed_or_variable_cost( + cap_or_flow=flows[ei], + cost_factor=value(model.cost_emission[ei.r, ei.p, ei.e]), + cost_years=model.period_length[ei.p], + global_discount_rate=global_discount_rate, + p_0=p_0_true, + p=ei.p, + ) + ud_costs[ei.r, ei.p, ei.t, ei.v] += float(value(undiscounted_emiss_cost)) + d_costs[ei.r, ei.p, ei.t, ei.v] += float(value(discounted_emiss_cost)) + + ########################### + # Embodied Emissions + ########################### + + # iterate through embodied flows + embodied_flows: dict[EI, float] = defaultdict(float) + for r, e, t, v in model.emission_embodied.sparse_keys(): + embodied_flows[EI(r, v, t, v, e)] += value( + model.v_new_capacity[r, t, v] + * model.emission_embodied[r, e, t, v] + / model.period_length[v] + ) # for embodied costs + flows[EI(r, v, t, v, e)] += value( + model.v_new_capacity[r, t, v] + * model.emission_embodied[r, e, t, v] + / model.period_length[v] + ) # add embodied to process emissions + + # add embodied costs to process costs + for ei in embodied_flows: + # zero out again if still tiny after embodied flows + if abs(flows[ei]) < epsilon: + flows[ei] = 0.0 + continue + # screen to see if there is an associated cost + cost_index = (ei.r, cast('Period', ei.v), ei.e) + if cost_index not in model.cost_emission: + continue + undiscounted_emiss_cost = ( + embodied_flows[ei] + * model.cost_emission[ei.r, cast('Period', ei.v), ei.e] + * model.period_length[ + cast('Period', ei.v) + ] # treat as fixed cost distributed over construction period + ) + discounted_emiss_cost = costs.fixed_or_variable_cost( + cap_or_flow=embodied_flows[ei], + cost_factor=value(model.cost_emission[ei.r, cast('Period', ei.v), ei.e]), + cost_years=model.period_length[ + cast('Period', ei.v) + ], # treat as fixed cost distributed over construction period + global_discount_rate=global_discount_rate, + p_0=p_0_true, + p=cast('Period', ei.v), + ) + ud_costs[ei.r, cast('Period', ei.v), ei.t, ei.v] += float(value(undiscounted_emiss_cost)) + d_costs[ei.r, cast('Period', ei.v), ei.t, ei.v] += float(value(discounted_emiss_cost)) + + ########################### + # End of life Emissions + ########################### + + # iterate through end of life flows + eol_flows: dict[EI, float] = defaultdict(float) + for r, e, t, v in model.emission_end_of_life.sparse_keys(): + if (r, t, v) not in model.retirement_periods: + continue + for p in model.retirement_periods[r, t, v]: + eol_flows[EI(r, p, t, v, e)] += value( + model.v_annual_retirement[r, p, t, v] * model.emission_end_of_life[r, e, t, v] + ) # for eol costs + flows[EI(r, p, t, v, e)] += value( + model.v_annual_retirement[r, p, t, v] * model.emission_end_of_life[r, e, t, v] + ) # add eol to process emissions + + # add embodied costs to process costs + for ei in eol_flows: + # zero out again if still tiny + if abs(flows[ei]) < epsilon: + flows[ei] = 0.0 + continue + # screen to see if there is an associated cost + cost_index = (ei.r, ei.p, ei.e) + if cost_index not in model.cost_emission: + continue + undiscounted_emiss_cost = ( + eol_flows[ei] + * model.cost_emission[ei.r, ei.p, ei.e] + * model.period_length[ei.p] # treat as fixed cost distributed over retirement period + ) + discounted_emiss_cost = costs.fixed_or_variable_cost( + cap_or_flow=eol_flows[ei], + cost_factor=value(model.cost_emission[ei.r, ei.p, ei.e]), + cost_years=model.period_length[ + ei.p + ], # treat as fixed cost distributed over retirement period + global_discount_rate=global_discount_rate, + p_0=p_0_true, + p=ei.p, + ) + ud_costs[ei.r, ei.p, ei.t, ei.v] += float(value(undiscounted_emiss_cost)) + d_costs[ei.r, ei.p, ei.t, ei.v] += float(value(discounted_emiss_cost)) + + # finally, now that all costs are added up for each rptv, put in cost dict + costs_dict: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]] = ( + defaultdict(dict) + ) + for rptv in ud_costs: + costs_dict[rptv][CostType.EMISS] = ud_costs[rptv] + for rptv in d_costs: + costs_dict[rptv][CostType.D_EMISS] = d_costs[rptv] + + # wow, that was like pulling teeth + return costs_dict, flows diff --git a/temoa/_internal/table_writer.py b/temoa/_internal/table_writer.py new file mode 100644 index 000000000..221c23e8f --- /dev/null +++ b/temoa/_internal/table_writer.py @@ -0,0 +1,844 @@ +""" +tool for writing outputs to database tables +""" + +from __future__ import annotations + +import math +import sqlite3 +import sys +from collections import defaultdict +from importlib import resources +from logging import getLogger +from typing import TYPE_CHECKING, Any + +from pyomo.core import value + +from temoa._internal.exchange_tech_cost_ledger import CostType +from temoa._internal.table_data_puller import ( + EI, + FI, + CapData, + FlowType, + poll_capacity_results, + poll_cost_results, + poll_emissions, + poll_flow_results, + poll_objective, + poll_storage_level_results, +) +from temoa.core.modes import TemoaMode + +if TYPE_CHECKING: + from collections.abc import Iterable + from pathlib import Path + from types import TracebackType + + from pyomo.opt import SolverResults + + from temoa._internal.data_brick import DataBrick + from temoa.core.config import TemoaConfig + from temoa.core.model import TemoaModel + from temoa.extensions.monte_carlo.mc_run import ChangeRecord + from temoa.model_checking.unit_checking.unit_propagator import UnitPropagator + from temoa.types.core_types import Period, Region, Technology, Vintage + +logger = getLogger(__name__) + +# Basic tables that are always cleared on run +BASIC_OUTPUT_TABLES = [ + 'output_built_capacity', + 'output_cost', + 'output_curtailment', + 'output_dual_variable', + 'output_emission', + 'output_flow_in', + 'output_flow_out', + 'output_net_capacity', + 'output_objective', + 'output_retired_capacity', +] + +OPTIONAL_OUTPUT_TABLES = [ + 'output_flow_out_summary', + 'output_mc_delta', + 'output_storage_level', +] + +OUTPUT_THRESHOLD_DEFAULTS = { + 'capacity': 1e-3, + 'activity': 1e-3, + 'emission': 1e-3, + 'cost': 1e-2, +} + +FLOW_SUMMARY_FILE_LOC = ( + resources.files('temoa.extensions.modeling_to_generate_alternatives') + / 'make_flow_summary_table.sql' +) +MC_TWEAKS_FILE_LOC = resources.files('temoa.extensions.monte_carlo') / 'make_deltas_table.sql' + + +class TableWriter: + con: sqlite3.Connection | None + + def __init__(self, config: TemoaConfig) -> None: + self.config = config + self.tech_sectors: dict[str, str] | None = None + self.flow_register: dict[FI, dict[FlowType, float]] = {} + self.emission_register: dict[EI, float] | None = None + self.con = None + + # Cache for table columns to avoid repeated PRAGMA calls + self._table_columns_cache: dict[str, set[str]] = {} + + try: + self.con = sqlite3.connect(config.output_database) + self.con.execute('PRAGMA foreign_keys = OFF') + except sqlite3.OperationalError as _: + logger.exception('Failed to connect to output database: %s', config.output_database) + sys.exit(-1) + + self.output_threshold_capacity = self._resolve_output_threshold('capacity') + self.output_threshold_activity = self._resolve_output_threshold('activity') + self.output_threshold_emission = self._resolve_output_threshold('emission') + self.output_threshold_cost = self._resolve_output_threshold('cost') + self.epsilon = min( + self.output_threshold_capacity, + self.output_threshold_activity, + self.output_threshold_emission, + self.output_threshold_cost, + ) + + # Unit propagator for populating units in output tables (lazy init) + self._unit_propagator: UnitPropagator | None = None + + @property + def connection(self) -> sqlite3.Connection: + """ + Returns the active database connection. + Raises RuntimeError if the connection is closed or not initialized. + This serves as a central type guard for Mypy. + """ + if self.con is None: + raise RuntimeError('Database connection is closed') + return self.con + + def __enter__(self) -> TableWriter: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """Explicitly close the database connection.""" + if self.con: + try: + self.con.close() + except (sqlite3.Error, OSError) as e: + logger.warning('Error closing database connection: %s', e) + finally: + self.con = None + + @property + def unit_propagator(self) -> UnitPropagator | None: + """ + Lazily initialize and return the unit propagator. + + Returns None if initialization fails, ensuring graceful fallback + for databases without unit information. + """ + if self._unit_propagator is None: + try: + from temoa.model_checking.unit_checking.unit_propagator import ( + UnitPropagator, + ) + + self._unit_propagator = UnitPropagator(self.connection) + if not self._unit_propagator.has_unit_data: + logger.debug('No unit data available in database') + except (ImportError, sqlite3.Error, RuntimeError) as e: + logger.debug('Could not initialize unit propagator: %s', e, exc_info=True) + # Leave as None - units will be None in output + return self._unit_propagator + + def _validate_foreign_keys(self) -> None: + """ + Re-enables foreign keys, runs a check, and logs any violations. + This is essentially a "soft" integrity check that won't crash the write process + but will alert the user to data consistency issues. + """ + if self.con is None: + return + + try: + # Re-enable foreign keys to check for violations + self.connection.execute('PRAGMA foreign_keys = ON') + + # Check for violations + cursor = self.connection.execute('PRAGMA foreign_key_check') + violations = cursor.fetchall() + + if violations: + logger.error('Foreign key constraint violations found in output database:') + for violation in violations: + # violation tuple: (table, rowid, parent, fkid) + table_name = violation[0] + row_id = violation[1] + parent_table = violation[2] + logger.error( + " Table '%s' (row %s) violates FK to parent '%s'", + table_name, + row_id, + parent_table, + ) + except sqlite3.Error as _: + logger.exception('Error during foreign key validation') + + def _get_table_columns(self, table_name: str) -> set[str]: + """Returns a set of column names for the given table.""" + if table_name not in self._table_columns_cache: + cursor = self.connection.execute(f'PRAGMA table_info({table_name})') + # row[1] is the column name in sqlite PRAGMA table_info + columns = {row[1] for row in cursor.fetchall()} + self._table_columns_cache[table_name] = columns + return self._table_columns_cache[table_name] + + def _bulk_insert(self, table_name: str, records: list[dict[str, Any]]) -> None: + """ + Dynamically inserts records into a table. + + 1. Checks the DB schema to see which columns exist. + 2. Filters the input dictionary to match existing columns. + 3. Handles the optional 'units' column automatically. + """ + if not records: + return + + valid_columns = self._get_table_columns(table_name) + + # Determine the columns we will actually write to based on the first record + # and the valid schema columns. + data_keys = set(records[0].keys()) + + # Intersection: keys present in data AND present in database table + target_columns = list(data_keys.intersection(valid_columns)) + target_columns.sort() # Sort to ensure consistent order + + if not target_columns: + logger.warning('No matching columns found for table %s. Skipping insert.', table_name) + return + + # Prepare SQL statement + cols_str = ', '.join(target_columns) + placeholders = ', '.join(['?'] * len(target_columns)) + query = f'INSERT INTO {table_name} ({cols_str}) VALUES ({placeholders})' + + # Prepare values tuple generator + rows_to_insert = [] + for rec in records: + rows_to_insert.append(tuple(rec[col] for col in target_columns)) + + self.connection.executemany(query, rows_to_insert) + + @staticmethod + def _validate_threshold( + threshold_name: str, + threshold_value: float | int | None, + ) -> float | None: + """Validate output threshold inputs and normalize to float.""" + if threshold_value is None: + return None + numeric_value = float(threshold_value) + if not math.isfinite(numeric_value): + raise ValueError(f'Output threshold "{threshold_name}" must be finite') + if numeric_value < 0: + raise ValueError(f'Output threshold "{threshold_name}" must be non-negative') + return numeric_value + + def _resolve_output_threshold(self, threshold_type: str) -> float: + """Resolve threshold: TOML config if set, else hardcoded default.""" + if threshold_type not in OUTPUT_THRESHOLD_DEFAULTS: + raise ValueError(f'Unknown output threshold type: {threshold_type}') + + toml_key = f'output_threshold_{threshold_type}' + toml_value = self._validate_threshold( + toml_key, + getattr(self.config, toml_key, None), + ) + if toml_value is not None: + return toml_value + + return OUTPUT_THRESHOLD_DEFAULTS[threshold_type] + + def write_results( + self, + model: TemoaModel, + results_with_duals: SolverResults | None = None, + save_storage_levels: bool = False, + append: bool = False, + iteration: int | None = None, + ) -> None: + try: + if not append: + self.clear_scenario() + + if not self.tech_sectors: + self._set_tech_sectors() + + self.write_objective(model, iteration=iteration) + self.write_capacity_tables(model, iteration=iteration) + + # Poll and Write Emissions + if self.config.scenario_mode == TemoaMode.MYOPIC: + p_0 = model.myopic_discounting_year + else: + p_0 = None + + e_costs, e_flows = poll_emissions( + model=model, + p_0=value(p_0), + epsilon=self.output_threshold_emission, + ) + self.emission_register = e_flows + self.write_emissions(iteration=iteration) + + # Costs and Flows + self.write_costs(model, emission_entries=e_costs, iteration=iteration) + + self.flow_register = self.calculate_flows(model) + self.check_flow_balance(model) + self.write_flow_tables(iteration=iteration) + + if results_with_duals: + self.write_dual_variables(results_with_duals, iteration=iteration) + + if save_storage_levels: + self.write_storage_level(model, iteration=iteration) + + finally: + self._validate_foreign_keys() + self.connection.commit() + + def write_mm_results(self, model: TemoaModel, iteration: int) -> None: + try: + if not self.tech_sectors: + self._set_tech_sectors() + self.write_objective(model, iteration=iteration) + _e_costs, e_flows = poll_emissions( + model=model, + epsilon=self.output_threshold_emission, + ) + self.emission_register = e_flows + self.write_emissions(iteration=iteration) + finally: + self._validate_foreign_keys() + self.connection.commit() + + def write_mc_results(self, brick: DataBrick, iteration: int) -> None: + try: + if not self.tech_sectors: + self._set_tech_sectors() + + e_costs, e_flows = brick.emission_cost_data, brick.emission_flows + self.emission_register = e_flows + self.write_emissions(iteration=iteration) + + self._insert_capacity_results(brick.capacity_data, iteration=iteration) + self._insert_summary_flow_results(flow_data=brick.flow_data, iteration=iteration) + self._insert_cost_results( + regular_entries=brick.cost_data, + exchange_entries=brick.exchange_cost_data, + emission_entries=e_costs, + iteration=iteration, + ) + self._insert_objective_results(brick.obj_data, iteration=iteration) + finally: + self._validate_foreign_keys() + self.connection.commit() + + def _set_tech_sectors(self) -> None: + qry = 'SELECT tech, sector FROM Technology' + data = self.connection.execute(qry).fetchall() + self.tech_sectors = dict(data) + + def _get_scenario_name(self, iteration: int | None) -> str: + if iteration is not None: + return f'{self.config.scenario}-{iteration}' + return self.config.scenario + + def clear_scenario(self) -> None: + cur = self.connection.cursor() + for table in BASIC_OUTPUT_TABLES: + cur.execute(f'DELETE FROM {table} WHERE scenario == ?', (self.config.scenario,)) + for table in OPTIONAL_OUTPUT_TABLES: + try: + cur.execute(f'DELETE FROM {table} WHERE scenario == ?', (self.config.scenario,)) + except sqlite3.OperationalError: + pass + self.connection.commit() + self.clear_iterative_runs() + + def clear_iterative_runs(self) -> None: + target = self.config.scenario + '-%' + cur = self.connection.cursor() + tables = BASIC_OUTPUT_TABLES + OPTIONAL_OUTPUT_TABLES + for table in tables: + try: + cur.execute(f'DELETE FROM {table} WHERE scenario like ?', (target,)) + except sqlite3.OperationalError: + pass + self.connection.commit() + + # ------------------------------------------------------------------------- + # WRITE IMPLEMENTATIONS + # ------------------------------------------------------------------------- + + def write_storage_level(self, model: TemoaModel, iteration: int | None = None) -> None: + if self.tech_sectors is None: + raise RuntimeError('tech sectors not available... code error') + + storage_levels = poll_storage_level_results(model=model) + scenario = self._get_scenario_name(iteration) + unit_prop = self.unit_propagator + + records = [] + for sli, val in storage_levels.items(): + records.append( + { + 'scenario': scenario, + 'region': sli.r, + 'sector': self.tech_sectors.get(sli.t), + 'period': sli.p, + 'season': sli.s, + 'tod': sli.d, + 'tech': sli.t, + 'vintage': sli.v, + 'level': val, + 'units': unit_prop.get_storage_units(sli.t) if unit_prop else None, + } + ) + + self._bulk_insert('output_storage_level', records) + self.connection.commit() + + def write_objective(self, model: TemoaModel, iteration: int | None = None) -> None: + obj_vals = poll_objective(model=model) + self._insert_objective_results(obj_vals, iteration=iteration) + + def _insert_objective_results( + self, obj_vals: list[tuple[str, float]], iteration: int | None + ) -> None: + scenario = self._get_scenario_name(iteration) + records = [ + { + 'scenario': scenario, + 'objective_name': obj_name, + 'total_system_cost': obj_value, + } + for obj_name, obj_value in obj_vals + ] + self._bulk_insert('output_objective', records) + self.connection.commit() + + def write_emissions(self, iteration: int | None = None) -> None: + if self.tech_sectors is None or self.emission_register is None: + raise RuntimeError('Dependencies missing (tech_sectors or emission_register)') + + scenario = self._get_scenario_name(iteration) + unit_prop = self.unit_propagator + records = [] + + for ei, val in self.emission_register.items(): + if abs(val) < self.output_threshold_emission: + continue + + row = { + 'scenario': scenario, + 'region': ei.r, + 'sector': self.tech_sectors.get(ei.t), + 'emission': val, + 'emis_comm': ei.e, + 'tech': ei.t, + 'vintage': ei.v, + 'units': unit_prop.get_emission_units(ei.e) if unit_prop else None, + } + + if hasattr(ei, 'p'): # emissions from flows + row['period'] = ei.p + else: # embodied emissions (use vintage as period) + row['period'] = ei.v + + records.append(row) + + self._bulk_insert('output_emission', records) + self.connection.commit() + + def write_capacity_tables(self, model: TemoaModel, iteration: int | None = None) -> None: + cap_data = poll_capacity_results(model=model, epsilon=self.output_threshold_capacity) + self._insert_capacity_results(cap_data=cap_data, iteration=iteration) + + def _insert_capacity_results(self, cap_data: CapData, iteration: int | None) -> None: + if self.tech_sectors is None: + raise RuntimeError('tech sectors not available... code error') + + scenario = self._get_scenario_name(iteration) + unit_prop = self.unit_propagator + + # 1. Built Capacity + built_recs = [] + for r, t, v, val in cap_data.built: + built_recs.append( + { + 'scenario': scenario, + 'region': r, + 'sector': self.tech_sectors.get(t), + 'tech': t, + 'vintage': v, + 'capacity': val, + 'units': unit_prop.get_capacity_units(t) if unit_prop else None, + } + ) + self._bulk_insert('output_built_capacity', built_recs) + + # 2. Net Capacity + net_recs = [] + for r, p, t, v, val in cap_data.net: + net_recs.append( + { + 'scenario': scenario, + 'region': r, + 'sector': self.tech_sectors.get(t), + 'period': p, + 'tech': t, + 'vintage': v, + 'capacity': val, + 'units': unit_prop.get_capacity_units(t) if unit_prop else None, + } + ) + self._bulk_insert('output_net_capacity', net_recs) + + # 3. Retired Capacity + ret_recs = [] + for r, p, t, v, eol, early in cap_data.retired: + ret_recs.append( + { + 'scenario': scenario, + 'region': r, + 'sector': self.tech_sectors.get(t), + 'period': p, + 'tech': t, + 'vintage': v, + 'cap_eol': eol, + 'cap_early': early, + 'units': unit_prop.get_capacity_units(t) if unit_prop else None, + } + ) + self._bulk_insert('output_retired_capacity', ret_recs) + + self.connection.commit() + + def write_flow_tables(self, iteration: int | None = None) -> None: + if not self.tech_sectors or not self.flow_register: + raise RuntimeError('Dependencies missing (tech_sectors or flow_register)') + + scenario = self._get_scenario_name(iteration) + + # Structure to hold list of dicts for each table type + table_data: defaultdict[str, list[dict[str, Any]]] = defaultdict(list) + + map_flow_to_table = { + FlowType.OUT: 'output_flow_out', + FlowType.IN: 'output_flow_in', + FlowType.CURTAIL: 'output_curtailment', + FlowType.FLEX: 'output_curtailment', + } + + for fi, flows in self.flow_register.items(): + sector = self.tech_sectors.get(fi.t) + + for flow_type, val in flows.items(): + if abs(val) < self.output_threshold_activity: + continue + + table_name = map_flow_to_table.get(flow_type) + if not table_name: + continue + + row: dict[str, Any] = { + 'scenario': scenario, + 'region': fi.r, + 'sector': sector, + 'period': fi.p, + 'season': fi.s, + 'tod': fi.d, + 'input_comm': fi.i, + 'tech': fi.t, + 'vintage': fi.v, + 'output_comm': fi.o, + 'units': self._get_flow_units(flow_type, fi.i, fi.o), + } + + # Assign value to correct column name based on table/type + if table_name == 'output_curtailment': + row['curtailment'] = val + else: + row['flow'] = val + + table_data[table_name].append(row) + + for table_name, records in table_data.items(): + self._bulk_insert(table_name, records) + + self.connection.commit() + + def _get_flow_units( + self, flow_type: FlowType, input_comm: str | None, output_comm: str | None + ) -> str | None: + """ + Get units for flow based on flow type. + + For output flows and curtailment, uses the output commodity units. + For input flows, uses the input commodity units. + + Args: + flow_type: Type of flow (IN, OUT, CURTAIL, FLEX). + input_comm: Input commodity name. + output_comm: Output commodity name. + + Returns: + Unit string or None if not available. + """ + unit_prop = self.unit_propagator + if not unit_prop: + return None + + if flow_type == FlowType.IN and input_comm is not None: + return unit_prop.get_flow_in_units(input_comm) + elif output_comm is not None: + # OUT, CURTAIL, FLEX all use output commodity units + return unit_prop.get_flow_out_units(output_comm) + return None + + def write_summary_flow(self, model: TemoaModel, iteration: int | None = None) -> None: + flow_data = self.calculate_flows(model=model) + self._insert_summary_flow_results(flow_data=flow_data, iteration=iteration) + + def _insert_summary_flow_results( + self, flow_data: dict[FI, dict[FlowType, float]], iteration: int | None + ) -> None: + if self.tech_sectors is None: + raise RuntimeError('tech sectors not available... code error') + + scenario = self._get_scenario_name(iteration) + self.flow_register = flow_data + + # Aggregate flows (sum across seasons/time of day) + output_flows: defaultdict[ + tuple[str, Period, str | None, Technology, Vintage, str | None], float + ] = defaultdict(float) + + for fi, flows in self.flow_register.items(): + val = flows.get(FlowType.OUT) + if val: + key = (fi.r, fi.p, fi.i, fi.t, fi.v, fi.o) + output_flows[key] += val + + records = [] + for (r, p, i, t, v, o), val in output_flows.items(): + if abs(val) < self.output_threshold_activity: + continue + records.append( + { + 'scenario': scenario, + 'region': r, + 'sector': self.tech_sectors.get(t), + 'period': p, + 'input_comm': i, + 'tech': t, + 'vintage': v, + 'output_comm': o, + 'flow': val, + 'units': None, + } + ) + + self._bulk_insert('output_flow_out_summary', records) + self.connection.commit() + + def check_flow_balance(self, model: TemoaModel) -> bool: + """Sanity check to ensure that the flow tables are balanced.""" + flows = self.flow_register + all_good = True + + for fi, flow_vals in flows.items(): + if fi.t in model.tech_storage: + continue + if fi.i is None or fi.o is None: + continue + + fin = flow_vals.get(FlowType.IN, 0) + fout = flow_vals.get(FlowType.OUT, 0) + flost = flow_vals.get(FlowType.LOST, 0) + fflex = flow_vals.get(FlowType.FLEX, 0) + + delta = fin - fout - flost - fflex + + # Check logic + if fin != 0: + if abs(delta / fin) > 0.02: + all_good = False + logger.warning('Flow imbalance > 2%% for %s: delta=%.2f', fi, delta) + elif abs(delta) > 0.02: + all_good = False + logger.warning('Flow imbalance (zero input) for %s: delta=%.2f', fi, delta) + + return all_good + + def calculate_flows(self, model: TemoaModel) -> dict[FI, dict[FlowType, float]]: + return poll_flow_results(model, self.output_threshold_activity) + + def write_costs( + self, + model: TemoaModel, + emission_entries: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]] + | None = None, + iteration: int | None = None, + ) -> None: + if self.config.scenario_mode == TemoaMode.MYOPIC: + p_0 = model.myopic_discounting_year + else: + p_0 = min(model.time_optimize) + + entries, exchange_entries = poll_cost_results(model, value(p_0), self.output_threshold_cost) + self._insert_cost_results(entries, exchange_entries, emission_entries, iteration) + + def _insert_cost_results( + self, + regular_entries: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], + exchange_entries: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], + emission_entries: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]] + | None, + iteration: int | None, + ) -> None: + if emission_entries: + # Create a copy to avoid mutating the input + regular_entries = dict(regular_entries) + + for k, v in emission_entries.items(): + if k in regular_entries: + regular_entries[k].update(v) + else: + regular_entries[k] = v + + self._write_cost_rows(regular_entries, iteration) + self._write_cost_rows(exchange_entries, iteration) + + def _write_cost_rows( + self, + entries: dict[tuple[Region, Period, Technology, Vintage], dict[CostType, float]], + iteration: int | None = None, + ) -> None: + if self.tech_sectors is None: + raise RuntimeError('tech sectors not available... code error') + + scenario = self._get_scenario_name(iteration) + unit_prop = self.unit_propagator + records = [] + + sorted_keys = sorted(entries.keys()) + + for r, p, t, v in sorted_keys: + costs = entries[(r, p, t, v)] + row_values = ( + costs.get(CostType.D_INVEST, 0), + costs.get(CostType.D_FIXED, 0), + costs.get(CostType.D_VARIABLE, 0), + costs.get(CostType.D_EMISS, 0), + costs.get(CostType.INVEST, 0), + costs.get(CostType.FIXED, 0), + costs.get(CostType.VARIABLE, 0), + costs.get(CostType.EMISS, 0), + ) + if all(abs(val) < self.output_threshold_cost for val in row_values): + continue + records.append( + { + 'scenario': scenario, + 'region': r, + 'sector': self.tech_sectors.get(t), + 'period': p, + 'tech': t, + 'vintage': v, + 'd_invest': costs.get(CostType.D_INVEST, 0), + 'd_fixed': costs.get(CostType.D_FIXED, 0), + 'd_var': costs.get(CostType.D_VARIABLE, 0), + 'd_emiss': costs.get(CostType.D_EMISS, 0), + 'invest': costs.get(CostType.INVEST, 0), + 'fixed': costs.get(CostType.FIXED, 0), + 'var': costs.get(CostType.VARIABLE, 0), + 'emiss': costs.get(CostType.EMISS, 0), + 'units': unit_prop.get_cost_units() if unit_prop else None, + } + ) + + self._bulk_insert('output_cost', records) + self.connection.commit() + + def write_dual_variables(self, results: SolverResults, iteration: int | None = None) -> None: + scenario = self._get_scenario_name(iteration) + constraint_data = results['Solution'].Constraint.items() + + records = [ + { + 'scenario': scenario, + 'constraint_name': name, + 'dual': data['Dual'], + } + for name, data in constraint_data + ] + self._bulk_insert('output_dual_variable', records) + self.connection.commit() + + def write_tweaks(self, iteration: int, change_records: Iterable[ChangeRecord]) -> None: + scenario = self._get_scenario_name(iteration) + records = [] + + for cr in change_records: + records.append( + { + 'scenario': scenario, + 'run': iteration, + 'param': cr.param_name, + 'param_index': str(cr.param_index).replace("'", ''), + 'old_val': cr.old_value, + 'new_val': cr.new_value, + } + ) + + self._bulk_insert('output_mc_delta', records) + self.connection.commit() + + def execute_script(self, script_file: str | Path | resources.abc.Traversable) -> None: + if isinstance(script_file, resources.abc.Traversable): + sql_commands = script_file.read_text() + else: + with open(script_file) as table_script: + sql_commands = table_script.read() + + self.connection.executescript(sql_commands) + self.connection.commit() + + def make_summary_flow_table(self) -> None: + self.execute_script(FLOW_SUMMARY_FILE_LOC) + + def make_mc_tweaks_table(self) -> None: + self.execute_script(MC_TWEAKS_FILE_LOC) + + def __del__(self) -> None: + self.close() diff --git a/temoa/_internal/temoa_sequencer.py b/temoa/_internal/temoa_sequencer.py new file mode 100644 index 000000000..ea3c69779 --- /dev/null +++ b/temoa/_internal/temoa_sequencer.py @@ -0,0 +1,276 @@ +""" +The Temoa Sequencer's job is to sequence the actions needed to execute a scenario. Each +scenario has a declared processing mode (regular, myopic, mga, etc.) and the Temoa Sequencer sets +up the necessary run(s) to accomplish that. Several processing modes have requirements +for multiple runs, and the Temoa Sequencer may hand off to a mode-specific sequencer + +""" + +import sqlite3 +from logging import getLogger +from typing import TYPE_CHECKING + +from temoa.__about__ import ( + DB_MAJOR_VERSION, + MIN_DB_MINOR_VERSION, + MIN_PYTHON_MAJOR, + MIN_PYTHON_MINOR, +) +from temoa._internal.run_actions import ( + build_instance, + check_database_version, + check_python_version, + check_solve_status, + handle_results, + solve_instance, +) +from temoa.core.config import TemoaConfig +from temoa.core.model import TemoaModel +from temoa.core.modes import TemoaMode +from temoa.data_io.hybrid_loader import HybridLoader +from temoa.extensions.method_of_morris.morris_sequencer import MorrisSequencer +from temoa.extensions.modeling_to_generate_alternatives.mga_sequencer import MgaSequencer +from temoa.extensions.monte_carlo.mc_sequencer import MCSequencer +from temoa.extensions.myopic.myopic_sequencer import MyopicSequencer +from temoa.extensions.single_vector_mga.sv_mga_sequencer import SvMgaSequencer +from temoa.extensions.stochastics.stochastic_sequencer import StochasticSequencer +from temoa.model_checking.pricing_check import price_checker +from temoa.utilities.sqlite_utils import tune_sqlite_connection + +if TYPE_CHECKING: + import pyomo.opt + +logger = getLogger(__name__) + + +class TemoaSequencer: + """A Sequencer instance to control all runs for a scenario based on the TemoaMode.""" + + def __init__( + self, + config: TemoaConfig, + mode_override: TemoaMode | None = None, + ) -> None: + """ + Create a new Sequencer. + + :param config: A fully constructed TemoaConfig object. + :param mode_override: Optional override to the execution mode from the config. + """ + self.config = config + self.temoa_mode = config.scenario_mode + + # Handle and log the mode override if provided + if mode_override and mode_override != self.config.scenario_mode: + self.temoa_mode = mode_override + self.config.scenario_mode = mode_override + logger.info('Temoa Mode overridden by caller to: %s', self.temoa_mode) + + # for results catching for perfect_foresight or testing + self.pf_results: pyomo.opt.SolverResults | None = None + self.pf_solved_instance: TemoaModel | None = None + self.stochastic_sequencer: StochasticSequencer | None = None + + def _run_preliminary_checks(self) -> None: + """Runs pre-flight system checks and (optionally) a non-fatal units check. + + Raises an error if Python or database version checks fail; unit-check + failures are logged but do not abort the run. + """ + # Unit checking - runs on database before model build + if self.config.check_units: + from temoa.model_checking.unit_checking.screener import screen + + logger.info('Running units consistency check on input database...') + report_dir = self.config.output_path / 'unit_check_reports' + success = screen(self.config.input_database, report_dir=report_dir) + + if not success: + logger.warning( + 'Units check found errors. See detailed report at: %s', + report_dir, + ) + logger.warning('Continuing with model build despite unit check warnings...') + else: + logger.info('Units check completed successfully - no errors found.') + + # System checks (Python version, database version) + checks_ok = check_python_version(MIN_PYTHON_MAJOR, MIN_PYTHON_MINOR) + checks_ok &= check_database_version( + self.config, db_major_reqd=DB_MAJOR_VERSION, min_db_minor=MIN_DB_MINOR_VERSION + ) + if not checks_ok: + # The specific reasons for failure are already in the log. + raise RuntimeError('Failed pre-run checks. See log file for details.') + + def build_model(self) -> TemoaModel: + """ + Builds and returns an unsolved TemoaModel instance. + This is the dedicated method for the 'BUILD_ONLY' mode. + """ + self._run_preliminary_checks() + logger.info('Starting model build process (build-only mode).') + + # Capture original values to restore later + original_source_trace = self.config.source_trace + original_plot_commodity_network = self.config.plot_commodity_network + original_price_check = self.config.price_check + + try: + # Ensure certain features that don't apply to a simple build are disabled + if self.config.source_trace: + self.config.source_trace = False + logger.warning('Source trace disabled for build-only mode.') + if self.config.plot_commodity_network: + self.config.plot_commodity_network = False + logger.warning('Commodity network plotting disabled for build-only mode.') + if self.config.price_check: + self.config.price_check = False + logger.warning('Price check disabled for build-only mode.') + + # Validate database before attempting to build model + if not check_database_version( + self.config, db_major_reqd=DB_MAJOR_VERSION, min_db_minor=MIN_DB_MINOR_VERSION + ): + raise RuntimeError('Database version check failed. See log file for details.') + + with sqlite3.connect(self.config.input_database) as con: + tune_sqlite_connection(con, self.config) + hybrid_loader = HybridLoader(db_connection=con, config=self.config) + data_portal = hybrid_loader.load_data_portal(myopic_index=None) + instance = build_instance(data_portal, silent=self.config.silent) + + logger.info('Model build process complete.') + return instance + finally: + # Restore original config values + self.config.source_trace = original_source_trace + self.config.plot_commodity_network = original_plot_commodity_network + self.config.price_check = original_price_check + + def start(self) -> None: + """ + Executes the full scenario run to completion. + This method returns None on success and raises an exception on failure. + """ + self._run_preliminary_checks() + + # The mode is now definitively set, so we can proceed. + logger.info('Executing scenario in mode: %s', self.temoa_mode) + + # Select execution path based on mode + match self.temoa_mode: + case TemoaMode.BUILD_ONLY: + # The `start` method's contract is to run to completion, not return a model. + # Raise an error to guide the developer to the correct API. + raise RuntimeError( + "For BUILD_ONLY mode, please use the 'build_model()' method instead of " + "'start()'." + ) + + case TemoaMode.CHECK: + self._run_check_mode() + + case TemoaMode.PERFECT_FORESIGHT: + self._run_perfect_foresight() + + case TemoaMode.MYOPIC: + myopic_sequencer = MyopicSequencer(config=self.config) + myopic_sequencer.start() + + case TemoaMode.MGA: + mga_sequencer = MgaSequencer(config=self.config) + mga_sequencer.start() + + case TemoaMode.SVMGA: + sv_mga_sequencer = SvMgaSequencer(config=self.config) + sv_mga_sequencer.start() + + case TemoaMode.METHOD_OF_MORRIS: + mm_sequencer = MorrisSequencer(config=self.config) + mm_sequencer.start() + + case TemoaMode.MONTE_CARLO: + self._run_monte_carlo() + + case TemoaMode.STOCHASTIC: + self.stochastic_sequencer = StochasticSequencer(config=self.config) + self.stochastic_sequencer.start() + + case _: + raise NotImplementedError( + f"The scenario mode '{self.temoa_mode}' is not yet implemented." + ) + + def _run_check_mode(self) -> None: + """Encapsulated logic for the CHECK mode.""" + with sqlite3.connect(self.config.input_database) as con: + tune_sqlite_connection(con, self.config) + if not self.config.source_trace: + logger.warning('Source trace is automatically enabled for CHECK mode.') + self.config.source_trace = True + hybrid_loader = HybridLoader(db_connection=con, config=self.config) + data_portal = hybrid_loader.load_data_portal(myopic_index=None) + instance = build_instance( + data_portal, + silent=self.config.silent, + keep_lp_file=self.config.save_lp_file, + lp_path=self.config.output_path, + ) + if not self.config.price_check: + logger.warning('Price check is automatically enabled for CHECK mode.') + price_checker(instance) + + def _run_perfect_foresight(self) -> None: + """Encapsulated logic for the PERFECT_FORESIGHT mode.""" + with sqlite3.connect(self.config.input_database) as con: + tune_sqlite_connection(con, self.config) + hybrid_loader = HybridLoader(db_connection=con, config=self.config) + data_portal = hybrid_loader.load_data_portal(myopic_index=None) + instance = build_instance( + data_portal, + silent=self.config.silent, + keep_lp_file=self.config.save_lp_file, + lp_path=self.config.output_path, + ) + if self.config.price_check: + price_checker(instance) + + suffixes = ['dual'] if self.config.save_duals else None + self.pf_solved_instance, self.pf_results = solve_instance( + instance, + self.config.solver_name, + silent=self.config.silent, + solver_suffixes=suffixes, + ) + good_solve, msg = check_solve_status(self.pf_results) + if not good_solve: + raise RuntimeError( + f"The solver reported a non-optimal status: '{msg}'. Aborting run. " + "This may be due to the solver's output messaging. If this status is " + 'acceptable, the `check_solve_status` function may need adjustment.' + ) + handle_results(self.pf_solved_instance, self.pf_results, self.config) + + def _run_monte_carlo(self) -> None: + """Encapsulated logic for the MONTE_CARLO mode.""" + + # Disable features not typically used in Monte Carlo runs to reduce noise/overhead + if self.config.plot_commodity_network: + self.config.plot_commodity_network = False + logger.warning('Commodity network plotting disabled for MONTE_CARLO mode.') + if self.config.price_check: + self.config.price_check = False + logger.warning('Price check disabled for MONTE_CARLO mode.') + if self.config.save_excel: + self.config.save_excel = False + logger.warning('Excel output disabled for MONTE_CARLO mode.') + if self.config.save_lp_file: + self.config.save_lp_file = False + logger.warning('LP file saving disabled for MONTE_CARLO mode.') + if self.config.save_duals: + self.config.save_duals = False + logger.warning('Saving of duals disabled for MONTE_CARLO mode.') + + mc_sequencer = MCSequencer(config=self.config) + mc_sequencer.start() diff --git a/temoa/cli.py b/temoa/cli.py new file mode 100644 index 000000000..c57a5b6be --- /dev/null +++ b/temoa/cli.py @@ -0,0 +1,789 @@ +import logging +import shutil +from datetime import UTC, datetime +from importlib import resources +from pathlib import Path +from typing import Annotated + +import rich +import tomlkit +import typer +from rich.logging import RichHandler +from rich.text import Text + +from temoa.__about__ import __version__ +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.modes import TemoaMode +from temoa.utilities import master_migration +from temoa.utilities.run_all_v4_migrations import run_migrations + +# ============================================================================= +# Logging & Helper Setup +# ============================================================================= +logger = logging.getLogger(__name__) + + +def _create_output_folder() -> Path: + """Create a default time-stamped folder for outputs.""" + output_path = Path('output_files', datetime.now().strftime('%Y-%m-%d_%H%M%S')) + output_path.mkdir(parents=True, exist_ok=True) + return output_path + + +def _setup_logging(output_path: Path, debug: bool = False, silent: bool = False) -> None: + """Set up logging with different levels for console and file.""" + # The root logger should be set to the most verbose level required by any handler. + # The file handler will always be more verbose than the console in silent mode. + root_level = logging.DEBUG if debug else logging.INFO + + # Determine console level based on flags. `debug` takes precedence. + if debug: + console_level = logging.DEBUG + elif silent: + console_level = logging.WARNING + else: + console_level = logging.INFO + + # Configure the rich handler for the console + rich_handler = RichHandler( + level=console_level, + rich_tracebacks=True, + show_path=False, + show_time=True, + log_time_format='[%X]', + ) + + # Configure the file handler (always verbose) + log_file = output_path / 'temoa-run.log' + file_handler = logging.FileHandler(log_file) + file_handler.setLevel(root_level) + file_handler.setFormatter( + logging.Formatter( + '%(asctime)s | %(name)s | %(levelname)s | %(message)s', + '%Y-%m-%d %H:%M:%S', + ) + ) + + # Configure the root logger + root_logger = logging.getLogger() + root_logger.setLevel(root_level) + root_logger.handlers = [file_handler, rich_handler] + + # Silence other overly verbose libraries + logging.getLogger('pyomo').setLevel(logging.WARNING) + logging.getLogger('matplotlib').setLevel(logging.WARNING) + + # Log the initialization message (will go to file, and to console if not silent) + logger.info('Logging initialized. Log file at: %s', log_file) + + +def _setup_sequencer( + config_file: Path, + output_path: Path | None, + silent: bool, + debug: bool, + mode_override: TemoaMode | None = None, +) -> tuple[TemoaSequencer, Path]: + """Handles the common setup logic for creating and configuring the sequencer.""" + final_output_path = output_path if output_path else _create_output_folder() + final_output_path.mkdir(parents=True, exist_ok=True) + + # Pass the silent flag to the logging setup + _setup_logging(final_output_path, debug=debug, silent=silent) + + config = TemoaConfig.build_config( + config_file=config_file, output_path=final_output_path, silent=silent + ) + sequencer = TemoaSequencer(config=config, mode_override=mode_override) + return sequencer, final_output_path + + +# ============================================================================= +# Callbacks and Typer App Setup +# ============================================================================= +def _version_callback(value: bool) -> None: + if value: + version = __version__ + rich.print(f'Temoa Version: [bold green]{version}[/bold green]') + raise typer.Exit() + + +def _cite_callback(value: bool) -> None: + if value: + citation_text = Text() + citation_text.append( + 'If you use Temoa in your research, please cite the following publication:\n\n' + ) + + citation_text.append( + """Hunter, K., Sreepathi, S., & DeCarolis, J. F. (2013). """ + """Modeling for insight using Tools for Energy Model Optimization and Analysis """ + """(Temoa). """ + """Energy Economics, 40, 339-349. """ + """https://doi.org/10.1016/j.eneco.2013.07.014""", + style='italic', + ) + citation_text.append('\n\n') + citation_text.append( + 'You can also find citation information in the CITATION.cff file in the repository.', + style='dim', + ) + + rich.print(citation_text) + raise typer.Exit() + + +def get_default_schema() -> Path: + """Get the default path to the v4 schema file, handling both installed and development cases.""" + try: + schema_path = resources.files('temoa.db_schema') / 'temoa_schema_v4.sql' + + if not schema_path.is_file(): + raise FileNotFoundError( + f'Schema file not found at expected resource path: {schema_path}' + ) + return Path(str(schema_path)) # Convert Traversable to concrete Path + except Exception as e: + logger.exception('Failed to load schema from resources') + # The fallback for development needs to reflect the current repository structure + # assuming `cli.py` is in `temoa/` and `db_schema/` is a sibling of `cli.py` within + # `temoa/`. + fallback_path = Path(__file__).parent / 'db_schema' / 'temoa_schema_v4.sql' + if fallback_path.is_file(): + logger.warning( + 'Using fallback schema path: %s. ' + 'This might indicate an issue with package installation or resource setup.', + fallback_path, + ) + return fallback_path + else: + raise FileNotFoundError( + f'Schema file not found using resource system or fallback at {fallback_path}' + ) from e + + +app = typer.Typer( + name='temoa', + help='The Temoa Project: Tools for Energy Model Optimization and Analysis.', + rich_markup_mode='markdown', + no_args_is_help=True, + context_settings={'help_option_names': ['-h', '--help']}, +) + + +# ============================================================================= +# CLI Commands +# ============================================================================= +@app.command() +def validate( + config_file: Annotated[ + Path, + typer.Argument( + help='Path to the configuration file to validate.', + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + resolve_path=True, + ), + ], + output_path: Annotated[ + Path | None, + typer.Option('--output', '-o', help='Directory to save validation log.'), + ] = None, + silent: Annotated[ + bool, typer.Option('--silent', '-q', help='Suppress informational output on success.') + ] = False, + debug: Annotated[ + bool, typer.Option('--debug', '-d', help='Enable debug-level logging.') + ] = False, +) -> None: + """ + Validates a configuration file and database by building the model instance without solving it. + """ + if not silent: + rich.print(f'Validating configuration: [cyan]{config_file}[/cyan]') + try: + ts, final_output_path = _setup_sequencer( + config_file=config_file, + output_path=output_path, + silent=True, # Sequencer is always non-interactive for validation + debug=debug, + mode_override=TemoaMode.BUILD_ONLY, + ) + _ = ts.build_model() + if not silent: + rich.print('\n[bold green]✅ Validation successful.[/bold green]') + rich.print('The model can be built from the provided configuration.') + rich.print(f'Log file is available in: [cyan]{final_output_path}[/cyan]') + except Exception as e: + logger.exception('An error occurred during validation.') + rich.print(f'\n[bold red]❌ Validation failed:[/bold red] {e}') + raise typer.Exit(code=1) from e + + +@app.command() +def run( + config_file: Annotated[ + Path, + typer.Argument( + help='Path to the model configuration file.', + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + resolve_path=True, + ), + ], + output_path: Annotated[ + Path | None, + typer.Option('--output', '-o', help='Directory to save outputs.'), + ] = None, + build_only: Annotated[ + bool, + typer.Option('--build-only', '-b', help='Build the model without solving.'), + ] = False, + silent: Annotated[ + bool, + typer.Option( + '--silent', '-q', help='Silent run. No interactive prompts or INFO logs on console.' + ), + ] = False, + debug: Annotated[ + bool, typer.Option('--debug', '-d', help='Enable debug-level logging.') + ] = False, +) -> None: + """ + Builds and solves a Temoa model based on the provided configuration. + """ + try: + mode_override = TemoaMode.BUILD_ONLY if build_only else None + ts, final_output_path = _setup_sequencer( + config_file=config_file, + output_path=output_path, + silent=silent, + debug=debug, + mode_override=mode_override, + ) + if not silent: + rich.print(ts.config) + typer.confirm('\nPlease confirm the settings above to continue', abort=True) + if build_only or ts.temoa_mode is TemoaMode.BUILD_ONLY: + logger.info('Build-only mode selected. Calling build_model().') + _ = ts.build_model() + if not silent: + rich.print('\n[bold green]✅ Model built successfully.[/bold green]') + rich.print(f'Log file is available in: [cyan]{final_output_path}[/cyan]') + else: + logger.info('Full run mode selected. Calling start().') + ts.start() + if not silent: + rich.print('\n[bold green]✅ Temoa run completed successfully.[/bold green]') + rich.print(f'Outputs are available in: [cyan]{final_output_path}[/cyan]') + except typer.Abort: + rich.print('\n[yellow]Run aborted by user.[/yellow]') + raise typer.Exit() from None + except Exception as e: + logger.exception('An unhandled error occurred during the Temoa run.') + rich.print(f'\n[bold red]❌ An error occurred:[/bold red] {e}') + raise typer.Exit(code=1) from e + + +@app.command('check-units') +def check_units( + database: Annotated[ + Path, + typer.Argument( + help='Path to the Temoa database file to check.', + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + resolve_path=True, + ), + ], + output_dir: Annotated[ + Path | None, + typer.Option( + '--output', + '-o', + help='Directory to save the unit check report. ' + 'Defaults to current directory/unit_check_reports.', + ), + ] = None, + silent: Annotated[ + bool, + typer.Option('--silent', '-q', help='Suppress informational output.'), + ] = False, +) -> None: + """ + Check units consistency in a Temoa database. + + Validates that units are properly defined and consistent across all tables + in the database. Generates a detailed report of any issues found. + + The unit checker verifies: + - Units format and registry compliance + - Technology input/output unit alignment + - Commodity unit consistency + - Cost table unit alignment + """ + from temoa.model_checking.unit_checking.screener import screen + + if not silent: + rich.print(f'Checking units in database: [cyan]{database}[/cyan]') + + # Determine output directory + if output_dir is None: + output_dir = Path.cwd() / 'unit_check_reports' + else: + output_dir = output_dir.resolve() + + # Create output directory if it doesn't exist + try: + output_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + rich.print(f'[red]Error: Could not create output directory: {e}[/red]') + raise typer.Exit(1) from e + + # Run the unit checker + try: + all_clear = screen(database, report_dir=output_dir) + + if all_clear: + if not silent: + rich.print('\n[bold green]✅ All unit checks passed![/bold green]') + rich.print('No unit inconsistencies found in the database.') + else: + # Find the most recent report + reports = sorted(output_dir.glob('units_check_*.txt'), reverse=True) + if reports: + report_file = reports[0] + if not silent: + rich.print('\n[bold yellow]⚠ Unit check found issues.[/bold yellow]') + rich.print(f'Detailed report saved to: [cyan]{report_file}[/cyan]') + # Brief summary of the report + if not silent: + rich.print('\n[bold]Report Summary:[/bold]') + shown = 0 + with open(report_file, encoding='utf-8') as f: + for idx, line in enumerate(f): + if idx >= 40: + break + if shown > 0 and line.strip() == '': + break + if line.strip(): + rich.print(f' {line.rstrip()}') + shown += 1 + else: + if not silent: + rich.print( + '\n[yellow]Unit check completed but no report was generated.[/yellow]' + ) + + except FileNotFoundError as e: + rich.print(f'[red]Error: Database file not found: {e}[/red]') + raise typer.Exit(1) from e + except Exception as e: + logger.exception('Unit check failed') + rich.print(f'\n[bold red]❌ Unit check failed:[/bold red] {e}') + raise typer.Exit(1) from e + + if not all_clear: + raise typer.Exit(1) + + +@app.command() +def migrate( + input_path: Annotated[ + Path, + typer.Argument( + help='Path to input file or directory to migrate (SQL dump or SQLite DB).', + exists=True, + resolve_path=True, + ), + ], + output_path: Annotated[ + Path | None, + typer.Option( + '--output', + '-o', + help='Output path for the migrated file. If not provided, a default name ' + '(e.g., input_v4.sql or input_v4.sqlite) will be used in a writable location.', + ), + ] = None, + schema_path: Annotated[ + Path | None, + typer.Option('--schema', '-s', help='Path to v4 schema SQL file.'), + ] = None, + migration_type: Annotated[ + str | None, + typer.Option( + '--type', + help='Migration type: "sql" for SQL dump to SQLite dump, "db" for SQLite DB in-place ' + 'migration, if omitted, infers from input extension.', + ), + ] = None, + silent: Annotated[ + bool, typer.Option('--silent', '-q', help='Suppress informational output on success.') + ] = False, + debug: Annotated[bool, typer.Option('--debug', '-d', help='Enable debug output.')] = False, +) -> None: + """ + Migrate a Temoa database file (SQL dump or SQLite DB) or directory from v3 to v4 format. + """ + if schema_path is None: + schema_path = get_default_schema() + if not schema_path.is_file(): + rich.print(f'[red]Error: Schema file {schema_path} does not exist or is not a file.[/red]') + raise typer.Exit(1) + + # 1. Directory Migration + if input_path.is_dir(): + if output_path is not None: + rich.print( + '[yellow]Warning: --output is ignored when migrating a directory. Originals are ' + 'overwritten after backup.[/yellow]' + ) + + migration_script = Path(__file__).parent / 'utilities' / 'master_migration.py' + if not silent: + rich.print(f'[green]Batch migrating directory: {input_path}[/green]') + + try: + run_migrations( + input_dir=input_path, + migration_script=migration_script, + schema_path=schema_path, + dry_run=False, + silent=silent, + ) + if not silent: + rich.print(f'[green]Directory migration completed for {input_path}[/green]') + except Exception as e: + logger.exception('Directory migration failed') + rich.print(f'[red]Directory migration failed for {input_path}: {e}[/red]') + raise typer.Exit(1) from e + return + + # 2. Single File Migration + ext = input_path.suffix.lower() + + effective_output_dir: Path + final_output_file: Path + + if output_path: + effective_output_dir = output_path.parent + try: + effective_output_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + rich.print( + f'[red]Error: Could not create output directory "{effective_output_dir}": {e}[/red]' + ) + raise typer.Exit(1) from e + final_output_file = effective_output_dir / output_path.name + else: + input_dir = input_path.parent + if _is_writable(input_dir): + effective_output_dir = input_dir + else: + current_dir = Path.cwd() + if _is_writable(current_dir): + effective_output_dir = current_dir + if not silent: + rich.print( + f'[yellow]Warning: Input directory "{input_dir}" is not writable. ' + f'Saving output to current directory: "{current_dir}"[/yellow]' + ) + else: + rich.print( + f'[red]Error: Neither input directory "{input_dir}" ' + f'nor current working directory "{current_dir}" are writable. ' + 'Please specify a writable output path with --output.[/red]' + ) + raise typer.Exit(1) + + try: + effective_output_dir.mkdir(parents=True, exist_ok=True) + except OSError as e: + rich.print( + f'[red]Error: Could not create auto-generated output directory ' + f'"{effective_output_dir}": {e}[/red]' + ) + raise typer.Exit(1) from e + + if migration_type == 'db' or (migration_type is None and ext in ['.db', '.sqlite']): + final_output_file = effective_output_dir / (input_path.stem + '_v4.sqlite') + else: + final_output_file = effective_output_dir / (input_path.stem + '_v4.sql') + + # --- Execute the migration based on type --- + if migration_type == 'sql' or (migration_type is None and ext == '.sql'): + try: + master_migration.migrate_sql_dump( + source_path=input_path, schema_path=schema_path, output_path=final_output_file + ) + if not silent: + rich.print(f'[green]SQL dump migration completed: {final_output_file}[/green]') + except Exception as e: + logger.exception('SQL dump migration failed for %s', input_path) + rich.print( + f'[red]SQL dump migration failed for {input_path} -> {final_output_file}: {e}[/red]' + ) + raise typer.Exit(1) from e + elif migration_type == 'db' or (migration_type is None and ext in ['.db', '.sqlite']): + try: + master_migration.migrate_database( + source_path=input_path, schema_path=schema_path, output_path=final_output_file + ) + if not silent: + rich.print(f'[green]Database migration completed: {final_output_file}[/green]') + except Exception as e: + logger.exception('Database migration failed for %s', input_path) + rich.print( + f'[red]Database migration failed for {input_path} -> {final_output_file}: {e}[/red]' + ) + raise typer.Exit(1) from e + else: + rich.print( + f'[red]Error: Cannot determine migration type for {input_path}. ' + 'Use --type sql, --type db, or ensure file has a .sql, .db, or .sqlite extension.[/red]' + ) + raise typer.Exit(1) + + +def _copy_tutorial_resources(target_config: Path, target_database: Path) -> None: + """ + Copy tutorial resource files directly to target locations. + + The database is generated from the SQL source file to ensure it uses + the latest schema with unit-compliant data (single source of truth). + + Args: + target_config: Path where configuration file should be copied + target_database: Path where database file should be created + """ + import sqlite3 + + try: + # Try to load resources from the package using resources.files() + base = resources.files('temoa') / 'tutorial_assets' + config_resource = base / 'config_sample.toml' + sql_resource = base / 'utopia.sql' + mc_settings_resource = base / 'mc_settings.csv' + + # Copy configuration file + with config_resource.open('rb') as source: + with open(target_config, 'wb') as target: + shutil.copyfileobj(source, target) + + # Delete existing database if it exists (required for overwrite to work) + if target_database.exists(): + target_database.unlink() + + # Generate database from SQL source (single source of truth) + sql_content = sql_resource.read_text(encoding='utf-8') + with sqlite3.connect(target_database) as conn: + conn.executescript(sql_content) + + # Copy Monte Carlo settings + with mc_settings_resource.open('rb') as source: + target_mc = target_config.parent / 'mc_settings.csv' + with open(target_mc, 'wb') as target: + shutil.copyfileobj(source, target) + + except (ModuleNotFoundError, FileNotFoundError, AttributeError) as e: + logger.exception('Failed to load tutorial resources from package') + # Fallback to development paths (for development environments) + fallback_config = Path(__file__).parent / 'tutorial_assets' / 'config_sample.toml' + fallback_sql = Path(__file__).parent / 'tutorial_assets' / 'utopia.sql' + fallback_mc = Path(__file__).parent / 'tutorial_assets' / 'mc_settings.csv' + + if not fallback_config.exists(): + raise FileNotFoundError( + f'Tutorial config not found. Tried package resources and fallback path:\n' + f'Config: {fallback_config}' + ) from e + + if not fallback_sql.exists(): + raise FileNotFoundError( + f'Tutorial SQL source not found. Tried package resources and fallback path:\n' + f'SQL: {fallback_sql}' + ) from e + + # Copy config file using fallback path + shutil.copy2(fallback_config, target_config) + + # Delete existing database if it exists (required for overwrite to work) + if target_database.exists(): + target_database.unlink() + + # Generate database from SQL source + with sqlite3.connect(target_database) as conn: + conn.executescript(fallback_sql.read_text(encoding='utf-8')) + + # Copy mc_settings from fallback + shutil.copy2(fallback_mc, target_config.parent / 'mc_settings.csv') + + +def _update_toml_database_paths(config_path: Path, new_database_name: str) -> None: + """ + Update database paths in a TOML configuration file using tomlkit. + + Args: + config_path: Path to the configuration file + new_database_name: Base name for the new database (without extension) + """ + try: + # Load TOML document with tomlkit + with open(config_path, 'rb') as f: + doc = tomlkit.load(f) + + # Update database paths safely + if 'input_database' in doc: + doc['input_database'] = f'{new_database_name}.sqlite' + + if 'output_database' in doc: + doc['output_database'] = f'{new_database_name}.sqlite' + + # Write back with tomlkit + with open(config_path, 'w', encoding='utf-8') as f: + tomlkit.dump(doc, f) + + except Exception as _e: + logger.warning('Failed to update TOML configuration %s', config_path) + raise + + +@app.command() +def tutorial( + config_name: Annotated[ + str, typer.Argument(help='Name for the tutorial configuration file (without extension).') + ] = 'tutorial_config', + database_name: Annotated[ + str, typer.Argument(help='Name for the tutorial database file (without extension).') + ] = 'tutorial_database', + force: Annotated[ + bool, typer.Option('--force', '-f', help='Overwrite existing files without prompting.') + ] = False, + verbose: Annotated[ + bool, + typer.Option('--verbose', '-v', help='Show detailed information about the tutorial setup.'), + ] = False, +) -> None: + """ + Create tutorial configuration and database files in the current directory with guidance. + + This command creates: + - A configuration file (.toml) + - A sample database (.sqlite) + + Both files will be configured to work together for running your first Temoa model. + """ + current_dir = Path.cwd() + + target_config = current_dir / f'{config_name}.toml' + target_database = current_dir / f'{database_name}.sqlite' + + # Check for existing files and handle conflicts + existing_files = [] + target_mc_settings = target_config.parent / 'mc_settings.csv' + if target_config.exists(): + existing_files.append(str(target_config)) + if target_database.exists(): + existing_files.append(str(target_database)) + if target_mc_settings.exists(): + existing_files.append(str(target_mc_settings)) + + if existing_files and not force: + rich.print('[yellow]Tutorial files already exist:[/yellow]') + for file in existing_files: + rich.print(f' - {file}') + + try: + typer.confirm('Do you want to overwrite these files?', abort=True) + except typer.Abort: + rich.print('[yellow]Tutorial setup cancelled.[/yellow]') + raise typer.Exit() from None + + try: + # Copy tutorial resources directly to target locations + if verbose: + rich.print('Copying tutorial resources...') + _copy_tutorial_resources(target_config, target_database) + + # Update database paths using tomlkit (preserves formatting/comments) + if verbose: + rich.print('Updating database paths in configuration...') + + _update_toml_database_paths(target_config, database_name) + + if verbose: + rich.print('\n[bold green]✅ Tutorial files created successfully![/bold green]') + + rich.print('\n[bold]Tutorial Setup Complete![/bold]') + rich.print(f'Configuration file: [cyan]{target_config.name}[/cyan]') + rich.print(f'Database file: [cyan]{target_database.name}[/cyan]') + + rich.print('\n[bold]Next Steps:[/bold]') + rich.print(f'1. Review the configuration: [cyan]{target_config.name}[/cyan]') + rich.print('2. Run your first model:') + rich.print(f' [green]uv run temoa run {target_config.name}[/green]') + rich.print(' or') + rich.print(f' [green]python -m temoa run {target_config.name}[/green]') + rich.print( + f'\nTo learn more about the configuration options, see the comments in ' + f'[cyan]{target_config.name}[/cyan]' + ) + + if verbose: + rich.print( + f"\n[dim]The configuration file points to your local '{database_name}.sqlite' " + 'database.[/dim]' + ) + rich.print("[dim]Results will be saved in the 'output_files' directory.[/dim]") + + except Exception as e: + logger.exception('Failed to create tutorial files') + rich.print(f'\n[bold red]❌ Failed to create tutorial files:[/bold red] {e}') + raise typer.Exit(1) from e + + +def _is_writable(path: Path) -> bool: + """Check if a path is writable.""" + try: + test_file = path / f'.temoa_write_test_{datetime.now(UTC).timestamp()}' + test_file.touch() + test_file.unlink() # Clean up + return True + except OSError: + return False + + +# ============================================================================= +# Global Options +# ============================================================================= +@app.callback() +def main_options( + version: bool | None = typer.Option( + None, + '--version', + '-v', + help='Show Temoa version and exit.', + callback=_version_callback, + is_eager=True, + ), + how_to_cite: bool | None = typer.Option( + None, + '--how-to-cite', + help='Show citation information and exit.', + callback=_cite_callback, + is_eager=True, + ), +) -> None: + """Manage global options for the Temoa CLI.""" + + +if __name__ == '__main__': + app() diff --git a/temoa/components/capacity.py b/temoa/components/capacity.py new file mode 100644 index 000000000..1383e09f7 --- /dev/null +++ b/temoa/components/capacity.py @@ -0,0 +1,660 @@ +# temoa/components/capacity.py +""" +Defines the capacity-related components of the Temoa model. + +This module is responsible for: +- Defining Pyomo index sets for variables. +- Defining the rules for all capacity-related constraints, such as capacity + production limits, retirement accounting, and available capacity aggregation. +- Pre-calculating sparse index sets for capacity, retirement, and material flows. +""" + +from __future__ import annotations + +from itertools import product as cross_product +from logging import getLogger +from typing import TYPE_CHECKING + +from deprecated import deprecated +from pyomo.environ import value + +from .utils import get_capacity_factor + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types import ( + ExprLike, + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, + ) + + +logger = getLogger(name=__name__) + + +# ============================================================================ +# HELPER FUNCTIONS AND VALIDATORS +# ============================================================================ + + +def check_capacity_factor_process(model: TemoaModel) -> None: + # Count of capacity factor process entries for this process in this region + count_rtv: dict[tuple[Region, Technology, Vintage], int] = {} + + # Pull capacity_factor_tech by default + unique_rt = {(r, t) for r, _s, _d, t in model.capacity_factor_rsdt} + for r, t in unique_rt: + for p in model.time_optimize: + for v in model.process_vintages.get((r, p, t), []): + if (r, t, v) not in count_rtv: + model.is_capacity_factor_process[r, t, v] = False + count_rtv[r, t, v] = 0 + + # Check for bad values and count up the good ones + for r, _s, _d, t, v in model.capacity_factor_process.sparse_keys(): + # Validate that vintage is active for some period + if not model.process_periods.get((r, t, v)): + msg = f'Invalid vintage {v} for {r, t} in capacity_factor_process table' + logger.error(msg) + raise ValueError(msg) + + # Good value, pull from capacity_factor_process table + count_rtv[r, t, v] += 1 + + # Check if all possible values have been set by process + # log a warning if some are missing (allowed but maybe accidental) + for (r, t, v), count in count_rtv.items(): + num_seg = len(model.time_season) * len(model.time_of_day) + if count > 0: + model.is_capacity_factor_process[r, t, v] = True + if count < num_seg: + logger.info( + 'Some but not all processes were set in capacity_factor_process (%i out of a ' + 'possible %i) for: %s Missing values will default to capacity_factor_tech ' + 'value or 1 if that is not set either.', + count, + num_seg, + (r, t, v), + ) + + +@deprecated('should not be needed. We are pulling the default on-the-fly where used') +def create_capacity_factors(model: TemoaModel) -> None: + """ + Steps to creating capacity factors: + 1. Collect all possible processes + 2. Find the ones _not_ specified in capacity_factor_process + 3. Set them, based on capacity_factor_tech. + """ + capacity_factor_process = model.capacity_factor_process + + # Step 1 + processes = {(r, t, v) for r, i, t, v, o in model.efficiency.sparse_keys()} + + all_cfs = { + (r, s, d, t, v) + for (r, t, v) in processes + for s, d in cross_product(model.time_season, model.time_of_day) + } + + # Step 2 + unspecified_cfs = all_cfs.difference(capacity_factor_process.sparse_keys()) + + # Step 3 + + # Some hackery: We futz with _constructed because Pyomo thinks that this + # Param is already constructed. However, in our view, it is not yet, + # because we're specifically targeting values that have not yet been + # constructed, that we know are valid, and that we will need. + + if unspecified_cfs: + # CFP._constructed = False + for r, s, d, t, v in unspecified_cfs: + capacity_factor_process[r, s, d, t, v] = model.capacity_factor_tech[r, s, d, t] + logger.debug( + 'Created Capacity Factors for %d processes without an explicit specification', + len(unspecified_cfs), + ) + # CFP._constructed = True + + +def get_default_capacity_factor( + model: TemoaModel, r: Region, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> float: + """ + This initializer is used to fill the capacity_factor_process from the capacity_factor_tech + where needed. + + Priority: + 1. As specified in data input (this function not called) + 2. Here + 3. The default from capacity_factor_tech param + :param M: generic model reference + :param r: region + :param s: season + :param d: time-of-day slice + :param t: tech + :param v: vintage + :return: the capacity factor + """ + return value(model.capacity_factor_tech[r, s, d, t]) + + +# ============================================================================ +# PYOMO INDEX SETS +# ============================================================================ + + +def new_capacity_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Technology, Vintage]]: + return model.new_capacity_rtv + + +def retired_capacity_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage]]: + return { + (r, p, t, v) + for r, p, t in model.process_vintages + if t in model.tech_retirement and t not in model.tech_uncap + for v in model.process_vintages[r, p, t] + if v < p <= v + value(model.lifetime_process[r, t, v]) - value(model.period_length[p]) + } + + +def annual_retirement_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage]]: + return { + (r, p, t, v) + for r, t, v in model.retirement_periods + for p in model.retirement_periods[r, t, v] + } + + +def capacity_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage]]: + return model.active_capacity_rptv + + +def capacity_available_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology]]: + return model.active_capacity_available_rpt + + +def regional_exchange_capacity_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Region, Period, Technology, Vintage]]: + return { + (r_to, r_from, p, t, v) + for r_from, p, i in model.export_regions + for r_to, t, v, _o in model.export_regions[r_from, p, i] + } + + +def capacity_annual_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage]]: + return { + (r, p, t, v) + for r, p, t, v in model.active_capacity_rptv + if t in model.tech_annual and t not in model.tech_demand + } + + +def capacity_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + return { + (r, p, s, d, t, v) + for r, p, t, v in model.active_capacity_rptv + for s in model.time_season + for d in model.time_of_day + if t not in model.tech_annual or t in model.tech_demand + if t not in model.tech_storage + } + + +def capacity_factor_tech_indices( + model: TemoaModel, +) -> set[tuple[Region, Season, TimeOfDay, Technology]]: + return { + (r, s, d, t) + for r, _p, t in model.active_capacity_available_rpt + for s in model.time_season + for d in model.time_of_day + } + + +@deprecated('switched over to validator... this set is typically VERY empty') +def capacity_factor_process_indices( + model: TemoaModel, +) -> set[tuple[Region, Season, TimeOfDay, Technology, Vintage]]: + return { + (r, s, d, t, v) + for r, _i, t, v, _o in model.efficiency.sparse_keys() + for s in model.time_season + for d in model.time_of_day + } + + +# ============================================================================ +# PYOMO CONSTRAINT RULES +# ============================================================================ + + +def annual_retirement_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage +) -> ExprLike: + r""" + Get the annualised retirement rate for a process in a given period. + Used to output retirement (including end of life, EOL) and to model end of + life flows and emissions. Assumes that retirement from the beginning of each period + is evenly distributed over that model period :math:`\frac{1}{\text{LEN}_p}` + for the accounting of retirement flows (in the same way we assume capacity is + deployed evenly over the model period for construction inputs and embodied emissions). + The factor :math:`\frac{\text{LSC}_{r,p,t,v}}{\text{PLF}_{r,p,t,v}}` + adjusts the average survival during a period to the survival at the beginning + of that period. + + .. math:: + :label: Annual Retirement + + \textbf{ART}_{r,p,t,v} = + \begin{cases} + \frac{1}{\text{LEN}_p} \cdot + \frac{\text{LSC}_{r,p,t,v}}{\text{PLF}_{r,p,t,v}} \cdot \textbf{CAP}_{r,p,t,v} + & \text{if EOL} \\ + \frac{1}{\text{LEN}_p} \cdot + \left( + \frac{\text{LSC}_{r,p,t,v}}{\text{PLF}_{r,p,t,v}} \cdot \textbf{CAP}_{r,p,t,v} + - \frac{\text{LSC}_{r,p_{next},t,v}}{\text{PLF}_{r,p_{next},t,v}} \cdot + \textbf{CAP}_{r,p_{next},t,v} + \right) + & \text{otherwise} \\ + \end{cases} + + \\\text{where EOL when } p \leq v + LTP_{r,t,v} < p + LEN_p + """ + + p_end = p + value(model.period_length[p]) + eol_year = v + value(model.lifetime_process[r, t, v]) + + ## Get the capacity at the start of this period + if p == eol_year: + # Exact EOL. No v_capacity or v_retired_capacity for this period. + if p == model.time_optimize.first(): + # Must be existing capacity. Apply survival curve to existing cap + cap_begin = model.existing_capacity[r, t, v] * model.lifetime_survival_curve[r, p, t, v] + else: + # Get previous capacity and continue survival curve + p_prev = model.time_optimize.prev(p) + cap_begin = ( + model.v_capacity[r, p_prev, t, v] + * value(model.lifetime_survival_curve[r, p, t, v]) + / value(model.process_life_frac[r, p_prev, t, v]) + ) + else: + # The capacity at the beginning of the period + cap_begin = ( + model.v_capacity[r, p, t, v] + * value(model.lifetime_survival_curve[r, p, t, v]) + / value(model.process_life_frac[r, p, t, v]) + ) + + ## Get the capacity at the end of this period + if p <= eol_year < p_end: + # EOL so capacity ends on zero no matter what + cap_end = 0 + elif p == model.time_optimize.last() or p_end == eol_year: + # No v_capacity or v_retired_capacity for next period so just continue down the + # survival curve. If eol_year = p_end then eol would be dumped in the next period + cap_end = ( + cap_begin + * value(model.lifetime_survival_curve[r, p_end, t, v]) + / value(model.lifetime_survival_curve[r, p, t, v]) + ) + else: + # Get the next period's beginning capacity + p_next = model.time_optimize.next(p) + cap_end = ( + model.v_capacity[r, p_next, t, v] + * value(model.lifetime_survival_curve[r, p_next, t, v]) + / value(model.process_life_frac[r, p_next, t, v]) + ) + # next v_capacity also accounts for decision retirement so need to undo that again + p_next_end = p_next + value(model.period_length[p_next]) + if t in model.tech_retirement and v < p_next and p_next_end <= eol_year: + cap_end += model.v_retired_capacity[r, p_next, t, v] + + # v_capacity already accounts for decision retirement so need to undo that for beginning cap + if t in model.tech_retirement and v < p and p_end <= eol_year: + cap_begin += model.v_retired_capacity[r, p, t, v] + + annualised_retirement = (cap_begin - cap_end) / value(model.period_length[p]) + return model.v_annual_retirement[r, p, t, v] == annualised_retirement + + +def capacity_available_by_period_and_tech_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology +) -> ExprLike: + r""" + + The :math:`\textbf{CAPAVL}` variable is nominally for reporting solution values, + but is also used in the Limit constraint calculations. + + .. math:: + :label: CapacityAvailable + + \textbf{CAPAVL}_{r, p, t} = \sum_{v, p_i \leq p} \textbf{CAP}_{r, p, t, v} + + \\ + \forall p \in \text{P}^o, r \in R, t \in T + """ + cap_avail = sum(model.v_capacity[r, p, t, S_v] for S_v in model.process_vintages[r, p, t]) + + expr = model.v_capacity_available_by_period_and_tech[r, p, t] == cap_avail + return expr + + +def capacity_annual_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage +) -> ExprLike: + r""" + Similar to Capacity_constraint, but for technologies belonging to the + :code:`tech_annual` set. Technologies in the tech_annual set have constant output + across different timeslices within a year, so we do not need to ensure + that installed capacity is sufficient across all timeslices, thus saving + some computational effort. Instead, annual output is sufficient to calculate + capacity. Hourly capacity factors cannot be defined to annual technologies + but annual capacity factors can be set using limit_annual_capacity_factor, + which will be implicitly accounted for here. + + .. math:: + :label: CapacityAnnual + + \text{C2A}_{r, t} + \cdot \textbf{CAP}_{r, p, t, v} + \ge + \sum_{I, O} \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} + + \\ + \forall \{r, p, t \in T^{a}, v\} \in \Theta_{\text{Activity}} + """ + activity_rptv = sum( + model.v_flow_out_annual[r, p, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + return value(model.capacity_to_activity[r, t]) * model.v_capacity[r, p, t, v] >= activity_rptv + + +def capacity_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> ExprLike: + r""" + This constraint ensures that the capacity of a given process is sufficient + to support its activity across all time periods and time slices. The calculation + on the left hand side of the equality is the maximum amount of energy a process + can produce in the timeslice :code:`(s,d)`. Note that the curtailment variable + shown below only applies to technologies that are members of the curtailment set. + Curtailment is necessary to track explicitly in scenarios that include a high + renewable target. Without it, the model can generate more activity than is used + to meet demand, and have all activity (including the portion curtailed) count + towards the target. Tracking activity and curtailment separately prevents this + possibility. + + .. math:: + :label: Capacity + + \left ( + \text{CFP}_{r, s, d, t, v} + \cdot \text{C2A}_{r, t} + \cdot \text{SEG}_{s, d} + \right ) + \cdot \textbf{CAP}_{r, p, t, v} + = + \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} + + + \sum_{I, O} \textbf{CUR}_{r, p, s, d, i, t, v, o} + + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{FO}} + """ + # The expressions below are defined in-line to minimize the amount of + # expression cloning taking place with Pyomo. + + if t in model.tech_annual: + # Annual demand technology + useful_activity = sum( + ( + value(model.demand_specific_distribution[r, p, s, d, S_o]) + if S_o in model.commodity_demand + else value(model.segment_fraction[s, d]) + ) + * model.v_flow_out_annual[r, p, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + else: + useful_activity = sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + if t in model.tech_curtailment: + # If technologies are present in the curtailment set, then enough + # capacity must be available to cover both activity and curtailment. + return get_capacity_factor(model, r, s, d, t, v) * value( + model.capacity_to_activity[r, t] + ) * value(model.segment_fraction[s, d]) * model.v_capacity[ + r, p, t, v + ] == useful_activity + sum( + model.v_curtailment[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + else: + return ( + get_capacity_factor(model, r, s, d, t, v) + * value(model.capacity_to_activity[r, t]) + * value(model.segment_fraction[s, d]) + * model.v_capacity[r, p, t, v] + >= useful_activity + ) + + +def adjusted_capacity_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage +) -> ExprLike: + r""" + This constraint updates the capacity of a process by taking into account retirements + and end of life. For a given :code:`(r,p,t,v)` index, this constraint sets the capacity + equal to the amount installed in period :code:`v` and subtracts from it any and all retirements + that occurred prior to the period in question, :code:`p`, and end of life from the + survival curve if defined. It finally adjusts for the process life fraction, which + accounts for a possible mid-period end of life where, for example, EOL 3 years into a 5-year + period would be treated as :math:`\frac{3}{5}` capacity for all 5 years. + + .. figure:: images/adjusted_capacity_plf.* + :align: center + :width: 100% + :figclass: align-center + :figwidth: 50% + + For processes reaching end of life mid-period, the process life fraction adjustment is + applied, distributing the effective capacity over the whole period. + + For processes using survival curves, the yearly survival curve :math:`\text{LSC}_{r,p,t,v}` is + averaged over the period to get the effective remaining capacity for that period Because this + implicitly handles mid-period end of life, :math:`\text{PLF}_{r,p,t,v}` is used to account for + both phenomena. + + .. figure:: images/adjusted_capacity_sc.* + :align: center + :width: 100% + :figclass: align-center + :figwidth: 50% + + For processes with a defined survival curve, the surviving capacity is averaged over each + period to get the adjusted capacity. This implicitly handles mid-period end of life as a + survival curve will always be zero after the end of life of a process. + + .. math:: + :label: Adjusted Capacity + + \textbf{CAP}_{r,p,t,v} = + \begin{cases} + \text{PLF}_{r,p,t,v} \cdot + \left( + \text{ECAP}_{r,t,v} - \sum\limits_{v < p' <= p} + \frac{\textbf{RCAP}_{r,p',t,v}}{\text{LSC}_{r,p',t,v}} + \right) + & \text{if } \ v \in T^e \\ + \text{PLF}_{r,p,t,v} \cdot + \left( + \textbf{NCAP}_{r,t,v} - \sum\limits_{v < p' <= p} + \frac{\textbf{RCAP}_{r,p',t,v}}{\text{LSC}_{r,p',t,v}} + \right) + & \text{if } \ v \notin T^e + \end{cases} + + \\\text{where } + \text{PLF}_{r,p,t,v} = + \begin{cases} + \frac{1}{\text{LEN}_p} \cdot \left( + \sum\limits_{y = p}^{p+\text{LEN}_{p}-1}{\text{LSC}_{r,y,t,v}} + \right) + & \text{if } t \in T^{sc} \\ + \frac{1}{\text{LEN}_p} \cdot \left( v + \text{LTP}_{r,t,v} - p \right) + & \text{if } t \notin T^{sc} \\ + \end{cases} + + We divide :math:`\frac{\textbf{RCAP}_{r,p',t,v}}{\text{LSC}_{r,p',t,v}}` + because the average survival factor in :math:`\text{PLF}_{r,p,t,v}` is indexed to the vintage + period (the beginning of the survival curve). So, we adjust for the relative survival from + the time when that retirement occurred (treated here as at the beginning of each period). + """ + + if v in model.time_exist: + built_capacity = value(model.existing_capacity[r, t, v]) + else: + built_capacity = model.v_new_capacity[r, t, v] + + early_retirements = 0 + if t in model.tech_retirement: + early_retirements = sum( + model.v_retired_capacity[r, S_p, t, v] + / value(model.lifetime_survival_curve[r, S_p, t, v]) # relative survival since then + for S_p in model.time_optimize + if v < S_p <= p + and S_p + value(model.period_length[S_p]) <= v + value(model.lifetime_process[r, t, v]) + ) + + remaining_capacity = (built_capacity - early_retirements) * value( + model.process_life_frac[r, p, t, v] + ) + return model.v_capacity[r, p, t, v] == remaining_capacity + + +# ============================================================================ +# PRE-COMPUTATION FUNCTIONS +# ============================================================================ + + +def create_capacity_and_retirement_sets(model: TemoaModel) -> None: + """ + Creates and populates component-specific Python sets and dictionaries on the model object. + + This function is called once during model initialization and is responsible for + creating the sparse indices related to technology capacity, retirement, and + construction/end-of-life material flows. These data structures are then + used by other functions in this module to build Pyomo components. + + Populates: + - model.retirement_periods: dict mapping (r, t, v) to a set of periods `p` + where retirement can occur. + - model.capacity_consumption_techs: dict mapping (r, v, i) to a set of techs `t` + that consume commodity `i` for construction. + - model.retirement_production_processes: dict mapping (r, p, o) to a set of `(t, v)` + processes that produce commodity `o` at end-of-life. + - model.new_capacity_rtv: set of (r, t, v) for new capacity investments. + - model.active_capacity_available_rpt: set of (r, p, t) where capacity is active. + - model.active_capacity_available_rptv: set of (r, p, t, v) where vintage capacity is + active. + """ + + logger.debug('Creating capacity, retirement, and construction/EOL sets.') + # Calculate retirement periods based on lifetime and survival curves + unique_rtv = {(r, t, v) for r, _i, t, v, _o in model.efficiency.sparse_keys()} | set( + model.existing_capacity.sparse_keys() + ) + for r, t, v in unique_rtv: + if t in model.tech_uncap: + # No capacity to retire + continue + if t not in model.tech_all: + # Not an active technology so wont have a lifetime + # If it has an EOLoutput it will be in tech_all + continue + lifetime = value(model.lifetime_process[r, t, v]) + for p in model.time_optimize: + # retires bang on start of horizon or survives into planning periods + is_p0_eol = ( + (p == model.time_optimize.first()) + and (v + lifetime == p) + and value(model.existing_capacity[r, t, v]) > 0 + ) + is_living = (r, t, v) in model.process_periods + if not (is_p0_eol or is_living): + continue + + is_natural_eol = p <= v + lifetime < p + value(model.period_length[p]) + is_early_retire = t in model.tech_retirement and v < p <= v + lifetime - value( + model.period_length[p] + ) + is_survival_curve = model.is_survival_curve_process[r, t, v] and v <= p <= v + lifetime + + if any((is_natural_eol, is_early_retire, is_survival_curve)): + model.retirement_periods.setdefault((r, t, v), set()).add(p) + + # Link construction materials to technologies + for r, i, t, v in model.construction_input.sparse_keys(): + model.capacity_consumption_techs.setdefault((r, v, i), set()).add(t) + model.used_techs.add(t) + + # Link end-of-life materials to retiring technologies + for r, t, v, o in model.end_of_life_output.sparse_keys(): + if (r, t, v) in model.retirement_periods: + for p in model.retirement_periods[r, t, v]: + model.retirement_production_processes.setdefault((r, p, o), set()).add((t, v)) + model.used_techs.add(t) + + # Link end-of-life emissions to retiring technologies + for r, _e, t, v in model.emission_end_of_life.sparse_keys(): + if (r, t, v) in model.retirement_periods: + model.used_techs.add(t) + + # Create active capacity index sets from the now-populated process_vintages + model.new_capacity_rtv = { + (r, t, v) + for r, t, v in model.process_periods + if t not in model.tech_uncap and v in model.time_optimize + } + model.active_capacity_available_rpt = { + (r, p, t) for r, p, t in model.process_vintages if t not in model.tech_uncap + } + model.active_capacity_rptv = { + (r, p, t, v) + for r, p, t in model.active_capacity_available_rpt + for v in model.process_vintages[r, p, t] + } diff --git a/temoa/components/commodities.py b/temoa/components/commodities.py new file mode 100644 index 000000000..1328d65a8 --- /dev/null +++ b/temoa/components/commodities.py @@ -0,0 +1,794 @@ +""" +Defines the commodity and demand-related components of the Temoa model. + +This module is responsible for: +- Pre-computing technology and commodity subsets (e.g., demand techs, flex techs). +- Calculating and validating demand distributions across time slices. +- Defining the core commodity balance and demand satisfaction constraints that + drive the model's energy system solution. +""" + +from __future__ import annotations + +from itertools import product as cross_product +from logging import getLogger +from typing import TYPE_CHECKING, Any, cast + +from pyomo.environ import Constraint, value + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types.core_types import Season, Technology, TimeOfDay, Vintage + + from ..types import Commodity, ExprLike, Period, Region + +from .utils import get_variable_efficiency + +logger = getLogger(name=__name__) + +# ============================================================================ +# HELPER FUNCTIONS AND VALIDATORS +# ============================================================================ + + +def commodity_balance_constraint_error_check( + supplied: Any, demanded: Any, r: Region, p: Period, s: Season, d: TimeOfDay, c: Commodity +) -> None: + # note: if a pyomo equation simplifies to an int, there are no variables in it, which + # is an indicator of a problem. How this might come up I do not know + if isinstance(supplied, int) and isinstance(demanded, int): + expr = str(supplied == demanded) + msg = ( + 'Unable to balance commodity {} in ({}, {}, {}, {}).\n' + 'Nothing produces or consumes it:\n' + ' {}\n' + 'Possible reasons:\n' + " - Is there a missing period in set 'time_future'?\n" + " - Is there a missing tech in set 'tech_resource'?\n" + " - Is there a missing tech in set 'tech_production'?\n" + " - Is there a missing commodity in set 'commodity_physical'?\n" + ' - Are there missing entries in the efficiency table?\n' + ' - Does a process need a longer Lifetime?' + ) + logger.error(msg.format(c, r, p, s, d, expr)) + raise Exception(msg.format(c, r, p, s, d, expr)) + + +def annual_commodity_balance_constraint_error_check( + supplied: Any, demanded: Any, r: Region, p: Period, c: Commodity +) -> None: + # note: if a pyomo equation simplifies to an int, there are no variables in it, which + # is an indicator of a problem. How this might come up I do not know + if isinstance(supplied, int) and isinstance(demanded, int): + expr = str(supplied == demanded) + msg = ( + 'Unable to balance annual commodity {} in ({}, {}).\n' + 'Nothing produces or consumes it:\n' + ' {}\n' + 'Possible reasons:\n' + " - Is there a missing period in set 'time_future'?\n" + " - Is there a missing tech in set 'tech_resource'?\n" + " - Is there a missing tech in set 'tech_production'?\n" + " - Is there a missing commodity in set 'commodity_physical'?\n" + ' - Are there missing entries in the efficiency table?\n' + ' - Does a process need a longer Lifetime?' + ) + logger.error(msg.format(c, r, p, expr)) + raise Exception(msg.format(c, r, p, expr)) + + +def demand_constraint_error_check(supply: Any, r: Region, p: Period, dem: Commodity) -> None: + # note: if a pyomo equation simplifies to an int, there are no variables in it, which + # is an indicator of a problem + if isinstance(supply, int): + msg = ( + "Error: Demand '{}' for ({}, {}) unable to be met by any " + 'technology.\n\tPossible reasons:\n' + ' - Is the efficiency parameter missing an entry for this demand?\n' + ' - Does a tech that satisfies this demand need a longer ' + 'Lifetime?\n' + ) + logger.error(msg.format(dem, r, p)) + raise Exception(msg.format(dem, r, p)) + + +def check_singleton_demands(model: TemoaModel) -> None: + """ + Check for demand commodities that are only produced by a single + (r, i, t, v, o) sub-process. If such a case is found, the flow variables are + fixed directly and demand activity and demand constraints are skipped + as these constraints would otherwise be overdefined and unstable. + """ + for r, p, dem in model.demand_constraint_rpc: + upstream_itv = { + (i, t, v) + for t, v in model.commodity_up_stream_process.get((r, p, dem), []) + for i in model.process_inputs_by_output.get((r, p, t, v, dem), []) + } + if len(upstream_itv) != 1: + # not singleton, regular demand constraints + continue + + # singleton, fix everything and skip demand constraints + val = value(model.demand[r, p, dem]) + i, t, v = next(iter(upstream_itv)) + model.v_flow_out_annual[r, p, i, t, v, dem].fix(val) + if t not in model.tech_annual: + for s, d in cross_product(model.time_season, model.time_of_day): + dsd = value(model.demand_specific_distribution[r, p, s, d, dem]) + model.v_flow_out[r, p, s, d, i, t, v, dem].fix(val * dsd) + model.singleton_demands.add((r, p, dem)) + + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def demand_activity_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage, Commodity]]: + """Index set for DemandActivity. Drops indices for single-tech demands. + + When exactly one non-annual (tech, vintage) pair with a single input serves a + demand commodity (and no annual techs co-serve it), the flow variables are fixed + directly and DAC indices are omitted. + """ + return { + (r, p, s, d, t, v, dem) + for r, p, dem in model.demand_constraint_rpc + if (r, p, dem) not in model.singleton_demands + for t, v in model.commodity_up_stream_process[r, p, dem] + if t not in model.tech_annual + for s in model.time_season + for d in model.time_of_day + } + + +def commodity_balance_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity]]: + # Generate indices only for those commodities that are produced by + # technologies with varying output at the time slice level. + return { + (r, p, s, d, c) + for r, p, c in model.commodity_balance_rpc + # r in this line includes interregional transfer combinations (not needed). + if r in model.regions # this line ensures only the regions are included. + and c not in model.commodity_annual + for s in model.time_season + for d in model.time_of_day + } + + +def annual_commodity_balance_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Commodity]]: + # Generate indices only for those commodities that are produced by + # technologies with constant annual output. + return { + (r, p, c) + for r, p, c in model.commodity_balance_rpc + # r in this line includes interregional transfer combinations (not needed). + if r in model.regions # this line ensures only the regions are included. + and c in model.commodity_annual + } + + +# ============================================================================ +# PYOMO CONSTRAINT RULES +# ============================================================================ + + +def demand_constraint(model: TemoaModel, r: Region, p: Period, dem: Commodity) -> ExprLike: + r""" + + The Demand constraint drives the model. This constraint ensures that supply + meets the demand specified by the Demand parameter in all periods, + by ensuring that the sum of all the annual demand output commodity (:math:`dem`) + generated by :math:`\textbf{FOA}` across all technologies and vintages + equals the modeler-specified demand. + + .. math:: + :label: Demand + + \sum_{I, T, V} \textbf{FOA}_{r, p, i, t, v, dem} + = + {DEM}_{r, p, dem} + + The per-timeslice distribution of demand across non-annual technologies + is enforced by the separate :code:`demand_activity_constraint`. In the case of + a singleton demand, where only one :code:`(r, i, t, v)` sub-process produces the + demand commodity, the flow variables are fixed directly and demand constraints + are skipped. + + Note that the validity of this constraint relies on the fact that the + :math:`C^d` set is distinct from both :math:`C^e` and :math:`C^p`. In other + words, an end-use demand must only be an end-use demand. Note that if an output + could satisfy both an end-use and internal system demand, then the output from + :math:`\textbf{FO}` and :math:`\textbf{FOA}` would be double counted.""" + + if (r, p, dem) in model.singleton_demands: + return Constraint.Skip + + supply_annual = sum( + model.v_flow_out_annual[r, p, s_i, s_t, s_v, dem] + for s_t, s_v in model.commodity_up_stream_process[r, p, dem] + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, dem] + ) + + demand_constraint_error_check(supply_annual, r, p, dem) + + expr = supply_annual == value(model.demand[r, p, dem]) + + return expr + + +# devnote: no longer needed +def demand_activity_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, + dem: Commodity, +) -> ExprLike: + r""" + + For end-use demands, it is unreasonable to let the model arbitrarily shift the + use of demand technologies across time slices. For instance, if household A buys + a natural gas furnace while household B buys an electric furnace, then both units + should be used throughout the year. Without this constraint, the model might choose + to only use the electric furnace during the day, and the natural gas furnace during the + night. + + This constraint ensures that the ratio of a process activity to demand is + constant for all time slices. In other words, while the model may choose how + each technology contributes to a demand at the annual level, the time slice + level distribution of the technology's contribution to the demand is fixed to + the demand specific distribution. + + .. math:: + :label: DemandActivity + + \sum_{I} + \textbf{FOA}_{r, p, i, t, v, dem} + \cdot \text{DSD}_{r, s, d, dem} + = + \sum_{I} + \textbf{FO}_{r, p, s, d, i, t, v, dem} + + \\ + \forall \{r, p, s, d, t, v, dem\} \in \Theta_{\text{DemandActivity}} + + Note that this constraint is unnecessary and therefore skipped for annual + technologies or singleton demands. + """ + + activity = sum( + model.v_flow_out[r, p, s, d, s_i, t, v, dem] + for s_i in model.process_inputs_by_output[r, p, t, v, dem] + ) + + annual_activity = sum( + model.v_flow_out_annual[r, p, s_i, t, v, dem] + for s_i in model.process_inputs_by_output[r, p, t, v, dem] + ) + + expr = annual_activity * value(model.demand_specific_distribution[r, p, s, d, dem]) == activity + return expr + + +def commodity_balance_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, c: Commodity +) -> ExprLike: + r""" + Where the Demand constraint :eq:`Demand` ensures that end-use demands are met, + the CommodityBalance constraint ensures that the endogenous system demands are + met. This constraint requires the total production of a given commodity + to equal the amount consumed, thus ensuring an energy balance at the system + level. In this most general form of the constraint, the energy commodity being + balanced has variable production at the time slice level. The energy commodity + can then be consumed by three types of processes: storage technologies, non-storage + technologies with output that varies at the time slice level, and non-storage + technologies with constant annual output. + + Separate expressions are required in order to account for the consumption of + commodity :math:`c` by downstream processes. For the commodity flow into storage + technologies, we use :math:`\textbf{FI}_{r, p, s, d, i, t, v, c}`. Note that the FlowIn + variable is defined only for storage technologies, and is required because storage + technologies balance production and consumption across time slices rather than + within a single time slice. For commodity flows into non-storage processes with time + varying output, we use :math:`\textbf{FO}_{r, p, s, d, i, t, v, c}/EFF_{r, i,t,v,o}`. + The division by :math:`EFF_{r, c,t,v,o}` is applied to the output flows that consume + commodity :math:`c` to determine input flows. Finally, we need to account + for the consumption of commodity :math:`c` by the processes in + :code:`tech_annual`. Since the commodity flow of these processes is on an + annual basis, we use :math:`SEG_{s,d}` to calculate the consumption of + commodity :math:`c` in time-slice :math:`(s,d)` from the annual flows. + Formulating an expression for the production of commodity :math:`c` is + more straightforward, and is simply calculated by + :math:`\textbf{FO}_{r, p, s, d, i, t, v, c}`. + + In some cases, the overproduction of a commodity may be required, such + that the supply exceeds the endogenous demand. Refineries represent a + common example, where the share of different refined products are governed + by TechOutputSplit, but total production is driven by a particular commodity + like gasoline. Such a situation can result in the overproduction of other + refined products, such as diesel or kerosene. In such cases, we need to + track the excess production of these commodities. To do so, the technology + producing the excess commodity should be added to the :code:`tech_flex` set. + This flexible technology designation will activate a slack variable + (:math:`\textbf{FLX}_{r, p, s, d, i, t, v, c}`) representing + the excess production in the :code:`CommodityBalanceAnnual_constraint`. Note + that the :code:`tech_flex` set is different from :code:`tech_curtailment` set; + the latter is technology- rather than commodity-focused and is used in the + :code:`Capacity_constraint` to track output that is used to produce useful + output and the amount curtailed, and to ensure that the installed capacity + covers both. Alternatively, the commodity can be added to the + :code:`commodity_waste` set, for which this equality constraint becomes an + inequality constraint, allowing production to exceed consumption for a single + commodity. + + This constraint also accounts for imports and exports between regions + when solving multi-regional systems. The import (:math:`\textbf{FIM}`) and export + (:math:`\textbf{FEX}`) variables are created on-the-fly by summing the + :math:`\textbf{FO}` variables over the appropriate import and export regions, + respectively, which are defined in :code:`temoa_initialize.py` by parsing the + :code:`tech_exchange` processes. + + Consumption of the commodity by construction inputs is annualised using the period + length. Production of the commodity by end-of-life outputs uses the AnnualRetirement + variable, which is already annualised. + + Finally, for annual commodities, AnnualCommodityBalance is used which balances + the sum of flows over each year. + + *process outputs + imports + end of life outputs = process inputs + construction inputs + + exports + flex waste* + + .. math:: + :label: CommodityBalance + + \begin{aligned} + &\sum_{I, t \notin T^a, V} \mathbf{FO}_{r, p, s, d, i, t, v, c} + && \text{(processes outputting commodity)} \\ + &+ SEG_{s,d} \cdot \sum_{I, t \in T^a, V} + \mathbf{FOA}_{r, p, i, t, v, c} + && \text{(annual processes outputting commodity)} \\ + &+ \sum_{\text{reg} \neq r, I, t \in T^x, V} + \mathbf{FIM}_{r - \text{reg}, p, s, d, i, t, v, c} + && \text{(inter-regional imports of commodity)} \\ + &+ SEG_{s,d} \sum_{T, V} \left ( EOLO_{r, t, v, c} \cdot \textbf{ART}_{r, p, t, v} + \right ) + && \text{(end-of-life outputs of commodity)} \\ + &\begin{cases} + &= \text{if } c \notin C^w \\ + &\geq \text{if } c \in C^w \end{cases} \\ + &\sum_{t \in T^s, V, O} \mathbf{FIS}_{r, p, s, d, c, t, v, o} + && \text{(commodity stored)} \\ + &+ \sum_{t \notin T^s, V, O} + \frac{\mathbf{FO}_{r, p, s, d, c, t, v, o}}{EFF_{r, c, t, v, o}} + && \text{(commodity consumed by processes)} \\ + &+ SEG_{s,d} \cdot \sum_{t \in T^a, V, O} + \frac{\mathbf{FOA}_{r, p, c, t, v, o}}{EFF_{r, c, t, v, o}} + && \text{(commodity consumed by annual processes)} \\ + &+ \sum_{\text{reg} \neq r, t \in T^x, V, O} + \mathbf{FEX}_{r - \text{reg}, p, s, d, c, t, v, o} + && \text{(inter-regional exports of commodity)} \\ + &+ \sum_{I, t \in T^f, V} \mathbf{FLX}_{r, p, s, d, i, t, v, c} + && \text{(flex wastes of commodity)} \\ + &+ SEG_{s,d} \cdot \sum_{T, V} \left ( CON_{r, c, t, v} \cdot + \frac{\textbf{NCAP}_{r, t, v}}{LEN_p} \right ) + && \text{(consumed annually by construction inputs)} + \end{aligned} + + \qquad \forall \{r, p, s, d, c\} \in \Theta_{\text{CommodityBalance}} + + """ + + produced = 0 + consumed = 0 + + if (r, p, c) in model.commodity_down_stream_process: + # Only storage techs have a flow in variable + # For other techs, it would be redundant as in = out / eff + consumed += sum( + model.v_flow_in[r, p, s, d, c, s_t, s_v, s_o] + for s_t, s_v in model.commodity_down_stream_process[r, p, c] + if s_t in model.tech_storage + for s_o in model.process_outputs_by_input[r, p, s_t, s_v, c] + ) + + # Into flows + consumed += sum( + model.v_flow_out[r, p, s, d, c, s_t, s_v, s_o] + / get_variable_efficiency(model, r, p, s, d, c, s_t, s_v, s_o) + for s_t, s_v in model.commodity_down_stream_process[r, p, c] + if s_t not in model.tech_storage and s_t not in model.tech_annual + for s_o in model.process_outputs_by_input[r, p, s_t, s_v, c] + ) + + # Into annual flows + consumed += sum( + ( + value(model.demand_specific_distribution[r, p, s, d, s_o]) + if s_o in model.commodity_demand + else value(model.segment_fraction[s, d]) + ) + * model.v_flow_out_annual[r, p, c, s_t, s_v, s_o] + / get_variable_efficiency(model, r, p, s, d, c, s_t, s_v, s_o) + for s_t, s_v in model.commodity_down_stream_process[r, p, c] + if s_t in model.tech_annual + for s_o in model.process_outputs_by_input[r, p, s_t, s_v, c] + ) + + if (r, p, c) in model.capacity_consumption_techs: + # Consumed by building capacity + # Assume evenly distributed over a year + consumed += ( + value(model.segment_fraction[s, d]) + * sum( + value(model.construction_input[r, c, s_t, p]) * model.v_new_capacity[r, s_t, p] + for s_t in model.capacity_consumption_techs[r, p, c] + ) + / model.period_length[p] + ) + + if (r, p, c) in model.commodity_up_stream_process: + # From flows including output from storage + produced += sum( + model.v_flow_out[r, p, s, d, s_i, s_t, s_v, c] + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t not in model.tech_annual + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + + # From annual flows + produced += value(model.segment_fraction[s, d]) * sum( + model.v_flow_out_annual[r, p, s_i, s_t, s_v, c] + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t in model.tech_annual + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + + if c in model.commodity_flex: + # Wasted by flex flows + consumed += sum( + model.v_flex[r, p, s, d, s_i, s_t, s_v, c] + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t not in model.tech_annual and s_t in model.tech_flex + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + # Wasted by annual flex flows + consumed += value(model.segment_fraction[s, d]) * sum( + model.v_flex_annual[r, p, s_i, s_t, s_v, c] + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t in model.tech_annual and s_t in model.tech_flex + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + + if (r, p, c) in model.retirement_production_processes: + # Produced by retiring capacity + # Assume evenly distributed over a year + produced += value(model.segment_fraction[s, d]) * sum( + value(model.end_of_life_output[r, s_t, s_v, c]) + * model.v_annual_retirement[r, p, s_t, s_v] + for s_t, s_v in model.retirement_production_processes[r, p, c] + ) + + # export of commodity c from region r to other regions + if (r, p, c) in model.export_regions: + consumed += sum( + model.v_flow_out[r + '-' + reg, p, s, d, c, s_t, s_v, S_o] + / get_variable_efficiency( + model, cast('Region', r + '-' + reg), p, s, d, c, s_t, s_v, S_o + ) + for reg, s_t, s_v, S_o in model.export_regions[r, p, c] + if s_t not in model.tech_annual + ) + consumed += sum( + value(model.segment_fraction[s, d]) + * model.v_flow_out_annual[r + '-' + reg, p, c, s_t, s_v, S_o] + / get_variable_efficiency( + model, cast('Region', r + '-' + reg), p, s, d, c, s_t, s_v, S_o + ) + for reg, s_t, s_v, S_o in model.export_regions[r, p, c] + if s_t in model.tech_annual + ) + + # import of commodity c from other regions into region r + if (r, p, c) in model.import_regions: + produced += sum( + model.v_flow_out[reg + '-' + r, p, s, d, s_i, s_t, s_v, c] + for reg, s_t, s_v, s_i in model.import_regions[r, p, c] + if s_t not in model.tech_annual + ) + produced += sum( + value(model.segment_fraction[s, d]) + * model.v_flow_out_annual[reg + '-' + r, p, s_i, s_t, s_v, c] + for reg, s_t, s_v, s_i in model.import_regions[r, p, c] + if s_t in model.tech_annual + ) + + commodity_balance_constraint_error_check( + produced, + consumed, + r, + p, + s, + d, + c, + ) + + if c in model.commodity_waste: + expr = produced >= consumed + else: + expr = produced == consumed + + return expr + + +def annual_commodity_balance_constraint( + model: TemoaModel, r: Region, p: Period, c: Commodity +) -> ExprLike: + r""" + Similar to CommodityBalance_constraint but only balances the supply and demand of the commodity + at the period level, summing all flows over the period but allowing imbalances at the time slice + or seasonal level. Applies only to commodities in the :code:`commodity_annual` set. + """ + + produced = 0 + consumed = 0 + + if (r, p, c) in model.commodity_down_stream_process: + # Only storage techs have a flow in variable + # For other techs, it would be redundant as in = out / eff + consumed += sum( + model.v_flow_in[r, p, s_s, s_d, c, s_t, s_v, s_o] + for s_s in model.time_season + for s_d in model.time_of_day + for s_t, s_v in model.commodity_down_stream_process[r, p, c] + if s_t in model.tech_storage + for s_o in model.process_outputs_by_input[r, p, s_t, s_v, c] + ) + + consumed += sum( + model.v_flow_out[r, p, s_s, s_d, c, s_t, s_v, s_o] + / get_variable_efficiency(model, r, p, s_s, s_d, c, s_t, s_v, s_o) + for s_s in model.time_season + for s_d in model.time_of_day + for s_t, s_v in model.commodity_down_stream_process[r, p, c] + if s_t not in model.tech_storage and s_t not in model.tech_annual + for s_o in model.process_outputs_by_input[r, p, s_t, s_v, c] + ) + + consumed += sum( + model.v_flow_out_annual[r, p, c, s_t, s_v, s_o] + / value(model.efficiency[r, c, s_t, s_v, s_o]) + for s_t, s_v in model.commodity_down_stream_process[r, p, c] + if s_t in model.tech_annual + for s_o in model.process_outputs_by_input[r, p, s_t, s_v, c] + ) + + if (r, p, c) in model.capacity_consumption_techs: + # Consumed by building capacity + # Assume evenly distributed over a year + consumed += ( + sum( + value(model.construction_input[r, c, s_t, p]) * model.v_new_capacity[r, s_t, p] + for s_t in model.capacity_consumption_techs[r, p, c] + ) + / model.period_length[p] + ) + + if (r, p, c) in model.commodity_up_stream_process: + # Includes output from storage + produced += sum( + model.v_flow_out[r, p, s_s, s_d, s_i, s_t, s_v, c] + for s_s in model.time_season + for s_d in model.time_of_day + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t not in model.tech_annual + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + + produced += sum( + model.v_flow_out_annual[r, p, s_i, s_t, s_v, c] + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t in model.tech_annual + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + + if c in model.commodity_flex: + consumed += sum( + model.v_flex[r, p, s_s, s_d, s_i, s_t, s_v, c] + for s_s in model.time_season + for s_d in model.time_of_day + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t not in model.tech_annual and s_t in model.tech_flex + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + consumed += sum( + model.v_flex_annual[r, p, s_i, s_t, s_v, c] + for s_t, s_v in model.commodity_up_stream_process[r, p, c] + if s_t in model.tech_flex and s_t in model.tech_annual + for s_i in model.process_inputs_by_output[r, p, s_t, s_v, c] + ) + + if (r, p, c) in model.retirement_production_processes: + # Produced by retiring capacity + # Assume evenly distributed over a year + produced += sum( + value(model.end_of_life_output[r, s_t, s_v, c]) + * model.v_annual_retirement[r, p, s_t, s_v] + for s_t, s_v in model.retirement_production_processes[r, p, c] + ) + + # export of commodity c from region r to other regions + if (r, p, c) in model.export_regions: + consumed += sum( + model.v_flow_out[cast('Region', r + '-' + s_r), p, s_s, s_d, c, s_t, s_v, s_o] + / get_variable_efficiency( + model, cast('Region', r + '-' + s_r), p, s_s, s_d, c, s_t, s_v, s_o + ) + for s_s in model.time_season + for s_d in model.time_of_day + for s_r, s_t, s_v, s_o in model.export_regions[r, p, c] + if s_t not in model.tech_annual + ) + consumed += sum( + model.v_flow_out_annual[cast('Region', r + '-' + s_r), p, c, s_t, s_v, s_o] + / model.efficiency[cast('Region', r + '-' + s_r), c, s_t, s_v, s_o] + for s_r, s_t, s_v, s_o in model.export_regions[r, p, c] + if s_t in model.tech_annual + ) + + # import of commodity c from other regions into region r + if (r, p, c) in model.import_regions: + produced += sum( + model.v_flow_out[cast('Region', s_r + '-' + r), p, s_s, S_d, s_i, s_t, s_v, c] + for s_s in model.time_season + for S_d in model.time_of_day + for s_r, s_t, s_v, s_i in model.import_regions[r, p, c] + if s_t not in model.tech_annual + ) + produced += sum( + model.v_flow_out_annual[cast('Region', s_r + '-' + r), p, s_i, s_t, s_v, c] + for s_r, s_t, s_v, s_i in model.import_regions[r, p, c] + if s_t in model.tech_annual + ) + + annual_commodity_balance_constraint_error_check( + produced, + consumed, + r, + p, + c, + ) + + if c in model.commodity_waste: + expr = produced >= consumed + else: + expr = produced == consumed + + return expr + + +# ============================================================================ +# PRE-COMPUTATION FUNCTIONS +# ============================================================================ + + +def create_technology_and_commodity_sets(model: TemoaModel) -> None: + """ + Populates technology and commodity subset definitions based on their roles + (e.g., demand, flexible) identified from the efficiency parameter. + + This function iterates through the `efficiency` parameter to identify and + add technologies and commodities to special `Pyomo.Set` objects on the model. + + Populates: + - M.commodity_flex: Commodities that can be over-produced. + - M.tech_demand: Technologies that directly satisfy an end-use demand. + """ + logger.debug('Creating technology and commodity subsets.') + for _r, _i, t, _v, o in model.efficiency.sparse_keys(): + if t in model.tech_flex and o not in model.commodity_flex: + model.commodity_flex.add(o) + + if o in model.commodity_demand and t not in model.tech_demand: + model.tech_demand.add(t) + + +def create_demands(model: TemoaModel) -> None: + """ + Steps to create the demand distributions + 1. Use Demand keys to ensure that all demands in commodity_demand are used + 2. Find any r p demands without any DSD and fill those with segfrac + (flat demand distribution) + 3. Warn if any subset of time slices missing for any r p demand with DSD defined + 4. Validate that the per-demand distributions sum to 1. + """ + logger.debug('Started creating demand distributions in CreateDemands()') + + demand_specific_distribution = model.demand_specific_distribution + + # Warn if any demand commodities are going unused + used_dems = {dem for _r, _p, dem in model.demand.sparse_keys()} + unused_dems = sorted(model.commodity_demand.difference(used_dems)) + if unused_dems: + for dem in unused_dems: + msg = "Warning: Demand '{}' is unused\n" + logger.warning(msg.format(dem)) + + # Iterate over defined r p demands + used_rp_dems = {(r, p, dem) for r, p, dem in model.demand.sparse_keys()} + all_time_slices = set(cross_product(model.time_season, model.time_of_day)) + expected_key_length = len(all_time_slices) + dsd_keys_by_rpd: dict[Any, Any] = {} + for _r, _p, _s, _d, _dem in demand_specific_distribution.sparse_keys(): + dsd_keys_by_rpd.setdefault((_r, _p, _dem), []).append((_r, _p, _s, _d, _dem)) + for r, p, dem in used_rp_dems: + keys = dsd_keys_by_rpd.get((r, p, dem), []) + + if len(keys) > expected_key_length: + msg = ( + f'Too many demand_specific_distribution keys defined for {(r, p, dem)}. ' + f'Expected at most one per time slice ({expected_key_length}) ' + f'but found {len(keys)}. Likely code error.' + ) + logger.error(msg) + raise ValueError(msg) + + # If DSD is not defined for any r p demand, fill in with segfrac (flat demand) + if len(keys) == 0: + for s, d in all_time_slices: + demand_specific_distribution[r, p, s, d, dem] = value(model.segment_fraction[s, d]) + # Remaining checks would be caught by the validation of segment_fraction so skip + continue + + # If any subset of timeslices missing, inform the user. Not technically a problem + # (will just default to zero) but likely not intended behaviour. + if len(keys) != expected_key_length: + # this could be very slow but only calls when there's a problem + defined_slices = {(s, d) for _r, _p, s, d, _dem in keys} + missing = all_time_slices - defined_slices + logger.info( + 'Missing some time slices for Demand Specific Distribution %s: %s', + (r, p, dem), + missing, + ) + + # Verify that the distribution sums to 1 (within some tolerance) + total = sum(value(demand_specific_distribution[i]) for i in keys) + if abs(value(total) - 1.0) > 0.001: + # We can't explicitly test for "!= 1.0" because of incremental rounding + # errors associated with the specification of demand shares by time slice, + # but we check to make sure it is within the specified tolerance. + def get_str_padding(obj: Any) -> int: + return len(str(obj)) + + key_padding = max(map(get_str_padding, keys)) + + fmt = '%%-%ds = %%s' % key_padding # noqa: UP031 + # Works out to something like "%-25s = %s" + + items_list: list[tuple[Any, Any]] = sorted( + [(k, value(demand_specific_distribution[k])) for k in keys] + ) + items = '\n '.join(fmt % (str(k), v) for k, v in items_list) + msg = ( + 'The values of the demand_specific_distribution parameter do not ' + 'sum to 1 for {}. The demand_specific_distribution specifies how end-use ' + 'demands are distributed per time-slice (i.e., time_season, ' + 'time_of_day). Within each region, period, end-use demand, then, the distribution ' + 'must total to 1.\n\n Demand-specific distribution in error: ' + ' \n {}\n\tsum = {}' + ) + logger.error(msg.format((r, p, dem), items, total)) + raise ValueError(msg.format((r, p, dem), items, total)) + + logger.debug('Finished creating demand distributions') diff --git a/temoa/components/costs.py b/temoa/components/costs.py new file mode 100644 index 000000000..7814c2c4e --- /dev/null +++ b/temoa/components/costs.py @@ -0,0 +1,717 @@ +# temoa/components/costs.py +""" +Defines the cost-related components and the objective function of the Temoa model. + +This module is responsible for: +- Defining financial helper functions for Present Value, Future Value, and Annuity calculations. +- Pre-computing and populating cost parameters. +- Defining the rules for calculating all investment, fixed, variable, and emission-related + costs incurred over the model horizon. +- Defining the model's objective function (total_cost_rule) to minimize the + net present value of the total system cost. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from deprecated import deprecated +from pyomo.environ import quicksum, value + +if TYPE_CHECKING: + from pyomo.core import Expression, Var + from pyomo.core.base.component import ComponentData + + from temoa.core.model import TemoaModel + from temoa.types.core_types import Period, Region, Technology, Vintage + +from logging import getLogger + +logger = getLogger(name=__name__) + + +# ============================================================================ +# FINANCIAL HELPER FUNCTIONS +# ============================================================================ + + +def get_default_loan_rate(model: TemoaModel, *_: Any) -> float: + """get the default loan rate from the default_loan_rate param""" + return value(model.default_loan_rate) + + +def annuity_to_pv(rate: float, periods: float) -> float | Expression: + r""" + Multiplication factor to convert an annuity to net present value + + .. math:: + :label: annuity_to_pv + + \frac{P}{A}(i, N) = \frac{(1 + i)^N - 1}{i (1 + i)^N} + + where: + + - :math:`i` is the interest/discount rate + - :math:`N` is the number of periods + """ + if rate == 0: + return periods + return ((1 + rate) ** periods - 1) / (rate * (1 + rate) ** periods) + + +def pv_to_annuity(rate: float, periods: float) -> float | Expression: + r""" + Multiplication factor to convert net present value to an annuity + + .. math:: + :label: pv_to_annuity + + \frac{A}{P}(i, N) = \frac{i + (1 + i)^N}{(1 + i)^N - 1} + """ + if rate == 0: + return 1 / periods + return (rate * (1 + rate) ** periods) / ((1 + rate) ** periods - 1) + + +def fv_to_pv(rate: float, periods: float) -> float | Expression: + r""" + Multiplication factor to convert a future value to net present value + + .. math:: + :label: fv_to_pv + + \frac{P}{F}(i, N) = \frac{1}{(1 + i)^N} + """ + if rate == 0: + return 1 + return 1 / (1 + rate) ** periods + + +def get_loan_life(model: TemoaModel, r: Region, t: Technology, v: Vintage) -> int: + return value(model.lifetime_process[r, t, v]) + + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def cost_fixed_indices(model: TemoaModel) -> set[tuple[Region, Period, Technology, Vintage]]: + return model.active_capacity_rptv + + +def cost_variable_indices(model: TemoaModel) -> set[tuple[Region, Period, Technology, Vintage]]: + return model.active_activity_rptv + + +def lifetime_loan_process_indices(model: TemoaModel) -> set[tuple[Region, Technology, Vintage]]: + """ + Based on the efficiency parameter's indices, this function returns the set of + process indices that may be specified in the loan_lifetime_process parameter. + + Note: We include all efficiency vintages (not just >= min optimization period) + because in myopic mode, previously optimized vintages remain active in later + windows and their data must be accepted by the param's index set. + """ + return {(r, t, v) for r, i, t, v, o in model.efficiency.sparse_keys()} + + +# ============================================================================ +# COST CALCULATION FUNCTIONS +# These functions are the building blocks for the objective function. +# ============================================================================ + + +def loan_cost( + capacity: float | Var | ComponentData, + invest_cost: float, + loan_annualize: float, + lifetime_loan_process: float | int, + lifetime_process: float, + p_0: int, + p_e: int, + global_discount_rate: float, + vintage: int, +) -> float | Expression: + """ + function to calculate the loan cost. It can be used with fixed values to produce a hard number + or pyomo variables/params to make a pyomo Expression + :param capacity: The capacity to use to calculate cost + :param invest_cost: the cost/capacity + :param loan_annualize: parameter + :param lifetime_loan_process: lifetime of the loan + :param P_0: the year to discount the costs back to + :param P_e: the 'end year' or cutoff year for loan payments + :param GDR: Global Discount Rate + :param vintage: the base year of the loan + :return: fixed number or pyomo expression based on input types + """ + + # calculate the amortised loan repayment (annuity) + annuity = ( + capacity + * invest_cost # lump investment cost is capacity times cost_invest + * loan_annualize # calculate loan annuities for investment cost, if used + ) + + if not global_discount_rate: + # Undiscounted result + res = ( + annuity + * lifetime_loan_process # sum of loan payments over loan period + / lifetime_process # redistributed over lifetime of process + * min( + lifetime_process, p_e - vintage + ) # sum of redistributed costs for life of process (within planning horizon) + ) + else: + # Discounted result + res = ( + annuity + * annuity_to_pv( + global_discount_rate, int(lifetime_loan_process) + ) # PV of all loan payments, discounted to vintage year using GDR + * pv_to_annuity( + global_discount_rate, lifetime_process + ) # reamortised over lifetime of process using GDR + * annuity_to_pv( + global_discount_rate, min(lifetime_process, p_e - vintage) + ) # PV of all reamortised costs (within planning horizon) + * fv_to_pv( + global_discount_rate, vintage - p_0 + ) # finally, discounted from vintage year to P_0 + ) + return res + + +def loan_cost_survival_curve( + model: TemoaModel, + r: Region, + t: Technology, + v: Vintage, + capacity: float | Var | ComponentData, + invest_cost: float, + loan_annualize: float, + lifetime_loan_process: float | int, + p_0: Period, + p_e: Period, + global_discount_rate: float, +) -> float | Expression: + """ + function to calculate the loan cost only in the case of processes :math:`(r, t, v)` using + survival curves. It can be used with fixed values to produce a hard number or pyomo + variables/params to make a pyomo Expression + :param capacity: The capacity to use to calculate cost + :param invest_cost: the cost/capacity + :param loan_annualize: parameter + :param lifetime_loan_process: lifetime of the loan + :param P_0: the year to discount the costs back to + :param P_e: the 'end year' or cutoff year for loan payments + :param GDR: Global Discount Rate + :return: fixed number or pyomo expression based on input types + """ + + # calculate the amortised loan repayment (annuity) + annuity = ( + capacity + * invest_cost # lump investment cost is capacity times cost_invest + * loan_annualize # calculate loan annuities for investment cost, if used + ) + + if not global_discount_rate: + # Undiscounted result + res = ( + annuity + * lifetime_loan_process # sum of loan payments over loan period + / sum( # redistributed over survival curve within horizon + value(model.lifetime_survival_curve[r, p, t, v]) + for p in model.survival_curve_periods[r, t, v] + if v <= p + ) + * sum( # summed over survival curve within horizon + value(model.lifetime_survival_curve[r, p, t, v]) + for p in model.survival_curve_periods[r, t, v] + if v <= p < p_e + ) + ) + else: + # Discounted result + res = ( + annuity + * annuity_to_pv( + global_discount_rate, int(lifetime_loan_process) + ) # PV of all loan payments, discounted to vintage year using GDR + / sum( # redistributed over survival curve within horizon + value( + model.lifetime_survival_curve[r, p, t, v] + ) # reamortised over survival curve of process using GDR + * fv_to_pv( + global_discount_rate, p - v + 1 + ) # +1 because LSC is indexed to start of p not end of p + for p in model.survival_curve_periods[r, t, v] + if v <= p # this shouldnt be possible but play it safe + ) + * sum( # PV of all reamortised costs (within planning horizon) + value(model.lifetime_survival_curve[r, p, t, v]) + * fv_to_pv(global_discount_rate, p - v + 1) + for p in model.survival_curve_periods[r, t, v] + if v <= p < p_e + ) + * fv_to_pv( + global_discount_rate, v - p_0 + ) # finally, discounted from vintage year to P_0 + ) + return res + + +def fixed_or_variable_cost( + cap_or_flow: float | Var | ComponentData, + cost_factor: float, + cost_years: float | ComponentData, + global_discount_rate: float | None, + p_0: float, + p: int, +) -> float | Expression: + """ + Extraction of the fixed and var cost formulation. (It is same for both with either capacity or + flow as the driving variable.) + :param cap_or_flow: Capacity if fixed cost / flow out if variable + :param cost_factor: the cost (either fixed or variable) of the cap/flow variable + :param cost_years: for how many years is this cost incurred + :param GDR: discount rate or None + :param P_0: the period to discount this back to + :param p: the period under evaluation + :return: + """ + + annual_cost = cap_or_flow * cost_factor # annual fixed, variable, or emission cost + + if not global_discount_rate: + # Undiscounted result + return annual_cost * cost_years # annual cost times years paying that cost + else: + # Discounted result + return ( + annual_cost + * annuity_to_pv( + global_discount_rate, int(cost_years) + ) # PV of annual costs over this period, discounted to period p + * fv_to_pv(global_discount_rate, int(p - p_0)) # discounted from p to p_0 + ) + + +# ============================================================================ +# PYOMO EXPRESSION AND OBJECTIVE FUNCTION RULES +# ============================================================================ + + +def period_cost_rule(model: TemoaModel, p: int) -> float | Expression: + p_0 = min(model.time_optimize) + p_e = model.time_future.last() # End point of modeled horizon + global_discount_rate = value(model.global_discount_rate) + # MPL = M.ModelProcessLife + + if value(model.myopic_discounting_year) != 0: + p_0 = value(model.myopic_discounting_year) + + loan_costs = quicksum( + loan_cost( + model.v_new_capacity[r, S_t, S_v], + value(model.cost_invest[r, S_t, S_v]), + value(model.loan_annualize[r, S_t, S_v]), + value(model.loan_lifetime_process[r, S_t, S_v]), + value(model.lifetime_process[r, S_t, S_v]), + p_0, + p_e, + global_discount_rate, + vintage=S_v, + ) + for r, S_t, S_v in model.cost_invest.sparse_keys() + if S_v == p and not model.is_survival_curve_process[r, S_t, S_v] + ) + loan_costs += quicksum( + loan_cost_survival_curve( + model, + r, + S_t, + S_v, + model.v_new_capacity[r, S_t, S_v], + value(model.cost_invest[r, S_t, S_v]), + value(model.loan_annualize[r, S_t, S_v]), + value(model.loan_lifetime_process[r, S_t, S_v]), + p_0, + p_e, + global_discount_rate, + ) + for r, S_t, S_v in model.cost_invest.sparse_keys() + if S_v == p and model.is_survival_curve_process[r, S_t, S_v] + ) + + fixed_costs = quicksum( + fixed_or_variable_cost( + model.v_capacity[r, p, S_t, S_v], + value(model.cost_fixed[r, p, S_t, S_v]), + value(model.period_length[p]), + global_discount_rate, + p_0, + p=p, + ) + for r, S_p, S_t, S_v in model.cost_fixed.sparse_keys() + if S_p == p + ) + + variable_costs = quicksum( + fixed_or_variable_cost( + model.v_flow_out[r, p, s, d, S_i, S_t, S_v, S_o], + value(model.cost_variable[r, p, S_t, S_v]), + value(model.period_length[p]), + global_discount_rate, + p_0, + p, + ) + for r, S_p, S_t, S_v in model.cost_variable.sparse_keys() + if S_p == p and S_t not in model.tech_annual + for S_i in model.process_inputs[r, S_p, S_t, S_v] + for S_o in model.process_outputs_by_input[r, S_p, S_t, S_v, S_i] + for s in model.time_season + for d in model.time_of_day + ) + + variable_costs_annual = quicksum( + fixed_or_variable_cost( + model.v_flow_out_annual[r, p, S_i, S_t, S_v, S_o], + value(model.cost_variable[r, p, S_t, S_v]), + value(model.period_length[p]), + global_discount_rate, + p_0, + p, + ) + for r, S_p, S_t, S_v in model.cost_variable.sparse_keys() + if S_p == p and S_t in model.tech_annual + for S_i in model.process_inputs[r, S_p, S_t, S_v] + for S_o in model.process_outputs_by_input[r, S_p, S_t, S_v, S_i] + ) + + # The emissions costs occur over the five possible emission sources. + # to do any/all of them we need 2 baseline sets: The regular and annual sets + # of indices that are valid which is basically the filter of: + # EmissionActivty by cost_emission + # and to ensure that the techology is active we need to filter that + # result with processInput + + # ================= Emissions and Flex and Curtailment ================= + # Flex flows are deducted from v_flow_out, so it is NOT NEEDED to tax them again. (See + # commodity balance constr) + # Curtailment does not draw any inputs, so it seems logical that curtailed flows not be taxed + # either + # Earlier versions of this code had accounting for flex & curtailment that have been removed. + + base = [ + (r, p, e, i, t, v, o) + for (r, e, i, t, v, o) in model.emission_activity.sparse_keys() + if (r, p, e) in model.cost_emission # tightest filter first + and (r, p, t, v) in model.process_inputs + ] + + # then expand the base for the normal (season/tod) set and annual separately: + normal = [ + (r, p, e, s, d, i, t, v, o) + for (r, p, e, i, t, v, o) in base + for s in model.time_season + for d in model.time_of_day + if t not in model.tech_annual + ] + + annual = [(r, p, e, i, t, v, o) for (r, p, e, i, t, v, o) in base if t in model.tech_annual] + + # 1. variable emissions + var_emissions = quicksum( + fixed_or_variable_cost( + cap_or_flow=model.v_flow_out[r, p, s, d, i, t, v, o] + * value(model.emission_activity[r, e, i, t, v, o]), + cost_factor=value(model.cost_emission[r, p, e]), + cost_years=value(model.period_length[p]), + global_discount_rate=global_discount_rate, + p_0=p_0, + p=p, + ) + for (r, p, e, s, d, i, t, v, o) in normal + ) + + # 2. flex emissions -- removed (double counting, flex wastes are SUBTRACTIVE from flowout) + + # 3. curtailment emissions -- removed (curtailment is no-flow, for accounting only, so no + # emissions) + + # 4. annual emissions + var_annual_emissions = quicksum( + fixed_or_variable_cost( + cap_or_flow=model.v_flow_out_annual[r, p, i, t, v, o] + * value(model.emission_activity[r, e, i, t, v, o]), + cost_factor=value(model.cost_emission[r, p, e]), + cost_years=value(model.period_length[p]), + global_discount_rate=global_discount_rate, + p_0=p_0, + p=p, + ) + for (r, p, e, i, t, v, o) in annual + if t not in model.tech_flex + ) + + # 5. flex annual emissions -- removed (double counting, flex wastes are SUBTRACTIVE from + # flowout) + + # 6. embodied - treated as a fixed cost distributed over the deployment period (vintage) + embodied_emissions = quicksum( + fixed_or_variable_cost( + cap_or_flow=model.v_new_capacity[r, t, v] + * value(model.emission_embodied[r, e, t, v]) + / value(model.period_length[p]), + cost_factor=value(model.cost_emission[r, p, e]), + cost_years=value(model.period_length[v]), + # We assume the embodied emissions are emitted in the same year as the capacity is + # installed. + global_discount_rate=global_discount_rate, + p_0=p_0, + p=p, + ) + for (r, e, t, v) in model.emission_embodied.sparse_keys() + if (r, p, e) in model.cost_emission + if v == p + ) + + # 6. endoflife - treated as a fixed cost distributed over the retirement period + endoflife_emissions = quicksum( + fixed_or_variable_cost( + cap_or_flow=model.v_annual_retirement[r, p, t, v] + * value(model.emission_end_of_life[r, e, t, v]), + cost_factor=value(model.cost_emission[r, p, e]), + cost_years=value(model.period_length[p]), + # We assume the embodied emissions are emitted in the same year as the capacity is + # installed. + global_discount_rate=global_discount_rate, + p_0=p_0, + p=p, + ) + for (r, e, t, v) in model.emission_end_of_life.sparse_keys() + if (r, p, e) in model.cost_emission + if (r, t, v) in model.retirement_periods and p in model.retirement_periods[r, t, v] + ) + + period_emission_cost = ( + var_emissions + var_annual_emissions + embodied_emissions + endoflife_emissions + ) + + period_costs = ( + loan_costs + fixed_costs + variable_costs + variable_costs_annual + period_emission_cost + ) + return period_costs + + +# --------------------------------------------------------------- +# Define the Objective Function +# --------------------------------------------------------------- +def total_cost_rule(model: TemoaModel) -> Expression: + r""" + + Using the :code:`FlowOut` and :code:`Capacity` variables, the Temoa objective + function calculates the cost of energy supply, under the assumption that capital + costs are paid through loans. This implementation sums up all the costs incurred, + and is defined as :math:`C_{tot} = C_{loans} + C_{fixed} + C_{variable} + C_{emissions}`. + Each term on the right-hand side represents the cost incurred over the model + time horizon and discounted to the initial year in the horizon (:math:`{P}_0`). + The calculation of each term is given below. + + .. math:: + :label: obj_invest + + \begin{aligned} + C_{loans} =& \sum_{r, t, v \in \Theta_{CI}} CI_{r, t, v} \cdot \textbf{NCAP}_{r, t, v} + && \text{(overnight capital cost)} \\ + &\cdot \frac{A}{P}(i=\text{LR}_{r,t,v}, N=\text{LLP}_{r,t,v}) + && \text{(overnight cost amortised into annual loan payments)} \\ + &\cdot \frac{P}{A}(i=GDR, N=\text{LLP}_{r,t,v}) + && \text{(annual loan payments discounted to NPV in vintage year)} \\ + &\cdot \frac{A}{P}(i=GDR, N=\text{LTP}_{r,t,v}) + && \text{(NPV reamortised over lifetime of process using GDR)} \\ + &\cdot \frac{P}{A}(i=GDR, N=\min(\text{LTP}_{r,t,v}, P_e - v)) + && \text{(costs within planning horizon discounted to NPV in vintage year)} \\ + &\cdot \frac{P}{F}(i=GDR, N=v - P_0) + && \text{(NPV in vintage year discounted to base year } P_0\text{)} \\ + \end{aligned} + + Note that capital costs (:math:`{CI}_{r,t,v}`) are handled in several steps. + + 1. Each capital cost is amortized using the loan rate (i.e., technology-specific discount + rate) and loan period. + + 2. The annual stream of payments is converted into a lump sum using the global discount + rate and loan period. + + 3. The new lump sum is amortized at the global discount rate over the process lifetime. + + 4. Loan payments beyond the model time horizon are removed and the lump sum recalculated. + + 5. Finally, the lump sum is discounted back to the beginning of the horizon (:math:`P_0`) + using the global discount rate. + + Steps 3 and 4 serve to correctly balance the cost-benefit of technologies whose useful lives + would extend beyond the planning horizon. While an explicit salvage term is not included, this + approach properly captures the capital costs incurred within the model time horizon, accounting + for process-specific loan rates and periods. + + In the case of processes using survival curves, steps 3 and 4 do not reamortise costs uniformly + over the process lifetime. Instead, costs are amortised over the life of the process in + proportion to the survival fraction in each year. Note that, for this calculation, a survival + curve :math:`{LSC}_{r,y,t,v}` must be defined out to the year in which the surviving fraction + is zero, even if that extends beyond the planning horizon. It must also be defined for each + integer year between model periods and, if not, these gaps will be filled by linear + interpolation ahead of this calculation. + + .. math:: + :label: obj_invest_survival_curve + + \begin{aligned} + C_{loans,LSC} =& \sum_{r, t, v \in \Theta_{CI}} CI_{r, t, v} \cdot + \textbf{NCAP}_{r, t, v} + && \text{(overnight capital cost)} \\ + &\cdot \frac{A}{P}(i=\text{LR}_{r,t,v}, N=\text{LLP}_{r,t,v}) + && \text{(overnight cost amortised into annual loan payments)} \\ + &\cdot \frac{P}{A}(i=GDR, N=\text{LLP}_{r,t,v}) + && \text{(annual loan payments discounted to NPV in vintage year)} \\ + &\cdot \left( + \sum_{v < Y} LSC_{r,y,t,v} \cdot \frac{P}{F}(i=GDR, N=P - v + 1) + \right)^{-1} + && \text{(reamortised over survival curve (normalized)} \\ + &\cdot \sum_{v < Y < P_e} LSC_{r,y,t,v} \cdot \frac{P}{F}(i=GDR, N=P - v + 1) + && \text{(costs within planning horizon discounted to NPV in vintage year)} \\ + &\cdot \frac{P}{F}(i=GDR, N=v - P_0) + && \text{(NPV in vintage year discounted to base year } P_0 \text{)} + \end{aligned} + + Where :math:`Y` is the set of each integer year :math:`y` within the planning horizon. + + .. figure:: images/survival_curve_discounting.* + :align: center + :width: 100% + :figclass: align-center + :figwidth: 60% + + Steps 3 and 4 for processes with survival curves. + + Fixed, variable, and emissions annual cost factors are determined by: + + .. math:: + :label: annual_fixed_variable_emission + + \begin{aligned} + C_{fixed} =& \sum_{r, p, t, v \in \Theta_{CF}} CF_{r, p, t, v} \cdot + \textbf{CAP}_{r, p, t, v} + && \text{(annual fixed cost)} \\ + \\ + C_{variable} =& \sum_{r, p, t \notin T^a, v \in \Theta_{CV}} + CV_{r, p, t, v} \cdot \sum_{S, D, I, O} \mathbf{FO}_{r, p, s, d, i, t, v, o} + && \text{(annual variable cost on flow)} \\ + & \text{where } t \notin T^a \\ + &+\\ + & \sum_{r, p, t \in T^a,\ v \in \Theta_{VC}} CV_{r, p, t, v} + \cdot \sum_{I, O} \mathbf{FOA}_{r, p, i, t, v, o} + && \text{(annual variable cost on annual flows)} \\ + & \text{where } t \in T^a \\ + &+\\ + C_{emissions} =& \sum_{r, p, e \in \Theta_{CE}} CE_{r, p, e} + \cdot EAC_{r, e, i, t, v, o} \cdot \sum_{S, D, I, O} + \mathbf{FO}_{r, p, s, d, i, t, v, o} + && \text{(annual emission cost on flow)} \\ + & \text{where } t \notin T^a \\ + &+\\ + & \sum_{r, p, e \in \Theta_{CE}} CE_{r, p, e} + \cdot EAC_{r, e, i, t, v, o} \cdot \sum_{I, O} \mathbf{FOA}_{r, p, i, t, v, o} + && \text{(annual emission cost on annual flows)} \\ + & \text{where } t \in T^a \\ + &+\\ + & \sum_{r, p, e \in \Theta_{CE}} \frac{CE_{r, p, e} + \cdot EE_{r, e, t, v} \cdot \mathbf{NCAP}_{r, t, v=p}}{{LEN}_p} + && \text{(annual embodied emission cost)} \\ + &+\\ + & \sum_{r, p, e \in \Theta_{CE}, v} CE_{r, p, e} + \cdot EEOL_{r, e, t, v} \cdot \mathbf{ART}_{r, p, t, v} + && \text{(annual retirement/end of life emission cost)} \\ + \end{aligned} + + Each of these costs are then discounted within each period and then to the base year: + + .. math:: + :label: obj_fixed_variable_emission + + \begin{aligned} + C_{fix,var,emiss} =& C_{fixed} + C_{variable} + C_{emissions} \\ + &\cdot \frac{P}{A}(i=GDR,\ N=LEN_p) + && \text{(for each year in period } p \text{ discounted to NPV in } p \text{)}\\ + &\cdot \frac{P}{F}(i=GDR,\ N=p - P_0) + && \text{(discounted from period } p \text{ to NPV in base year } P_0 \text{)} + \end{aligned} + """ + + return quicksum(period_cost_rule(model, p) for p in model.time_optimize) + + +# ============================================================================ +# PRE-COMPUTATION AND PARAMETER INITIALIZATION +# ============================================================================ + + +@deprecated(reason='vintage defaults are no longer available, so this should not be needed') +def create_costs(model: TemoaModel) -> None: + """ + Steps to creating fixed and variable costs: + 1. Collect all possible cost indices (cost_fixed, cost_variable) + 2. Find the ones _not_ specified in cost_fixed and cost_variable + 3. Set them, based on Cost*VintageDefault + """ + logger.debug('Started Creating Fixed and Variable costs in CreateCosts()') + cost_fixed = model.cost_fixed + cost_variable = model.cost_variable + + # Step 1 + fixed_indices = set(model.cost_fixed_rptv) + var_indices = set(model.cost_variable_rptv) + + # Step 2 + unspecified_fixed_prices = fixed_indices.difference(cost_fixed.sparse_keys()) + unspecified_var_prices = var_indices.difference(cost_variable.sparse_keys()) + + # Step 3 + + # Some hackery: We futz with _constructed because Pyomo thinks that this + # Param is already constructed. However, in our view, it is not yet, + # because we're specifically targeting values that have not yet been + # constructed, that we know are valid, and that we will need. + + if unspecified_fixed_prices: + # CF._constructed = False + for r, p, t, v in unspecified_fixed_prices: + if (r, t, v) in model.cost_fixed_vintage_default: + cost_fixed[r, p, t, v] = model.cost_fixed_vintage_default[ + r, t, v + ] # CF._constructed = True + + if unspecified_var_prices: + # CV._constructed = False + for r, p, t, v in unspecified_var_prices: + if (r, t, v) in model.cost_variable_vintage_default: + cost_variable[r, p, t, v] = model.cost_variable_vintage_default[r, t, v] + # CV._constructed = True + logger.debug('Created M.cost_fixed with size: %d', len(value(model.cost_fixed))) + logger.debug('Created M.cost_variable with size: %d', len(value(model.cost_variable))) + logger.debug('Finished creating Fixed and Variable costs') + + +def param_loan_annualize_rule( + model: TemoaModel, r: Region, t: Technology, v: Vintage +) -> float | Expression: + """Rule to calculate the annualized loan rate from the loan rate and lifetime.""" + dr = value(model.loan_rate[r, t, v]) + lln = value(model.loan_lifetime_process[r, t, v]) + annualized_rate = pv_to_annuity(dr, lln) + return annualized_rate diff --git a/temoa/components/emissions.py b/temoa/components/emissions.py new file mode 100644 index 000000000..ff834788e --- /dev/null +++ b/temoa/components/emissions.py @@ -0,0 +1,153 @@ +# temoa/components/emissions.py +""" +Defines the components of the Temoa model related to emissions accounting. + +This module is responsible for: +- Defining index sets for emission-related parameters and constraints. +- Defining the constraint rule for 'linked technologies', a special case where + an emission commodity (e.g., captured CO2) is also treated as a physical + input to a downstream process (e.g., synthetic fuel production). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pyomo.core import quicksum +from pyomo.environ import value + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types import ExprLike + from temoa.types.core_types import ( + Commodity, + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, + ) + + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def emission_activity_indices( + model: TemoaModel, +) -> set[tuple[Region, Commodity, Commodity, Technology, Vintage, Commodity]]: + return { + (r, e, i, t, v, o) + for r, i, t, v, o in model.efficiency.sparse_keys() + for e in model.commodity_emissions + if r in model.regions # omit any exchange/groups + } + + +def linked_tech_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage, Commodity]]: + return { + (r, p, s, d, t, v, e) + for r, t, e in model.linked_techs.sparse_keys() + for p in model.time_optimize + if (r, p, t) in model.process_vintages + for v in model.process_vintages[r, p, t] + if model.active_activity_rptv and (r, p, t, v) in model.active_activity_rptv + for s in model.time_season + for d in model.time_of_day + } + + +# ============================================================================ +# PYOMO CONSTRAINT RULES +# ============================================================================ + + +def linked_emissions_tech_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, + e: Commodity, +) -> ExprLike: + r""" + This constraint can be used for carbon capture technologies that produce + CO2 as an emissions commodity, but the CO2 also serves as a physical + input commodity to a downstream process, such as synthetic fuel production. + To accomplish this, a dummy technology is linked to the CO2-producing + technology, converting the emissions activity into a physical commodity + amount as follows: + + .. math:: + :label: linked_emissions_tech + + - \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} \cdot EAC_{r, e, i, t, v, o} + = \sum_{I, O} \textbf{FO}_{r, p, s, d, i, \hat{t}, v, o} + + \forall \{r, p, s, d, t, v, e\} \in \Theta_{\text{linked\_techs}} + + where :math:`\hat{t} = LT_{r,t,e}` is the linked technology for primary technology + :math:`t` and emissions commodity :math:`e`. For annual technologies + (:math:`t \in T^a` or :math:`\hat{t} \in T^a`), the corresponding + :math:`\textbf{FOA}` variable scaled by :math:`DSD` (for end use demand techs) + or :math:`SEG` (for all other annual techs) is used instead. + + The relationship between the primary and linked technologies is given + in the :code:`linked_techs` table. It is implicit that + the primary region corresponds to the linked technology as well. The lifetimes + of the primary and linked technologies should be specified and identical. + """ + + if t in model.tech_annual: + primary_flow = quicksum( + ( + value(model.demand_specific_distribution[r, p, s, d, S_o]) + if S_o in model.commodity_demand + else value(model.segment_fraction[s, d]) + ) + * model.v_flow_out_annual[r, p, S_i, t, v, S_o] + * value(model.emission_activity[r, e, S_i, t, v, S_o]) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + else: + primary_flow = quicksum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + * value(model.emission_activity[r, e, S_i, t, v, S_o]) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + linked_t = value(model.linked_techs[r, t, e]) + + # linked_flow = sum( + # M.v_flow_out[r, p, s, d, S_i, linked_t, v, S_o] + # for S_i in M.processInputs[r, p, linked_t, v] + # for S_o in M.process_outputs_by_input[r, p, linked_t, v, S_i] + # ) + + if linked_t in model.tech_annual: + linked_flow = quicksum( + ( + value(model.demand_specific_distribution[r, p, s, d, S_o]) + if S_o in model.commodity_demand + else value(model.segment_fraction[s, d]) + ) + * model.v_flow_out_annual[r, p, S_i, linked_t, v, S_o] + for S_i in model.process_inputs[r, p, linked_t, v] + for S_o in model.process_outputs_by_input[r, p, linked_t, v, S_i] + ) + else: + linked_flow = quicksum( + model.v_flow_out[r, p, s, d, S_i, linked_t, v, S_o] + for S_i in model.process_inputs[r, p, linked_t, v] + for S_o in model.process_outputs_by_input[r, p, linked_t, v, S_i] + ) + + return -primary_flow == linked_flow diff --git a/temoa/components/flows.py b/temoa/components/flows.py new file mode 100644 index 000000000..656848a6a --- /dev/null +++ b/temoa/components/flows.py @@ -0,0 +1,209 @@ +# temoa/components/flows.py +""" +Defines the flow-related components of the Temoa model. + +This module is responsible for: +- Pre-computing the sparse index sets for all types of commodity flows + (standard, annual, flexible, storage, curtailment). +- Defining the Pyomo index set functions used to construct the flow-related + decision variables (v_flow_out, v_flow_in, v_flex, etc.). +""" + +from __future__ import annotations + +from logging import getLogger +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types import ( + ActiveFlexAnnualSet, + ActiveFlowAnnualSet, + ) + from temoa.types.core_types import ( + Commodity, + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, + ) + + +logger = getLogger(__name__) + + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def flow_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: + return model.active_flow_rpsditvo + + +def flow_variable_annual_indices( + model: TemoaModel, +) -> ActiveFlowAnnualSet: + return model.active_flow_rpitvo + + +def flex_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: + return model.active_flex_rpsditvo + + +def flex_variable_annual_indices( + model: TemoaModel, +) -> ActiveFlexAnnualSet: + return model.active_flex_rpitvo + + +def flow_in_storage_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: + return model.active_flow_in_storage_rpsditvo + + +def curtailment_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]]: + return model.active_curtailment_rpsditvo + + +# ============================================================================ +# PRE-COMPUTATION FUNCTION +# ============================================================================ + + +def create_commodity_balance_and_flow_sets(model: TemoaModel) -> None: + """ + Creates aggregated sets for commodity balances and detailed index sets for active flows. + + This function is a critical part of the model setup, responsible for + creating the large, sparse index sets that define where decision variables + for flows, capacity, and storage levels will be created. + + Populates: + - model.commodity_balance_rpc: The master set of (r, p, c) for balance constraints. + - model.active_flow_rpsditvo: Indices for time-sliced flows (v_flow_out). + - model.active_flow_rpitvo: Indices for annual flows (v_flow_out_annual). + - model.active_flex_rpsditvo: Indices for flexible time-sliced flows (v_flex). + - model.active_flex_rpitvo: Indices for flexible annual flows (v_flex_annual). + - model.active_flow_in_storage_rpsditvo: Indices for flows into storage (v_flow_in). + - model.active_curtailment_rpsditvo: Indices for curtailed generation (v_curtailment). + - model.active_activity_rptv: Master set of active (r, p, t, v) processes. + - model.storage_level_indices_rpsdtv: Indices for storage state variables (v_storage_level). + - model.seasonal_storage_level_indices_rpstv: Indices for seasonal storage levels. + """ + logger.debug('Creating commodity balance and active flow index sets.') + # 1. Commodity Balance + commodity_upstream_rpo = { + (r, p, o) + for r, p, o in ( + model.commodity_up_stream_process + | model.retirement_production_processes + | model.import_regions + ) + if o not in model.commodity_sink # only balanced if input to another process (caught below) + } + commodity_downstream_rpi = { + (r, p, i) + for r, p, i in ( + model.commodity_down_stream_process + | model.capacity_consumption_techs + | model.export_regions + ) + if i not in model.commodity_source # sources are never balanced (infinite source) + } + model.commodity_balance_rpc = commodity_upstream_rpo.union(commodity_downstream_rpi) + + # 2. Active Flow Indices (Time-Sliced) + model.active_flow_rpsditvo = { + (r, p, s, d, i, t, v, o) + for r, p, t in model.process_vintages + if t not in model.tech_annual + for v in model.process_vintages[r, p, t] + for i in model.process_inputs.get((r, p, t, v), set()) + for o in model.process_outputs_by_input.get((r, p, t, v, i), set()) + for s in model.time_season + for d in model.time_of_day + } + + # 3. Active Flow Indices (Annual) + model.active_flow_rpitvo = { + (r, p, i, t, v, o) + for r, p, t in model.process_vintages + for v in model.process_vintages[r, p, t] + for i in model.process_inputs.get((r, p, t, v), set()) + for o in model.process_outputs_by_input.get((r, p, t, v, i), set()) + if t in model.tech_annual or (t in model.tech_demand and o in model.commodity_demand) + } + + # 4. Active Flexible Technology Flow Indices + model.active_flex_rpsditvo = { + (r, p, s, d, i, t, v, o) + for r, p, t in model.process_vintages + if (t not in model.tech_annual) and (t in model.tech_flex) + for v in model.process_vintages[r, p, t] + for i in model.process_inputs.get((r, p, t, v), set()) + for o in model.process_outputs_by_input.get((r, p, t, v, i), set()) + for s in model.time_season + for d in model.time_of_day + } + + model.active_flex_rpitvo = { + (r, p, i, t, v, o) + for r, p, t in model.process_vintages + if (t in model.tech_annual) and (t in model.tech_flex) + for v in model.process_vintages[r, p, t] + for i in model.process_inputs.get((r, p, t, v), set()) + for o in model.process_outputs_by_input.get((r, p, t, v, i), set()) + } + + # 5. Active Storage and Curtailment Indices + model.active_flow_in_storage_rpsditvo = { + (r, p, s, d, i, t, v, o) + for r, p, t in model.storage_vintages + for v in model.storage_vintages[r, p, t] + for i in model.process_inputs.get((r, p, t, v), set()) + for o in model.process_outputs_by_input.get((r, p, t, v, i), set()) + for s in model.time_season + for d in model.time_of_day + } + + model.active_curtailment_rpsditvo = { + (r, p, s, d, i, t, v, o) + for r, p, t in model.curtailment_vintages + for v in model.curtailment_vintages[r, p, t] + for i in model.process_inputs.get((r, p, t, v), set()) + for o in model.process_outputs_by_input.get((r, p, t, v, i), set()) + for s in model.time_season + for d in model.time_of_day + } + + # 6. Active Technology and Capacity Indices + model.active_activity_rptv = { + (r, p, t, v) for r, p, t in model.process_vintages for v in model.process_vintages[r, p, t] + } + + # 7. Storage Level Indices + model.storage_level_indices_rpsdtv = { + (r, p, s, d, t, v) + for r, p, t in model.storage_vintages + for v in model.storage_vintages[r, p, t] + for s in model.time_season + for d in model.time_of_day + } + + model.seasonal_storage_level_indices_rpstv = { + (r, p, s_seq, t, v) + for r, p, t in model.storage_vintages + if t in model.tech_seasonal_storage + for v in model.storage_vintages[r, p, t] + for s_seq in model.sequential_to_season + } diff --git a/temoa/components/geography.py b/temoa/components/geography.py new file mode 100644 index 000000000..45e8f788e --- /dev/null +++ b/temoa/components/geography.py @@ -0,0 +1,156 @@ +# temoa/components/geography.py +""" +Defines the geography-related components of the Temoa model. + +This module is responsible for handling all logic related to multi-region models, +including: +- Pre-computing the data structures for inter-regional commodity transfers + (imports and exports). +- Defining the sets of valid regions and regional groupings. +- Defining constraints that govern inter-regional capacity and flows. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import TYPE_CHECKING, cast + +from deprecated import deprecated +from pyomo.environ import value + +if TYPE_CHECKING: + from collections.abc import Iterable + + from temoa.core.model import TemoaModel + from temoa.types import ExprLike, Period, Region, Technology, Vintage + +# Import type annotations + +logger = getLogger(name=__name__) + +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ + + +def gather_group_regions(model: TemoaModel, region: Region) -> Iterable[Region]: + regions: list[Region] + if region == 'global': + regions = list(model.regions) + elif '+' in region: + regions = [cast('Region', r) for r in region.split('+')] + else: + regions = [region] + return regions + + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def create_regional_indices(model: TemoaModel) -> list[Region]: + """Create the set of all regions and all region-region pairs""" + regional_indices: set[Region] = set() + for r_i in model.regions: + if '-' in r_i: + logger.error("Individual region names can not have '-' in their names: %s", str(r_i)) + raise ValueError("Individual region names can not have '-' in their names: " + str(r_i)) + for r_j in model.regions: + if r_i == r_j: + regional_indices.add(r_i) + else: + regional_indices.add(cast('Region', r_i + '-' + r_j)) + # dev note: Sorting these passed them to pyomo in an ordered container and prevents warnings + return sorted(regional_indices) + + +@deprecated('No longer used. See the region_group_check in validators.py') +def regional_global_initialized_indices(model: TemoaModel) -> set[Region]: + from itertools import permutations + + indices: set[Region] = set() + for n in range(1, len(model.regions) + 1): + regional_perms = permutations(model.regions, n) + for i in regional_perms: + indices.add(cast('Region', '+'.join(i))) + indices.add(cast('Region', 'global')) + indices = indices.union(model.regional_indices) + + return indices + + +# ============================================================================ +# PYOMO CONSTRAINT RULES +# ============================================================================ + + +def regional_exchange_capacity_constraint( + model: TemoaModel, r_e: Region, r_i: Region, p: Period, t: Technology, v: Vintage +) -> ExprLike: + r""" + + This constraint ensures that the process (t,v) connecting regions + r_e and r_i is handled by one capacity variables. + + .. math:: + :label: RegionalExchangeCapacity + + \textbf{CAP}_{r_e,t,v} + = + \textbf{CAP}_{r_i,t,v} + + \\ + \forall \{r_e, r_i, t, v\} \in \Theta_{\text{RegionalExchangeCapacity}} + """ + + expr = model.v_capacity[r_e + '-' + r_i, p, t, v] == model.v_capacity[r_i + '-' + r_e, p, t, v] + + return expr + + +# ============================================================================ +# PRE-COMPUTATION FUNCTION +# ============================================================================ + + +def create_geography_sets(model: TemoaModel) -> None: + """ + Populates dictionaries related to inter-regional commodity exchange. + + This function iterates through exchange technologies (identified by a '-' in + their region name) and populates the `M.export_regions` and `M.import_regions` + dictionaries. These are used later in the commodity balance constraints. + + Populates: + - M.export_regions: dict mapping (region_from, p, commodity) to a set + of (region_to, t, v, o) tuples. + - M.import_regions: dict mapping (region_to, p, commodity) to a set + of (region_from, t, v, i) tuples. + """ + logger.debug('Creating geography-related sets for exchange technologies.') + for r, i, t, v, o in model.efficiency.sparse_keys(): + if t not in model.tech_exchange: + continue + + if '-' not in r: + msg = ( + f"Exchange technology {t} has an invalid region '{r}'. Must be " + "'region_from-region_to'." + ) + logger.error(msg) + raise ValueError(msg) + + region_from_str, region_to_str = r.split('-', 1) + region_from = cast('Region', region_from_str) + region_to = cast('Region', region_to_str) + + lifetime: float = value(model.lifetime_process[r, t, v]) + for p in model.time_optimize: + if p >= v and v + lifetime > p: + model.export_regions.setdefault((region_from, p, i), set()).add( + (region_to, t, v, o) + ) + model.import_regions.setdefault((region_to, p, o), set()).add( + (region_from, t, v, i) + ) diff --git a/temoa/components/limits.py b/temoa/components/limits.py new file mode 100644 index 000000000..f7f5d5d9c --- /dev/null +++ b/temoa/components/limits.py @@ -0,0 +1,1492 @@ +# temoa/components/limits.py +""" +Defines the various limit-related components of the Temoa model. + +This module contains a wide variety of constraints that enforce +limits on the energy system. These include, but are not limited to: +- Input/Output splits for technologies like refineries. +- Growth and degrowth rates for capacity deployment. +- Shares of capacity or activity for technology groups (e.g., for RPS policies). +- Absolute limits on capacity, new investment, or emissions. +""" + +from __future__ import annotations + +import sys +from logging import getLogger +from typing import TYPE_CHECKING, cast + +from pyomo.environ import Constraint, quicksum, value + +import temoa.components.geography as geography +import temoa.components.technology as technology +from temoa.components.utils import Operator, get_variable_efficiency, operator_expression + +if TYPE_CHECKING: + from pyomo.core import Expression + + from temoa.core.model import TemoaModel + from temoa.types import ExprLike, Period, Region, Technology, Vintage + from temoa.types.core_types import Commodity, Season, TimeOfDay + +logger = getLogger(__name__) + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def limit_tech_input_split_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, str]]: + indices = { + (r, p, s, d, i, t, v, op) + for r, p, i, t, op in model.input_split_vintages + if t not in model.tech_annual + for v in model.input_split_vintages[r, p, i, t, op] + for s in model.time_season + for d in model.time_of_day + } + ann_indices = { + (r, p, i, t, op) for r, p, i, t, op in model.input_split_vintages if t in model.tech_annual + } + if len(ann_indices) > 0: + msg = ( + 'Warning: Annual technologies included in limit_tech_input_split table. ' + 'Use limit_tech_input_split_annual table instead or these constraints will ' + 'be ignored: {}' + ) + logger.warning(msg.format(ann_indices)) + + return indices + + +def limit_tech_input_split_annual_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Commodity, Technology, Vintage, str]]: + return { + (r, p, i, t, v, op) + for r, p, i, t, op in model.input_split_annual_vintages + if t in model.tech_annual + for v in model.input_split_annual_vintages[r, p, i, t, op] + } + + +def limit_tech_input_split_average_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Commodity, Technology, Vintage, str]]: + return { + (r, p, i, t, v, op) + for r, p, i, t, op in model.input_split_annual_vintages + if t not in model.tech_annual + for v in model.input_split_annual_vintages[r, p, i, t, op] + } + + +def limit_tech_output_split_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage, Commodity, str]]: + indices = { + (r, p, s, d, t, v, o, op) + for r, p, t, o, op in model.output_split_vintages + if t not in model.tech_annual + for v in model.output_split_vintages[r, p, t, o, op] + for s in model.time_season + for d in model.time_of_day + } + ann_indices = { + (r, p, t, o, op) for r, p, t, o, op in model.output_split_vintages if t in model.tech_annual + } + if len(ann_indices) > 0: + msg = ( + 'Warning: Annual technologies included in limit_tech_output_split table. ' + 'Use limit_tech_output_split_annual table instead or these constraints will ' + 'be ignored: {}' + ) + logger.warning(msg.format(ann_indices)) + + return indices + + +def limit_tech_output_split_annual_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage, Commodity, str]]: + return { + (r, p, t, v, o, op) + for r, p, t, o, op in model.output_split_annual_vintages + if t in model.tech_annual + for v in model.output_split_annual_vintages[r, p, t, o, op] + } + + +def limit_tech_output_split_average_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage, Commodity, str]]: + return { + (r, p, t, v, o, op) + for r, p, t, o, op in model.output_split_annual_vintages + if t not in model.tech_annual + for v in model.output_split_annual_vintages[r, p, t, o, op] + } + + +def limit_growth_capacity_indices(model: TemoaModel) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_growth_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_degrowth_capacity_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_degrowth_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_growth_new_capacity_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_growth_new_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_degrowth_new_capacity_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_degrowth_new_capacity.sparse_keys() + for p in model.time_optimize + } + + +def limit_growth_new_capacity_delta_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_growth_new_capacity_delta.sparse_keys() + for p in model.time_optimize + } + + +def limit_degrowth_new_capacity_delta_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, str]]: + return { + (r, p, t, op) + for r, t, op in model.limit_degrowth_new_capacity_delta.sparse_keys() + for p in model.time_optimize + } + + +def limit_seasonal_capacity_factor_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, Technology, str]]: + """Expand the period-free param set to include all time_optimize periods.""" + return { + (r, p, s, t, op) + for r, s, t, op in model.limit_seasonal_capacity_factor_constraint_rst + for p in model.time_optimize + } + + +def limit_annual_capacity_factor_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage, Commodity, str]]: + return { + (r, p, t, v, o, op) + for r, t, v, o, op in model.limit_annual_capacity_factor_constraint_rtvo + for _r in geography.gather_group_regions(model, r) + for _t in technology.gather_group_techs(model, t) + for p in model.time_optimize + if o in model.process_outputs.get((_r, p, _t, v), []) + } + + +# ============================================================================ +# PYOMO CONSTRAINT RULES +# ============================================================================ + + +# @deprecated('Deprecated. Use limit_activityGroupShare instead') # doesn't play well with pyomo +def renewable_portfolio_standard_constraint( + model: TemoaModel, r: Region, p: Period, g: str +) -> ExprLike: + r""" + Allows users to specify the share of electricity generation in a region + coming from RPS-eligible technologies. + """ + # devnote: this formulation leans on the reserve set, which is not necessarily + # the super set we want. We can also generalise this to all groups and so + # it has been deprecated in favour of the limit_activityGroupShare constraint. + + inp = quicksum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for t in model.tech_group_members[g] + for (_t, v) in model.process_reserve_periods.get((r, p), []) + if _t == t + for s in model.time_season + for d in model.time_of_day + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + total_inp = quicksum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for (t, v) in model.process_reserve_periods[r, p] + for s in model.time_season + for d in model.time_of_day + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + expr = inp >= (value(model.renewable_portfolio_standard[r, p, g]) * total_inp) + return expr + + +def limit_resource_constraint(model: TemoaModel, r: Region, t: Technology, op: str) -> ExprLike: + r""" + + The limit_resource constraint sets a limit on the available resource of a + given technology across all model time periods. Note that the indices for these + constraints are region and tech. + + .. math:: + :label: limit_resource + + \sum_{P,S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t \notin T^a, v, o} + + +\sum_{P,I,V,O} \textbf{FOA}_{r, p, i, t \in T^a, v, o} + + \quad \le, \ge, \text{or} = \quad LS_{r, t} + + \forall \{r, t\} \in \Theta_{\text{limit\_resource}}""" + # dev note: this constraint is a misnomer. It is actually a "global activity constraint on a + # tech" regardless of whatever "resources" are consumed. + # dev note: this would generally be applied to a "dummy import" technology to restrict + # something like oil/mineral extraction across all model periods. Looks fine to me. + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + activity = quicksum( + model.v_flow_out_annual[_r, p, S_i, _t, S_v, S_o] + for _t in techs + if _t in model.tech_annual + for p in model.time_optimize + for _r in regions + if (_r, p, _t) in model.process_vintages + for S_v in model.process_vintages[_r, p, _t] + for S_i in model.process_inputs[_r, p, _t, S_v] + for S_o in model.process_outputs_by_input[_r, p, _t, S_v, S_i] + ) + activity += quicksum( + model.v_flow_out[_r, p, s, d, S_i, _t, S_v, S_o] + for _t in techs + if _t not in model.tech_annual + for p in model.time_optimize + for _r in regions + if (_r, p, _t) in model.process_vintages + for S_v in model.process_vintages[_r, p, _t] + for S_i in model.process_inputs[_r, p, _t, S_v] + for S_o in model.process_outputs_by_input[_r, p, _t, S_v, S_i] + for s in model.time_season + for d in model.time_of_day + ) + + resource_lim = value(model.limit_resource[r, t, op]) + expr = operator_expression(activity, Operator(op), resource_lim) + return expr + + +def limit_activity_share_constraint( + model: TemoaModel, r: Region, p: Period, g1: Technology, g2: Technology, op: str +) -> ExprLike: + r""" + Limits the activity of a given technology or group as a fraction of another + technology or group, summed over a period. This can be used to set, for example, + a renewable portfolio scheme constraint. + + .. math:: + :label: Limit Activity Share + + \sum_{R_g \subseteq R,\ S,\ D,\ I,\ (T^{g_1} \setminus T^a) \subseteq T,\ V,\ O} + \mathbf{FO}_{r,p,s,d,i,t,v,o} + + \sum_{R_g \subseteq R,\ I,\ (T^{g_1} \cap T^a) \subseteq T,\ V,\ O} + \mathbf{FOA}_{r,p,i,t,v,o} + \\ + \quad \le, \ge, \text{or} = \quad + \\ + LAS_{r,p,g_1,g_2} \cdot + \sum_{R_g \subseteq R,\ S,\ D,\ I,\ (T^{g_2} \setminus T^a) \subseteq T,\ V,\ O} + \mathbf{FO}_{r,p,s,d,i,t,v,o} + + \sum_{R_g \subseteq R,\ I,\ (T^{g_2} \cap T^a) \subseteq T,\ V,\ O} + \mathbf{FOA}_{r,p,i,t,v,o} + + \qquad \forall \{r, p, g_1, g_2\} \in \Theta_{\text{limit\_activity\_share}} + """ + + regions = geography.gather_group_regions(model, r) + + sub_group = technology.gather_group_techs(model, g1) + sub_activity = quicksum( + model.v_flow_out[_r, p, s, d, S_i, S_t, S_v, S_o] + for S_t in sub_group + if S_t not in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, S_t), []) + for S_i in model.process_inputs[_r, p, S_t, S_v] + for S_o in model.process_outputs_by_input[_r, p, S_t, S_v, S_i] + for s in model.time_season + for d in model.time_of_day + ) + sub_activity += quicksum( + model.v_flow_out_annual[_r, p, S_i, S_t, S_v, S_o] + for S_t in sub_group + if S_t in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, S_t), []) + for S_i in model.process_inputs[_r, p, S_t, S_v] + for S_o in model.process_outputs_by_input[_r, p, S_t, S_v, S_i] + ) + + super_group = technology.gather_group_techs(model, g2) + super_activity = quicksum( + model.v_flow_out[_r, p, s, d, S_i, S_t, S_v, S_o] + for S_t in super_group + if S_t not in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, S_t), []) + for S_i in model.process_inputs[_r, p, S_t, S_v] + for S_o in model.process_outputs_by_input[_r, p, S_t, S_v, S_i] + for s in model.time_season + for d in model.time_of_day + ) + super_activity += quicksum( + model.v_flow_out_annual[_r, p, S_i, S_t, S_v, S_o] + for S_t in super_group + if S_t in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, S_t), []) + for S_i in model.process_inputs[_r, p, S_t, S_v] + for S_o in model.process_outputs_by_input[_r, p, S_t, S_v, S_i] + ) + + share_lim = value(model.limit_activity_share[r, p, g1, g2, op]) + expr = operator_expression(sub_activity, Operator(op), share_lim * super_activity) + # in the case that there is nothing to sum, skip + if isinstance(expr, bool): # an empty list was generated + return Constraint.Skip + logger.debug( + 'created limit activity share constraint for (%s, %d, %s, %s) of %0.2f', + r, + p, + g1, + g2, + share_lim, + ) + return expr + + +def limit_capacity_share_constraint( + model: TemoaModel, r: Region, p: Period, g1: Technology, g2: Technology, op: str +) -> ExprLike: + r""" + The limit_capacity_share constraint limits the available capacity of a given + technology or technology group as a fraction of another technology or group. + """ + + regions = geography.gather_group_regions(model, r) + + sub_group = technology.gather_group_techs(model, g1) + sub_capacity = quicksum( + model.v_capacity_available_by_period_and_tech[_r, p, _t] + for _t in sub_group + for _r in regions + if (_r, p, _t) in model.process_vintages + ) + + super_group = technology.gather_group_techs(model, g2) + super_capacity = quicksum( + model.v_capacity_available_by_period_and_tech[_r, p, _t] + for _t in super_group + for _r in regions + if (_r, p, _t) in model.process_vintages + ) + share_lim = value(model.limit_capacity_share[r, p, g1, g2, op]) + + expr = operator_expression(sub_capacity, Operator(op), share_lim * super_capacity) + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +def limit_new_capacity_share_constraint( + model: TemoaModel, r: Region, g1: Technology, g2: Technology, v: Vintage, op: str +) -> ExprLike: + r""" + The limit_new_capacity_share constraint limits the share of new capacity + of a given technology or group as a fraction of another technology or + group.""" + + regions = geography.gather_group_regions(model, r) + + sub_group = technology.gather_group_techs(model, g1) + sub_new_cap = quicksum( + model.v_new_capacity[_r, _t, v] + for _t in sub_group + for _r in regions + if (_r, _t, v) in model.process_periods + ) + + super_group = technology.gather_group_techs(model, g2) + super_new_cap = quicksum( + model.v_new_capacity[_r, _t, v] + for _t in super_group + for _r in regions + if (_r, _t, v) in model.process_periods + ) + + share_lim = value(model.limit_new_capacity_share[r, g1, g2, v, op]) + expr = operator_expression(sub_new_cap, Operator(op), share_lim * super_new_cap) + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +def limit_annual_capacity_factor_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage, o: Commodity, op: str +) -> ExprLike: + r""" + The limit_annual_capacity_factor sets an upper bound on the annual capacity factor + from a specific process. The first portion of the constraint pertains to + technologies with variable output at the time slice level, and the second portion + pertains to technologies with constant annual output belonging to the + :code:`tech_annual` set. + + .. math:: + :label: limit_annual_capacity_factor + + \sum_{S,D,I} \textbf{FO}_{r, p, s, d, i, t, v, o} + \quad \le, \ge, \text{or} = \quad + LIMACF_{r, t, v, o} \cdot + \textbf{CAP}_{r, p, t, v} \cdot \text{C2A}_{r, t} + + \forall \{r, p, t \notin T^{a}, v, o\} + \in \Theta_{\text{limit\_annual\_capacity\_factor}} + + \\\sum_{I} \textbf{FOA}_{r, p, i, t, v, o} + \quad \le, \ge, \text{or} = \quad + LIMACF_{r, t, v, o} \cdot + \textbf{CAP}_{r, p, t, v} \cdot \text{C2A}_{r, t} + + \forall \{r, p, t \in T^{a}, v, o\} \in \Theta_{\text{limit\_annual\_capacity\_factor}} + + """ + # r can be an individual region (r='US'), or a combination of regions separated by plus + # (r='Mexico+US+Canada'), or 'global'. + # if r == 'global', the constraint is system-wide + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + if TYPE_CHECKING: + activity_rptvo = cast('Expression', 0) + else: + activity_rptvo = 0 + for _t in techs: + if _t not in model.tech_annual: + activity_rptvo += quicksum( + model.v_flow_out[_r, p, s, d, S_i, _t, v, o] + for _r in regions + for S_i in model.process_inputs_by_output.get((_r, p, _t, v, o), []) + for s in model.time_season + for d in model.time_of_day + ) + else: + activity_rptvo += quicksum( + model.v_flow_out_annual[_r, p, S_i, _t, v, o] + for _r in regions + for S_i in model.process_inputs_by_output.get((_r, p, _t, v, o), []) + ) + + possible_activity_rptvo = quicksum( + model.v_capacity[_r, p, _t, v] * value(model.capacity_to_activity[_r, _t]) + for _r in regions + for _t in techs + if v in model.process_vintages.get((_r, p, _t), []) + ) + annual_cf = value(model.limit_annual_capacity_factor[r, t, v, o, op]) + expr = operator_expression(activity_rptvo, Operator(op), annual_cf * possible_activity_rptvo) + # in the case that there is nothing to sum, skip + if isinstance(expr, bool): # an empty list was generated + return Constraint.Skip + return expr + + +def limit_seasonal_capacity_factor_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, t: Technology, op: str +) -> ExprLike: + r""" + The limit_seasonal_capacity_factor sets an upper bound on the seasonal capacity factor + from a specific technology. The first portion of the constraint pertains to + technologies with variable output at the time slice level, and the second portion + pertains to technologies with constant annual output belonging to the + :code:`tech_annual` set. + + .. math:: + :label: Limit Seasonal Capacity Factor + + \sum_{D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} + \quad \le, \ge, \text{or} = \quad + LIMSCF_{r, s, t} \cdot + \textbf{CAPAVL}_{r, p, t} \cdot \text{C2A}_{r, t} \cdot SFS_s + + \forall \{r, p, s, t \notin T^{a}\} \in \Theta_{\text{limit\_seasonal\_capacity\_factor}} + + \\\sum_{I,V,O} \textbf{FOA}_{r, p, i, t, v, o} \cdot SFS_s + \quad \le, \ge, \text{or} = \quad + LIMSCF_{r, s, t} \cdot + \textbf{CAPAVL}_{r, p, t} \cdot \text{C2A}_{r, t} \cdot SFS_s + + \forall \{r, p, s, t \in T^{a}\} \in \Theta_{\text{limit\_seasonal\_capacity\_factor}} + """ + # r can be an individual region (r='US'), or a combination of regions separated by plus + # (r='Mexico+US+Canada'), or 'global'. + # if r == 'global', the constraint is system-wide + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + # we need to screen here because it is possible that the restriction extends beyond the + # lifetime of any vintage of the tech... + if all( + (_r, p, _t) not in model.v_capacity_available_by_period_and_tech + for _r in regions + for _t in techs + ): + return Constraint.Skip + + if TYPE_CHECKING: + activity_rpst = cast('Expression', 0) + else: + activity_rpst = 0 + for _t in techs: + if _t not in model.tech_annual: + activity_rpst += quicksum( + model.v_flow_out[_r, p, s, d, S_i, _t, S_v, S_o] + for _r in regions + for S_v in model.process_vintages.get((_r, p, _t), []) + for S_i in model.process_inputs.get((_r, p, _t, S_v), []) + for S_o in model.process_outputs_by_input.get((_r, p, _t, S_v, S_i), []) + for d in model.time_of_day + ) + else: + activity_rpst += quicksum( + model.v_flow_out_annual[_r, p, S_i, _t, S_v, S_o] + * model.segment_fraction_per_season[s] + for _r in regions + for S_v in model.process_vintages.get((_r, p, _t), []) + for S_i in model.process_inputs.get((_r, p, _t, S_v), []) + for S_o in model.process_outputs_by_input.get((_r, p, _t, S_v, S_i), []) + ) + + possible_activity_rpst = quicksum( + model.v_capacity_available_by_period_and_tech[_r, p, _t] + * value(model.capacity_to_activity[_r, _t]) + * value(model.segment_fraction_per_season[s]) + for _r in regions + for _t in techs + if (_r, p, _t) in model.v_capacity_available_by_period_and_tech + ) + seasonal_cf = value(model.limit_seasonal_capacity_factor[r, s, t, op]) + expr = operator_expression(activity_rpst, Operator(op), seasonal_cf * possible_activity_rpst) + # in the case that there is nothing to sum, skip + if isinstance(expr, bool): # an empty list was generated + return Constraint.Skip + return expr + + +def limit_tech_input_split_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + i: Commodity, + t: Technology, + v: Vintage, + op: str, +) -> ExprLike: + r""" + Allows users to limit shares of commodity inputs to a process + producing a single output. These shares can vary by model time period. See + limit_tech_output_split_constraint for an analogous explanation. Under this constraint, + only the technologies with variable output at the timeslice level (i.e., + NOT in the :code:`tech_annual` set) are considered.""" + inp = quicksum( + model.v_flow_out[r, p, s, d, i, t, v, S_o] + / get_variable_efficiency(model, r, p, s, d, i, t, v, S_o) + for S_o in model.process_outputs_by_input[r, p, t, v, i] + ) + + total_inp = quicksum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + / get_variable_efficiency(model, r, p, s, d, S_i, t, v, S_o) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + expr = operator_expression( + inp, Operator(op), value(model.limit_tech_input_split[r, p, i, t, op]) * total_inp + ) + return expr + + +def limit_tech_input_split_annual_constraint( + model: TemoaModel, r: Region, p: Period, i: Commodity, t: Technology, v: Vintage, op: str +) -> ExprLike: + r""" + Allows users to limit shares of commodity inputs to a process + producing a single output. These shares can vary by model time period. See + limit_tech_output_split_annual_constraint for an analogous explanation. Under this + function, only the technologies with constant annual output (i.e., members + of the :code:`tech_annual` set) are considered.""" + inp = quicksum( + model.v_flow_out_annual[r, p, i, t, v, S_o] / value(model.efficiency[r, i, t, v, S_o]) + for S_o in model.process_outputs_by_input[r, p, t, v, i] + ) + + total_inp = quicksum( + model.v_flow_out_annual[r, p, S_i, t, v, S_o] / value(model.efficiency[r, S_i, t, v, S_o]) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + expr = operator_expression( + inp, Operator(op), value(model.limit_tech_input_split_annual[r, p, i, t, op]) * total_inp + ) + return expr + + +def limit_tech_input_split_average_constraint( + model: TemoaModel, r: Region, p: Period, i: Commodity, t: Technology, v: Vintage, op: str +) -> ExprLike: + r""" + Allows users to limit shares of commodity inputs to a process + producing a single output. Under this constraint, only the technologies with variable + output at the timeslice level (i.e., NOT in the :code:`tech_annual` set) are considered. + This constraint differs from limit_tech_input_split as it specifies shares on an annual basis, + so even though it applies to technologies with variable output at the timeslice level, + the constraint only fixes the input shares over the course of a year.""" + + inp = quicksum( + model.v_flow_out[r, p, S_s, S_d, i, t, v, S_o] + / get_variable_efficiency(model, r, p, S_s, S_d, i, t, v, S_o) + for S_s in model.time_season + for S_d in model.time_of_day + for S_o in model.process_outputs_by_input[r, p, t, v, i] + ) + total_inp = quicksum( + model.v_flow_out[r, p, S_s, S_d, S_i, t, v, S_o] + / get_variable_efficiency(model, r, p, S_s, S_d, S_i, t, v, S_o) + for S_s in model.time_season + for S_d in model.time_of_day + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + expr = operator_expression( + inp, Operator(op), value(model.limit_tech_input_split_annual[r, p, i, t, op]) * total_inp + ) + return expr + + +def limit_tech_output_split_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, + o: Commodity, + op: str, +) -> ExprLike: + r""" + + Some processes take a single input and make multiple outputs, and the user would like to + specify either a constant or time-varying ratio of outputs per unit input. The most + canonical example is an oil refinery. Crude oil is used to produce many different refined + products. In many cases, the modeler would like to limit the share of each refined + product produced by the refinery. + + For example, a hypothetical (and highly simplified) refinery might have a crude oil input + that produces 4 parts diesel, 3 parts gasoline, and 2 parts kerosene. The relative + ratios to the output then are: + + .. math:: + + d = \tfrac{4}{9} \cdot \text{total output}, \qquad + g = \tfrac{3}{9} \cdot \text{total output}, \qquad + k = \tfrac{2}{9} \cdot \text{total output} + + Note that it is possible to specify output shares that sum to less than unity. In such + cases, the model optimizes the remaining share. In addition, it is possible to change the + specified shares by model time period. Under this constraint, only the + technologies with variable output at the timeslice level (i.e., NOT in the + :code:`tech_annual` set) are considered. + + The constraint is formulated as follows: + + .. math:: + :label: limit_tech_output_split + + \sum_{I, t \not \in T^{a}} \textbf{FO}_{r, p, s, d, i, t, v, o} + \quad \le, \ge, \text{or} = \quad + TOS_{r, p, t, o} \cdot \sum_{I, O, t \not \in T^{a}} \textbf{FO}_{r, p, s, d, i, t, v, o} + + \forall \{r, p, s, d, t, v, o\} \in \Theta_{\text{limit\_tech\_output\_split}}""" + out = quicksum( + model.v_flow_out[r, p, s, d, S_i, t, v, o] + for S_i in model.process_inputs_by_output[r, p, t, v, o] + ) + + total_out = quicksum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + expr = operator_expression( + out, Operator(op), value(model.limit_tech_output_split[r, p, t, o, op]) * total_out + ) + return expr + + +def limit_tech_output_split_annual_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage, o: Commodity, op: str +) -> ExprLike: + r""" + This constraint operates similarly to limit_tech_output_split_constraint. + However, under this function, only the technologies with constant annual + output (i.e., members of the :code:`tech_annual` set) are considered. + + .. math:: + :label: limit_tech_output_split_annual + + \sum_{I, T^{a}} \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} + \quad \le, \ge, \text{or} = \quad + TOSA_{r, p, t, o} \cdot + \sum_{I, O, T^{a}} \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} + + \forall \{r, p, t \in T^{a}, v, o\} \in + \Theta_{\text{limit\_tech\_output\_split\_annual}}""" + out = quicksum( + model.v_flow_out_annual[r, p, S_i, t, v, o] + for S_i in model.process_inputs_by_output[r, p, t, v, o] + ) + + total_out = quicksum( + model.v_flow_out_annual[r, p, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + expr = operator_expression( + out, Operator(op), value(model.limit_tech_output_split_annual[r, p, t, o, op]) * total_out + ) + return expr + + +def limit_tech_output_split_average_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage, o: Commodity, op: str +) -> ExprLike: + r""" + Allows users to limit shares of commodity outputs from a process. + Under this constraint, only the technologies with variable + output at the timeslice level (i.e., NOT in the :code:`tech_annual` set) are considered. + This constraint differs from limit_tech_output_split as it specifies shares on an annual basis, + so even though it applies to technologies with variable output at the timeslice level, + the constraint only fixes the output shares over the course of a year.""" + + out = quicksum( + model.v_flow_out[r, p, S_s, S_d, S_i, t, v, o] + for S_i in model.process_inputs_by_output[r, p, t, v, o] + for S_s in model.time_season + for S_d in model.time_of_day + ) + + total_out = quicksum( + model.v_flow_out[r, p, S_s, S_d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + for S_s in model.time_season + for S_d in model.time_of_day + ) + + expr = operator_expression( + out, Operator(op), value(model.limit_tech_output_split_annual[r, p, t, o, op]) * total_out + ) + return expr + + +def limit_emission_constraint( + model: TemoaModel, r: Region, p: Period, e: Commodity, op: str +) -> ExprLike: + r""" + + A modeler can track emissions through use of the :code:`commodity_emissions` + set and :code:`emission_activity` parameter. The :math:`EAC` parameter is + analogous to the efficiency table, tying emissions to a unit of activity. The + limit_emission constraint allows the modeler to assign an upper bound per period + to each emission commodity. Note that this constraint sums emissions from + technologies with output varying at the time slice and those with constant annual + output in separate terms. It also includes embodied emissions from new capacity + and end-of-life emissions from retiring capacity. + + .. math:: + :label: limit_emission + + \sum_{S,D,I,T,V,O|{r,e,i,t,v,o} \in EAC} \left ( + EAC_{r, e, i, t, v, o} \cdot \textbf{FO}_{r, p, s, d, i, t, v, o} + \right ) & \\ + + + \sum_{I,T,V,O|{r,e,i,t \in T^{a},v,o} \in EAC} ( + EAC_{r, e, i, t, v, o} \cdot & \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} + ) \\ + + + \sum_{T} \frac{EE_{r, e, t, v=p} \cdot \textbf{NCAP}_{r, t, v=p}}{LEN_p} & \\ + + + \sum_{T,V} EEOL_{r, e, t, v} \cdot \textbf{ART}_{r, p, t, v} & + \\ + \quad \le, \ge, \text{or} = \quad + LE_{r, p, e} + + \\ + & \forall \{r, p, e\} \in \Theta_{\text{limit\_emission}} + + """ + emission_limit = value(model.limit_emission[r, p, e, op]) + + # r can be an individual region (r='US'), or a combination of regions separated by a + + # (r='Mexico+US+Canada'), or 'global'. Note that regions!=M.regions. We iterate over regions + # to find actual_emissions and actual_emissions_annual. + + # if r == 'global', the constraint is system-wide + + regions = geography.gather_group_regions(model, r) + + # ================= Emissions and Flex and Curtailment ================= + # Flex flows are deducted from v_flow_out, so it is NOT NEEDED to tax them again. + # (See commodity balance constr) + # Curtailment does not draw any inputs, so it seems logical that curtailed flows not be taxed + # either + + process_emissions = quicksum( + model.v_flow_out[reg, p, S_s, S_d, S_i, S_t, S_v, S_o] + * value(model.emission_activity[reg, e, S_i, S_t, S_v, S_o]) + for reg in regions + for tmp_r, tmp_e, S_i, S_t, S_v, S_o in model.emission_activity.sparse_keys() + if tmp_e == e and tmp_r == reg and S_t not in model.tech_annual + # EmissionsActivity not indexed by p, so make sure (r,p,t,v) combos valid + if (reg, p, S_t, S_v) in model.process_inputs + for S_s in model.time_season + for S_d in model.time_of_day + ) + + process_emissions_annual = quicksum( + model.v_flow_out_annual[reg, p, S_i, S_t, S_v, S_o] + * value(model.emission_activity[reg, e, S_i, S_t, S_v, S_o]) + for reg in regions + for tmp_r, tmp_e, S_i, S_t, S_v, S_o in model.emission_activity.sparse_keys() + if tmp_e == e and tmp_r == reg and S_t in model.tech_annual + # EmissionsActivity not indexed by p, so make sure (r,p,t,v) combos valid + if (reg, p, S_t, S_v) in model.process_inputs + ) + + embodied_emissions = quicksum( + model.v_new_capacity[reg, t, v] + * value(model.emission_embodied[reg, e, t, v]) + / value(model.period_length[v]) + for reg in regions + for (S_r, S_e, t, v) in model.emission_embodied.sparse_keys() + if v == p and S_r == reg and S_e == e + ) + + retirement_emissions = quicksum( + model.v_annual_retirement[reg, p, t, v] * value(model.emission_end_of_life[reg, e, t, v]) + for reg in regions + for (S_r, S_e, t, v) in model.emission_end_of_life.sparse_keys() + if (reg, t, v) in model.retirement_periods and p in model.retirement_periods[reg, t, v] + if S_r == reg and S_e == e + ) + + lhs = ( + process_emissions + process_emissions_annual + embodied_emissions + retirement_emissions + # + emissions_flex # NO! flex is subtracted from flowout, already accounted by flowout + # + emissions_curtail # NO! curtailed flows are not actual flows, just an accounting tool + # + emissions_flex_annual # NO! flexannual is subtracted from flowoutannual, already + # accounted + ) + expr = operator_expression(lhs, Operator(op), emission_limit) + + # in the case that there is nothing to sum, skip + if isinstance(expr, bool): # an empty list was generated + msg = "Warning: No technology produces emission '%s', though limit was specified as %s.\n" + logger.warning(msg, (e, emission_limit)) + sys.stderr.write(msg % (e, emission_limit)) + return Constraint.Skip + + return expr + + +def limit_growth_capacity_constraint_rule( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r"""Constrain ramp up rate of available capacity""" + return limit_growth_capacity(model, r, p, t, op, False) + + +def limit_degrowth_capacity_constraint_rule( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r"""Constrain ramp down rate of available capacity""" + return limit_growth_capacity(model, r, p, t, op, True) + + +def limit_growth_capacity( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str, degrowth: bool = False +) -> ExprLike: + r""" + Constrain the change of capacity available between periods. + Forces the model to ramp up and down the availability of new technologies + more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional + (rate, :math:`R_{r,t}`) terms. This can be defined for a technology group + instead of one technology, in which case, capacity available is summed over + all technologies in the group. In the first period, previous available + capacity :math:`\mathbf{CAPAVL}_{r,p,t}` is replaced by previous existing + capacity, if any can be found. + + .. math:: + :label: Limit (De)Growth Capacity + + \begin{aligned}\text{Growth:}\\ + &\mathbf{CAPAVL}_{r,p,t} + \quad \le, \ge, \text{or} = \quad + S_{r,t} + (1+R_{r,t}) \cdot \mathbf{CAPAVL}_{r,p_{prev},t} + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacity}} + + + \begin{aligned}\text{Degrowth:}\\ + &\mathbf{CAPAVL}_{r,p_{prev},t} + \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot \mathbf{CAPAVL}_{r,p,t} + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacity}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + growth = model.limit_degrowth_capacity if degrowth else model.limit_growth_capacity + rate = 1 + value(growth[r, t, op][0]) + seed = value(growth[r, t, op][1]) + cap_rpt = model.v_capacity_available_by_period_and_tech + + # relevant r, p, t indices + cap_indices = {(_r, _p, _t) for _r, _p, _t in cap_rpt.keys() if _t in techs and _r in regions} + # periods the technology can have capacity in this region (sorted) + periods = sorted({_p for _r, _p, _t in cap_rpt}) + + if len(periods) == 0: + if p == model.time_optimize.first(): + msg = ( + 'Tried to set {}rowthCapacity constraint {} but there are no periods where this ' + 'technology is available in this region. Constraint skipped.' + ).format('Deg' if degrowth else 'G', (r, t)) + logger.warning(msg) + return Constraint.Skip + + # Only warn in p0 so we dont dump multiple warnings + if p == periods[0]: + if seed == 0: + msg = ( + 'No constant term (seed) provided for {}rowthCapacity constraint {}. ' + 'No capacity will be built in any period following one with zero capacity.' + ).format('Deg' if degrowth else 'G', (r, t)) + logger.info(msg) + gaps = [ + _p + for _p in model.time_optimize + if _p not in periods and min(periods) < _p < max(periods) + ] + if gaps: + msg = ( + 'Constructing {}rowthCapacity constraint {} and there are period gaps in which' + 'capacity cannot exist in this region ({}). Capacity in these periods ' + 'will be treated as zero which may cause infeasibility or other problems.' + ).format('Deg' if degrowth else 'G', (r, t), gaps) + logger.warning(msg) + + # sum available capacity in this period + capacity = quicksum(cap_rpt[_r, _p, _t] for _r, _p, _t in cap_indices if _p == p) + + if p == model.time_optimize.first(): + # First future period. Grab available capacity in last existing period + # Adjust in-line for past PLF because we are constraining available capacity + p_prev = model.time_exist.last() + capacity_prev = sum( + value(model.existing_capacity[_r, _t, _v]) + * min(1.0, (_v + value(model.lifetime_process[_r, _t, _v]) - p_prev) / (p - p_prev)) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions + and _t in techs + and _v + value(model.lifetime_process[_r, _t, _v]) > p_prev + ) + else: + # Otherwise, grab previous future period + p_prev = model.time_optimize.prev(p) + capacity_prev = quicksum(cap_rpt[_r, _p, _t] for _r, _p, _t in cap_indices if _p == p_prev) + + if degrowth: + expr = operator_expression(capacity_prev, Operator(op), seed + capacity * rate) + else: + expr = operator_expression(capacity, Operator(op), seed + capacity_prev * rate) + + # Check if any variables are actually included before returning + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +def limit_growth_new_capacity_constraint_rule( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r"""Constrain ramp up rate of new capacity deployment""" + return limit_growth_new_capacity(model, r, p, t, op, False) + + +def limit_degrowth_new_capacity_constraint_rule( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r"""Constrain ramp down rate of new capacity deployment""" + return limit_growth_new_capacity(model, r, p, t, op, True) + + +def limit_growth_new_capacity( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str, degrowth: bool = False +) -> ExprLike: + r""" + Constrain the change of new capacity deployed between periods. + Forces the model to ramp up and down the deployment of new technologies + more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional + (rate, :math:`R_{r,t}`) terms. This can be defined for a technology group + instead of one technology, in which case, new capacity is summed over + all technologies in the group. In the first period, previous new capacity + :math:`\mathbf{NCAP}_{r,t,v_prev}` is replaced by previous existing capacity, + if any can be found. + + .. math:: + :label: Limit (De)Growth New Capacity + + \begin{aligned}\text{Growth:}\\ + &\mathbf{NCAP}_{r,t,v} + \quad \le, \ge, \text{or} = \quad + S_{r,t} + (1+R_{r,t}) \cdot \mathbf{NCAP}_{r,t,v_{prev}} + \text{ where } v=p + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacity}} + + \begin{aligned}\text{Degrowth:}\\ + &\mathbf{NCAP}_{r,t,v_{prev}} + \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot \mathbf{NCAP}_{r,t,v} + \text{ where } v=p + \end{aligned} + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacity}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + growth = model.limit_degrowth_new_capacity if degrowth else model.limit_growth_new_capacity + rate = 1 + value(growth[r, t, op][0]) + seed = value(growth[r, t, op][1]) + new_cap_rtv = model.v_new_capacity + + # relevant r, t, v indices + cap_rtv = {(_r, _t, _v) for _r, _t, _v in new_cap_rtv.keys() if _t in techs and _r in regions} + # periods the technology can be built in this region (sorted) + periods = sorted({_v for _r, _t, _v in cap_rtv}) + + if len(periods) == 0: + if p == model.time_optimize.first(): + msg = ( + 'Tried to set {}rowthNewCapacity constraint {} but there are no periods where this ' + 'technology can be built in this region. Constraint skipped.' + ).format('Deg' if degrowth else 'G', (r, t)) + logger.warning(msg) + return Constraint.Skip + + # Only warn in p0 so we dont dump multiple warnings + if p == periods[0]: + if seed == 0: + msg = ( + 'No constant term (seed) provided for {}rowthNewCapacity constraint {}. ' + 'No capacity will be built in any period following one with zero new capacity.' + ).format('Deg' if degrowth else 'G', (r, t)) + logger.info(msg) + gaps = [ + _p + for _p in model.time_optimize + if _p not in periods and min(periods) < _p < max(periods) + ] + if gaps: + msg = ( + 'Constructing {}rowthNewCapacity constraint {} and there are period gaps in which' + 'new capacity cannot be built in this region ({}). New capacity in these periods ' + 'will be treated as zero which may cause infeasibility or other problems.' + ).format('Deg' if degrowth else 'G', (r, t), gaps) + logger.warning(msg) + + # sum new capacity in this period + new_cap = quicksum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p) + + if p == model.time_optimize.first(): + # First future period. Grab last existing vintage + p_prev = model.time_exist.last() + new_cap_prev = sum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev + ) + else: + # Otherwise, grab previous future vintage + p_prev = model.time_optimize.prev(p) + new_cap_prev = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev) + + if degrowth: + expr = operator_expression(new_cap_prev, Operator(op), seed + new_cap * rate) + else: + expr = operator_expression(new_cap, Operator(op), seed + new_cap_prev * rate) + + # Check if any variables are actually included before returning + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +def limit_growth_new_capacity_delta_constraint_rule( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r"""Constrain ramp up rate of change in new capacity deployment""" + return limit_growth_new_capacity_delta(model, r, p, t, op, False) + + +def limit_degrowth_new_capacity_delta_constraint_rule( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r"""Constrain ramp down rate of change in new capacity deployment""" + return limit_growth_new_capacity_delta(model, r, p, t, op, True) + + +def limit_growth_new_capacity_delta( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str, degrowth: bool = False +) -> ExprLike: + r""" + Constrain the acceleration of new capacity deployed between periods. + Forces the model to ramp up and down the change in deployment of new technologies + more smoothly. Has constant (seed, :math:`S_{r,t}`) and proportional + (rate, :math:`R_{r,t}`) terms. It is recommended to leave the rate term empty + as it would prevent the possibility of inflection in the rate of deployment. + This constraint can be defined for a technology group instead of one technology, + in which case, new capacity is summed over all technologies in the group. In the + first period, previous new capacities are replaced by previous existing capacities, + if any can be found. + + .. math:: + :label: Limit (De)Growth New Capacity Delta + + \begin{aligned}\text{Growth:}\\ + &\mathbf{NCAP}_{r,t,v_i} - \mathbf{NCAP}_{r,t,v_{i-1}} + \quad \le, \ge, \text{or} = \quad S_{r,t} + (1+R_{r,t}) \cdot + (\mathbf{NCAP}_{r,t,v_{i-1}} - \mathbf{NCAP}_{r,t,v_{i-2}}) + \end{aligned} + + \text{ where } v_i=p + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_growth\_capacityDelta}} + + \begin{aligned}\text{Degrowth:}\\ + &\mathbf{NCAP}_{r,t,v_{i-1}} - \mathbf{NCAP}_{r,t,v_{i-2}} + \quad \le, \ge, \text{or} = \quad + S_{r,t} + (1+R_{r,t}) \cdot (\mathbf{NCAP}_{r,t,v_i} - \mathbf{NCAP}_{r,t,v_{i-1}}) + \end{aligned} + + \text{ where } v_i=p + + \qquad \forall \{r, p, t\} \in \Theta_{\text{limit\_degrowth\_capacityDelta}} + """ + + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + growth = ( + model.limit_degrowth_new_capacity_delta + if degrowth + else model.limit_growth_new_capacity_delta + ) + rate = 1 + value(growth[r, t, op][0]) + seed = value(growth[r, t, op][1]) + new_cap_rtv = model.v_new_capacity + + # relevant r, t, v indices + cap_rtv = {(_r, _t, _v) for _r, _t, _v in new_cap_rtv.keys() if _t in techs and _r in regions} + # periods the technology can be built in this region (sorted) + periods = sorted({_v for _r, _t, _v in cap_rtv}) + + if len(periods) == 0: + if p == model.time_optimize.first(): + msg = ( + 'Tried to set {}rowthNewCapacityDelta constraint {} but there are no periods where ' + 'this technology can be built in this region. Constraint skipped.' + ).format('Deg' if degrowth else 'G', (r, t)) + logger.warning(msg) + return Constraint.Skip + + # Only warn in p0 so we dont dump multiple warnings + if p == periods[0]: + if seed == 0: + msg = ( + 'No constant term (seed) provided for {}rowthNewCapacityDelta constraint {}. ' + 'This is not recommended as deployment rates cannot inflect (change from ' + 'accelerating to decelerating or vice-versa).' + ).format('Deg' if degrowth else 'G', (r, t)) + logger.warning(msg) + gaps = [ + _p + for _p in model.time_optimize + if _p not in periods and min(periods) < _p < max(periods) + ] + if gaps: + msg = ( + 'Constructing {}rowthNewCapacityDelta constraint {} and there are period gaps in ' + 'which new capacity cannot be built in this region ({}). New capacity in these ' + 'periods will be treated as zero which may cause infeasibility or other problems.' + ).format('Deg' if degrowth else 'G', (r, t), gaps) + logger.warning(msg) + + # sum new capacity in this period + new_cap = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p) + + if p == model.time_optimize.first(): + # First planning period, pull last two existing vintages + p_prev = model.time_exist.last() + new_cap_prev = sum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev + ) + p_prev2 = model.time_exist.prev(p_prev) + new_cap_prev2 = sum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev2 + ) + else: + # Not the first future period. Grab previous future period + p_prev = model.time_optimize.prev(p) + new_cap_prev = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev) + if p == model.time_optimize.at(2): # apparently pyomo sets are indexed 1-based + # Second future period, grab last existing vintage + p_prev2 = model.time_exist.last() + new_cap_prev2 = sum( + value(model.existing_capacity[_r, _t, _v]) + for _r, _t, _v in model.existing_capacity.sparse_keys() + if _r in regions and _t in techs and _v == p_prev2 + ) + else: + # At least the third future period. Grab last two future vintages + p_prev2 = model.time_optimize.prev(p_prev) + new_cap_prev2 = sum(new_cap_rtv[_r, _t, _v] for _r, _t, _v in cap_rtv if _v == p_prev2) + + nc_delta_prev = new_cap_prev - new_cap_prev2 + nc_delta = new_cap - new_cap_prev + + if degrowth: + expr = operator_expression(nc_delta_prev, Operator(op), seed + nc_delta * rate) + else: + expr = operator_expression(nc_delta, Operator(op), seed + nc_delta_prev * rate) + + # Check if any variables are actually included before returning + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +def limit_activity_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r""" + + Sets a limit on the activity from a specific technology. + Note that the indices for these constraints are region, period and tech, not tech + and vintage. The first version of the constraint pertains to technologies with + variable output at the time slice level, and the second version pertains to + technologies with constant annual output belonging to the :code:`tech_annual` + set. + + .. math:: + :label: limit_activity + + \sum_{S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} + + \forall \{r, p, t \notin T^{a}\} \in \Theta_{\text{limit\_activity}} + + +\sum_{I,V,O} \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} + + \forall \{r, p, t \in T^{a}\} \in \Theta_{\text{limit\_activity}} + + \quad \le, \ge, \text{or} = \quad LA_{r, p, t} + """ + # r can be an individual region (r='US'), or a combination of regions separated by + # a + (r='Mexico+US+Canada'), or 'global'. + # if r == 'global', the constraint is system-wide + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + + activity = quicksum( + model.v_flow_out[_r, p, s, d, S_i, _t, S_v, S_o] + for _t in techs + if _t not in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, _t), []) + for S_i in model.process_inputs[_r, p, _t, S_v] + for S_o in model.process_outputs_by_input[_r, p, _t, S_v, S_i] + for s in model.time_season + for d in model.time_of_day + ) + activity += quicksum( + model.v_flow_out_annual[_r, p, S_i, _t, S_v, S_o] + for _t in techs + if _t in model.tech_annual + for _r in regions + for S_v in model.process_vintages.get((_r, p, _t), []) + for S_i in model.process_inputs[_r, p, _t, S_v] + for S_o in model.process_outputs_by_input[_r, p, _t, S_v, S_i] + ) + + act_lim = value(model.limit_activity[r, p, t, op]) + expr = operator_expression(activity, Operator(op), act_lim) + # in the case that there is nothing to sum, skip + if isinstance(expr, bool): # an empty list was generated + return Constraint.Skip + return expr + + +def limit_new_capacity_constraint( + model: TemoaModel, r: Region, t: Technology, v: Vintage, op: str +) -> ExprLike: + r""" + The limit_new_capacity constraint sets a limit on the newly installed capacity of a + given technology or group in a given vintage year. + + .. math:: + :label: limit_new_capacity + + \textbf{NCAP}_{r, t, v} \quad \le, \ge, \text{or} = \quad LNC_{r, t, v} + """ + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + cap_lim = value(model.limit_new_capacity[r, t, v, op]) + new_cap = quicksum( + model.v_new_capacity[_r, _t, v] + for _t in techs + for _r in regions + if (_r, _t, v) in model.process_periods + ) + expr = operator_expression(new_cap, Operator(op), cap_lim) + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +def limit_capacity_constraint( + model: TemoaModel, r: Region, p: Period, t: Technology, op: str +) -> ExprLike: + r""" + + The limit_capacity constraint sets a limit on the available capacity of a + given technology. Note that the indices for these constraints are region, period and + tech, not tech and vintage. + + .. math:: + :label: limit_capacity + + \textbf{CAPAVL}_{r, p, t} \quad \le, \ge, \text{or} = \quad LC_{r, p, t} + + \forall \{r, p, t\} \in \Theta_{\text{limit\_capacity}}""" + regions = geography.gather_group_regions(model, r) + techs = technology.gather_group_techs(model, t) + cap_lim = value(model.limit_capacity[r, p, t, op]) + capacity = quicksum( + model.v_capacity_available_by_period_and_tech[_r, p, _t] + for _t in techs + for _r in regions + if (_r, p, _t) in model.v_capacity_available_by_period_and_tech + ) + expr = operator_expression(capacity, Operator(op), cap_lim) + if isinstance(expr, bool): + return Constraint.Skip + return expr + + +# ============================================================================ +# PRE-COMPUTATION FUNCTION +# ============================================================================ + + +def create_limit_vintage_sets(model: TemoaModel) -> None: + """ + Populates vintage-specific dictionaries for input/output split limit constraints. + + This function iterates through active processes and identifies which vintages are + subject to split constraints, populating dictionaries that are then used by + the index set functions below. + + Populates: + - M.input_split_vintages: dict mapping (r, p, i, t, op) to a set of vintages `v`. + - M.input_split_annual_vintages: dict for annual-specific input splits. + - M.output_split_vintages: dict mapping (r, p, t, o, op) to a set of vintages `v`. + - M.output_split_annual_vintages: dict for annual-specific output splits. + """ + logger.debug('Creating vintage sets for split limits.') + # Assuming M.process_vintages is already populated + for r, p, t in model.process_vintages: + for v in model.process_vintages[r, p, t]: + for i in model.process_inputs.get((r, p, t, v), []): + for op in model.operator: + if (r, p, i, t, op) in model.limit_tech_input_split: + model.input_split_vintages.setdefault((r, p, i, t, op), set()).add(v) + if (r, p, i, t, op) in model.limit_tech_input_split_annual: + model.input_split_annual_vintages.setdefault((r, p, i, t, op), set()).add(v) + + for o in model.process_outputs.get((r, p, t, v), []): + for op in model.operator: + if (r, p, t, o, op) in model.limit_tech_output_split: + model.output_split_vintages.setdefault((r, p, t, o, op), set()).add(v) + if (r, p, t, o, op) in model.limit_tech_output_split_annual: + model.output_split_annual_vintages.setdefault((r, p, t, o, op), set()).add( + v + ) diff --git a/temoa/components/operations.py b/temoa/components/operations.py new file mode 100644 index 000000000..ab49431c6 --- /dev/null +++ b/temoa/components/operations.py @@ -0,0 +1,565 @@ +# temoa/components/operations.py +""" +Defines operational constraints for technologies in the Temoa model. + +This module is responsible for constraints that govern the dispatch behavior +of technologies across time slices, including: +- Pre-computing sets for technologies with special operational characteristics. +- Baseload constraints, which force constant output within a season. +- Ramping constraints, which limit the rate of change in output between + adjacent time slices. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, value + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types import ExprLike + from temoa.types.core_types import Period, Region, Season, Technology, TimeOfDay, Vintage + +logger = getLogger(__name__) + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def baseload_diurnal_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + return { + (r, p, s, d, t, v) + for r, p, t in model.baseload_vintages + for v in model.baseload_vintages[r, p, t] + for s in model.time_season + for d in model.time_of_day + } + + +def ramp_up_day_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + return { + (r, p, s, d, t, v) + for r, p, t in model.ramp_up_vintages + for v in model.ramp_up_vintages[r, p, t] + for s in model.time_season + for d in model.time_of_day + } + + +def ramp_down_day_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + return { + (r, p, s, d, t, v) + for r, p, t in model.ramp_down_vintages + for v in model.ramp_down_vintages[r, p, t] + for s in model.time_season + for d in model.time_of_day + } + + +def ramp_up_season_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, Season, Technology, Vintage]]: + # Season-to-season ramp constraints require full inter-season ordering; + # skip for consecutive_days (no season links) and seasonal_timeslices (no TOD ordering). + if model.time_sequencing.first() in ('consecutive_days', 'seasonal_timeslices'): + return set() + + # s, s_next indexing ensures we dont build redundant constraints + return { + (r, p, s, s_next, t, v) + for r, p, t in model.ramp_up_vintages + for v in model.ramp_up_vintages[r, p, t] + for s_seq, s in model.ordered_season_sequential + for s_next in (model.sequential_to_season[model.time_next_sequential[s_seq]],) + if s_next != model.time_next[s, model.time_of_day.last()][0] + } + + +def ramp_down_season_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, Season, Technology, Vintage]]: + # Season-to-season ramp constraints require full inter-season ordering; + # skip for consecutive_days (no season links) and seasonal_timeslices (no TOD ordering). + if model.time_sequencing.first() in ('consecutive_days', 'seasonal_timeslices'): + return set() + + # s, s_next indexing ensures we dont build redundant constraints + return { + (r, p, s, s_next, t, v) + for r, p, t in model.ramp_down_vintages + for v in model.ramp_down_vintages[r, p, t] + for s_seq, s in model.ordered_season_sequential + for s_next in (model.sequential_to_season[model.time_next_sequential[s_seq]],) + if s_next != model.time_next[s, model.time_of_day.last()][0] + } + + +# ============================================================================ +# PYOMO CONSTRAINT RULES +# ============================================================================ + + +def baseload_diurnal_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, +) -> ExprLike: + r""" + + Some electric generators cannot ramp output over a short period of time (e.g., + hourly or daily). Temoa models this behavior by forcing technologies in the + :code:`tech_baseload` set to maintain a constant output across all times-of-day + within the same season. Note that the output of a baseload process can vary + between seasons. + + Ideally, this constraint would not be necessary, and baseload processes would + simply not have a :math:`d` index. However, implementing the more efficient + functionality is currently on the Temoa TODO list. + + .. math:: + :label: BaseloadDaily + + SEG_{s, D_0} + \cdot \sum_{I, O} \textbf{FO}_{r, p, s, d,i, t, v, o} + = + SEG_{s, d} + \cdot \sum_{I, O} \textbf{FO}_{r, p, s, D_0,i, t, v, o} + + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{BaseloadDiurnal}} + """ + # Question: How to set the different times of day equal to each other? + + # Step 1: Acquire a "canonical" representation of the times of day + l_times = sorted(model.time_of_day) # i.e. a sorted Python list. + # This is the commonality between invocations of this method. + + index = l_times.index(d) + if 0 == index: + # When index is 0, it means that we've reached the beginning of the array + # For the algorithm, this is a terminating condition: do not create + # an effectively useless constraint + return Constraint.Skip + + # Step 2: Set the rest of the times of day equal in output to the first. + # i.e. create a set of constraints that look something like: + # tod[ 2 ] == tod[ 1 ] + # tod[ 3 ] == tod[ 1 ] + # tod[ 4 ] == tod[ 1 ] + # and so on ... + d_0 = l_times[0] + + # Step 3: the actual expression. For baseload, must compute the /average/ + # activity over the segment. By definition, average is + # (segment activity) / (segment length) + # So: (ActA / SegA) == (ActB / SegB) + # computationally, however, multiplication is cheaper than division, so: + # (ActA * SegB) == (ActB * SegA) + activity_sd = sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + activity_sd_0 = sum( + model.v_flow_out[r, p, s, d_0, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + expr = activity_sd * value(model.segment_fraction[s, d_0]) == activity_sd_0 * value( + model.segment_fraction[s, d] + ) + + return expr + + +# ============================================================================ +# PRE-COMPUTATION FUNCTION +# ============================================================================ + + +def create_operational_vintage_sets(model: TemoaModel) -> None: + """ + Populates vintage-based dictionaries for technologies with special + operational characteristics like curtailment, baseload, storage, ramping, and reserves. + + Populates: + - M.curtailment_vintages, M.baseload_vintages, M.storage_vintages, + M.ramp_up_vintages, M.ramp_down_vintages: Dictionaries mapping (r, p, t) + to a set of vintages `v`. + - M.process_reserve_periods: Dictionary mapping (r, p) to a set of (t, v) tuples. + - M.is_seasonal_storage: A boolean lookup for seasonal storage technologies. + """ + logger.debug('Creating vintage sets for operational constraints.') + + for r, p, t in model.process_vintages: + for v in model.process_vintages[r, p, t]: + key_rpt = (r, p, t) + key_rp = (r, p) + if t in model.tech_curtailment: + model.curtailment_vintages.setdefault(key_rpt, set()).add(v) + if t in model.tech_baseload: + model.baseload_vintages.setdefault(key_rpt, set()).add(v) + if t in model.tech_storage: + model.storage_vintages.setdefault(key_rpt, set()).add(v) + if t in model.tech_upramping: + model.ramp_up_vintages.setdefault(key_rpt, set()).add(v) + if t in model.tech_downramping: + model.ramp_down_vintages.setdefault(key_rpt, set()).add(v) + if t in model.tech_reserve: + model.process_reserve_periods.setdefault(key_rp, set()).add((t, v)) + + # A dictionary of whether a storage tech is seasonal, just to speed things up + for t in model.tech_storage: + model.is_seasonal_storage[t] = t in model.tech_seasonal_storage + + +def ramp_up_day_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, +) -> ExprLike: + r""" + One of two constraints built from the ramp_up_hourly table, along with the + ramp_up_season_constraint. ramp_up_day constrains ramp rates between time slices + within each season and ramp_up_season constrains ramp rates between sequential + seasons. If the :code:`time_sequencing` parameter is set to :code:`consecutive_days` + then the ramp_up_season constraint is skipped as seasons already connect together. + + The ramp rate constraint is utilized to limit the rate of electricity generation + increase and decrease between two adjacent time slices in order to account for + physical limits associated with thermal power plants. This constraint is only + applied to technologies in the set :code:`tech_upramping`. We assume for + simplicity the rate limits do not vary with technology vintage. The ramp rate + limits for a technology should be expressed in percentage of its rated capacity + per hour. + + In a representative periods or seasonal time slices model, the next time slice, + :math:`(s_{next},d_{next})`, from the end of each season, :math:`(s,d_{last})` + is the beginning of the same season, :math:`(s,d_{first})` + + .. math:: + :label: ramp_up_day + + \frac{ + \sum_{I,O} \mathbf{FO}_{r,p,s_{next},d_{next},i,t,v,o} + }{ + SEG_{s_{next},d_{next}} \cdot 24 \cdot DPP + } + - + \frac{ + \sum_{I,O} \mathbf{FO}_{r,p,s,d,i,t,v,o} + }{ + SEG_{s,d} \cdot 24 \cdot DPP + } + \leq + RUH_{r,t} \cdot \Delta H \cdot \frac{CAP_{r,p,t,v} \cdot C2A_{r,t}}{24 \cdot DPP} + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{ramp\_up\_day}} + \\ + \text{where: } \Delta H = \frac{H_d + H_{d_{next}}}{2} + + where: + + - :math:`SEG_{s,d}` is the fraction of the period in time slice :math:`(s,d)` + - :math:`DPP` is the number of days in each period + - :math:`RUH_{r,t}` is the ramp up rate per hour + - :math:`\Delta H` is the average of the hours in timeslice :math:`d` and :math:`d_{next}`, + i.e. :math:`(H_d + H_{d_{next}}) / 2` + - :math:`CAP \cdot C2A / (24 \cdot DPP)` gives the maximum hourly capacity + """ + + s_next, d_next = model.time_next[s, d] + + # How many hours does this time slice represent + hours_adjust = value(model.segment_fraction[s, d]) * value(model.days_per_period) * 24 + hours_adjust_next = ( + value(model.segment_fraction[s_next, d_next]) * value(model.days_per_period) * 24 + ) + + hourly_activity_sd = ( + sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust + ) + + hourly_activity_sd_next = ( + sum( + model.v_flow_out[r, p, s_next, d_next, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust_next + ) + + # elapsed hours from middle of this time slice to middle of next time slice + hours_elapsed = (model.time_of_day_hours[d] + model.time_of_day_hours[d_next]) / 2 + ramp_fraction = hours_elapsed * value(model.ramp_up_hourly[r, t]) + + if ramp_fraction >= 1: + msg = ( + 'Warning: Hourly ramp up rate ({}, {}) is too large to be constraining from ' + '({}, {}, {}) to ({}, {}, {}). ' + f'Should be less than {1 / hours_elapsed:.4f}. Constraint skipped.' + ) + logger.warning(msg.format(r, t, p, s, d, p, s_next, d_next)) + return Constraint.Skip + + activity_increase = hourly_activity_sd_next - hourly_activity_sd # opposite sign from rampdown + rampable_activity = ( + ramp_fraction + * model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + / (24 * value(model.days_per_period)) # adjust capacity to hourly basis + ) + expr = activity_increase <= rampable_activity + + return expr + + +def ramp_down_day_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, +) -> ExprLike: + r""" + + Similar to the :code`ramp_up_day` constraint, we use the :code:`ramp_down_day` + constraint to limit ramp down rates between any two adjacent time slices. + + .. math:: + :label: ramp_down_day + + \frac{ + \sum_{I,O} \mathbf{FO}_{r,p,s,d,i,t,v,o} + }{ + SEG_{s,d} \cdot 24 \cdot DPP + } + - + \frac{ + \sum_{I,O} \mathbf{FO}_{r,p,s_{next},d_{next},i,t,v,o} + }{ + SEG_{s_{next},d_{next}} \cdot 24 \cdot DPP + } + \leq + RDH_{r,t} \cdot \Delta H \cdot \frac{CAP_{r,p,t,v} \cdot C2A_{r,t}}{24 \cdot DPP} + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{ramp\_down\_day}} + \\ + \text{where: } \Delta H = \frac{H_d + H_{d_{next}}}{2} + """ + + s_next, d_next = model.time_next[s, d] + + # How many hours does this time slice represent + hours_adjust = value(model.segment_fraction[s, d]) * value(model.days_per_period) * 24 + hours_adjust_next = ( + value(model.segment_fraction[s_next, d_next]) * value(model.days_per_period) * 24 + ) + + hourly_activity_sd = ( + sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust + ) + + hourly_activity_sd_next = ( + sum( + model.v_flow_out[r, p, s_next, d_next, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust_next + ) + + # elapsed hours from middle of this time slice to middle of next time slice + hours_elapsed = (model.time_of_day_hours[d] + model.time_of_day_hours[d_next]) / 2 + ramp_fraction = hours_elapsed * value(model.ramp_down_hourly[r, t]) + + if ramp_fraction >= 1: + msg = ( + 'Warning: Hourly ramp down rate ({}, {}) is too large to be constraining from ' + '({}, {}, {}) to ({}, {}, {}). ' + f'Should be less than {1 / hours_elapsed:.4f}. Constraint skipped.' + ) + logger.warning(msg.format(r, t, p, s, d, p, s_next, d_next)) + return Constraint.Skip + + activity_decrease = hourly_activity_sd - hourly_activity_sd_next # opposite sign from rampup + rampable_activity = ( + ramp_fraction + * model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + / (24 * value(model.days_per_period)) # adjust capacity to hourly basis + ) + expr = activity_decrease <= rampable_activity + + return expr + + +def ramp_up_season_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + s_next: Season, + t: Technology, + v: Vintage, +) -> ExprLike: + r""" + Constrains the ramp up rate of activity between time slices at the boundary + of sequential seasons. Same as ramp_up_day but only applies to the boundary + between sequential seasons, i.e., :math:`(s^{seq},d_{last})` to + :math:`(s^{seq}_{next},d_{first})` + and :math:`s^{seq}_{next}` is based on the TimeSequential table rather than the + time_season table. + """ + + d = model.time_of_day.last() + d_next = model.time_of_day.first() + + # How many hours does this time slice represent + hours_adjust = value(model.segment_fraction[s, d]) * value(model.days_per_period) * 24 + hours_adjust_next = ( + value(model.segment_fraction[s_next, d_next]) * value(model.days_per_period) * 24 + ) + + hourly_activity_sd = ( + sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust + ) + + hourly_activity_sd_next = ( + sum( + model.v_flow_out[r, p, s_next, d_next, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust_next + ) + + # elapsed hours from middle of this time slice to middle of next time slice + hours_elapsed = (model.time_of_day_hours[d] + model.time_of_day_hours[d_next]) / 2 + ramp_fraction = hours_elapsed * value(model.ramp_up_hourly[r, t]) + + if ramp_fraction >= 1: + msg = ( + 'Warning: Hourly ramp up rate ({}, {}) is too large to be constraining from ' + '({}, {}, {}) to ({}, {}, {}). ' + f'Should be less than {1 / hours_elapsed:.4f}. Constraint skipped.' + ) + logger.warning(msg.format(r, t, p, s, d, p, s_next, d_next)) + return Constraint.Skip + + activity_increase = hourly_activity_sd_next - hourly_activity_sd # opposite sign from rampdown + rampable_activity = ( + ramp_fraction + * model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + / (24 * value(model.days_per_period)) # adjust capacity to hourly basis + ) + expr = activity_increase <= rampable_activity + + return expr + + +def ramp_down_season_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + s_next: Season, + t: Technology, + v: Vintage, +) -> ExprLike: + r""" + Constrains the ramp down rate of activity between time slices at the boundary + of sequential seasons. Same as ramp_down_day but only applies to the boundary + between sequential seasons, i.e., :math:`(s^{seq},d_{last})` to + :math:`(s^{seq}_{next},d_{first})` + and :math:`s^{seq}_{next}` is based on the TimeSequential table rather than the + time_season table. + """ + + d = model.time_of_day.last() + d_next = model.time_of_day.first() + + # How many hours does this time slice represent + hours_adjust = value(model.segment_fraction[s, d]) * value(model.days_per_period) * 24 + hours_adjust_next = ( + value(model.segment_fraction[s_next, d_next]) * value(model.days_per_period) * 24 + ) + + hourly_activity_sd = ( + sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust + ) + + hourly_activity_sd_next = ( + sum( + model.v_flow_out[r, p, s_next, d_next, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + / hours_adjust_next + ) + + # elapsed hours from middle of this time slice to middle of next time slice + hours_elapsed = (model.time_of_day_hours[d] + model.time_of_day_hours[d_next]) / 2 + ramp_fraction = hours_elapsed * value(model.ramp_down_hourly[r, t]) + + if ramp_fraction >= 1: + msg = ( + 'Warning: Hourly ramp down rate ({}, {}) is too large to be constraining from ' + '({}, {}, {}) to ({}, {}, {}). ' + f'Should be less than {1 / hours_elapsed:.4f}. Constraint skipped.' + ) + logger.warning(msg.format(r, t, p, s, d, p, s_next, d_next)) + return Constraint.Skip + + activity_decrease = hourly_activity_sd - hourly_activity_sd_next # opposite sign from rampup + rampable_activity = ( + ramp_fraction + * model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + / (24 * value(model.days_per_period)) # adjust capacity to hourly basis + ) + expr = activity_decrease <= rampable_activity + + return expr diff --git a/temoa/components/reserves.py b/temoa/components/reserves.py new file mode 100644 index 000000000..390ffc718 --- /dev/null +++ b/temoa/components/reserves.py @@ -0,0 +1,370 @@ +# temoa/components/reserves.py +""" +Defines the reserve margin components of the Temoa model. + +This module is responsible for ensuring the energy system maintains a sufficient +planning reserve margin to ensure reliability. It supports both a 'static' +(based on installed capacity) and a 'dynamic' (based on available generation +in a time slice) formulation for calculating available reserves. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, value + +from .utils import get_capacity_factor, get_variable_efficiency + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types import ExprLike + from temoa.types.core_types import Period, Region, Season, TimeOfDay + +logger = getLogger(__name__) + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def reserve_margin_indices(model: TemoaModel) -> set[tuple[Region, Period, Season, TimeOfDay]]: + return { + (r, p, s, d) + for r in model.planning_reserve_margin.sparse_keys() + for p in model.time_optimize + if (r, p) in model.process_reserve_periods + for s in model.time_season + for d in model.time_of_day + } + + +# ============================================================================ +# HELPER FUNCTIONS FOR CONSTRAINT LOGIC +# ============================================================================ + + +def reserve_margin_dynamic( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay +) -> ExprLike: + r""" + A dynamic alternative to the traditional, static reserve margin constraint. Capacity values + are calculated from availability of generation in each hour—like an operating reserve margin—\ + accounting for a capacity derate factor subtracting, for example, forced outage due to icing. + + .. math:: + :label: reserve_margin_dynamic + + &\sum_{t \in T^{res} \setminus T^{x} \setminus T^s,\ V} CFP_{r,s^*,d^*,t,v}\ + \cdot RCD_{r,s^*,t,v}\ + \cdot \mathbf{CAP}_{r,p,t,v} \cdot SEG_{s^*,d^*}\ + \cdot C2A_{r,t} \\ + &+ \sum_{t \in T^{res} \cap T^{x} \setminus T^s,\ V} CFP_{r_i - r, s^*, d^*, t, v}\ + \cdot RCD_{r_i - r, s^*, t, v}\ + \cdot \mathbf{CAP}_{r_i - r,p,t,v} \cdot SEG_{s^*,d^*}\ + \cdot C2A_{r_i - r, t} \\ + &- \sum_{t \in T^{res} \cap T^{x} \setminus T^s,\ V} CFP_{r - r_i, s^*, d^*, t, v}\ + \cdot RCD_{r - r_i, s^*, t, v}\ + \cdot \mathbf{CAP}_{r - r_i,p,t,v}\ + \cdot SEG_{s^*,d^*} \cdot C2A_{r - r_i, t} \\ + &+ \sum_{t \in (T^s \cap T^{res}), V, I, O} \ + \left(\ + \mathbf{FO}_{r,p,s,d,i,t,v,o} - \mathbf{FI}_{r,p,s,d,i,t,v,o}\ + \right)\ + \cdot RCD_{r,s,t,v} \\ + &\geq\ + \left[\ + \sum_{t \in T^{res} \setminus T^{x} \setminus T^a, V, I, O}\ + \mathbf{FO}_{r, p, s, d, i, t, v, o}\ + \right. \\ + &+ \sum_{t \in T^{res} \cap T^a, V, I, O} + \begin{cases} DSD_{r,s,d,o} & \text{if } o \in C^d \\ + SEG_{s,d} & \text{otherwise} \end{cases} + \cdot \mathbf{FOA}_{r, p, i, t, v, o} \\ + &+ \sum_{t \in T^{res} \cap T^{x}, V, I, O} \ + \mathbf{FO}_{r_i - r, p, s, d, i, t, v, o} \\ + &- \sum_{t \in T^{res} \cap T^{x}, V, I, O} \ + \mathbf{FI}_{r - r_i, p, s, d, i, t, v, o} \\ + &- \left. \sum_{t \in T^{res} \cap T^{s}, V, I, O} \ + \mathbf{FI}_{r, p, s, d, i, t, v, o} \right] \cdot (1 + PRM_r) \\ + \\ + &\qquad \qquad \forall \{r, p, s, d\} \in \ + \Theta_{\text{ReserveMargin}} \text{ and } \forall r_i \in R + """ + if (not model.tech_reserve) or ( + (r, p) not in model.process_reserve_periods + ): # If reserve set empty or if r,p not in M.processReservePeriod, skip the constraint + return Constraint.Skip + + # Everything but storage and exchange techs + # Derated available generation + available = sum( + model.v_capacity[r, p, t, v] + * value(model.reserve_capacity_derate[r, s, t, v]) + * get_capacity_factor(model, r, s, d, t, v) + * value(model.capacity_to_activity[r, t]) + * value(model.segment_fraction[s, d]) + for (t, v) in model.process_reserve_periods[r, p] + if t not in model.tech_uncap and t not in model.tech_storage + ) + + # Storage + # Derated net output flow + available += sum( + model.v_flow_out[r, p, s, d, i, t, v, o] * value(model.reserve_capacity_derate[r, s, t, v]) + for (t, v) in model.process_reserve_periods[r, p] + if t in model.tech_storage + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] + ) + available -= sum( + model.v_flow_in[r, p, s, d, i, t, v, o] * value(model.reserve_capacity_derate[r, s, t, v]) + for (t, v) in model.process_reserve_periods[r, p] + if t in model.tech_storage + for i in model.process_inputs[r, p, t, v] + for o in model.process_outputs_by_input[r, p, t, v, i] + ) + + # The above code does not consider exchange techs, e.g. electricity + # transmission between two distinct regions. + # We take exchange takes into account below. + # Note that a single exchange tech linking regions Ri and Rj is twice + # defined: once for region "Ri-Rj" and once for region "Rj-Ri". + + # First, determine the amount of firm capacity each exchange tech + # contributes. + for r1r2 in model.regional_indices: + if '-' not in r1r2: + continue + if ( + r1r2, + p, + ) not in model.process_reserve_periods: # ensure r1r2 is a valid reserve provider in p + continue + r1, r2 = r1r2.split('-') + + # Only consider exchange technologies connecting to this region + if r2 == r: + # Add the firm capacity commitment TO this region + # (this region was guaranteed an import of power) + available += sum( + model.v_capacity[r1r2, p, t, v] + * value(model.reserve_capacity_derate[r1r2, s, t, v]) + * get_capacity_factor(model, r1r2, s, d, t, v) + * value(model.capacity_to_activity[r1r2, t]) + * value(model.segment_fraction[s, d]) + for (t, v) in model.process_reserve_periods[r1r2, p] + ) + elif r1 == r: + # Subtract the firm capacity commitment FROM this region + # (this region guaranteed an export of power) + available -= sum( + model.v_capacity[r1r2, p, t, v] + * value(model.reserve_capacity_derate[r1r2, s, t, v]) + * get_capacity_factor(model, r1r2, s, d, t, v) + * value(model.capacity_to_activity[r1r2, t]) + * value(model.segment_fraction[s, d]) + for (t, v) in model.process_reserve_periods[r1r2, p] + ) + + return available + + +def reserve_margin_static( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay +) -> ExprLike: + r""" + + During each period :math:`p`, the sum of capacity values of all reserve + technologies :math:`\sum_{t \in T^{res}} \textbf{CAP}_{r,p,t,v}`, which are + defined in the set :math:`\textbf{T}^{res}`, should exceed the peak load by + :math:`PRM`, the regional reserve margin. Note that the reserve + margin is expressed in percentage of the peak load. Generally speaking, in + a database we may not know the peak demand before running the model, therefore, + we write this equation for all the time-slices defined in the database in each region. + Each generator is allowed to contribute its available capacity times a pre-defined + capacity credit, :math:`CC_{r,p,t,v}`. + + For exchange technologies (i.e., inter-regional transmission), reserve contributions + are added to the downstream region but *subtracted* from the upstream region. This is + because, since they are not generating any power, their summed contribution across + regions should be zero. + + .. math:: + :label: reserve_margin_static + + &\sum_{t \in T^{res} \setminus T^{x}, V} {CC_{r,p,t,v} + \cdot \textbf{CAP}_{r,p,t,v} \cdot + SEG_{s^*,d^*} \cdot C2A_{r,t} }\\ + &+ \sum_{t \in T^{res} \cap T^{x}, V} {CC_{r_i-r,p,t,v} + \cdot \textbf{CAP}_{r_i-r,p,t,v} \cdot + SEG_{s^*,d^*} \cdot C2A_{r_i-r,t} }\\ + &- \sum_{t \in T^{res} \cap T^{x}, V} {CC_{r-r_i,p,t,v} + \cdot \textbf{CAP}_{r-r_i,p,t,v} \cdot + SEG_{s^*,d^*} \cdot C2A_{r-r_i,t} }\\ + &\geq \left [ \sum_{ t \in T^{res} \setminus T^{x} \setminus T^a,V,I,O } + \textbf{FO}_{r, p, s, d, i, t, v, o}\right.\\ + &+ \sum_{ t \in T^{res} \cap T^a,V,I,O } + \begin{cases} DSD_{r,s,d,o} & \text{if } o \in C^d \\ + SEG_{s,d} & \text{otherwise} \end{cases} + \cdot \textbf{FOA}_{r, p, i, t, v, o}\\ + &+ \sum_{ t \in T^{res} \cap T^{x},V,I,O } \textbf{FO}_{r_i-r, p, s, d, i, t, v, o}\\ + &- \sum_{ t \in T^{res} \cap T^{x},V,I,O } \textbf{FI}_{r-r_i, p, s, d, i, t, v, o}\\ + &- \left.\sum_{ t \in T^{res} \cap T^{s},V,I,O } \textbf{FI}_{r, p, s, d, i, t, v, o} + \right] + \cdot (1 + PRM_r)\\ + + \\ + &\qquad\qquad\forall \{r, p, s, d\} \in \Theta_{\text{ReserveMargin}} \text{and} \forall + r_i \in R + """ + if (not model.tech_reserve) or ( + (r, p) not in model.process_reserve_periods + ): # If reserve set empty or if r,p not in M.processReservePeriod, skip the constraint + return Constraint.Skip + + available = sum( + value(model.capacity_credit[r, p, t, v]) + * model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * value(model.segment_fraction[s, d]) + for (t, v) in model.process_reserve_periods[r, p] + if t not in model.tech_uncap + ) + + # The above code does not consider exchange techs, e.g. electricity + # transmission between two distinct regions. + # We take exchange takes into account below. + # Note that a single exchange tech linking regions Ri and Rj is twice + # defined: once for region "Ri-Rj" and once for region "Rj-Ri". + + # First, determine the amount of firm capacity each exchange tech + # contributes. + for r1r2 in model.regional_indices: + if '-' not in r1r2: + continue + if ( + r1r2, + p, + ) not in model.process_reserve_periods: # ensure r1r2 is a valid reserve provider in p + continue + r1, r2 = r1r2.split('-') + + # Only consider exchange technologies connecting to this region + if r2 == r: + # Add the firm capacity commitment TO this region + # (this region was guaranteed an import of power) + available += sum( + value(model.capacity_credit[r1r2, p, t, v]) + * model.v_capacity[r1r2, p, t, v] + * value(model.capacity_to_activity[r1r2, t]) + * value(model.segment_fraction[s, d]) + for (t, v) in model.process_reserve_periods[r1r2, p] + ) + elif r1 == r: + # Subtract the firm capacity commitment FROM this region + # (this region guaranteed an export of power) + available -= sum( + value(model.capacity_credit[r1r2, p, t, v]) + * model.v_capacity[r1r2, p, t, v] + * value(model.capacity_to_activity[r1r2, t]) + * value(model.segment_fraction[s, d]) + for (t, v) in model.process_reserve_periods[r1r2, p] + ) + + return available + + +# ============================================================================ +# PYOMO CONSTRAINT RULE +# ============================================================================ + + +def reserve_margin_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay +) -> ExprLike: + # Get available generation in this time slice depending on method specified in config file + match model.reserve_margin_method.first(): + case 'static': + available = reserve_margin_static(model, r, p, s, d) + case 'dynamic': + available = reserve_margin_dynamic(model, r, p, s, d) + case _: + msg = ( + f"Invalid reserve margin parameter '{model.reserve_margin_method.first()}'. " + 'Check the config file.' + ) + logger.error(msg) + raise ValueError(msg) + + # In most Temoa input databases, demand is endogenous, so we use electricity + # generation instead as a proxy for electricity demand. + # Non-annual generation + total_generation = sum( + model.v_flow_out[r, p, s, d, S_i, t, S_v, S_o] + for (t, S_v) in model.process_reserve_periods[r, p] + if t not in model.tech_annual + for S_i in model.process_inputs[r, p, t, S_v] + for S_o in model.process_outputs_by_input[r, p, t, S_v, S_i] + ) + + # Generators might serve demands directly + # Annual generation + total_generation += sum( + ( + value(model.demand_specific_distribution[r, p, s, d, S_o]) + if S_o in model.commodity_demand + else value(model.segment_fraction[s, d]) + ) + * model.v_flow_out_annual[r, p, S_i, t, S_v, S_o] + for (t, S_v) in model.process_reserve_periods[r, p] + if t in model.tech_annual + for S_i in model.process_inputs[r, p, t, S_v] + for S_o in model.process_outputs_by_input[r, p, t, S_v, S_i] + ) + + # We must take into account flows into storage technologies. + # Flows into storage technologies need to be subtracted from the + # load calculation. + total_generation -= sum( + model.v_flow_in[r, p, s, d, S_i, t, S_v, S_o] + for (t, S_v) in model.process_reserve_periods[r, p] + if t in model.tech_storage + for S_i in model.process_inputs[r, p, t, S_v] + for S_o in model.process_outputs_by_input[r, p, t, S_v, S_i] + ) + + # Electricity imports and exports via exchange techs are accounted + # for below: + for r1r2 in model.regional_indices: # ensure the region is of the form r1-r2 + if '-' not in r1r2: + continue + if ( + r1r2, + p, + ) not in model.process_reserve_periods: # ensure r1r2 is a valid reserve provider in p + continue + r1, r2 = r1r2.split('-') + # First, determine the exports, and subtract this value from the + # total generation. + if r1 == r: + total_generation -= sum( + model.v_flow_out[r1r2, p, s, d, S_i, t, S_v, S_o] + / get_variable_efficiency(model, r1r2, p, s, d, S_i, t, S_v, S_o) + for (t, S_v) in model.process_reserve_periods[r1r2, p] + for S_i in model.process_inputs[r1r2, p, t, S_v] + for S_o in model.process_outputs_by_input[r1r2, p, t, S_v, S_i] + ) + # Second, determine the imports, and add this value from the + # total generation. + elif r2 == r: + total_generation += sum( + model.v_flow_out[r1r2, p, s, d, S_i, t, S_v, S_o] + for (t, S_v) in model.process_reserve_periods[r1r2, p] + for S_i in model.process_inputs[r1r2, p, t, S_v] + for S_o in model.process_outputs_by_input[r1r2, p, t, S_v, S_i] + ) + + requirement = total_generation * (1 + value(model.planning_reserve_margin[r])) + return available >= requirement diff --git a/temoa/components/storage.py b/temoa/components/storage.py new file mode 100644 index 000000000..2cce3ce57 --- /dev/null +++ b/temoa/components/storage.py @@ -0,0 +1,614 @@ +# temoa/components/storage.py +""" +Defines the energy storage-related components of the Temoa model. + +This module is responsible for modeling the behavior of storage technologies, +including: +- Defining the state variables for storage levels (both daily and seasonal). +- Enforcing the conservation of energy from one time slice to the next. +- Constraining the storage level to be within the device's energy capacity. +- Constraining the charge, discharge, and throughput rates to be within the + device's power capacity. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import TYPE_CHECKING + +from pyomo.environ import Constraint, value + +from .utils import Operator, get_capacity_factor, get_variable_efficiency, operator_expression + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + + from ..types import ExprLike, Period, Region, Season, Technology, TimeOfDay, Vintage + + +logger = getLogger(__name__) + + +# ============================================================================ +# PYOMO INDEX SET FUNCTIONS +# ============================================================================ + + +def storage_level_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + return model.storage_level_indices_rpsdtv + + +def seasonal_storage_level_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, Technology, Vintage]]: + return model.seasonal_storage_level_indices_rpstv + + +def seasonal_storage_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + return { + (r, p, s, d, t, v) + for r, p, s, t, v in model.seasonal_storage_level_indices_rpstv + for d in model.time_of_day + } + + +def storage_init_variable_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, Technology, Vintage]]: + """Index set for v_storage_init: one per (r, p, s, t, v) for non-seasonal storage. + + Introduces a hub variable at the daily cycle wrap point. Structurally equivalent + to the original closed-cycle formulation (presolve can substitute back), but + empirically improved barrier solve time ~25% on a 16-region national model. + The mechanism is not fully understood. + """ + return { + (r, p, s, t, v) + for r, p, s, _d, t, v in model.storage_level_indices_rpsdtv + if not model.is_seasonal_storage[t] + } + + +def storage_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]]: + return model.storage_level_indices_rpsdtv + + +def limit_storage_fraction_constraint_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage, str]]: + """Expand the period-free param set to include all valid process (period, vintage) combos.""" + + bad_keys = { + (r, s, d, t, op) + for r, s, d, t, op in model.limit_storage_fraction_param_rsdt + if (not model.is_seasonal_storage[t]) != (s in model.time_season) + } + if bad_keys: + msg = ( + 'Bad keys identified in limit_storage_level_fraction table. ' + 'Regular season used for seasonal storage or sequential season ' + f'used for diurnal storage. Bad keys: {bad_keys}' + ) + logger.error(msg) + raise ValueError(msg) + + all_storage_constraints = set( + model.storage_constraints_rpsdtv | model.seasonal_storage_constraints_rpsdtv + ) + valid_keys = { + (r, p, s, d, t, v, op) + for r, s, d, t, op in model.limit_storage_fraction_param_rsdt + for p in model.time_optimize + for v in model.process_vintages.get((r, p, t), []) + if (r, p, s, d, t, v) in all_storage_constraints + } + + return valid_keys + + +# ============================================================================ +# PYOMO CONSTRAINT RULES +# ============================================================================ + +# --- Core Energy Balance Constraints --- + + +def storage_energy_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> ExprLike: + r""" + This constraint enforces the continuity of storage level between time slices. + + Uses the :code:`time_next` mapping to chain storage levels forward. For + non-seasonal storage, :math:`v\_storage\_init` replaces :math:`v\_storage\_level` + on the LHS at the daily cycle wrap point (last time-of-day). Combined with + the tie-back constraint (:math:`SL_{d_{last}} = SI`), this is structurally + equivalent to a closed cycle. + + Empirically, this swap improved barrier solve time ~25% on a 16-region + national model, though the structural reason is not fully understood. + + **Non-seasonal, last time-of-day (d_last):** + + .. math:: + {SI}_{r,p,s,t,v} + \text{net\_charge} = {SL}_{r,p,s_{next},d_{next},t,v} + + **All other time slices (non-seasonal and seasonal):** + + .. math:: + {SL}_{r,p,s,d,t,v} + \text{net\_charge} = {SL}_{r,p,s_{next},d_{next},t,v} + + For seasonal storage, the last time-of-day is skipped (handled by + SeasonalStorageEnergy_constraint). + """ + + # Seasonal storage: skip d_last (handled by SeasonalStorageEnergy_constraint) + if model.is_seasonal_storage[t] and d == model.time_of_day.last(): + return Constraint.Skip + + charge = sum( + model.v_flow_in[r, p, s, d, S_i, t, v, S_o] + * get_variable_efficiency(model, r, p, s, d, S_i, t, v, S_o) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + discharge = sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_o in model.process_outputs[r, p, t, v] + for S_i in model.process_inputs_by_output[r, p, t, v, S_o] + ) + + stored_energy = charge - discharge + + s_next: Season + d_next: TimeOfDay + s_next, d_next = model.time_next[s, d] + + if d == model.time_of_day.last(): + # Non-seasonal at d_last: use v_storage_init instead of v_storage_level + expr = ( + model.v_storage_init[r, p, s, t, v] + stored_energy + == model.v_storage_level[r, p, s_next, d_next, t, v] + ) + else: + expr = ( + model.v_storage_level[r, p, s, d, t, v] + stored_energy + == model.v_storage_level[r, p, s_next, d_next, t, v] + ) + + return expr + + +def storage_level_at_last_tod_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, t: Technology, v: Vintage +) -> ExprLike: + """Tie v_storage_level at d_last to v_storage_init for non-seasonal storage. + + The storage_energy_constraint uses v_storage_init at d_last instead of + v_storage_level. This equality ensures v_storage_level[d_last] still + reflects the correct state for any constraints that reference it. + """ + d_last = model.time_of_day.last() + return model.v_storage_level[r, p, s, d_last, t, v] == model.v_storage_init[r, p, s, t, v] + + +def seasonal_storage_energy_constraint( + model: TemoaModel, r: Region, p: Period, s_seq: Season, t: Technology, v: Vintage +) -> ExprLike: + r""" + This constraint enforces the continuity of state of charge between seasons for seasonal + storage. Sequential season storage level increases by the matched season's net charge + over that entire day, adjusted for number of days represented by sequential vs non-sequential + seasons. Only applies to storage technologies in the :code:`tech_seasonal_storage` set. + :math:`s^*` represents the matching non-sequential season for the sequential season + :math:`s^{seq}`. + + .. math:: + :label: Storage Energy (Sequential Seasons) + + \mathbf{SSL}_{r,p,s^{seq},t,v} + + \frac{\text{SFS}_{s^{seq}}}{\text{SFS}_{s^*}} + \cdot \left(\mathbf{SL}_{r,p,s^*,d_{last},t,v} + + \sum_{I,O} \mathbf{FI}_{r,p,s^*,d_{last},i,t,v,o} \cdot EFF_{r,i,t,v,o} + - \sum_{I,O} \mathbf{FO}_{r,p,s^*,d_{last},i,t,v,o} + \right) + + = \frac{\text{SFS}_{s^{seq}_{next}}}{\text{SFS}_{s^*_{next}}} + \cdot \mathbf{SL}_{r,p,s_{next}^*,d_{first},t,v} + + \mathbf{SSL}_{r,p,s^{seq}_{next},t,v} + + .. figure:: images/ldes_chain.* + :align: center + :width: 100% + :figclass: align-center + :figwidth: 60% + + How sequential seasons chain together for seasonal storage. Hatched area is + seasonal_storage_level :math:`SSL_{r,p,s^{seq},t,v}`. Vertical lines are + StorageLevel :math:`SL_{r,p,s^*,d,t,v}`. Green line is net seasonal storage + level :math:`SSL_{r,p,s^{seq},t,v} + SL_{r,p,s^*,d,t,v}`. Background grey + lines show how storage levels from non-sequential seasons are combined + in sequential seasons. Dashed line is SeasonalStorageEnergyUpperBound. + Sequential seasons two and four here are each two days while one and three + are each one day. + """ + + s: Season = model.sequential_to_season[s_seq] + + # This is the sum of all input=i sent TO storage tech t of vintage v with + # output=o in p,s + charge = sum( + model.v_flow_in[r, p, s, model.time_of_day.last(), S_i, t, v, S_o] + * get_variable_efficiency(model, r, p, s, model.time_of_day.last(), S_i, t, v, S_o) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + # This is the sum of all output=o withdrawn FROM storage tech t of vintage v + # with input=i in p,s + discharge = sum( + model.v_flow_out[r, p, s, model.time_of_day.last(), S_i, t, v, S_o] + for S_o in model.process_outputs[r, p, t, v] + for S_i in model.process_inputs_by_output[r, p, t, v, S_o] + ) + + s_seq_next: Season = model.time_next_sequential[s_seq] + s_next: Season = model.sequential_to_season[s_seq_next] + + # Flows and StorageLevel are adjusted for the weight of seasons and so must be + # readjusted for the relative weight of the sequential season + days_adjust = value(model.segment_fraction_per_sequential_season[s_seq]) / value( + model.segment_fraction_per_season[s] + ) + days_adjust_next = value(model.segment_fraction_per_sequential_season[s_seq_next]) / value( + model.segment_fraction_per_season[s_next] + ) + + stored_energy = (charge - discharge) * days_adjust + + start = ( + model.v_seasonal_storage_level[r, p, s_seq, t, v] + + model.v_storage_level[r, p, s, model.time_of_day.last(), t, v] * days_adjust + ) + end = ( + model.v_seasonal_storage_level[r, p, s_seq_next, t, v] + + model.v_storage_level[r, p, s_next, model.time_of_day.first(), t, v] * days_adjust_next + ) + + expr = start + stored_energy == end + return expr + + +# --- Capacity and Rate Limit Constraints --- + + +def storage_energy_upper_bound_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> ExprLike: + r""" + This constraint ensures that the amount of energy stored does not exceed + the upper bound set by the energy capacity of the storage device, as calculated + on the right-hand side. + + Because the number and duration of time slices are user-defined, we need to adjust + the storage duration, which is specified in hours. First, the hourly duration is divided + by the number of hours in a year to obtain the duration as a fraction of the year. + Since the :math:`C2A` parameter assumes the conversion of capacity to annual activity, + we need to express the storage duration as fraction of a year. Then, :math:`SEG_{s,d}` + summed over the time-of-day slices (:math:`d`) multiplied by :math:`DPP` yields the + number of days per season. This step is necessary because conventional time sliced models + use a single day to represent many days within a given season. Thus, it is necessary to + scale the storage duration to account for the number of days in each season. + + .. math:: + :label: StorageEnergyUpperBound + + \textbf{SL}_{r, p, s, d, t, v} \le + \textbf{CAP}_{r,p,t,v} \cdot C2A_{r,t} \cdot \frac {SD_{r,t}}{24 \cdot DPP} + \cdot \sum_{d} SEG_{s,d} \cdot DPP + + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageEnergyUpperBound}} + + A season can represent many days. Within each season, flows are multiplied by the + number of days each season represents and, so, the upper bound needs to be adjusted + to allow day-scale flows (e.g., charge in the morning, discharge in the afternoon). + + .. figure:: images/daily_storage_representation.* + :align: center + :width: 100% + :figclass: center + :figwidth: 40% + + Representation of a 3-day season for non-seasonal (daily) storage. + """ + + if model.is_seasonal_storage[t]: + return Constraint.Skip # redundant on SeasonalStorageEnergyUpperBound + + energy_capacity = ( + model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * (value(model.storage_duration[r, t]) / (24 * value(model.days_per_period))) + * value(model.segment_fraction_per_season[s]) + * model.days_per_period # adjust for days in season + ) + + expr = model.v_storage_level[r, p, s, d, t, v] <= energy_capacity + + return expr + + +def seasonal_storage_energy_upper_bound_constraint( + model: TemoaModel, r: Region, p: Period, s_seq: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> ExprLike: + r""" + Builds off of StorageEnergyUpperBound_constraint. Enforces the max charge capacity + of seasonal storage, summing the real storage level with the superimposed sequential + seasonal storage level. :math:`s^*` represents the matching non-sequential season for + the sequential season :math:`s^{seq}`. + + .. math:: + :label: Seasonal Storage Energy Capacity + + \mathbf{SSL}_{r,p,s^{seq},t,v} + + \mathbf{SL}_{r,p,s^*,d,t,v} \cdot \frac{\text{SFS}_{s^{seq}}}{\text{SFS}_{s^*}} + \leq \mathbf{CAP}_{r,p,t,v} \cdot C2A_{r,t} \cdot \frac{SD_{r,t}}{24 \cdot DPP} + + + + Unlike non-seasonal (daily) storage, seasonal storage is allowed to carry energy + between seasons. However, through seasons representing multiple days, many days' + charge deltas have accumulated, multiplied by the number of days the season + represents. If we allowed these stacked deltas to carry between seasons then we would + be multiplying the effective energy capacity of the storage. We could just constrain + the seasonal delta to the unadjusted energy capacity, but then the final day in the + season would sit atop a season's worth of deltas, possibly exceeding our upper or + lower bound by a factor of :math:`\frac{N-1}{N}` where :math:`N` is the number of + days the sequential season represents. + + .. figure:: images/ldes_delta_problem.* + :align: center + :width: 100% + :figclass: center + :figwidth: 100% + + The energy upper bound or non-negative lower bound could be violated in a + season representing multiple days if we both adjusted the upper bound to + the number of days and allowed a seasonal delta. + + So, we do not adjust the upper energy bound for seasonal storage. This limits the + ability of seasonal storage to perform arbitrage within each season, but allows it to + carry energy between seasons realistically. + + .. figure:: images/ldes_delta_representation.* + :align: center + :width: 100% + :figclass: center + :figwidth: 40% + + Unadjusted energy upper bound constraint for seasonal storage. + """ + + s: Season = model.sequential_to_season[s_seq] + + energy_capacity = ( + model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * (value(model.storage_duration[r, t]) / (24 * value(model.days_per_period))) + ) + + # Flows and StorageLevel are adjusted for the weight of seasons and so must be + # readjusted for the relative weight of the sequential season + days_adjust = value(model.segment_fraction_per_sequential_season[s_seq]) / value( + model.segment_fraction_per_season[s] + ) + + # v_storage_level tracks the running cumulative delta in the non-sequential season, + # so must be adjusted + # to the size of the sequential season + running_day_delta = model.v_storage_level[r, p, s, d, t, v] * days_adjust + + expr = model.v_seasonal_storage_level[r, p, s_seq, t, v] + running_day_delta <= energy_capacity + + return expr + + +def storage_charge_rate_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> ExprLike: + r""" + + This constraint ensures that the charge rate of the storage unit is + limited by the power capacity (typically GW) of the storage unit. + + .. math:: + :label: StorageChargeRate + + \sum_{I, O} \textbf{FIS}_{r, p, s, d, i, t, v, o} \cdot EFF_{r,i,t,v,o} + \le + \textbf{CAP}_{r,p,t,v} \cdot C2A_{r,t} \cdot SEG_{s,d} + + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageChargeRate}} + + """ + # Calculate energy charge in each time slice + slice_charge = sum( + model.v_flow_in[r, p, s, d, S_i, t, v, S_o] + * get_variable_efficiency(model, r, p, s, d, S_i, t, v, S_o) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + # Maximum energy charge in each time slice + max_charge = ( + model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * value(model.segment_fraction[s, d]) + ) + + # Energy charge cannot exceed the power capacity of the storage unit + expr = slice_charge <= max_charge + + return expr + + +def storage_discharge_rate_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> ExprLike: + r""" + + This constraint ensures that the discharge rate of the storage unit + is limited by the power capacity (typically GW) of the storage unit. + + .. math:: + :label: StorageDischargeRate + + \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} + \le + \textbf{CAP}_{r,p,t,v} \cdot C2A_{r,t} \cdot SEG_{s,d} + + \\ + \forall \{r,p, s, d, t, v\} \in \Theta_{\text{StorageDischargeRate}} + """ + # Calculate energy discharge in each time slice + slice_discharge = sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_o in model.process_outputs[r, p, t, v] + for S_i in model.process_inputs_by_output[r, p, t, v, S_o] + ) + + # Maximum energy discharge in each time slice + max_discharge = ( + model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * value(model.segment_fraction[s, d]) + ) + + # Energy discharge cannot exceed the capacity of the storage unit + expr = slice_discharge <= max_discharge + + return expr + + +def storage_throughput_constraint( + model: TemoaModel, r: Region, p: Period, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> ExprLike: + r""" + + It is not enough to only limit the charge and discharge rate separately. We also + need to ensure that the maximum throughput (charge + discharge) does not exceed + the capacity (typically GW) of the storage unit. + + .. math:: + :label: StorageThroughput + + \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} + + + \sum_{I, O} \textbf{FIS}_{r, p, s, d, i, t, v, o} \cdot EFF_{r,i,t,v,o} + \le + \textbf{CAP}_{r,p,t,v} \cdot C2A_{r,t} \cdot SEG_{s,d} + + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageThroughput}} + """ + discharge = sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o] + for S_o in model.process_outputs[r, p, t, v] + for S_i in model.process_inputs_by_output[r, p, t, v, S_o] + ) + + charge = sum( + model.v_flow_in[r, p, s, d, S_i, t, v, S_o] + * get_variable_efficiency(model, r, p, s, d, S_i, t, v, S_o) + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + + throughput = charge + discharge + max_throughput = ( + model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * get_capacity_factor(model, r, s, d, t, v) + * value(model.segment_fraction[s, d]) + ) + expr = throughput <= max_throughput + return expr + + +# A limit but more cohesive here than in limits.py +def limit_storage_fraction_constraint( + model: TemoaModel, + r: Region, + p: Period, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, + op: str, +) -> ExprLike: + r""" + + This constraint is used if the users wishes to force a specific storage charge level + for certain storage technologies and vintages at a certain time slice. User-specified + storage charge levels that are sufficiently different from the optimal could impact the + cost-effectiveness of storage. + + :code:`s` can be a season from the :code:`time_season` set or a sequential season from + the :code:`time_sequential_season` set. + + + .. math:: + :label: limit_storage_fraction + + \frac{\textbf{SL}_{r,p,s,d,t,v}}{\text{SFS}_s \cdot \text{DPP}} + \quad \le, \ge, \text{or} = \quad + \textbf{CAP}_{r,p,t,v} \cdot \text{C2A}_{r,t} + \cdot \frac{\text{SD}_{r,t}}{24 \cdot \text{DPP}} + \cdot \text{LSF}_{r,s,d,t} + + \\ + \forall \{r, p, s, d, t, v\} \in \Theta_{\text{limit\_storage\_fraction}} + + For seasonal storage technologies, the LHS becomes: + + .. math:: + + \textbf{SSL}_{r,p,s_{seq},t,v} + + \frac{\text{SFS}_{s_{seq}}}{\text{SFS}_{s^*}} + \cdot \textbf{SL}_{r,p,s^*,d,t,v} + """ + + energy_limit = ( + model.v_capacity[r, p, t, v] + * value(model.capacity_to_activity[r, t]) + * (value(model.storage_duration[r, t]) / (24 * value(model.days_per_period))) + * value(model.limit_storage_fraction[r, s, d, t, op]) + ) + + if model.is_seasonal_storage[t]: + s_seq: Season = s # sequential season + s = model.sequential_to_season[s_seq] # non-sequential season + + # adjust the storage level to the individual-day level + energy_level = model.v_storage_level[r, p, s, d, t, v] / ( + value(model.segment_fraction_per_season[s]) * value(model.days_per_period) + ) + + if model.is_seasonal_storage[t]: + # seasonal storage upper energy limit is absolute + energy_level = model.v_seasonal_storage_level[r, p, s_seq, t, v] + energy_level * value( + model.segment_fraction_per_sequential_season[s_seq] * value(model.days_per_period) + ) + + expr = operator_expression(energy_level, Operator(op), energy_limit) + + return expr diff --git a/temoa/components/technology.py b/temoa/components/technology.py new file mode 100644 index 000000000..e9cc254d0 --- /dev/null +++ b/temoa/components/technology.py @@ -0,0 +1,437 @@ +# temoa/components/technology.py +""" +Defines the core technology-related components of the Temoa model. + +This module is the foundation of the model, responsible for: +- Pre-computing the core data structures that link technologies to commodities, + time periods, and vintages based on the `efficiency` parameter. +- Handling technology lifetimes, including survival curve validation and interpolation. +- Defining Pyomo index sets for core technology parameters. +- Validating model inputs related to technologies, efficiencies, and commodities. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import TYPE_CHECKING, cast + +from pyomo.environ import value + +if TYPE_CHECKING: + from collections.abc import Iterable + + from temoa.core.model import TemoaModel + from temoa.types import Period, Region, Technology, Vintage + +logger = getLogger(__name__) + +# ============================================================================ +# HELPER FUNCTIONS +# ============================================================================ + + +def gather_group_techs(model: TemoaModel, t_or_g: Technology) -> Iterable[Technology]: + if t_or_g in model.tech_group_names: + return model.tech_group_members[t_or_g] + elif '+' in t_or_g: + return [cast('Technology', tech) for tech in t_or_g.split('+')] + else: + return (t_or_g,) + + +# ============================================================================ +# PYOMO INDEX SETS AND PARAMETER RULES +# ============================================================================ + + +def model_process_life_indices( + model: TemoaModel, +) -> set[tuple[Region, Period, Technology, Vintage]]: + """ + Returns the set of sensical (region, period, tech, vintage) tuples. The tuple indicates + the periods in which a process is active, distinct from TechLifeFracIndices that + returns indices only for processes that EOL mid-period. + """ + return model.active_activity_rptv + + +def lifetime_process_indices(model: TemoaModel) -> set[tuple[Region, Technology, Vintage]]: + """ + Based on the efficiency parameter's indices, this function returns the set of + process indices that may be specified in the lifetime_process parameter. + """ + indices = {(r, t, v) for r, i, t, v, o in set(model.efficiency.sparse_keys())} + indices = indices | set(model.existing_capacity.sparse_keys()) + + return indices + + +def get_default_survival( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage +) -> float: + """ + Getting lifetime_survival_curve where it is not defined + If this is a survival curve process, return 0 (likely beyond EOL) + Otherwise return 1 (no survival curve based EOL) + """ + return 0.0 if model.is_survival_curve_process[r, t, v] else 1.0 + + +def get_default_process_lifetime(model: TemoaModel, r: Region, t: Technology, v: Vintage) -> int: + """ + This initializer used to initialize the lifetime_process parameter from lifetime_tech where + needed + + Priority: + 1. Specified in lifetime_process data (provided as a fill and would not call this function) + 2. Specified in lifetime_tech data + 3. The default value from the lifetime_tech param (automatic) + :param M: generic model reference (not used) + :param r: region + :param t: tech + :param v: vintage + :return: the final lifetime value + """ + return value(model.lifetime_tech[r, t]) + + +def param_process_life_fraction_rule( + model: TemoaModel, r: Region, p: Period, t: Technology, v: Vintage +) -> float: + r""" + Get the effective capacity of a process :math:`` in a period :math:`p`. + + Accounts for mid-period end of life or average survival over the period + for processes using survival curves. + """ + + period_length = value(model.period_length[p]) + + if model.is_survival_curve_process[r, t, v]: + # Sum survival fraction over the period + years_remaining = sum( + value(model.lifetime_survival_curve[r, _p, t, v]) + for _p in range(p, p + period_length, 1) + ) + else: + # Remaining life years within the EOL period + years_remaining = v + value(model.lifetime_process[r, t, v]) - p + + if years_remaining >= period_length: + # try to avoid floating point round-off errors for the common case. + return 1 + + frac = years_remaining / float(period_length) + return frac + + +# ============================================================================ +# PRE-COMPUTATION AND VALIDATION FUNCTIONS +# ============================================================================ + + +def populate_core_dictionaries(model: TemoaModel) -> None: + """ + Populates the core sparse dictionaries from the `efficiency` parameter. + + This function is foundational for creating the sparse indices used throughout + the model, defining process relationships, inputs, outputs, and active periods. + + Populates: + - M.process_inputs, M.process_outputs + - M.commodity_down_stream_process, M.commodity_up_stream_process + - M.process_outputs_by_input, M.process_inputs_by_output + - M.process_vintages, M.process_periods + - M.used_techs + """ + logger.debug('Populating core sparse dictionaries from efficiency parameter.') + first_period = min(model.time_future) + exist_indices = set(model.existing_capacity.sparse_keys()) + + for r, i, t, v, o in set(model.efficiency.sparse_keys()): + # A. Basic data validation and warnings + process = (r, t, v) + lifetime = value(model.lifetime_process[process]) + if v in model.vintage_exist: + if process not in exist_indices and t not in model.tech_uncap: + logger.warning( + 'Warning: %s has a specified efficiency, but does not ' + 'have any existing install base (existing_capacity).', + process, + ) + continue + if t not in model.tech_uncap and value(model.existing_capacity[process]) <= 0: + msg = ( + f'Notice: Non-positive existing capacity declaration {process}. ' + 'This is not supported for processes surviving into future periods.' + ) + logger.error(msg) + raise ValueError(msg) + if v + lifetime <= first_period: + logger.info( + '%s specified as existing_capacity, but its ' + 'lifetime (%s years) does not extend past the ' + 'beginning of time_future (%s).', + process, + lifetime, + first_period, + ) + + if model.efficiency[r, i, t, v, o] == 0: + logger.info( + 'Notice: Unnecessary specification of efficiency for %s. ' + 'Specifying an efficiency of zero may be omitted.', + (r, i, t, v, o), + ) + continue + + model.used_techs.add(t) + + # B. Loop through time periods to build time-dependent relationships + for p in model.time_optimize: + # Skip if tech is not invented or is already retired + if p < v or v + lifetime <= p: + continue + + pindex = (r, p, t, v) + + # C. Initialize dictionary keys if not present + if pindex not in model.process_inputs: + model.process_inputs[pindex] = set() + model.process_outputs[pindex] = set() + if (r, p, i) not in model.commodity_down_stream_process: + model.commodity_down_stream_process[r, p, i] = set() + if (r, p, o) not in model.commodity_up_stream_process: + model.commodity_up_stream_process[r, p, o] = set() + if (r, p, t, v, i) not in model.process_outputs_by_input: + model.process_outputs_by_input[r, p, t, v, i] = set() + if (r, p, t, v, o) not in model.process_inputs_by_output: + model.process_inputs_by_output[r, p, t, v, o] = set() + if (r, p, t) not in model.process_vintages: + model.process_vintages[r, p, t] = set() + if (r, t, v) not in model.process_periods: + model.process_periods[r, t, v] = set() + + # D. Populate the dictionaries + model.process_inputs[pindex].add(i) + model.process_outputs[pindex].add(o) + model.commodity_down_stream_process[r, p, i].add((t, v)) + model.commodity_up_stream_process[r, p, o].add((t, v)) + model.process_outputs_by_input[r, p, t, v, i].add(o) + model.process_inputs_by_output[r, p, t, v, o].add(i) + model.process_vintages[r, p, t].add(v) + model.process_periods[r, t, v].add(p) + + +def create_survival_curve(model: TemoaModel) -> None: + rtv_interpolated = set() # so we only need one warning + + for r, _, t, v, _ in model.efficiency.sparse_keys(): + model.is_survival_curve_process[r, t, v] = False # by default + for r, t, v in model.existing_capacity.sparse_keys(): + model.is_survival_curve_process[r, t, v] = False # by default + + # Collect rptv indices into (r, t, v): p dictionary + for r, p, t, v in model.lifetime_survival_curve.sparse_keys(): + model.survival_curve_periods.setdefault((r, t, v), set()).add(p) + model.is_survival_curve_process[r, t, v] = True + + # Go through all the periods for each (r, t, v) in order + for r, t, v in model.survival_curve_periods: + periods_rtv: list[int] = sorted(model.survival_curve_periods[r, t, v]) + + p_first = periods_rtv[0] + p_last = periods_rtv[-1] + eol_year = v + value(model.lifetime_process[r, t, v]) + + if ( + p_first != v + or p_last != eol_year + or value(model.lifetime_survival_curve[r, v, t, v]) != 1 + or value(model.lifetime_survival_curve[r, eol_year, t, v]) != 0 + ): + msg = ( + 'lifetime_survival_curve must be defined as 1 at start and 0 at end of life. Must ' + f'define ({r}, >{v}<, {t}, {v}) = 1 and ({r}, >{eol_year}<, {t}, {v}) = 0.' + ) + logger.error(msg) + raise ValueError(msg) + + # Collect a list of processes that needed to be interpolated, for warning + if periods_rtv != list(range(p_first, p_last + 1, 1)): + rtv_interpolated.add((r, t, v)) + + between_periods: list[Period] = [] + for i, p in enumerate(periods_rtv): + if i == 0: + continue # Cant look back from first period. Could be zero but hey why not + + # Check that the survival curve monotonically decreases + p_prev = periods_rtv[i - 1] + lsc = value(model.lifetime_survival_curve[r, p, t, v]) + lsc_prev = value(model.lifetime_survival_curve[r, p_prev, t, v]) + if lsc - lsc_prev > 0.0001: + msg = ( + f'lifetime_survival_curve fraction increases going forward in time from ' + f'{(r, p_prev, t, v)} to {(r, p, t, v)}. ' + 'This is not allowed.' + ) + logger.error(msg) + raise ValueError(msg) + + # Linearly interpolate to fill in missing years + if p - p_prev > 1: + _between_periods = [cast('Period', _p) for _p in range(p_prev + 1, p, 1)] + for _p in _between_periods: + x = (_p - p_prev) / (p - p_prev) + lsc_x = lsc_prev + x * (lsc - lsc_prev) + model.lifetime_survival_curve[r, _p, t, v] = lsc_x + between_periods.extend(_between_periods) + + if lsc < 0.0001: + if p != p_last: + msg = ( + 'Survival curve must be strictly positive during the ' + 'lifetime of the process. Found non-positive value at ' + f'period {p} for ({r, t, v}) which could cause a ' + 'division by zero error.' + ) + logger.error(msg) + raise ValueError(msg) + continue + + model.survival_curve_periods[r, t, v].update(between_periods) + + if rtv_interpolated: + logger.info( + 'For the purposes of investment cost accounting, lifetime_survival_curve must be ' + 'defined for each individual year. Gaps between defined years will be filled by ' + 'linear interpolation. Otherwise, these individual years can be defined manually. ' + 'Interpolated processes: %s', + list(rtv_interpolated), + ) + + +def check_efficiency_indices(model: TemoaModel) -> None: + """ + Ensure that there are no unused items in any of the efficiency index sets. + """ + # TODO: This could be upgraded to scan for finer resolution + # by checking by REGION and PERIOD... Each region/period is unique. + c_outputs = {o for r, i, t, v, o in model.efficiency.sparse_keys()} + c_outputs = c_outputs | {o for r, t, v, o in model.end_of_life_output.sparse_keys()} + + diff = model.commodity_demand - c_outputs + if diff: + msg = ( + 'Unmet demands. The following demand commodities are ' + 'not the output of any process.' + '\n\n Element(s): {}' + ) + diff_str = (str(i) for i in diff) + f_msg = msg.format(', '.join(diff_str)) + logger.error(f_msg) + raise ValueError(f_msg) + + c_inputs = {i for r, i, t, v, o in model.efficiency.sparse_keys()} + c_inputs = c_inputs | {i for r, i, t, v in model.construction_input.sparse_keys()} + c_carrier = c_inputs | c_outputs + + symdiff = c_carrier.symmetric_difference(model.commodity_carrier) + if symdiff: + msg = ( + 'Unused or unspecified physical carriers. Either add or remove ' + 'the following elements to the Set commodity_carrier.' + '\n\n Element(s): {}' + ) + symdiff_str: set[str] = {str(i) for i in symdiff} + f_msg = msg.format(', '.join(symdiff_str)) + logger.error(f_msg) + raise ValueError(f_msg) + + techs = {t for r, i, t, v, o in model.efficiency.sparse_keys()} + techs = techs | {t for r, t, v, o in model.end_of_life_output.sparse_keys()} + techs = techs | {t for r, i, t, v in model.construction_input.sparse_keys()} + techs = techs | {t for r, e, t, v in model.emission_end_of_life.sparse_keys()} + + symdiff = techs.symmetric_difference(model.tech_production) + if symdiff: + msg = ( + 'Unused or unspecified technologies. Either add or remove ' + 'the following technologies.\n\n Technologies: {}' + ) + symdiff_str2: set[str] = {str(i) for i in symdiff} + f_msg = msg.format(', '.join(symdiff_str2)) + logger.error(f_msg) + raise ValueError(f_msg) + + +def check_efficiency_variable(model: TemoaModel) -> None: + count_ritvo = {} + # Pull non-variable efficiency by default + for r, i, t, v, o in model.efficiency.sparse_keys(): + if (r, t, v) not in model.process_periods: + # Probably an existing vintage that retires in p0 + # Still want it for end of life flows + continue + model.is_efficiency_variable[r, i, t, v, o] = False + count_ritvo[r, i, t, v, o] = 0 + + annual = set() + # Check for bad values and count up the good ones + for r, _s, _d, i, t, v, o in model.efficiency_variable.sparse_keys(): + if t in model.tech_annual: + annual.add(t) + + # Good value, pull from efficiency_variable table + if (r, i, t, v, o) in count_ritvo: + count_ritvo[r, i, t, v, o] += 1 + + for t in annual: + msg = ( + f'Variable efficiencies were provided for the annual technology {t}, which has ' + 'no variable output. This will only be applied to flows on non-annual commodities. ' + 'This is ambiguous behaviour and not recommended.' + ) + logger.warning(msg) + + # Check if all possible values have been set as variable + # log a warning if some are missing (allowed but maybe accidental) + num_seg = len(model.time_season) * len(model.time_of_day) + for (r, i, t, v, o), count in count_ritvo.items(): + if count > 0: + model.is_efficiency_variable[r, i, t, v, o] = True + if count < num_seg: + logger.info( + 'Some but not all efficiency_variable values were set (%i out of a possible ' + '%i) for: %s Missing values will default to value set in efficiency table.', + count, + num_seg, + (r, i, t, v, o), + ) + + +def check_existing_capacity(model: TemoaModel) -> None: + """ + Check that all existing capacities are properly accounted for in the model. + """ + for r, t, v in model.existing_capacity.sparse_keys(): + cap = value(model.existing_capacity[r, t, v]) + if cap <= 0: + msg = ( + f'Existing capacity {r, t, v} has non-positive capacity {cap}. ' + 'This entry will be ignored.' + ) + logger.warning(msg) + continue + if t not in model.tech_all: + continue + life = value(model.lifetime_process[r, t, v]) + if (r, t, v) not in model.process_periods and v + life > model.time_optimize.first(): + msg = ( + f'Existing capacity {r, t, v} with lifetime {life} and capacity {cap} ' + 'should extend into future periods but it is not in process periods. ' + 'Was it included in the Efficiency table?' + ) + logger.error(msg) + raise ValueError(msg) diff --git a/temoa/components/time.py b/temoa/components/time.py new file mode 100644 index 000000000..0877a40b6 --- /dev/null +++ b/temoa/components/time.py @@ -0,0 +1,402 @@ +# temoa/components/time.py +""" +This module contains components related to time indexing in the Temoa model. + +It is responsible for: +- Validating the core time sets (`time_exist`, `time_future`). +- Validating the user-defined time-slice fractions (`time_segment_fraction`). +- Creating the sequence of time slices (`time_next`) based on the chosen + sequencing method (e.g., `seasonal_timeslices`, `consecutive_days`). +- Creating and validating the superimposed sequential seasons used for + seasonal storage and inter-season ramping constraints. +""" + +from __future__ import annotations + +from logging import getLogger +from typing import TYPE_CHECKING + +from pyomo.environ import value + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types import Period, Season, TimeOfDay + + +logger = getLogger(__name__) + + +# ============================================================================ +# INITIAL VALIDATION FUNCTIONS +# These are called early in the model build to ensure time data is coherent. +# ============================================================================ + + +def validate_time(model: TemoaModel) -> None: + """ + We check for integer status here, rather than asking Pyomo to do this via + a 'within=Integers' clause in the definition so that we can have a very + specific error message. If we instead use Pyomo's mechanism, the + python invocation of Temoa throws an error (including a traceback) + that has proven to be scary and/or impenetrable for the typical modeler. + """ + logger.debug('Started validating time index') + for year in model.time_exist: + if isinstance(year, int): + continue + + msg: str = ( + f'Set "time_exist" requires integer-only elements.\n\n Invalid element: "{year}"' + ) + logger.error(msg) + raise Exception(msg) + + for year in model.time_future: + if isinstance(year, int): + continue + + msg = f'Set "time_future" requires integer-only elements.\n\n invalid element: "{year}"' + logger.error(msg) + raise Exception(msg) + + if len(model.time_future) < 2: + msg = ( + 'Set "time_future" needs at least 2 specified years. \nTemoa ' + 'treats the integer numbers specified in this set as boundary years \n' + 'between periods, and uses them to automatically ascertain the length \n' + '(in years) of each period. Note that this means that there will be \n' + 'one less optimization period than the number of elements in this set.' + ) + + logger.error(msg) + raise RuntimeError(msg) + + # Ensure that the time_exist < time_future + if len(model.time_exist) > 0: + max_exist: int = max(model.time_exist) + min_horizon: int = min(model.time_future) + + if not (max_exist < min_horizon): + msg = ( + 'All items in time_future must be larger than in time_exist.' + '\ntime_exist max: {}' + '\ntime_future min: {}' + ) + logger.error(msg.format(max_exist, min_horizon)) + raise Exception(msg.format(max_exist, min_horizon)) + logger.debug('Finished validating time') + + +def validate_segment_fraction(model: TemoaModel) -> None: + """Compute and validate segment_fraction from season fractions and TOD hours.""" + + # Compute segment_fraction[s, d] = segment_fraction_per_season[s] * hours[d] / total_hours + total_hours: float = sum(value(model.time_of_day_hours[d]) for d in model.time_of_day) + for s in model.time_season: + for d in model.time_of_day: + model.segment_fraction[s, d] = ( + value(model.segment_fraction_per_season[s]) + * value(model.time_of_day_hours[d]) + / total_hours + ) + expected_keys: set[tuple[str, str]] = { + (s, d) for s in model.time_season for d in model.time_of_day + } + keys: set[tuple[str, str]] = set(model.segment_fraction.sparse_keys()) + + if expected_keys != keys: + extra: set[tuple[str, str]] = keys.difference(expected_keys) + missing: set[tuple[str, str]] = expected_keys.difference(keys) + msg: str = ( + 'Computed segment_fraction elements do not match time_season × time_of_day.' + f'\n\nIndices missing from segment_fraction:\n{missing}' + f'\n\nIndices in segment_fraction not matching time_season/time_of_day:\n' + f'{extra}' + ) + logger.error(msg) + raise ValueError(msg) + + total: float = sum(value(model.segment_fraction[k]) for k in keys) + + if abs(float(total) - 1.0) > 0.001: + # We can't explicitly test for "!= 1.0" because of incremental rounding + # errors associated with the specification of segment_fraction by time slice, + # but we check to make sure it is within the specified tolerance. + + def get_str_padding(obj: object) -> int: + return len(str(obj)) + + key_padding: int = max(map(get_str_padding, keys)) + + # Works out to something like "%-25s = %s" + + items_list: list[tuple[tuple[object, object], object]] = sorted( + [(k, model.segment_fraction[k]) for k in keys] + ) + items: str = '\n '.join(f'{str(k):<{key_padding}} = {v}' for k, v in items_list) + + msg = ( + 'The values of segment_fraction do not sum to 1. ' + 'Each item in segment_fraction represents a fraction of a year, so they must ' + f'total to 1. Current values:\n {items}\n\tsum = {total}' + ) + logger.error(msg) + raise Exception(msg) + + +def validate_time_manual(model: TemoaModel) -> None: + """ + If using the manual sequencing table, check that all defined states are valid. + segment_fraction keys are already validated, so we compare time_manual against those. + + """ + # Only check TimeNext if it is actually being used + if model.time_sequencing.first() != 'manual': + return + + segment_fraction_sd: set[tuple[str, str]] = set(model.segment_fraction.sparse_keys()) + time_manual_sd: set[tuple[str, str]] = {(s, d) for s, d, s_next, d_next in model.time_manual} + time_manual_sd_next: set[tuple[str, str]] = { + (s_next, d_next) for s, d, s_next, d_next in model.time_manual + } + + missing_sd: set[tuple[str, str]] = segment_fraction_sd.difference(time_manual_sd) + missing_sd_next: set[tuple[str, str]] = segment_fraction_sd.difference(time_manual_sd_next) + if missing_sd or missing_sd_next: + msg: str = ( + 'Failed to build state sequence. ' + f'\nThese segment_fraction states were not given a next state:\n' + f'{missing_sd}\n' + f'\nThese segment_fraction states do not follow any state:\n' + f'{missing_sd_next}' + ) + logger.error(msg) + raise ValueError(msg) + + +# ============================================================================ +# PYOMO SET INITIALIZERS AND PARAMETER RULES +# ============================================================================ + + +def init_set_time_optimize(model: TemoaModel) -> list[int]: + """Initializes the `time_optimize` set (all future years except the last).""" + return sorted(model.time_future)[:-1] + + +def init_set_vintage_exist(model: TemoaModel) -> list[int]: + """Initializes the `vintage_exist` set.""" + return sorted(model.time_exist) + + +def init_set_vintage_optimize(model: TemoaModel) -> list[int]: + """Initializes the `vintage_optimize` set.""" + return sorted(model.time_optimize) + + +def param_period_length(model: TemoaModel, p: Period) -> int: + """Rule to calculate the length of each optimization period in years.""" + periods: list[int] = sorted(model.time_future) + i: int = periods.index(p) + return periods[i + 1] - periods[i] + + +# ============================================================================ +# HELPER FUNCTIONS FOR TIME SEQUENCING +# ============================================================================ + + +def loop_period_next_timeslice( + model: TemoaModel, s: Season, d: TimeOfDay +) -> tuple[Season, TimeOfDay]: + # Final time slice of final season (end of period) + # Loop state back to initial state of first season + # Loop the period + if s == model.time_season.last() and d == model.time_of_day.last(): + s_next: Season = model.time_season.first() + d_next: TimeOfDay = model.time_of_day.first() + + # Last time slice of any season that is NOT the last season + # Carry state to initial state of next season + # Carry state between seasons + elif d == model.time_of_day.last(): + s_next = model.time_season.next(s) + d_next = model.time_of_day.first() + + # Any other time slice + # Carry state to next time slice in the same season + # Continuing through this season + else: + s_next = s + d_next = model.time_of_day.next(d) + + return s_next, d_next + + +def loop_season_next_timeslice( + model: TemoaModel, s: Season, d: TimeOfDay +) -> tuple[Season, TimeOfDay]: + # We loop each season so never carrying state between seasons + s_next: Season = s + + # Final time slice of any season + # Loop state back to initial state of same season + # Loop each season + if d == model.time_of_day.last(): + d_next = model.time_of_day.first() + + # Any other time slice + # Carry state to next time slice in the same season + # Continuing through this season + else: + d_next = model.time_of_day.next(d) + + return s_next, d_next + + +# ============================================================================ +# PRE-COMPUTATION & SEQUENCE CREATION +# ============================================================================ + + +def create_time_sequence(model: TemoaModel) -> None: + logger.debug('Creating sequence of time slices.') + + # Establishing sequence of states + match model.time_sequencing.first(): + case 'consecutive_days': + msg: str = 'Running a consecutive days database.' + for s, d in model.time_season * model.time_of_day: + model.time_next[s, d] = loop_period_next_timeslice(model, s, d) + case 'seasonal_timeslices': + msg = 'Running a seasonal time slice database.' + for s, d in model.time_season * model.time_of_day: + model.time_next[s, d] = loop_season_next_timeslice(model, s, d) + case 'representative_periods': + msg = 'Running a representative periods database.' + for s, d in model.time_season * model.time_of_day: + model.time_next[s, d] = loop_season_next_timeslice(model, s, d) + case 'manual': + # Hidden feature. Define the sequence directly in the time_manual table + msg = 'Pulling time sequence from time_manual table.' + for s, d, s_next, d_next in model.time_manual: + model.time_next[s, d] = s_next, d_next + case _: + # This should have been caught in hybrid_loader + msg = ( + f"Invalid time sequencing parameter loaded '{model.time_sequencing.first()}'. " + 'Likely code error.' + ) + logger.error(msg) + raise ValueError(msg) + + msg += ' This behaviour can be changed using the time_sequencing parameter in the config file. ' + logger.info(msg) + + logger.debug('Creating superimposed sequential seasons.') + + # Superimposed sequential seasons (global, period-independent) + seasons: list[tuple[Season, Season]] = list(model.ordered_season_sequential) + for i, (s_seq, s) in enumerate(seasons): + model.sequential_to_season[s_seq] = s + if (s_seq, s) == seasons[-1]: + model.time_next_sequential[s_seq] = seasons[0][0] + else: + model.time_next_sequential[s_seq] = seasons[i + 1][0] + + logger.debug('Created time sequence.') + + +def create_time_season_to_sequential(model: TemoaModel) -> None: + if all( + ( + not model.tech_seasonal_storage, + not model.ramp_up_hourly, + not model.ramp_down_hourly, + ) + ): + # Don't need it anyway + return + + if not model.segment_fraction_per_sequential_season: + if model.time_sequencing.first() in ('consecutive_days', 'seasonal_timeslices'): + logger.info( + 'No data in time_season_sequential. By default, assuming sequential seasons ' + 'match time_season.' + ) + for s in model.time_season: + model.time_season_sequential.add(s) + model.ordered_season_sequential.add((s, s)) + model.segment_fraction_per_sequential_season[s] = value( + model.segment_fraction_per_season[s] + ) + + else: + msg = ( + f'No data in time_season_sequential but time_sequencing parameter set to ' + f'{model.time_sequencing.first()} and inter-season features used. ' + 'time_season_sequential must be filled for this type of time sequencing if ' + 'seasonal storage or inter-season constraints like ramp_up/ramp_down are used. ' + 'Check ' + 'the config file.' + ) + logger.error(msg) + raise ValueError(msg) + + seas_fracs_seq: dict[str, float] = {} + prev_frac: float = 0 + for s_seq, s in model.ordered_season_sequential: + seg_frac_seq: float = value(model.segment_fraction_per_sequential_season[s_seq]) + if ( + model.time_sequencing.first() == 'consecutive_days' + and prev_frac + and abs(seg_frac_seq - prev_frac) >= 0.001 + ): + msg = ( + 'time_sequencing set to consecutive_days but two consecutive seasons do not ' + 'represent the same fraction of the year. This discontinuity will lead to bad ' + f'model behaviour: {s_seq}, fraction: {seg_frac_seq}. ' + f'Previous fraction: {prev_frac}. Check the config file for more information.' + ) + logger.error(msg) + raise ValueError(msg) + prev_frac = seg_frac_seq # for validating next in sequence + + # Regardless of their order, make sure the total fractions add up + seas_fracs_seq[s] = seas_fracs_seq.get(s, 0) + seg_frac_seq + + # Check that time_season_sequential segment_fraction totals to 1.0 + frac_total: float = sum(seas_fracs_seq.values()) + if abs(frac_total - 1.0) >= 0.001: + logger.warning( + 'Sum of segment_fraction in time_season_sequential (%s) does not sum to 1.0.', + frac_total, + ) + + # Check that seasons used in storage seasons are actual seasons + for s in seas_fracs_seq: + if s not in model.time_season: + msg = ( + f'Season {s!r} referenced in time_season_sequential does not exist in time_season.' + ) + logger.error(msg) + raise ValueError(msg) + + for s in model.time_season: + # Check that all seasons are used in sequential seasons + if s not in seas_fracs_seq: + msg = f'Season {s!r} absent from time_season_sequential' + logger.warning(msg) + + # Check that the two tables agree on the total seasonal composition + season_frac = value(model.segment_fraction_per_season[s]) + season_frac_seq = seas_fracs_seq[s] + if abs(season_frac - season_frac_seq) >= 0.001: + msg = ( + 'Discrepancy of total seasonal composition between ' + 'time_season and time_season_sequential. Total fraction ' + 'assigned to each season should match: ' + f'time_season: {(s, season_frac)}' + f', time_season_sequential: {(s, season_frac_seq)}' + ) + logger.warning(msg) diff --git a/temoa/components/utils.py b/temoa/components/utils.py new file mode 100644 index 000000000..943b90183 --- /dev/null +++ b/temoa/components/utils.py @@ -0,0 +1,89 @@ +# temoa/components/utils.py +""" +This module contains generic, reusable utility functions for the Temoa model. + +These helpers are used by various components to perform common tasks like +building Pyomo expressions from strings or calculating time-variable efficiencies. +""" + +from __future__ import annotations + +from enum import StrEnum +from logging import getLogger +from typing import TYPE_CHECKING + +from pyomo.environ import value + +if TYPE_CHECKING: + from pyomo.core import Expression + + from temoa.core.model import TemoaModel + from temoa.types import ( + Commodity, + ExprLike, + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, + ) + + +logger = getLogger(__name__) + + +class Operator(StrEnum): + EQUAL = 'e' + LESS_EQUAL = 'le' + GREATER_EQUAL = 'ge' + + +def operator_expression(lhs: Expression, operator: Operator, rhs: Expression) -> ExprLike: + match operator: + case Operator.EQUAL: + return lhs == rhs + case Operator.LESS_EQUAL: + return lhs <= rhs + case Operator.GREATER_EQUAL: + return lhs >= rhs + raise ValueError(f'Invalid operator: {operator!r}') + + +def get_variable_efficiency( + model: TemoaModel, + r: Region, + _p: Period, + s: Season, + d: TimeOfDay, + i: Commodity, + t: Technology, + v: Vintage, + o: Commodity, +) -> float: + """ + Calculates the effective efficiency for a process in a specific time slice. + + This function handles time-varying efficiencies. It checks a pre-computed boolean, + `M.is_efficiency_variable`, to determine if a variable efficiency is defined for + the given process. + + - If True, it returns `efficiency * efficiency_variable`. + - If False, it returns the base `efficiency`. + + This dictionary-lookup approach is used for performance, as it is much faster + than repeatedly checking the indices of a large Pyomo parameter during model build. + """ + if model.is_efficiency_variable[r, i, t, v, o]: + return value(model.efficiency[r, i, t, v, o]) * value( + model.efficiency_variable[r, s, d, i, t, v, o] + ) + return value(model.efficiency[r, i, t, v, o]) + + +def get_capacity_factor( + model: TemoaModel, r: Region, s: Season, d: TimeOfDay, t: Technology, v: Vintage +) -> float: + if model.is_capacity_factor_process[r, t, v]: + return value(model.capacity_factor_process[r, s, d, t, v]) + return value(model.capacity_factor_tech[r, s, d, t]) diff --git a/temoa/core/__init__.py b/temoa/core/__init__.py new file mode 100644 index 000000000..9322473d9 --- /dev/null +++ b/temoa/core/__init__.py @@ -0,0 +1,13 @@ +""" +TEMOA Core API + +This module provides the main public API for the TEMOA energy systems modeling library. +""" + +from .config import TemoaConfig +from .model import TemoaModel +from .modes import TemoaMode + +__version__ = '4.0.0a1' + +__all__ = ['TemoaModel', 'TemoaConfig', 'TemoaMode', '__version__'] diff --git a/temoa/core/config.py b/temoa/core/config.py new file mode 100644 index 000000000..de3e34302 --- /dev/null +++ b/temoa/core/config.py @@ -0,0 +1,430 @@ +import shutil +import sys +import tomllib +from logging import getLogger +from pathlib import Path + +from temoa.core.modes import TemoaMode + +logger = getLogger(__name__) + + +class SolverNotAvailableError(Exception): + """Raised when a required solver executable cannot be found in the system's PATH.""" + + +# Dictionary to store installation documentation links for common solvers +SOLVER_DOC_LINKS = { + 'cbc': ( + 'https://github.com/coin-or/Cbc#download ' + '(refer to temoa documentation for specific OS steps)' + ), + 'gurobi': 'https://www.gurobi.com/downloads/ (requires license and installation)', + 'cplex': ( + 'https://www.ibm.com/products/ilog-cplex-optimization-studio ' + '(requires license and installation)' + ), + 'highs': ('Did you mean to use appsi_highs? '), + 'glpk': 'https://www.gnu.org/software/glpk/', +} + + +class TemoaConfig: + """ + The overall configuration for a Temoa Scenario + """ + + def __init__( + self, + scenario: str, + scenario_mode: TemoaMode | str, + input_database: Path, + output_database: Path, + output_path: Path, + solver_name: str, + neos: bool = False, + save_excel: bool = False, + save_duals: bool = False, + save_storage_levels: bool = False, + save_lp_file: bool = False, + time_sequencing: str | None = None, + days_per_period: int = 365, + reserve_margin: str | None = None, + MGA: dict[str, object] | None = None, + SVMGA: dict[str, object] | None = None, + myopic: dict[str, object] | None = None, + morris: dict[str, object] | None = None, + monte_carlo: dict[str, object] | None = None, + stochastic_config: Path | None = None, + config_file: Path | None = None, + silent: bool = False, + stream_output: bool = False, + price_check: bool = True, + source_trace: bool = False, + check_units: bool = False, + plot_commodity_network: bool = False, + graphviz_output: bool = False, + cycle_count_limit: int = 100, + cycle_length_limit: int = 1, + output_threshold_capacity: float | None = None, + output_threshold_activity: float | None = None, + output_threshold_emission: float | None = None, + output_threshold_cost: float | None = None, + sqlite: dict[str, object] | None = None, + ): + if '-' in scenario: + raise ValueError( + 'Scenario name must not contain "-". Dashes are used internally to indicate' + 'iterative runs. Please rename scenario' + ) + self.scenario = scenario + # capture the operating mode + self.scenario_mode: TemoaMode + match scenario_mode: + case TemoaMode(): + self.scenario_mode = scenario_mode + case str(): + try: + self.scenario_mode = TemoaMode[scenario_mode.upper()] + except KeyError as err: + raise AttributeError( + f'The mode selection received by TemoaConfig: ' + f'{scenario_mode} is invalid.\nPossible choices are ' + f'{list(TemoaMode.__members__.keys())} (case ' + f'insensitive).' + ) from err + case _: + raise AttributeError( + f'The mode selection received by TemoaConfig: ' + f'{scenario_mode} is invalid.\nPossible choices are ' + f'{list(TemoaMode.__members__.keys())} (case ' + f'insensitive).' + ) + + self.config_file = config_file + + # accept and screen the input file + self.input_database = Path(input_database) + if not self.input_database.is_file(): + raise FileNotFoundError(f'could not locate the input database: {self.input_database}') + if self.input_database.suffix not in {'.db', '.sqlite'}: + logger.error('Input file is not of type .ddb or .sqlite') + raise AttributeError('Input file is not of type .db or .sqlite') + + # accept and validate the output db + self.output_database = Path(output_database) + if not self.output_database.is_file(): + raise FileNotFoundError(f'Could not locate the output db: {self.output_database}') + if self.output_database.suffix != '.sqlite': + logger.error('Output DB does not appear to be a sqlite db') + raise AttributeError('Output DB should be .sqlite type') + + # create a placeholder for .dat file. If conversion is needed, this + # is the destination... + self.dat_file: Path | None = None + + self.output_path = output_path + self.neos = neos + if self.neos: + raise NotImplementedError('Neos is currently not supported.') + self.solver_name = solver_name + + self.save_excel = save_excel + self.save_duals = save_duals + self.save_storage_levels = save_storage_levels + self.save_lp_file = save_lp_file + self.time_sequencing = time_sequencing + self.days_per_period = days_per_period + self.reserve_margin = reserve_margin + + self.mga_inputs = MGA + self.svmga_inputs = SVMGA + self.myopic_inputs = myopic + self.morris_inputs = morris + self.monte_carlo_inputs = monte_carlo + self.silent = silent + self.stream_output = stream_output + self.price_check = price_check + self.source_trace = source_trace + self.check_units = check_units + if plot_commodity_network and not self.source_trace: + logger.warning( + 'Commodity Network plotting was selected, but Source Trace was not selected. ' + 'Both are required to produce plots.' + ) + self.plot_commodity_network = plot_commodity_network and self.source_trace + self.graphviz_output = graphviz_output + self.stochastic_config = stochastic_config + self.output_threshold_capacity = output_threshold_capacity + self.output_threshold_activity = output_threshold_activity + self.output_threshold_emission = output_threshold_emission + self.output_threshold_cost = output_threshold_cost + self.sqlite_inputs = sqlite or {} + + # SQLite performance settings + # journal_mode: DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF + jm_allowed = {'DELETE', 'TRUNCATE', 'PERSIST', 'MEMORY', 'WAL', 'OFF'} + jm = self.sqlite_inputs.get('journal_mode', 'WAL') + if isinstance(jm, str) and jm.upper() in jm_allowed: + self.sqlite_journal_mode: str | int = jm.upper() + elif isinstance(jm, (int, float, str)) and str(jm).isdigit(): + self.sqlite_journal_mode = int(jm) + else: + self.sqlite_journal_mode = 'WAL' + + # synchronous: OFF (0) | NORMAL (1) | FULL (2) | EXTRA (3) + sync_allowed = {'OFF', 'NORMAL', 'FULL', 'EXTRA'} + sync = self.sqlite_inputs.get('synchronous', 'NORMAL') + if isinstance(sync, str) and sync.upper() in sync_allowed: + self.sqlite_synchronous: str | int = sync.upper() + elif isinstance(sync, (int, float, str)) and str(sync).isdigit(): + self.sqlite_synchronous = int(sync) + else: + self.sqlite_synchronous = 'NORMAL' + + # temp_store: DEFAULT (0) | FILE (1) | MEMORY (2) + temp_allowed = {'DEFAULT', 'FILE', 'MEMORY'} + ts = self.sqlite_inputs.get('temp_store', 'MEMORY') + if isinstance(ts, str) and ts.upper() in temp_allowed: + self.sqlite_temp_store: str | int = ts.upper() + elif isinstance(ts, (int, float, str)) and str(ts).isdigit(): + self.sqlite_temp_store = int(ts) + else: + self.sqlite_temp_store = 'MEMORY' + + mmap_size = self.sqlite_inputs.get('mmap_size', 8589934592) + if isinstance(mmap_size, (int, float, str)): + self.sqlite_mmap_size = int(mmap_size) + else: + self.sqlite_mmap_size = 8589934592 + + cache_size = self.sqlite_inputs.get('cache_size', -512000) + if isinstance(cache_size, (int, float, str)): + self.sqlite_cache_size = int(cache_size) + else: + self.sqlite_cache_size = -512000 + + # Cycle detection limits + if not isinstance(cycle_count_limit, int) or cycle_count_limit < -1: + raise ValueError('cycle_count_limit must be an integer >= -1') + if not isinstance(cycle_length_limit, int) or cycle_length_limit < 1: + raise ValueError('cycle_length_limit must be an integer >= 1') + self.cycle_count_limit = cycle_count_limit + self.cycle_length_limit = cycle_length_limit + + self.sqlite_settings = sqlite or {} + + # warn if output db != input db + if self.input_database.suffix == self.output_database.suffix: # they are both .db/.sqlite + if self.input_database != self.output_database: # they are not the same db + msg = ( + 'Input file, which is a database, does not match the output file\n User ' + 'is responsible to ensure the data ~ results congruency in the output db' + ) + logger.warning(msg) + if not self.silent: + sys.stderr.write('Warning: ' + msg) + + @staticmethod + def _check_solver_availability(solver_name: str) -> tuple[bool, str | None]: + """ + Check if a solver is available, supporting both Python-based and system executable solvers. + + Args: + solver_name: Name of the solver to check + + Returns: + Tuple of (is_available, location_or_message) + """ + # First, try to check if it's available through Pyomo (works for Python-based solvers) + try: + import pyomo.environ as pyo + + solver = pyo.SolverFactory(solver_name) + if solver.available(): + # For Python-based solvers like appsi_highs + if solver_name.startswith('appsi_'): + return True, 'available via Pyomo (Python package)' + # For other solvers, try to get the executable path + try: + exec_path = solver.executable() + return True, exec_path.name if hasattr(exec_path, 'name') else str(exec_path) + except Exception as e: + logger.debug('Could not get executable path for %s: %s', solver_name, e) + return True, 'available via Pyomo' + + except Exception as e: + logger.debug('Could not check solver availability for %s: %s', solver_name, e) + pass + + # Fallback: check if it's a system executable + solver_executable = shutil.which(solver_name) + if solver_executable is not None: + return True, solver_executable + + return False, None + + @staticmethod + def build_config(config_file: Path, output_path: Path, silent: bool = False) -> 'TemoaConfig': + """ + build a Temoa Config from a config file + :param silent: suppress warnings and confirmations + :param output_path: + :param config_file: the path to the config file to use + :return: a TemoaConfig instance + """ + with open(config_file, 'rb') as f: + data = tomllib.load(f) + + if 'solver_name' in data: + is_available, location = TemoaConfig._check_solver_availability(data['solver_name']) + if not is_available: + error_message = ( + f"The specified solver '{data['solver_name']}' was not found.\n" + 'Please ensure the solver is installed and accessible.\n' + ) + if data['solver_name'].lower() in SOLVER_DOC_LINKS: + link = SOLVER_DOC_LINKS[data['solver_name'].lower()] + error_message += f'For installation instructions, refer to: {link}\n' + else: + error_message += ( + "Refer to the solver's official documentation for " + 'installation instructions.' + ) + raise SolverNotAvailableError(error_message) + else: + logger.info('Using solver: %s (%s)', data['solver_name'], location) + else: + raise SolverNotAvailableError('No solver name specified in the configuration.') + + if 'stochastic' in data: + stoch_data = data.pop('stochastic') + if 'stochastic_config' in stoch_data: + # Resolve relative to config file Path + sc = config_file.parent / stoch_data['stochastic_config'] + data['stochastic_config'] = sc + + # Pop other sections that are not top-level arguments in __init__ + for section in ['execution', 'model', 'pricing', 'monte_carlo_tweaks']: + if section in data: + data.pop(section) + + if 'output_path' in data: + data.pop('output_path') + + tc = TemoaConfig(output_path=output_path, config_file=config_file, silent=silent, **data) + logger.info('Scenario Name: %s', tc.scenario) + logger.info('Data source: %s', tc.input_database) + logger.info('Data target: %s', tc.output_database) + logger.info('Mode: %s', tc.scenario_mode.name) + return tc + + def __repr__(self) -> str: + width = 25 + spacer = '\n' + '-' * width + '\n' + msg = spacer + + msg += '{:>{}s}: {}\n'.format('Scenario', width, self.scenario) + msg += '{:>{}s}: {}\n'.format('Scenario mode', width, self.scenario_mode.name) + msg += '{:>{}s}: {}\n'.format('Config file', width, self.config_file) + msg += '{:>{}s}: {}\n'.format('Data source', width, self.input_database) + msg += '{:>{}s}: {}\n'.format('Output database target', width, self.output_database) + msg += '{:>{}s}: {}\n'.format('Path for outputs and log', width, self.output_path) + + msg += spacer + msg += '{:>{}s}: {}\n'.format('Price check', width, self.price_check) + msg += '{:>{}s}: {}\n'.format('Source trace', width, self.source_trace) + msg += '{:>{}s}: {}\n'.format('Unit checking', width, self.check_units) + msg += '{:>{}s}: {}\n'.format('Commodity network plots', width, self.plot_commodity_network) + msg += '{:>{}s}: {}\n'.format('Graphviz output', width, self.graphviz_output) + msg += '{:>{}s}: {}\n'.format('Cycle count limit', width, self.cycle_count_limit) + msg += '{:>{}s}: {}\n'.format('Cycle length limit', width, self.cycle_length_limit) + + msg += spacer + msg += '{:>{}s}: {}\n'.format('Selected solver', width, self.solver_name) + msg += '{:>{}s}: {}\n'.format('NEOS status', width, self.neos) + + msg += spacer + msg += '{:>{}s}: {}\n'.format('Spreadsheet output', width, self.save_excel) + msg += '{:>{}s}: {}\n'.format('Pyomo LP write status', width, self.save_lp_file) + msg += '{:>{}s}: {}\n'.format('Save duals to output db', width, self.save_duals) + msg += '{:>{}s}: {}\n'.format('Save storage to output db', width, self.save_storage_levels) + + msg += spacer + msg += '{:>{}s}: {}\n'.format('SQLite journal mode', width, self.sqlite_journal_mode) + msg += '{:>{}s}: {}\n'.format('SQLite synchronous', width, self.sqlite_synchronous) + msg += '{:>{}s}: {}\n'.format('SQLite temp store', width, self.sqlite_temp_store) + msg += '{:>{}s}: {}\n'.format('SQLite mmap size (bytes)', width, self.sqlite_mmap_size) + msg += '{:>{}s}: {}\n'.format( + 'SQLite cache size (pages or KiB if negative)', width, self.sqlite_cache_size + ) + + msg += spacer + msg += '{:>{}s}: {}\n'.format('Time sequencing', width, self.time_sequencing) + msg += '{:>{}s}: {}\n'.format('Days per period', width, self.days_per_period) + msg += '{:>{}s}: {}\n'.format('Planning reserve margin', width, self.reserve_margin) + + if self.scenario_mode == TemoaMode.MYOPIC and self.myopic_inputs is not None: + msg += spacer + msg += '{:>{}s}: {}\n'.format( + 'Myopic view depth', width, self.myopic_inputs.get('view_depth') + ) + msg += '{:>{}s}: {}\n'.format( + 'Myopic step size', width, self.myopic_inputs.get('step_size') + ) + msg += '{:>{}s}: {}\n'.format( + 'Evolving mode', width, bool(self.myopic_inputs.get('evolving')) + ) + if bool(self.myopic_inputs.get('evolving')): + msg += '{:>{}s}: {}\n'.format( + 'Evolution script', width, str(self.myopic_inputs.get('evolution_script')) + ) + + if self.scenario_mode == TemoaMode.MGA and self.mga_inputs is not None: + msg += spacer + msg += '{:>{}s}: {}\n'.format( + 'MGA Cost Epsilon', width, self.mga_inputs.get('cost_epsilon') + ) + msg += '{:>{}s}: {}\n'.format( + 'MGA Iteration Limit', width, self.mga_inputs.get('iteration_limit') + ) + msg += '{:>{}s}: {}\n'.format( + 'MGA Time Limit (hrs)', width, self.mga_inputs.get('time_limit_hrs') + ) + msg += '{:>{}s}: {}\n'.format('MGA Axis:', width, self.mga_inputs.get('axis')) + msg += '{:>{}s}: {}\n'.format('MGA Weighting', width, self.mga_inputs.get('weighting')) + + if self.scenario_mode == TemoaMode.METHOD_OF_MORRIS and self.morris_inputs is not None: + msg += spacer + msg += '{:>{}s}: {}\n'.format( + 'Morris Perturbation', width, self.morris_inputs.get('perturbation') + ) + msg += '{:>{}s}: {}\n'.format( + 'Morris Param Levels', width, self.morris_inputs.get('levels') + ) + msg += '{:>{}s}: {}\n'.format( + 'Morris Trajectories', width, self.morris_inputs.get('trajectories') + ) + msg += '{:>{}s}: {}\n'.format( + 'Morris Random Seed', width, self.morris_inputs.get('seed', 'Auto') + ) + msg += '{:>{}s}: {}\n'.format( + 'Morris CPU Cores Requested', width, self.morris_inputs.get('cores') + ) + + if self.scenario_mode == TemoaMode.SVMGA and self.svmga_inputs is not None: + msg += spacer + msg += '{:>{}s}: {}\n'.format( + 'SVMGA Cost Epsilon', width, self.svmga_inputs.get('cost_epsilon') + ) + msg += '{:>{}s}: {}\n'.format( + 'Emission Labels', width, self.svmga_inputs.get('emission_labels') + ) + msg += '{:>{}s}: {}\n'.format( + 'Capacity Labels', width, self.svmga_inputs.get('capacity_labels') + ) + msg += '{:>{}s}: {}\n'.format( + 'Activity Labels', width, self.svmga_inputs.get('activity_labels') + ) + + return msg diff --git a/temoa/core/model.py b/temoa/core/model.py new file mode 100755 index 000000000..6c492e2a2 --- /dev/null +++ b/temoa/core/model.py @@ -0,0 +1,1152 @@ +#!/usr/bin/env python + +""" +Tools for Energy Model Optimization and Analysis (Temoa): +An open source framework for energy systems optimization modeling + +SPDX-License-Identifier: MIT + +""" + +import logging +from typing import TYPE_CHECKING + +from pyomo.core import BuildCheck, Set, Var +from pyomo.environ import ( + AbstractModel, + BuildAction, + Constraint, + Integers, + NonNegativeReals, + Objective, + Param, + PositiveReals, + minimize, +) + +from temoa.components import ( + capacity, + commodities, + costs, + emissions, + flows, + geography, + limits, + operations, + reserves, + storage, + technology, + time, +) +from temoa.model_checking.validators import ( + no_slash_or_pipe, + region_check, + region_group_check, + validate_0to1, + validate_efficiency, + validate_linked_tech, + validate_reserve_margin, + validate_tech_sets, +) + +if TYPE_CHECKING: + from temoa import types as t + from temoa.types.core_types import Technology + +logger = logging.getLogger(__name__) + + +def create_sparse_dicts(model: 'TemoaModel') -> None: + """ + Creates and populates all sparse dictionaries and sets required for the model + by calling component-specific precomputation functions. + """ + + # Call the decomposed functions in logical order + # 1. Populate core relationships from efficiency table + technology.populate_core_dictionaries(model) + + # 2. Classify technologies and commodities + commodities.create_technology_and_commodity_sets(model) + + # 3. Create sets for specific components + operations.create_operational_vintage_sets(model) # For operations, storage, ramping + limits.create_limit_vintage_sets(model) # For limits + geography.create_geography_sets(model) # For geography/exchange + capacity.create_capacity_and_retirement_sets(model) # For capacity + + # 4. Create final aggregated sets for constraints + flows.create_commodity_balance_and_flow_sets(model) # For flows and commodities + + # Final check for unused technologies + unused_techs = model.tech_all - model.used_techs + if unused_techs: + for tech in sorted(unused_techs): + logger.warning( + "Notice: '%s' is specified as a technology but is not " + 'utilized in the process network.', + tech, + ) + + logger.debug('Completed creation of SparseDicts') + + +class TemoaModel(AbstractModel): + """ + An instance of the abstract Temoa model + """ + + # this is used in several places outside this class, and this provides no-build access to it + default_lifetime_tech = 40 + + def __init__(self, *args: object, **kwargs: object) -> None: + AbstractModel.__init__(self, *args, **kwargs) + + ################################################ + # Internally used Data Containers # + # (not formal model elements) # + ################################################ + + self.process_inputs: t.ProcessInputsDict = {} + self.process_outputs: t.ProcessOutputsDict = {} + self.process_loans: t.ProcessLoansDict = {} + self.active_flow_rpsditvo: t.ActiveFlowSet = set() + """a flow index for techs NOT in tech_annual""" + + self.active_flow_rpitvo: t.ActiveFlowAnnualSet = set() + """a flow index for techs in tech_annual only""" + + self.active_flex_rpsditvo: t.ActiveFlexSet = set() + self.active_flex_rpitvo: t.ActiveFlexAnnualSet = set() + self.active_flow_in_storage_rpsditvo: t.ActiveFlowInStorageSet = set() + self.active_curtailment_rpsditvo: t.ActiveCurtailmentSet = set() + self.active_activity_rptv: t.ActiveActivitySet = set() + self.storage_level_indices_rpsdtv: t.StorageLevelIndicesSet = set() + self.seasonal_storage_level_indices_rpstv: t.SeasonalStorageLevelIndicesSet = set() + """ + currently available (within lifespan) (r, p, t, v) tuples (from model.process_vintages) + """ + + self.active_regions_for_tech: t.ActiveRegionsForTechDict = {} + """currently available regions by period and tech {(p, t) : r}""" + + self.new_capacity_rtv: t.NewCapacitySet = set() + self.active_capacity_available_rpt: t.ActiveCapacityAvailableSet = set() + self.active_capacity_rptv: t.ActiveCapacityAvailableVintageSet = set() + self.group_region_active_flow_rpt: t.GroupRegionActiveFlowSet = ( + set() # Set of valid group-region, period, tech indices + ) + # demands served by a single r, i, t, v, o sub-process + self.singleton_demands: t.SingletonDemandsSet = set() + self.commodity_balance_rpc: t.CommodityBalancedSet = ( + set() + ) # Set of valid region-period-commodity indices to balance + # The downstream process of a commodity during a period + self.commodity_down_stream_process: t.CommodityStreamProcessDict = {} + # The upstream process of a commodity during a period + self.commodity_up_stream_process: t.CommodityStreamProcessDict = {} + # New capacity consuming a commodity during a period [r,p,c] -> t + self.capacity_consumption_techs: t.CapacityConsumptionTechsDict = {} + # Retired capacity producing a commodity during a period [r,p,c] -> t,v + self.retirement_production_processes: t.RetirementProductionProcessesDict = {} + self.process_inputs_by_output: t.ProcessInputsByOutputDict = {} + self.process_outputs_by_input: t.ProcessOutputsByInputDict = {} + self.process_reserve_periods: t.ProcessReservePeriodsDict = {} + self.process_periods: t.ProcessPeriodsDict = {} # {(r, t, v): set(p)} + # {(r, t, v): set(p)} periods in which a process can economically or naturally retire + self.retirement_periods: t.RetirementPeriodsDict = {} + self.process_vintages: t.ProcessVintagesDict = {} + # {(r, t, v): set(p)} periods for which the process has a defined survival fraction + self.survival_curve_periods: t.SurvivalCurvePeriodsDict = {} + """current available (within lifespan) vintages {(r, p, t) : set(v)}""" + + self.baseload_vintages: t.BaseloadVintagesDict = {} + self.curtailment_vintages: t.CurtailmentVintagesDict = {} + self.storage_vintages: t.StorageVintagesDict = {} + self.ramp_up_vintages: t.RampUpVintagesDict = {} + self.ramp_down_vintages: t.RampDownVintagesDict = {} + self.input_split_vintages: t.InputSplitVintagesDict = {} + self.input_split_annual_vintages: t.InputSplitAnnualVintagesDict = {} + self.output_split_vintages: t.OutputSplitVintagesDict = {} + self.output_split_annual_vintages: t.OutputSplitAnnualVintagesDict = {} + # M.processByPeriodAndOutput = {} # not currently used + self.export_regions: t.ExportRegionsDict = {} + self.import_regions: t.ImportRegionsDict = {} + + # These establish time sequencing + # {(s, d): (s_next, d_next)} sequence of following time slices + self.time_next: t.TimeNextDict = {} + # {s_seq: s_seq_next} next virtual storage season + self.time_next_sequential: t.TimeNextSequentialDict = {} + # {s_seq: s} season matching this virtual storage season + self.sequential_to_season: t.SequentialToSeasonDict = {} + + ################################################ + # Switching Sets # + # (to avoid slow searches in initialisation) # + ################################################ + + # {(r, i, t, v, o): bool} which efficiencies have variable indexing + self.is_efficiency_variable: t.EfficiencyVariableDict = {} + # {(r, t, v): bool} which processes use capacity_factor_process + # table (instead of capacity_factor_tech) + self.is_capacity_factor_process: t.CapacityFactorProcessDict = {} + # {t: bool} whether a storage tech is seasonal storage + self.is_seasonal_storage: t.SeasonalStorageDict = {} + # {(r, t, v): bool} whether a process uses survival curves. + self.is_survival_curve_process: t.SurvivalCurveProcessDict = {} + + ################################################ + # Model Sets # + # (used for indexing model elements) # + ################################################ + + self.progress_marker_1 = BuildAction(['Starting to build Sets'], rule=progress_check) + + self.operator = Set() + + # Define time periods + self.time_exist = Set(ordered=True) + self.time_future = Set(ordered=True) + self.time_optimize = Set( + ordered=True, initialize=time.init_set_time_optimize, within=self.time_future + ) + # Define time period vintages to track capacity installation + self.vintage_exist = Set(ordered=True, initialize=time.init_set_vintage_exist) + self.vintage_optimize = Set(ordered=True, initialize=time.init_set_vintage_optimize) + self.vintage_all = Set(initialize=self.time_exist | self.time_optimize) + # Perform some basic validation on the specified time periods. + self.validate_time = BuildAction(rule=time.validate_time) + + # Define the model time slices + self.time_season = Set(ordered=True, validate=no_slash_or_pipe) + self.time_season_sequential = Set(ordered=True, validate=no_slash_or_pipe) + self.time_of_day = Set(ordered=True, validate=no_slash_or_pipe) + + # This is just to get the TimeStorageSeason table sequentially. + # There must be a better way but this works for now + self.ordered_season_sequential = Set( + dimen=2, + within=self.time_season_sequential * self.time_season, + ordered=True, + ) + + # Define regions + self.regions = Set(validate=region_check) + # regional_indices is the set of all the possible combinations of interregional exchanges + # plus original region indices. If tech_exchange is empty, RegionalIndices =regions. + self.regional_indices = Set(initialize=geography.create_regional_indices) + self.regional_global_indices = Set(validate=region_group_check) + + # Define technology-related sets + # M.tech_resource = Set() # not actually used by + self.tech_production = Set() + self.tech_all = Set( + initialize=self.tech_production, validate=no_slash_or_pipe + ) # was M.tech_resource | M.tech_production + self.tech_baseload = Set(within=self.tech_all) + self.tech_annual = Set(within=self.tech_all) + self.tech_demand = Set(within=self.tech_all) + # annual storage not supported in Storage constraint or TableWriter, so exclude from domain + self.tech_storage = Set(within=self.tech_all) + self.tech_reserve = Set(within=self.tech_all) + self.tech_upramping = Set(within=self.tech_all) + self.tech_downramping = Set(within=self.tech_all) + self.tech_curtailment = Set(within=self.tech_all) + self.tech_flex = Set(within=self.tech_all) + # ensure there is no overlap flex <=> curtailable technologies + self.tech_exchange = Set(within=self.tech_all) + + # Define groups for technologies + self.tech_group_names = Set() + self.tech_group_members = Set(self.tech_group_names, within=self.tech_all) + self.tech_or_group = Set(initialize=self.tech_group_names | self.tech_all) + + self.tech_seasonal_storage = Set(within=self.tech_storage) + """storage technologies using the interseasonal storage feature""" + + self.tech_uncap = Set(within=self.tech_all - self.tech_reserve) + """techs with unlimited capacity, ALWAYS available within lifespan""" + + self.tech_exist = Set() + """techs with existing capacity, want to keep these for accounting reasons""" + + self.used_techs: set[Technology] = set() + """ track techs used in efficiency table used in create_sparse_dicts """ + + # the below is a convenience for domain checking in params below that should not accept + # uncap techs... + self.tech_with_capacity = Set(initialize=self.tech_all - self.tech_uncap) + """techs eligible for capacitization""" + # Define techs for which economic retirement is an option + # Note: Storage techs cannot (currently) be retired due to linkage to initialization + # process, which is currently incapable of reducing initializations on retirements. + # Note2: I think this has been fixed but I can't tell what the problem was. Suspect + # it was the old storage_init constraint + self.tech_retirement = Set(within=self.tech_with_capacity) # - M.tech_storage) + + self.validate_techs = BuildAction(rule=validate_tech_sets) + + # Define commodity-related sets + self.commodity_demand = Set() + self.commodity_emissions = Set() + self.commodity_physical = Set() + self.commodity_waste = Set() + self.commodity_flex = Set(within=self.commodity_physical) + self.commodity_source = Set(within=self.commodity_physical) + self.commodity_sink = Set(initialize=self.commodity_demand | self.commodity_waste) + self.commodity_annual = Set(within=self.commodity_physical) + self.commodity_carrier = Set(initialize=self.commodity_physical | self.commodity_sink) + self.commodity_all = Set( + initialize=self.commodity_carrier | self.commodity_emissions, + validate=no_slash_or_pipe, + ) + + ################################################ + # Model Parameters # + # (data gathered/derived from source) # + ################################################ + + # --------------------------------------------------------------- + # Dev Note: + # In order to increase model efficiency, we use sparse + # indexing of parameters, variables, and equations to prevent the + # creation of indices for which no data exists. While basic model sets + # are defined above, sparse index sets are defined below adjacent to the + # appropriate parameter, variable, or constraint and all are initialized + # in temoa_initialize.py. + # Because the function calls that define the sparse index sets obscure the + # sets utilized, we use a suffix that includes a one character name for each + # set. Example: "_tv" indicates a set defined over "technology" and "vintage". + # The complete index set is: psditvo, where p=period, s=season, d=day, + # i=input commodity, t=technology, v=vintage, o=output commodity. + # --------------------------------------------------------------- + + # these "progress markers" report build progress in the log, if the level == debug + self.progress_marker_2 = BuildAction(['Starting to build Params'], rule=progress_check) + + self.global_discount_rate = Param(default=0.05) + + # These need to come before validate_season_sequential as they tell us whether + # we need sequential seasons + self.ramp_up_hourly = Param(self.regions, self.tech_upramping, validate=validate_0to1) + self.ramp_down_hourly = Param(self.regions, self.tech_downramping, validate=validate_0to1) + + # Define time-related parameters + # Basic period construction + self.time_sequencing = Set() # How do states carry between time segments? + self.period_length = Param(self.time_optimize, initialize=time.param_period_length) + self.days_per_period = Param(domain=PositiveReals, default=365.0) + self.time_of_day_hours = Param(self.time_of_day, domain=PositiveReals, default=1.0) + self.segment_fraction_per_season = Param(self.time_season) + self.segment_fraction = Param(self.time_season, self.time_of_day, mutable=True) + self.validate_segment_fraction = BuildAction(rule=time.validate_segment_fraction) + self.segment_fraction_per_sequential_season = Param( + self.time_season_sequential, mutable=True + ) + self.validate_season_sequential = BuildAction(rule=time.create_time_season_to_sequential) + self.create_time_sequence = BuildAction(rule=time.create_time_sequence) + self.time_manual = Set( + ordered=True + ) # This is just to get data from the table. Hidden feature and usually not used + self.validate_time_next = BuildAction(rule=time.validate_time_manual) + + # Define demand- and resource-related parameters + # Dev Note: There does not appear to be a DB table supporting DemandDefaultDistro. + # This does not cause any problems, so let it be for now. + # Doesn't seem to be much point in the table. Just clones segment_fraction + # M.DemandDefaultDistribution = Param( + # M.time_optimize, M.time_season, M.time_of_day, mutable=True + # ) + self.demand_specific_distribution = Param( + self.regions, + self.time_optimize, + self.time_season, + self.time_of_day, + self.commodity_demand, + mutable=True, + default=0, + ) + + self.demand_constraint_rpc = Set( + within=self.regions * self.time_optimize * self.commodity_demand + ) + self.demand = Param(self.demand_constraint_rpc) + + # Dev Note: This parameter is currently NOT implemented. Preserved for later refactoring + # limit_resource IS implemented but sums cumulatively for a technology rather than + # resource commodity + # M.ResourceConstraint_rpr = Set(within=M.regions * M.time_optimize * M.commodity_physical) + # M.resource_bound = Param(M.ResourceConstraint_rpr) + + # Define technology performance parameters + self.capacity_to_activity = Param(self.regional_indices, self.tech_all, default=1) + + self.existing_capacity = Param(self.regional_indices, self.tech_exist, self.vintage_exist) + + # Dev Note: The below is temporarily useful for passing down to validator to find + # set violations + # Uncomment this assignment, and comment out the orig below it... + # M.efficiency = Param( + # Any, Any, Any, Any, Any, + # within=NonNegativeReals, validate=validate_efficiency + # ) + + # devnote: need these here or CheckefficiencyIndices may flag these commodities as unused + self.construction_input = Param( + self.regions, + self.commodity_physical, + self.tech_with_capacity, + self.vintage_optimize, + ) + self.end_of_life_output = Param( + self.regions, self.tech_with_capacity, self.vintage_all, self.commodity_carrier + ) + self.emission_end_of_life = Param( + self.regions, self.commodity_emissions, self.tech_with_capacity, self.vintage_all + ) + + self.efficiency = Param( + self.regional_indices, + self.commodity_physical, + self.tech_all, + self.vintage_all, + self.commodity_carrier, + within=PositiveReals, + validate=validate_efficiency, + ) + self.validate_used_efficiency_indices = BuildAction( + rule=technology.check_efficiency_indices + ) + + self.efficiency_variable = Param( + self.regional_indices, + self.time_season, + self.time_of_day, + self.commodity_physical, + self.tech_all, + self.vintage_all, + self.commodity_carrier, + within=PositiveReals, + default=1, + ) + + self.lifetime_tech = Param( + self.regional_indices, self.tech_all, default=TemoaModel.default_lifetime_tech + ) + + self.lifetime_process_rtv = Set(dimen=3, initialize=technology.lifetime_process_indices) + self.lifetime_process = Param( + self.lifetime_process_rtv, default=technology.get_default_process_lifetime + ) + + self.lifetime_survival_curve = Param( + self.regional_indices, + Integers, + self.tech_all, + self.vintage_all, + default=technology.get_default_survival, + validate=validate_0to1, + mutable=True, + ) + self.create_survival_curve = BuildAction(rule=technology.create_survival_curve) + + self.loan_lifetime_process_rtv = Set( + dimen=3, initialize=costs.lifetime_loan_process_indices + ) + + # Dev Note: The loan_lifetime_process table *could* be removed. There is no longer a + # supporting table in the database. It is just a "passthrough" now to the + # default loan_lifetime_tech. It is already stitched in to the model, + # so will leave it for now. Table may be revived. + + self.loan_lifetime_process = Param( + self.loan_lifetime_process_rtv, default=costs.get_loan_life + ) + + self.limit_tech_input_split = Param( + self.regions, + self.time_optimize, + self.commodity_physical, + self.tech_all, + self.operator, + validate=validate_0to1, + ) + self.limit_tech_input_split_annual = Param( + self.regions, + self.time_optimize, + self.commodity_physical, + self.tech_all, + self.operator, + validate=validate_0to1, + ) + + self.limit_tech_output_split = Param( + self.regions, + self.time_optimize, + self.tech_all, + self.commodity_carrier, + self.operator, + validate=validate_0to1, + ) + self.limit_tech_output_split_annual = Param( + self.regions, + self.time_optimize, + self.tech_all, + self.commodity_carrier, + self.operator, + validate=validate_0to1, + ) + + self.renewable_portfolio_standard_constraint_rpg = Set( + within=self.regions * self.time_optimize * self.tech_group_names + ) + self.renewable_portfolio_standard = Param( + self.renewable_portfolio_standard_constraint_rpg, validate=validate_0to1 + ) + + # The method below creates a series of helper functions that are used to + # perform the sparse matrix of indexing for the parameters, variables, and + # equations below. + self.create_sparse_dicts = BuildAction(rule=create_sparse_dicts) + self.initialize_demands = BuildAction(rule=commodities.create_demands) + self.validate_existing_capacity = BuildAction(rule=technology.check_existing_capacity) + + self.capacity_factor_rsdt = Set(dimen=4, initialize=capacity.capacity_factor_tech_indices) + self.capacity_factor_tech = Param( + self.capacity_factor_rsdt, default=1, validate=validate_0to1 + ) + + # Dev note: using a default function below alleviates need to make this set. + # M.CapacityFactor_rsdtv = Set(dimen=5, initialize=capacity_factor_processIndices) + self.capacity_factor_process = Param( + self.regional_indices, + self.time_season, + self.time_of_day, + self.tech_with_capacity, + self.vintage_all, + # validate=validate_capacity_factor_process, + # opting for a quicker validation, just 0->1 + validate=validate_0to1, + # slow but only called if a value is missing + default=capacity.get_default_capacity_factor, + ) + + self.capacity_constraint_rpsdtv = Set( + dimen=6, initialize=capacity.capacity_constraint_indices + ) + self.initialize_CapacityFactors = BuildAction(rule=capacity.check_capacity_factor_process) + self.initialize_efficiency_variable = BuildAction(rule=technology.check_efficiency_variable) + + # Define technology cost parameters + self.cost_fixed_rptv = Set(dimen=4, initialize=costs.cost_fixed_indices) + self.cost_fixed = Param(self.cost_fixed_rptv) + self.cost_variable_rptv = Set(dimen=4, initialize=costs.cost_variable_indices) + self.cost_variable = Param(self.cost_variable_rptv) + + self.cost_invest_rtv = Set( + within=self.regional_indices * self.tech_all * self.time_optimize + ) + self.cost_invest = Param(self.cost_invest_rtv) + + self.default_loan_rate = Param(domain=NonNegativeReals) + self.loan_rate = Param( + self.cost_invest_rtv, domain=NonNegativeReals, default=costs.get_default_loan_rate + ) + self.loan_annualize = Param( + self.cost_invest_rtv, initialize=costs.param_loan_annualize_rule + ) + + self.cost_emission_rpe = Set( + within=self.regions * self.time_optimize * self.commodity_emissions + ) + self.cost_emission = Param(self.cost_emission_rpe) + + self.process_life_frac_rptv = Set(dimen=4, initialize=technology.model_process_life_indices) + self.process_life_frac = Param( + self.process_life_frac_rptv, initialize=technology.param_process_life_fraction_rule + ) + + self.limit_capacity_constraint_rpt = Set( + within=self.regional_global_indices + * self.time_optimize + * self.tech_or_group + * self.operator + ) + self.limit_capacity = Param(self.limit_capacity_constraint_rpt) + + self.limit_new_capacity_constraint_rtv = Set( + within=self.regional_global_indices + * self.tech_or_group + * self.vintage_optimize + * self.operator + ) + self.limit_new_capacity = Param(self.limit_new_capacity_constraint_rtv) + + self.limit_resource_constraint_rt = Set( + within=self.regional_global_indices * self.tech_or_group * self.operator + ) + self.limit_resource = Param(self.limit_resource_constraint_rt) + + self.limit_activity_constraint_rpt = Set( + within=self.regional_global_indices + * self.time_optimize + * self.tech_or_group + * self.operator + ) + self.limit_activity = Param(self.limit_activity_constraint_rpt) + + self.limit_seasonal_capacity_factor_constraint_rst = Set() + self.limit_seasonal_capacity_factor = Param( + self.limit_seasonal_capacity_factor_constraint_rst, validate=validate_0to1 + ) + self.limit_seasonal_capacity_factor_constraint_rpst = Set( + within=self.regional_global_indices + * self.time_optimize + * self.time_season + * self.tech_or_group + * self.operator, + initialize=limits.limit_seasonal_capacity_factor_constraint_indices, + ) + + self.limit_annual_capacity_factor_constraint_rtvo = Set( + within=self.regional_global_indices + * self.tech_or_group + * self.vintage_all + * self.commodity_carrier + * self.operator + ) + self.limit_annual_capacity_factor = Param( + self.limit_annual_capacity_factor_constraint_rtvo, validate=validate_0to1 + ) + + self.limit_growth_capacity = Param( + self.regional_global_indices, self.tech_or_group, self.operator + ) + self.limit_degrowth_capacity = Param( + self.regional_global_indices, self.tech_or_group, self.operator + ) + self.limit_growth_new_capacity = Param( + self.regional_global_indices, self.tech_or_group, self.operator + ) + self.limit_degrowth_new_capacity = Param( + self.regional_global_indices, self.tech_or_group, self.operator + ) + self.limit_growth_new_capacity_delta = Param( + self.regional_global_indices, self.tech_or_group, self.operator + ) + self.limit_degrowth_new_capacity_delta = Param( + self.regional_global_indices, self.tech_or_group, self.operator + ) + + self.limit_emission_constraint_rpe = Set( + within=self.regional_global_indices + * self.time_optimize + * self.commodity_emissions + * self.operator + ) + self.limit_emission = Param(self.limit_emission_constraint_rpe) + self.emission_activity_reitvo = Set(dimen=6, initialize=emissions.emission_activity_indices) + self.emission_activity = Param(self.emission_activity_reitvo) + + self.limit_capacity_share_constraint_rpgg = Set( + within=self.regional_global_indices + * self.time_optimize + * self.tech_or_group + * self.tech_or_group + * self.operator + ) + self.limit_capacity_share = Param(self.limit_capacity_share_constraint_rpgg) + + self.limit_activity_share_constraint_rpgg = Set( + within=self.regional_global_indices + * self.time_optimize + * self.tech_or_group + * self.tech_or_group + * self.operator + ) + self.limit_activity_share = Param(self.limit_activity_share_constraint_rpgg) + + self.limit_new_capacity_share_constraint_rggv = Set( + within=self.regional_global_indices + * self.tech_or_group + * self.tech_or_group + * self.vintage_optimize + * self.operator + ) + self.limit_new_capacity_share = Param(self.limit_new_capacity_share_constraint_rggv) + + # This set works for all storage-related constraints + self.storage_constraints_rpsdtv = Set( + dimen=6, initialize=storage.storage_constraint_indices + ) + self.seasonal_storage_constraints_rpsdtv = Set( + dimen=6, initialize=storage.seasonal_storage_constraint_indices + ) + self.limit_storage_fraction_param_rsdt = ( + Set() + ) # populated by hybrid_loader with (r, s, d, t, op) keys + self.limit_storage_fraction = Param( + self.limit_storage_fraction_param_rsdt, validate=validate_0to1 + ) + self.limit_storage_fraction_constraint_rpsdtv = Set( + within=(self.storage_constraints_rpsdtv | self.seasonal_storage_constraints_rpsdtv) + * self.operator, + initialize=storage.limit_storage_fraction_constraint_indices, + ) + + # Storage duration is expressed in hours + self.storage_duration = Param(self.regions, self.tech_storage, default=4) + + self.linked_techs = Param(self.regional_indices, self.tech_all, self.commodity_emissions) + + # Define parameters associated with electric sector operation + self.reserve_margin_method = Set() # How contributions to the reserve margin are calculated + self.capacity_credit = Param( + self.regional_indices, + self.time_optimize, + self.tech_reserve, + self.vintage_all, + default=0, + validate=validate_0to1, + ) + self.reserve_capacity_derate = Param( + self.regional_indices, + self.time_season, + self.tech_reserve, + self.vintage_all, + default=1, + validate=validate_0to1, + ) + self.planning_reserve_margin = Param(self.regions) + + self.emission_embodied = Param( + self.regions, + self.commodity_emissions, + self.tech_with_capacity, + self.vintage_optimize, + ) + + self.myopic_discounting_year = Param(default=0) + + ################################################ + # Model Variables # + # (assigned by solver) # + ################################################ + + # --------------------------------------------------------------- + # Dev Note: + # Decision variables are optimized in order to minimize cost. + # Base decision variables represent the lowest-level variables + # in the model. Derived decision variables are calculated for + # convenience, where 1 or more indices in the base variables are + # summed over. + # --------------------------------------------------------------- + + self.progress_marker_3 = BuildAction(['Starting to build Variables'], rule=progress_check) + + # Define base decision variables + self.flow_var_rpsditvo = Set(dimen=8, initialize=flows.flow_variable_indices) + self.v_flow_out = Var(self.flow_var_rpsditvo, domain=NonNegativeReals) + + self.flow_var_annual_rpitvo = Set(dimen=6, initialize=flows.flow_variable_annual_indices) + self.v_flow_out_annual = Var(self.flow_var_annual_rpitvo, domain=NonNegativeReals) + + self.flex_var_rpsditvo = Set(dimen=8, initialize=flows.flex_variable_indices) + self.v_flex = Var(self.flex_var_rpsditvo, domain=NonNegativeReals) + + self.flex_var_annual_rpitvo = Set(dimen=6, initialize=flows.flex_variable_annual_indices) + self.v_flex_annual = Var(self.flex_var_annual_rpitvo, domain=NonNegativeReals) + + self.curtailment_var_rpsditvo = Set(dimen=8, initialize=flows.curtailment_variable_indices) + self.v_curtailment = Var( + self.curtailment_var_rpsditvo, domain=NonNegativeReals, initialize=0 + ) + + self.flow_in_storage_rpsditvo = Set( + dimen=8, initialize=flows.flow_in_storage_variable_indices + ) + self.v_flow_in = Var(self.flow_in_storage_rpsditvo, domain=NonNegativeReals) + + self.storage_level_rpsdtv = Set(dimen=6, initialize=storage.storage_level_variable_indices) + self.v_storage_level = Var(self.storage_level_rpsdtv, domain=NonNegativeReals) + + self.storage_init_rpstv = Set(dimen=5, initialize=storage.storage_init_variable_indices) + self.v_storage_init = Var(self.storage_init_rpstv, domain=NonNegativeReals) + + self.seasonal_storage_level_rpstv = Set( + dimen=5, initialize=storage.seasonal_storage_level_variable_indices + ) + self.v_seasonal_storage_level = Var( + self.seasonal_storage_level_rpstv, domain=NonNegativeReals + ) + + # Derived decision variables + self.capacity_var_rptv = Set(dimen=4, initialize=capacity.capacity_variable_indices) + self.v_capacity = Var(self.capacity_var_rptv, domain=NonNegativeReals) + + self.new_capacity_var_rtv = Set(dimen=3, initialize=capacity.new_capacity_variable_indices) + self.v_new_capacity = Var(self.new_capacity_var_rtv, domain=NonNegativeReals, initialize=0) + + self.retired_capacity_var_rptv = Set( + dimen=4, initialize=capacity.retired_capacity_variable_indices + ) + self.v_retired_capacity = Var( + self.retired_capacity_var_rptv, domain=NonNegativeReals, initialize=0 + ) + + self.annual_retirement_var_rptv = Set( + dimen=4, initialize=capacity.annual_retirement_variable_indices + ) + self.v_annual_retirement = Var( + self.annual_retirement_var_rptv, domain=NonNegativeReals, initialize=0 + ) + + self.capacity_available_var_rpt = Set( + dimen=3, initialize=capacity.capacity_available_variable_indices + ) + self.v_capacity_available_by_period_and_tech = Var( + self.capacity_available_var_rpt, domain=NonNegativeReals, initialize=0 + ) + + ################################################ + # Objective Function # + # (minimize total cost) # + ################################################ + + self.total_cost = Objective(rule=costs.total_cost_rule, sense=minimize) + + ################################################ + # Constraints # + # # + ################################################ + + self.progress_marker_4 = BuildAction(['Starting to build Constraints'], rule=progress_check) + + # Declare constraints to calculate derived decision variables + self.capacity_constraint = Constraint( + self.capacity_constraint_rpsdtv, rule=capacity.capacity_constraint + ) + + self.capacity_annual_constraint_rptv = Set( + dimen=4, initialize=capacity.capacity_annual_constraint_indices + ) + self.capacity_annual_constraint = Constraint( + self.capacity_annual_constraint_rptv, rule=capacity.capacity_annual_constraint + ) + + self.capacity_available_by_period_and_tech_constraint = Constraint( + self.capacity_available_var_rpt, + rule=capacity.capacity_available_by_period_and_tech_constraint, + ) + + # devnote: I think this constraint is redundant + # M.Retiredcapacity_constraint = Constraint( + # M.retired_capacity_var_rptv, rule=RetiredCapacity_constraint + # ) + self.progress_marker_4a = BuildAction( + ['Starting annual_retirement_constraint'], rule=progress_check + ) + self.annual_retirement_constraint = Constraint( + self.annual_retirement_var_rptv, rule=capacity.annual_retirement_constraint + ) + self.progress_marker_4b = BuildAction( + ['Starting adjusted_capacity_constraint'], rule=progress_check + ) + self.adjusted_capacity_constraint = Constraint( + self.capacity_var_rptv, rule=capacity.adjusted_capacity_constraint + ) + self.progress_marker_5 = BuildAction(['Finished Capacity Constraints'], rule=progress_check) + + # Declare core model constraints that ensure proper system functioning + # In driving order, starting with the need to meet end-use demands + + self.check_singleton_demands = BuildAction(rule=commodities.check_singleton_demands) + self.demand_constraint = Constraint( + self.demand_constraint_rpc, rule=commodities.demand_constraint + ) + + # devnote: testing a workaround + self.demand_activity_constraint_rpsdtv_dem = Set( + dimen=7, initialize=commodities.demand_activity_constraint_indices + ) + self.demand_activity_constraint = Constraint( + self.demand_activity_constraint_rpsdtv_dem, rule=commodities.demand_activity_constraint + ) + + self.commodity_balance_constraint_rpsdc = Set( + dimen=5, initialize=commodities.commodity_balance_constraint_indices + ) + self.commodity_balance_constraint = Constraint( + self.commodity_balance_constraint_rpsdc, rule=commodities.commodity_balance_constraint + ) + + self.annual_commodity_balance_constraint_rpc = Set( + dimen=3, initialize=commodities.annual_commodity_balance_constraint_indices + ) + self.annual_commodity_balance_constraint = Constraint( + self.annual_commodity_balance_constraint_rpc, + rule=commodities.annual_commodity_balance_constraint, + ) + + # M.ResourceExtractionConstraint = Constraint( + # M.ResourceConstraint_rpr, rule=ResourceExtraction_constraint + # ) + + self.baseload_diurnal_constraint_rpsdtv = Set( + dimen=6, initialize=operations.baseload_diurnal_constraint_indices + ) + self.baseload_diurnal_constraint = Constraint( + self.baseload_diurnal_constraint_rpsdtv, rule=operations.baseload_diurnal_constraint + ) + + self.regional_exchange_capacity_constraint_rrptv = Set( + dimen=5, initialize=capacity.regional_exchange_capacity_constraint_indices + ) + self.regional_exchange_capacity_constraint = Constraint( + self.regional_exchange_capacity_constraint_rrptv, + rule=geography.regional_exchange_capacity_constraint, + ) + + self.progress_marker_6 = BuildAction(['Starting Storage Constraints'], rule=progress_check) + + self.storage_energy_constraint = Constraint( + self.storage_constraints_rpsdtv, rule=storage.storage_energy_constraint + ) + + self.storage_level_last_tod_constraint = Constraint( + self.storage_init_rpstv, rule=storage.storage_level_at_last_tod_constraint + ) + + self.storage_energy_upper_bound_constraint = Constraint( + self.storage_constraints_rpsdtv, rule=storage.storage_energy_upper_bound_constraint + ) + + self.seasonal_storage_energy_constraint = Constraint( + self.seasonal_storage_level_rpstv, rule=storage.seasonal_storage_energy_constraint + ) + + self.seasonal_storage_energy_upper_bound_constraint = Constraint( + self.seasonal_storage_constraints_rpsdtv, + rule=storage.seasonal_storage_energy_upper_bound_constraint, + ) + + self.storage_charge_rate_constraint = Constraint( + self.storage_constraints_rpsdtv, rule=storage.storage_charge_rate_constraint + ) + + self.storage_discharge_rate_constraint = Constraint( + self.storage_constraints_rpsdtv, rule=storage.storage_discharge_rate_constraint + ) + + self.storage_throughput_constraint = Constraint( + self.storage_constraints_rpsdtv, rule=storage.storage_throughput_constraint + ) + + self.limit_storage_fraction_constraint = Constraint( + self.limit_storage_fraction_constraint_rpsdtv, + rule=storage.limit_storage_fraction_constraint, + ) + + self.ramp_up_day_constraint_rpsdtv = Set( + dimen=6, initialize=operations.ramp_up_day_constraint_indices + ) + self.ramp_up_day_constraint = Constraint( + self.ramp_up_day_constraint_rpsdtv, rule=operations.ramp_up_day_constraint + ) + self.ramp_down_day_constraint_rpsdtv = Set( + dimen=6, initialize=operations.ramp_down_day_constraint_indices + ) + self.ramp_down_day_constraint = Constraint( + self.ramp_down_day_constraint_rpsdtv, rule=operations.ramp_down_day_constraint + ) + + self.ramp_up_season_constraint_rpsstv = Set( + dimen=6, initialize=operations.ramp_up_season_constraint_indices + ) + self.ramp_up_season_constraint = Constraint( + self.ramp_up_season_constraint_rpsstv, rule=operations.ramp_up_season_constraint + ) + self.ramp_down_season_constraint_rpsstv = Set( + dimen=6, initialize=operations.ramp_down_season_constraint_indices + ) + self.ramp_down_season_constraint = Constraint( + self.ramp_down_season_constraint_rpsstv, rule=operations.ramp_down_season_constraint + ) + + self.reserve_margin_rpsd = Set(dimen=4, initialize=reserves.reserve_margin_indices) + self.validate_reserve_margin = BuildAction(rule=validate_reserve_margin) + self.reserve_margin_constraint = Constraint( + self.reserve_margin_rpsd, rule=reserves.reserve_margin_constraint + ) + + self.limit_emission_constraint = Constraint( + self.limit_emission_constraint_rpe, rule=limits.limit_emission_constraint + ) + self.progress_marker_7 = BuildAction( + ['Starting LimitGrowth and Activity Constraints'], rule=progress_check + ) + + self.limit_growth_capacity_constraint_rpt = Set( + dimen=4, initialize=limits.limit_growth_capacity_indices + ) + self.limit_growth_capacity_constraint = Constraint( + self.limit_growth_capacity_constraint_rpt, + rule=limits.limit_growth_capacity_constraint_rule, + ) + self.limit_degrowth_capacity_constraint_rpt = Set( + dimen=4, initialize=limits.limit_degrowth_capacity_indices + ) + self.limit_degrowth_capacity_constraint = Constraint( + self.limit_degrowth_capacity_constraint_rpt, + rule=limits.limit_degrowth_capacity_constraint_rule, + ) + + self.limit_growth_new_capacity_constraint_rpt = Set( + dimen=4, initialize=limits.limit_growth_new_capacity_indices + ) + self.limit_growth_new_capacity_constraint = Constraint( + self.limit_growth_new_capacity_constraint_rpt, + rule=limits.limit_growth_new_capacity_constraint_rule, + ) + self.limit_degrowth_new_capacity_constraint_rpt = Set( + dimen=4, initialize=limits.limit_degrowth_new_capacity_indices + ) + self.limit_degrowth_new_capacity_constraint = Constraint( + self.limit_degrowth_new_capacity_constraint_rpt, + rule=limits.limit_degrowth_new_capacity_constraint_rule, + ) + + self.limit_growth_new_capacity_delta_constraint_rpt = Set( + dimen=4, initialize=limits.limit_growth_new_capacity_delta_indices + ) + self.limit_growth_new_capacity_delta_constraint = Constraint( + self.limit_growth_new_capacity_delta_constraint_rpt, + rule=limits.limit_growth_new_capacity_delta_constraint_rule, + ) + self.limit_degrowth_new_capacity_delta_constraint_rpt = Set( + dimen=4, initialize=limits.limit_degrowth_new_capacity_delta_indices + ) + self.limit_degrowth_new_capacity_delta_constraint = Constraint( + self.limit_degrowth_new_capacity_delta_constraint_rpt, + rule=limits.limit_degrowth_new_capacity_delta_constraint_rule, + ) + + self.limit_activity_constraint = Constraint( + self.limit_activity_constraint_rpt, rule=limits.limit_activity_constraint + ) + + self.limit_seasonal_capacity_factor_constraint = Constraint( + self.limit_seasonal_capacity_factor_constraint_rpst, + rule=limits.limit_seasonal_capacity_factor_constraint, + ) + + self.limit_capacity_constraint = Constraint( + self.limit_capacity_constraint_rpt, rule=limits.limit_capacity_constraint + ) + + self.limit_new_capacity_constraint = Constraint( + self.limit_new_capacity_constraint_rtv, rule=limits.limit_new_capacity_constraint + ) + + self.limit_capacity_share_constraint = Constraint( + self.limit_capacity_share_constraint_rpgg, rule=limits.limit_capacity_share_constraint + ) + + self.limit_activity_share_constraint = Constraint( + self.limit_activity_share_constraint_rpgg, rule=limits.limit_activity_share_constraint + ) + + self.limit_new_capacity_share_constraint = Constraint( + self.limit_new_capacity_share_constraint_rggv, + rule=limits.limit_new_capacity_share_constraint, + ) + + self.progress_marker_8 = BuildAction( + ['Starting Limit Capacity and Tech Split Constraints'], rule=progress_check + ) + + self.limit_resource_constraint = Constraint( + self.limit_resource_constraint_rt, rule=limits.limit_resource_constraint + ) + + self.limit_annual_capacity_factor_constraint_rptvo = Set( + dimen=6, initialize=limits.limit_annual_capacity_factor_indices + ) + self.limit_annual_capacity_factor_constraint = Constraint( + self.limit_annual_capacity_factor_constraint_rptvo, + rule=limits.limit_annual_capacity_factor_constraint, + ) + + ## Tech input splits + self.limit_tech_input_split_constraint_rpsditv = Set( + dimen=8, initialize=limits.limit_tech_input_split_constraint_indices + ) + self.limit_tech_input_split_constraint = Constraint( + self.limit_tech_input_split_constraint_rpsditv, + rule=limits.limit_tech_input_split_constraint, + ) + + self.limit_tech_input_split_annual_constraint_rpitv = Set( + dimen=6, initialize=limits.limit_tech_input_split_annual_constraint_indices + ) + self.limit_tech_input_split_annual_constraint = Constraint( + self.limit_tech_input_split_annual_constraint_rpitv, + rule=limits.limit_tech_input_split_annual_constraint, + ) + + self.limit_tech_input_split_average_constraint_rpitv = Set( + dimen=6, initialize=limits.limit_tech_input_split_average_constraint_indices + ) + self.limit_tech_input_split_average_constraint = Constraint( + self.limit_tech_input_split_average_constraint_rpitv, + rule=limits.limit_tech_input_split_average_constraint, + ) + + ## Tech output splits + self.limit_tech_output_split_constraint_rpsdtvo = Set( + dimen=8, initialize=limits.limit_tech_output_split_constraint_indices + ) + self.limit_tech_output_split_constraint = Constraint( + self.limit_tech_output_split_constraint_rpsdtvo, + rule=limits.limit_tech_output_split_constraint, + ) + + self.limit_tech_output_split_annual_constraint_rptvo = Set( + dimen=6, initialize=limits.limit_tech_output_split_annual_constraint_indices + ) + self.limit_tech_output_split_annual_constraint = Constraint( + self.limit_tech_output_split_annual_constraint_rptvo, + rule=limits.limit_tech_output_split_annual_constraint, + ) + + self.limit_tech_output_split_average_constraint_rptvo = Set( + dimen=6, initialize=limits.limit_tech_output_split_average_constraint_indices + ) + self.limit_tech_output_split_average_constraint = Constraint( + self.limit_tech_output_split_average_constraint_rptvo, + rule=limits.limit_tech_output_split_average_constraint, + ) + + self.renewable_portfolio_standard_constraint = Constraint( + self.renewable_portfolio_standard_constraint_rpg, + rule=limits.renewable_portfolio_standard_constraint, + ) + + self.linked_emissions_tech_constraint_rpsdtve = Set( + dimen=7, initialize=emissions.linked_tech_constraint_indices + ) + # the validation requires that the set above be built first: + self.validate_LinkedTech_lifetimes = BuildCheck(rule=validate_linked_tech) + + self.linked_emissions_tech_constraint = Constraint( + self.linked_emissions_tech_constraint_rpsdtve, + rule=emissions.linked_emissions_tech_constraint, + ) + + self.progress_marker_9 = BuildAction(['Finished Constraints'], rule=progress_check) + + +def progress_check(model: TemoaModel, checkpoint: str) -> None: + """A quick widget which is called by BuildAction in order to log creation progress""" + logger.debug('Model build progress: %s', checkpoint) diff --git a/temoa/core/modes.py b/temoa/core/modes.py new file mode 100644 index 000000000..5d55d4e3a --- /dev/null +++ b/temoa/core/modes.py @@ -0,0 +1,20 @@ +""" +The possible operating modes for a scenario +""" + +from enum import Enum, unique + + +@unique +class TemoaMode(Enum): + """The processing mode for the scenario""" + + PERFECT_FORESIGHT = 1 # Normal run, single execution for full time horizon + MGA = 2 # Modeling for Generation of Alternatives, multiple runs w/ changing constrained obj + MYOPIC = 3 # Step-wise execution through the future + METHOD_OF_MORRIS = 4 # Method-of-Morris run + BUILD_ONLY = 5 # Just build the model, no solve + CHECK = 6 # build and run price check, source trace it + SVMGA = 7 # single-vector MGA + MONTE_CARLO = 8 # MC optimization + STOCHASTIC = 9 # Stochastic optimization diff --git a/temoa/data_io/README.md b/temoa/data_io/README.md new file mode 100644 index 000000000..0e85cf30c --- /dev/null +++ b/temoa/data_io/README.md @@ -0,0 +1,115 @@ +# Temoa Data I/O Subsystem + +This directory contains the core data loading engine for the Temoa model. It is responsible for reading data from a SQLite database, validating it, and preparing it for instantiation in a Pyomo model. + +## Architecture: Manifest-Driven Loading + +The data loading process follows a declarative, **manifest-driven architecture**. This design separates the *configuration* of what to load from the *procedural logic* of how to load it. This makes the system easier to understand, maintain, and extend. + +The key files are: + +- `hybrid_loader.py`: The main engine. Its `HybridLoader` class orchestrates the entire process. It iterates through the manifest, fetches data, applies validation, and calls specialized "custom loaders" for components that require complex logic. +- `component_manifest.py`: **This is the primary file for developers to modify.** It contains the `build_manifest` function, which returns a declarative list of all data components to be loaded into the model. Each entry in this list is a `LoadItem`. +- `loader_manifest.py`: Defines the `LoadItem` dataclass, which is the schema for each entry in the manifest. It tells the `HybridLoader` everything it needs to know about a component: its source table, columns, validation rules, etc. + +## How to Add a New Model Component + +Adding new data components to the model is now a straightforward process that primarily involves editing the `component_manifest.py` file. + +### Case 1: Adding a Simple Set or Parameter + +This is the most common case, for a component that maps directly to a database table. + +**Goal:** Add a new parameter `MyNewParam(region, tech, value)`. + +**Steps:** + +1. **Define the component** in `temoa/core/model.py` (e.g., `M.MyNewParam = Param(M.regions, M.tech_production)`). +2. **Open `temoa/data_io/component_manifest.py`**. +3. **Add a `LoadItem`** to the manifest list in the appropriate logical section (e.g., under `Operational Constraints`). + + ```python + # temoa/data_io/component_manifest.py + + # ... inside the manifest list + LoadItem( + component=M.MyNewParam, + table='MyNewParamTableName', + columns=['region', 'tech', 'value'], + # Optional: Add validation if this component should be filtered + # by the source-trace analysis. + validator_name='viable_rt', + validation_map=(0, 1), # Corresponds to 'region' and 'tech' columns + ), + # ... + ``` + +4. **You're done.** The `HybridLoader` engine will automatically handle fetching, validating, and loading this component. + +### Case 2: Adding a Component with a Simple Fallback + +If a component is optional and should have a default value if its table is missing, use the `fallback_data` attribute. + +**Goal:** Add an optional set `MyOptionalSet(some_value)` that defaults to `[('A',), ('B',)]`. + +**Steps:** + +1. **Define the component** in `temoa/core/model.py`. +2. **Add a `LoadItem`** to `component_manifest.py`. + + ```python + # temoa/data_io/component_manifest.py + + LoadItem( + component=M.MyOptionalSet, + table='MyOptionalSetTable', + columns=['some_value'], + is_table_required=False, # Mark the table as optional + fallback_data=[('A',), ('B',)] # Provide default data + ), + ``` + +### Case 3: Adding a Component with Complex Logic + +If a component requires logic that doesn't fit the standard pattern (e.g., aggregating from multiple tables, complex myopic queries, dynamic fallbacks), use a custom loader. + +**Goal:** Add a parameter `MyComplexParam` that requires special handling. + +**Steps:** + +1. **Define the component** in `temoa/core/model.py`. +2. **Open `temoa/data_io/hybrid_loader.py`**. +3. **Create a new custom loader method** inside the `HybridLoader` class. The method must accept `self, data, raw_data, filtered_data` as arguments. + + ```python + # temoa/data_io/hybrid_loader.py + + # ... inside the HybridLoader class, in the custom loaders section + def _load_my_complex_param(self, data: dict, raw_data: Sequence[tuple], filtered_data: Sequence[tuple]): + """Custom loader for MyComplexParam.""" + M = TemoaModel() + # --- Add your custom logic here --- + # For example, perform a special query or transform the data. + final_data_to_load = [(r, t, v * 2) for r, t, v in filtered_data] # Example transformation + + # Use the standard helper to load the final data + self._load_component_data(data, M.MyComplexParam, final_data_to_load) + ``` + +4. **Open `temoa/data_io/component_manifest.py`**. +5. **Add a `LoadItem`** that points to your new custom loader. + + ```python + # temoa/data_io/component_manifest.py + + LoadItem( + component=M.MyComplexParam, + table='MyComplexParamTable', # Can be a real table or a placeholder + columns=['region', 'tech', 'value'], + # Point to your new method + custom_loader_name='_load_my_complex_param', + is_table_required=False # Usually False if the loader has complex logic + ), + ``` + +This pattern keeps the main engine clean while providing unlimited flexibility to handle any data loading scenario. diff --git a/temoa/data_io/component_manifest.py b/temoa/data_io/component_manifest.py new file mode 100644 index 000000000..e22fdf673 --- /dev/null +++ b/temoa/data_io/component_manifest.py @@ -0,0 +1,732 @@ +# temoa/data_io/component_manifest.py +""" +Defines the data loading manifest for the Temoa model. + +This module contains a single function, `build_manifest`, which constructs a +list of `LoadItem` objects. This list serves as the declarative configuration +for the `HybridLoader`, specifying every data component to be loaded from the +database into the Pyomo model. + +The manifest is organized into logical groups that mirror the structure of the +Temoa model itself (e.g., Time, Regions, Technologies, Costs, Constraints). +This cohesive grouping makes it easier for developers to find and understand +how specific parts of the model are populated with data. + +To add a new standard component to the model, a developer typically only needs +to add a new `LoadItem` to this manifest. +""" + +from temoa.core.model import TemoaModel +from temoa.data_io.loader_manifest import LoadItem + + +def build_manifest(model: TemoaModel) -> list[LoadItem]: + """ + Builds the manifest of all data components to be loaded into the Pyomo model. + + This declarative approach separates the configuration of what to load from the + procedural logic of how to load it. The manifest is ordered logically to + enhance readability and maintainability. + + Args: + M: An instance of TemoaModel to link components. + + Returns: + A list of LoadItem objects describing all data to be loaded. + """ + manifest = [ + # ========================================================================= + # Core Model Structure (Regions, Techs, Commodities, Groups) + # ========================================================================= + LoadItem( + component=model.regions, + table='region', + columns=['region'], + is_period_filtered=False, + ), + LoadItem( + component=model.regional_global_indices, + table='meta_regional_groups', # Placeholder, custom loader does the work + columns=['region_or_group'], + custom_loader_name='_load_regional_global_indices', + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.tech_production, + table='technology', + columns=['tech'], + where_clause="flag LIKE 'p%'", + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_uncap, + table='technology', + columns=['tech'], + where_clause='unlim_cap > 0', + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.tech_baseload, + table='technology', + columns=['tech'], + where_clause="flag = 'pb'", + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_storage, + table='technology', + columns=['tech'], + where_clause="flag = 'ps'", + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_seasonal_storage, + table='technology', + columns=['tech'], + where_clause="flag = 'ps' AND seas_stor > 0", + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_reserve, + table='technology', + columns=['tech'], + where_clause='reserve > 0', + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_curtailment, + table='technology', + columns=['tech'], + where_clause='curtail > 0', + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_flex, + table='technology', + columns=['tech'], + where_clause='flex > 0', + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_exchange, + table='technology', + columns=['tech'], + where_clause='exchange > 0', + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_annual, + table='technology', + columns=['tech'], + where_clause='annual > 0', + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_retirement, + table='technology', + columns=['tech'], + where_clause='retire > 0', + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.tech_group_names, + table='tech_group', + columns=['group_name'], + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.tech_group_members, + table='tech_group_member', + columns=['group_name', 'tech'], + custom_loader_name='_load_tech_group_members', + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.commodity_demand, + table='commodity', + columns=['name'], + where_clause="flag = 'd'", + validator_name='viable_comms', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.commodity_emissions, + table='commodity', + columns=['name'], + where_clause="flag = 'e'", + is_period_filtered=False, + ), + LoadItem( + component=model.commodity_physical, + table='commodity', + columns=['name'], + where_clause="flag LIKE '%p%' OR flag = 's' OR flag LIKE '%a%'", + validator_name='viable_comms', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.commodity_source, + table='commodity', + columns=['name'], + where_clause="flag = 's'", + validator_name='viable_input_comms', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.commodity_annual, + table='commodity', + columns=['name'], + where_clause="flag LIKE '%a%'", + validator_name='viable_comms', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.commodity_waste, + table='commodity', + columns=['name'], + where_clause="flag LIKE '%w%'", + validator_name='viable_output_comms', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.operator, + table='operator', + columns=['operator'], + is_period_filtered=False, + is_table_required=False, + ), + # ========================================================================= + # Time-Related Components + # ========================================================================= + LoadItem( + component=model.time_of_day, + table='time_of_day', + columns=['tod'], + is_period_filtered=False, + is_table_required=False, + fallback_data=[('D',)], + order_by=['sequence'], + ), + LoadItem( + component=model.time_of_day_hours, + table='time_of_day', + columns=['tod', 'hours'], + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.time_season, + table='time_season', + columns=['season'], + custom_loader_name='_load_time_season', + is_period_filtered=False, # Custom loader handles myopic filtering + is_table_required=False, + order_by=['sequence'], + ), + LoadItem( + component=model.segment_fraction_per_season, + table='time_season', + columns=['season', 'segment_fraction'], + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.time_season_sequential, + table='time_season_sequential', + columns=['seas_seq', 'season', 'segment_fraction'], + custom_loader_name='_load_time_season_sequential', + is_period_filtered=False, + is_table_required=False, + order_by=['sequence'], + ), + LoadItem( + component=model.time_manual, + table='time_manual', + columns=['season', 'tod', 'season_next', 'tod_next'], + is_period_filtered=False, + is_table_required=False, + ), + # ========================================================================= + # Capacity and Cost Components + # ========================================================================= + LoadItem( + component=model.existing_capacity, + table='existing_capacity', + columns=['region', 'tech', 'vintage', 'capacity'], + custom_loader_name='_load_existing_capacity', + is_period_filtered=False, # Custom loader handles all logic + is_table_required=False, + ), + LoadItem( + component=model.cost_invest, + table='cost_invest', + columns=['region', 'tech', 'vintage', 'cost'], + validator_name='viable_rtv_new', + validation_map=(0, 1, 2), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.cost_fixed, + table='cost_fixed', + columns=['region', 'period', 'tech', 'vintage', 'cost'], + validator_name='viable_rtv', + validation_map=(0, 2, 3), + ), + LoadItem( + component=model.cost_variable, + table='cost_variable', + columns=['region', 'period', 'tech', 'vintage', 'cost'], + validator_name='viable_rtv', + validation_map=(0, 2, 3), + ), + LoadItem( + component=model.cost_emission, + table='cost_emission', + columns=['region', 'period', 'emis_comm', 'cost'], + is_table_required=False, + ), + LoadItem( + component=model.loan_rate, + table='loan_rate', + columns=['region', 'tech', 'vintage', 'rate'], + validator_name='viable_rtv_new', + validation_map=(0, 1, 2), + is_period_filtered=False, + is_table_required=False, + ), + # ========================================================================= + # Singleton and Configuration-based Components + # ========================================================================= + LoadItem( + component=model.global_discount_rate, + table='metadata_real', + columns=['value'], + where_clause="element = 'global_discount_rate'", + custom_loader_name='_load_global_discount_rate', + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.default_loan_rate, + table='metadata_real', + columns=['value'], + where_clause="element = 'default_loan_rate'", + custom_loader_name='_load_default_loan_rate', + is_period_filtered=False, + is_table_required=False, + ), + # ========================================================================= + # Operational Constraints and Parameters + # ========================================================================= + LoadItem( + component=model.efficiency, + table='meta_efficiency', # Placeholder, custom loader does the work + columns=[], + custom_loader_name='_load_efficiency', + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.efficiency_variable, + table='efficiency_variable', + columns=[ + 'region', + 'season', + 'tod', + 'input_comm', + 'tech', + 'vintage', + 'output_comm', + 'efficiency', + ], + validator_name='viable_ritvo', + validation_map=(0, 3, 4, 5, 6), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.demand, + table='demand', + columns=['region', 'period', 'commodity', 'demand'], + ), + LoadItem( + component=model.demand_specific_distribution, + table='demand_specific_distribution', + columns=['region', 'period', 'season', 'tod', 'demand_name', 'dsd'], + is_period_filtered=True, + is_table_required=False, + ), + LoadItem( + component=model.capacity_to_activity, + table='capacity_to_activity', + columns=['region', 'tech', 'c2a'], + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.capacity_factor_tech, + table='capacity_factor_tech', + columns=['region', 'season', 'tod', 'tech', 'factor'], + validator_name='viable_rt', + validation_map=(0, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.capacity_factor_process, + table='capacity_factor_process', + columns=['region', 'season', 'tod', 'tech', 'vintage', 'factor'], + validator_name='viable_rtv', + validation_map=(0, 3, 4), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.lifetime_tech, + table='lifetime_tech', + columns=['region', 'tech', 'lifetime'], + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.lifetime_process, + table='lifetime_process', + columns=['region', 'tech', 'vintage', 'lifetime'], + validator_name='viable_rtv', + validation_map=(0, 1, 2), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.lifetime_survival_curve, + table='lifetime_survival_curve', + columns=['region', 'period', 'tech', 'vintage', 'fraction'], + validator_name='viable_rtv', + validation_map=(0, 2, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.loan_lifetime_process, + table='loan_lifetime_process', + columns=['region', 'tech', 'vintage', 'lifetime'], + validator_name='viable_rtv_new', + validation_map=(0, 1, 2), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.ramp_up_hourly, + table='ramp_up_hourly', + columns=['region', 'tech', 'rate'], + custom_loader_name='_load_ramping_up', + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.tech_upramping, + table='ramp_up_hourly', + columns=['tech'], + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.ramp_down_hourly, + table='ramp_down_hourly', + columns=['region', 'tech', 'rate'], + custom_loader_name='_load_ramping_down', + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.tech_downramping, + table='ramp_down_hourly', + columns=['tech'], + validator_name='viable_techs', + validation_map=(0,), + is_period_filtered=False, + ), + LoadItem( + component=model.renewable_portfolio_standard, + table='rps_requirement', + columns=['region', 'period', 'tech_group', 'requirement'], + custom_loader_name='_load_rps_requirement', + is_table_required=False, + ), + LoadItem( + component=model.capacity_credit, + table='capacity_credit', + columns=['region', 'period', 'tech', 'vintage', 'credit'], + validator_name='viable_rtv', + validation_map=(0, 2, 3), + is_table_required=False, + ), + LoadItem( + component=model.reserve_capacity_derate, + table='reserve_capacity_derate', + columns=['region', 'season', 'tech', 'vintage', 'factor'], + validator_name='viable_rtv', + validation_map=(0, 2, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.planning_reserve_margin, + table='planning_reserve_margin', + columns=['region', 'margin'], + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.storage_duration, + table='storage_duration', + columns=['region', 'tech', 'duration'], + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.limit_storage_fraction, + table='limit_storage_level_fraction', + columns=[ + 'region', + 'season', + 'tod', + 'tech', + 'operator', + 'fraction', + ], + validator_name='viable_rt', + validation_map=(0, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.emission_activity, + table='emission_activity', + columns=[ + 'region', + 'emis_comm', + 'input_comm', + 'tech', + 'vintage', + 'output_comm', + 'activity', + ], + validator_name='viable_ritvo', + validation_map=(0, 2, 3, 4, 5), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.emission_embodied, + table='emission_embodied', + columns=['region', 'emis_comm', 'tech', 'vintage', 'value'], + validator_name='viable_rtv_new', + validation_map=(0, 2, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.emission_end_of_life, + table='emission_end_of_life', + columns=['region', 'emis_comm', 'tech', 'vintage', 'value'], + validator_name='viable_rtv', + validation_map=(0, 2, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.construction_input, + table='construction_input', + columns=['region', 'input_comm', 'tech', 'vintage', 'value'], + validator_name='viable_rtv_new', + validation_map=(0, 2, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.end_of_life_output, + table='end_of_life_output', + columns=['region', 'tech', 'vintage', 'output_comm', 'value'], + validator_name='viable_rtv_eol', + validation_map=(0, 1, 2), + is_period_filtered=False, + is_table_required=False, + ), + # ========================================================================= + # Limit Constraints + # ========================================================================= + LoadItem( + component=model.limit_capacity, + table='limit_capacity', + columns=['region', 'period', 'tech_or_group', 'operator', 'capacity'], + validator_name='viable_rpt', + validation_map=(0, 1, 2), + is_table_required=False, + ), + LoadItem( + component=model.limit_new_capacity, + table='limit_new_capacity', + columns=['region', 'tech_or_group', 'vintage', 'operator', 'new_cap'], + validator_name='viable_rtv_new', + validation_map=(0, 1, 2), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.limit_capacity_share, + table='limit_capacity_share', + columns=['region', 'period', 'sub_group', 'super_group', 'operator', 'share'], + validator_name='viable_rpt', + validation_map=(0, 1, 2), + is_table_required=False, + ), + LoadItem( + component=model.limit_new_capacity_share, + table='limit_new_capacity_share', + columns=['region', 'sub_group', 'super_group', 'vintage', 'operator', 'share'], + validator_name='viable_rtv_new', + validation_map=(0, 1, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.limit_activity, + table='limit_activity', + columns=['region', 'period', 'tech_or_group', 'operator', 'activity'], + validator_name='viable_rpt', + validation_map=(0, 1, 2), + is_table_required=False, + ), + LoadItem( + component=model.limit_activity_share, + table='limit_activity_share', + columns=['region', 'period', 'sub_group', 'super_group', 'operator', 'share'], + validator_name='viable_rpt', + validation_map=(0, 1, 2), + is_table_required=False, + ), + LoadItem( + component=model.limit_resource, + table='limit_resource', + columns=['region', 'tech_or_group', 'operator', 'cum_act'], + validator_name='viable_rt', + validation_map=(0, 1), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.limit_seasonal_capacity_factor, + table='limit_seasonal_capacity_factor', + columns=['region', 'season', 'tech_or_group', 'operator', 'factor'], + validator_name='viable_rt', + validation_map=(0, 2), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.limit_annual_capacity_factor, + table='limit_annual_capacity_factor', + columns=['region', 'tech_or_group', 'vintage', 'output_comm', 'operator', 'factor'], + validator_name='viable_rtvo', + validation_map=(0, 1, 2, 3), + is_period_filtered=False, + is_table_required=False, + ), + LoadItem( + component=model.limit_emission, + table='limit_emission', + columns=['region', 'period', 'emis_comm', 'operator', 'value'], + is_table_required=False, + ), + LoadItem( + component=model.limit_tech_input_split, + table='limit_tech_input_split', + columns=['region', 'period', 'input_comm', 'tech', 'operator', 'proportion'], + validator_name='viable_rpit', + validation_map=(0, 1, 2, 3), + custom_loader_name='_load_limit_tech_input_split', + is_table_required=False, + ), + LoadItem( + component=model.limit_tech_input_split_annual, + table='limit_tech_input_split_annual', + columns=['region', 'period', 'input_comm', 'tech', 'operator', 'proportion'], + validator_name='viable_rpit', + validation_map=(0, 1, 2, 3), + custom_loader_name='_load_limit_tech_input_split_annual', + is_table_required=False, + ), + LoadItem( + component=model.limit_tech_output_split, + table='limit_tech_output_split', + columns=['region', 'period', 'tech', 'output_comm', 'operator', 'proportion'], + validator_name='viable_rpto', + validation_map=(0, 1, 2, 3), + custom_loader_name='_load_limit_tech_output_split', + is_table_required=False, + ), + LoadItem( + component=model.limit_tech_output_split_annual, + table='limit_tech_output_split_annual', + columns=['region', 'period', 'tech', 'output_comm', 'operator', 'proportion'], + validator_name='viable_rpto', + validation_map=(0, 1, 2, 3), + custom_loader_name='_load_limit_tech_output_split_annual', + is_table_required=False, + ), + LoadItem( + component=model.linked_techs, + table='linked_tech', + columns=['primary_region', 'primary_tech', 'emis_comm', 'driven_tech'], + validator_name='viable_rtt', + validation_map=(0, 1, 3), + custom_loader_name='_load_linked_techs', + is_period_filtered=False, + is_table_required=False, + ), + ] + return manifest diff --git a/temoa/data_io/hybrid_loader.py b/temoa/data_io/hybrid_loader.py new file mode 100644 index 000000000..a4e2ce4d2 --- /dev/null +++ b/temoa/data_io/hybrid_loader.py @@ -0,0 +1,872 @@ +# temoa/data_io/hybrid_loader.py +""" +Defines the main data loading engine for the Temoa model. + +The primary component of this module is the `HybridLoader` class, which is +responsible for reading, validating, and formatting data from a Temoa SQLite +database for use in a Pyomo model. + +Architecture: + The loader operates on a declarative, manifest-driven architecture. The + configuration for what data to load is defined externally in + `temoa.data_io.component_manifest.py`. This separation of concerns means + that adding new, standard model components often only requires a change to + the manifest, not this procedural code. + + For components that require complex logic (e.g., conditional queries for + myopic mode, data aggregation, or dynamic fallbacks), the manifest directs + the engine to use specialized 'custom loader' methods within the + `HybridLoader` class. +""" + +from __future__ import annotations + +import time +from collections import defaultdict +from logging import getLogger +from sqlite3 import Connection, Cursor, OperationalError +from typing import TYPE_CHECKING, cast + +from pyomo.core import Param, Set +from pyomo.dataportal import DataPortal + +from temoa.core.model import TemoaModel +from temoa.core.modes import TemoaMode +from temoa.data_io.component_manifest import build_manifest +from temoa.extensions.myopic.myopic_index import MyopicIndex +from temoa.model_checking import element_checker, network_model_data +from temoa.model_checking.commodity_network_manager import CommodityNetworkManager +from temoa.model_checking.element_checker import ValidationPrimitive, ViableSet + +if TYPE_CHECKING: + from collections.abc import Sequence + + from temoa.core.config import TemoaConfig + from temoa.data_io.loader_manifest import LoadItem + +logger = getLogger(__name__) + +# A manifest of tables that may contain region groups, used by a custom loader. +tables_with_regional_groups = { + 'limit_annual_capacity_factor': 'region', + 'limit_emission': 'region', + 'limit_seasonal_capacity_factor': 'region', + 'limit_capacity': 'region', + 'limit_activity': 'region', + 'limit_new_capacity': 'region', + 'limit_activity_share': 'region', + 'limit_capacity_share': 'region', + 'limit_new_capacity_share': 'region', + 'limit_resource': 'region', + 'limit_growth_capacity': 'region', + 'limit_degrowth_capacity': 'region', + 'limit_growth_new_capacity': 'region', + 'limit_degrowth_new_capacity': 'region', + 'limit_growth_new_capacity_delta': 'region', + 'limit_degrowth_new_capacity_delta': 'region', +} + + +class HybridLoader: + """ + Drives the loading of model data from a SQLite database into a format + suitable for Pyomo's DataPortal. + + This loader is manifest-driven. The `component_manifest.py` file provides a + declarative list of all components to be loaded, separating the configuration + of what to load from the procedural logic of how to load it. + """ + + def __init__(self, db_connection: Connection, config: TemoaConfig) -> None: + """ + Initializes the loader. + + :param db_connection: An active SQLite database connection. + :param config: The Temoa configuration object. + """ + self.debugging = False + self.con = db_connection + self.config = config + self.myopic_index: MyopicIndex | None = None + + # Build the data loading manifest and a name-based map for quick lookup + model = TemoaModel() + self.manifest = build_manifest(model) + self.manifest_map = {item.component.name: item for item in self.manifest} + + # --- Data containers and filters populated during loading --- + self.manager: CommodityNetworkManager | None = None + self.efficiency_values: list[tuple[object, ...]] = [] + self.data: dict[str, object] | None = None + + # --- Viable sets for source-trace filtering --- + self.viable_techs: ViableSet | None = None + self.viable_comms: ViableSet | None = None + self.viable_input_comms: ViableSet | None = None + self.viable_output_comms: ViableSet | None = None + self.viable_vintages: ViableSet | None = None + self.viable_ritvo: ViableSet | None = None + self.viable_rtvo: ViableSet | None = None + self.viable_rtv_new: ViableSet | None = None + self.viable_rpt: ViableSet | None = None + self.viable_rpto: ViableSet | None = None + self.viable_rtv: ViableSet | None = None + self.viable_rtv_eol: ViableSet | None = None + self.viable_rt: ViableSet | None = None + self.viable_rpit: ViableSet | None = None + self.viable_rtt: ViableSet | None = None + + def source_trace_only(self, myopic_index: MyopicIndex | None = None) -> None: + """ + Runs only the source-trace analysis without a full data load. + This is primarily for the 'check' mode. + + :param myopic_index: The MyopicIndex for the run, if applicable. + """ + if myopic_index and not isinstance(myopic_index, MyopicIndex): + raise ValueError('myopic_index must be an instance of MyopicIndex') + self._source_trace(myopic_index) + self.manager = None # Prevent use of stale data + + def load_data_portal(self, myopic_index: MyopicIndex | None = None) -> DataPortal: + """ + Main entry point to create and load a DataPortal object. + + :param myopic_index: The MyopicIndex for the run, if applicable. + :return: A populated Pyomo DataPortal object. + """ + tic = time.time() + data_dict = self.create_data_dict(myopic_index=myopic_index) + + namespace = {None: data_dict} + dp = DataPortal(data_dict=namespace) + + toc = time.time() + logger.debug('Data Portal Load time: %0.5f seconds', (toc - tic)) + return dp + + @staticmethod + def data_portal_from_data(data_source: dict[str, object]) -> DataPortal: + """ + Creates a DataPortal object from an existing data dictionary. + Useful for model runs where the data has been modified in memory. + + :param data_source: The data dictionary to use. + :return: A new DataPortal object. + """ + namespace = {None: data_source} + dp = DataPortal(data_dict=namespace) + return dp + + # ================================================================================= + # Main Data Loading Engine + # ================================================================================= + + def create_data_dict(self, myopic_index: MyopicIndex | None = None) -> dict[str, object]: + """ + The main manifest-driven engine for loading model data. + + This method orchestrates the loading process: + 1. Performs setup (source tracing, critical time sets). + 2. Iterates through the manifest from `component_manifest.py`. + 3. For each component, it fetches, filters, and loads the data. + 4. Delegates to custom loader methods for special cases. + 5. Finalizes the data dictionary with derived index sets. + + :param myopic_index: The MyopicIndex for myopic runs. None for other modes. + :return: A dictionary of model data suitable for a DataPortal. + """ + logger.info('Loading data dictionary') + + # --------------------------------------------------------------------- + # Preamble: Setup, source tracing, and loading critical index sets + # --------------------------------------------------------------------- + if myopic_index: + if not isinstance(myopic_index, MyopicIndex): + raise ValueError(f'Received illegal entry for myopic index: {myopic_index}') + if self.config.scenario_mode != TemoaMode.MYOPIC: + raise RuntimeError('Myopic Index passed, but mode is not Myopic.') + elif not myopic_index and self.config.scenario_mode == TemoaMode.MYOPIC: + raise RuntimeError('Mode is myopic, but no MyopicIndex specified.') + + self.myopic_index = myopic_index + + use_raw_data = not ( + self.config.source_trace or self.config.scenario_mode == TemoaMode.MYOPIC + ) + if not use_raw_data: + self._source_trace(myopic_index=myopic_index) + + self._build_efficiency_dataset(use_raw_data=use_raw_data, myopic_index=myopic_index) + + data: dict[str, object] = {} + cur = self.con.cursor() + model = TemoaModel() + + # Load critical time sets first, as they index other components + if myopic_index: + raw_exist = cur.execute( + 'SELECT period FROM time_period WHERE period < ? ORDER BY sequence', + (myopic_index.base_year,), + ).fetchall() + raw_future = cur.execute( + 'SELECT period FROM time_period WHERE flag = "f" AND period >= ? AND period <= ? ' + 'ORDER BY sequence', + (myopic_index.base_year, myopic_index.last_year), + ).fetchall() + else: + raw_exist = cur.execute( + "SELECT period FROM time_period WHERE flag = 'e' ORDER BY sequence" + ).fetchall() + raw_future = cur.execute( + "SELECT period FROM time_period WHERE flag = 'f' ORDER BY sequence" + ).fetchall() + self._load_component_data(data, model.time_exist, raw_exist) + self._load_component_data(data, model.time_future, raw_future) + data['time_optimize'] = [p[0] for p in raw_future[:-1]] + + # --------------------------------------------------------------------- + # Manifest-driven loading loop + # --------------------------------------------------------------------- + for item in self.manifest: + # 1. Fetch data from the database + raw_data = self._fetch_data(cur, item, myopic_index) + + # 2. Validate/filter data + filtered_data = self._filter_data(raw_data, item, use_raw_data) + + # 3. Load data using either a custom loader or the standard path + if item.custom_loader_name: + loader_func = getattr(self, item.custom_loader_name) + loader_func(data, raw_data, filtered_data) + else: + # Standard loading path for non-custom components + if not raw_data and not item.is_table_required and item.fallback_data: + logger.warning( + "Table '%s' not found or is empty. Using default values for %s.", + item.table, + item.component.name, + ) + raw_data = item.fallback_data + filtered_data = self._filter_data(raw_data, item, use_raw_data) + + if len(filtered_data) < len(raw_data): + ignored_count = len(raw_data) - len(filtered_data) + msg = '%d values for %s failed to validate and were ignored.' + if myopic_index: + logger.info(msg, ignored_count, item.component.name) + else: + logger.warning(msg, ignored_count, item.component.name) + self._load_component_data(data, item.component, filtered_data) + + # --------------------------------------------------------------------- + # Finalization + # --------------------------------------------------------------------- + # Load simple config-based or myopic-specific values + self._load_component_data(data, model.time_sequencing, [(self.config.time_sequencing,)]) + self._load_component_data(data, model.days_per_period, [(self.config.days_per_period,)]) + self._load_component_data( + data, model.reserve_margin_method, [(self.config.reserve_margin,)] + ) + if myopic_index: + p0_result = cur.execute( + "SELECT min(period) FROM time_period WHERE flag == 'f'" + ).fetchone() + if p0_result: + data[model.myopic_discounting_year.name] = {None: int(p0_result[0])} + + # Create derived index sets for parameters now that all base data is loaded + set_data = self.load_param_idx_sets(data=data) + data.update(set_data) + self.data = data + + return data + + # ================================================================================= + # Core Engine Helpers + def _fetch_data( + self, cur: Cursor, item: LoadItem, mi: MyopicIndex | None + ) -> list[tuple[object, ...]]: + """ + Fetches data for a component based on its manifest item. + + :param cur: The database cursor. + :param item: The LoadItem describing what to fetch. + :param mi: The MyopicIndex for period filtering, if applicable. + :return: A list of tuples containing the raw data. + """ + # If this is a custom loader and no columns are specified, no fetch is needed. + if item.custom_loader_name and not item.columns: + return [] + + if not self.table_exists(item.table): + if item.is_table_required: + raise FileNotFoundError(f"Required table '{item.table}' not found in the database.") + return [] + + query = f'SELECT {", ".join(item.columns)} FROM main.{item.table}' + params = [] + + where_clauses = [] + if item.where_clause: + where_clauses.append(f'({item.where_clause})') + if item.is_period_filtered and mi: + where_clauses.append('period >= ? AND period <= ?') + params.extend([mi.base_year, mi.last_demand_year]) + + if where_clauses: + query += ' WHERE ' + ' AND '.join(where_clauses) + + if item.order_by: + query += ' ORDER BY ' + ', '.join(item.order_by) + + try: + return cur.execute(query, params).fetchall() + except OperationalError as e: + if not item.is_table_required: + logger.info( + 'Could not load optional component %s, likely due to older schema. Skipping. ' + 'Error: %s', + item.component.name, + e, + ) + return [] + else: + raise + + def _filter_data( + self, values: Sequence[tuple[object, ...]], item: LoadItem, use_raw_data: bool + ) -> Sequence[tuple[object, ...]]: + """ + Applies validation filters to a list of data tuples. + + :param values: The raw data tuples from the database. + :param item: The LoadItem describing the component. + :param use_raw_data: If True, skips filtering. + :return: A filtered sequence of data tuples. + """ + if use_raw_data or not item.validator_name: + return values + + validator = getattr(self, item.validator_name, None) + if validator is None: + return values + + typed_values = cast('Sequence[tuple[ValidationPrimitive, ...]]', values) + return element_checker.filter_elements( + values=typed_values, validation=validator, value_locations=item.validation_map + ) + + def _load_component_data( + self, + data: dict[str, object], + component: Set | Param, + values: Sequence[tuple[object, ...]], + ) -> None: + """ + Loads a sequence of values into the data dictionary for a given Pyomo component. + + :param data: The main data dictionary being built. + :param component: The Pyomo Set or Param to load. + :param values: The sequence of data tuples to load. + """ + if not values: + return + if isinstance(component, Set): + if len(values[0]) == 1: + data[component.name] = [t[0] for t in values] + else: + data[component.name] = list(values) + elif isinstance(component, Param): + # A singleton/scalar Param is represented by a single tuple with one + # element, e.g., [(value,)]. The data dictionary needs to map this + # to {None: value}. An indexed Param has tuples with len > 1, + # e.g., [(key1, key2, value)], which map to {(key1, key2): value}. + if len(values[0]) == 1: + if len(values) > 1: + logger.warning( + "Component '%s' appears to be a scalar Param but has multiple values. " + 'Using only the first value.', + component.name, + ) + data[component.name] = {None: values[0][0]} + else: # Indexed Param + data[component.name] = {t[:-1]: t[-1] for t in values} + + def table_exists(self, table_name: str) -> bool: + """ + Checks if a table exists in the database schema. + + :param table_name: The name of the table to check. + :return: True if the table exists, False otherwise. + """ + table_name_check = ( + self.con.cursor() + .execute("SELECT name FROM sqlite_master WHERE type='table' AND name= ?", (table_name,)) + .fetchone() + ) + return bool(table_name_check) + + # ================================================================================= + # Internal Setup Methods + # ================================================================================= + + def _source_trace(self, myopic_index: MyopicIndex | None = None) -> None: + """ + Performs the source-trace analysis to identify viable components. + """ + network_data = network_model_data.build(self.con, myopic_index) + cur = self.con.cursor() + periods = set( + [ + p + for (p,) in cur.execute( + "SELECT period FROM time_period WHERE flag = 'f' ORDER BY period" + ) + ][:-1] # drop last period + ) + + if myopic_index: + periods = { + p for p in periods if myopic_index.base_year <= p <= myopic_index.last_demand_year + } + + self.manager = CommodityNetworkManager(periods=periods, network_data=network_data) + if not self.manager.analyze_network() and not self.config.silent: + print('\nWarning: Orphaned processes detected. See log file for details.') + self.manager.analyze_graphs(self.config) + + def _build_efficiency_dataset( + self, use_raw_data: bool, myopic_index: MyopicIndex | None = None + ) -> None: + """ + Builds the efficiency dataset, applying source-trace filters if necessary. + """ + cur = self.con.cursor() + if myopic_index: + contents = cur.execute( + 'SELECT region, input_comm, tech, vintage, output_comm, efficiency, lifetime ' + 'FROM myopic_efficiency WHERE vintage + lifetime > ?', + (myopic_index.base_year,), + ).fetchall() + else: + contents = cur.execute( + 'SELECT region, input_comm, tech, vintage, output_comm, efficiency, NULL FROM ' + 'main.efficiency' + ).fetchall() + + if use_raw_data: + self.efficiency_values = sorted([row[:-1] for row in contents]) + return + + if not self.manager: + raise RuntimeError('Source trace manager not initialized for filtering.') + + # Build viable sets for tech_or_group + # Create a dictionary from the tech_group_members table + tech_groups = defaultdict(set) + if self.table_exists('tech_group_member'): + for group_name, tech in cur.execute('SELECT group_name, tech FROM tech_group_member'): + tech_groups[tech].add(group_name) + + filts = self.manager.build_filters(tech_groups) + self.viable_ritvo = filts['ritvo'] + self.viable_rtvo = filts['rtvo'] + self.viable_rtv_new = filts['rtv_new'] + self.viable_rpt = filts['rpt'] + self.viable_rtv = filts['rtv'] + self.viable_rt = filts['rt'] + self.viable_rpit = filts['rpit'] + self.viable_rpto = filts['rpto'] + self.viable_rtv_eol = filts['rtv_eol'] + self.viable_techs = filts['t'] + self.viable_input_comms = filts['ic'] + self.viable_output_comms = filts['oc'] + + # NOTE: Using member_tuples here is safer as it's unambiguously typed + ic_tuples = self.viable_input_comms.member_tuples + oc_tuples = self.viable_output_comms.member_tuples + self.viable_comms = ViableSet(elements=ic_tuples | oc_tuples) + + rt_tuples = filts['rt'].member_tuples + t_tuples = filts['t'].member_tuples + rtt = {(r, t1, t2) for r, t1 in rt_tuples for (t2,) in t_tuples} + self.viable_rtt = ViableSet( + elements=rtt, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES + ) + + efficiency_entries = { + row[:-1] + for row in contents + if (row[0], row[1], row[2], row[3], row[4]) in self.viable_ritvo.members + } + logger.debug('Polled %d elements from efficiency tables', len(efficiency_entries)) + self.efficiency_values = sorted(efficiency_entries) + + # ================================================================================= + # Custom Loaders (Grouped by Cohesion) + # ================================================================================= + + # --- Core Model Structure --- + def _load_regional_global_indices( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """ + Aggregates region and group names from the Region table and all Limit tables. + """ + model = TemoaModel() + cur = self.con.cursor() + regions_and_groups: set[str] = set() + + if self.table_exists('region'): + regions_and_groups.update( + t[0] for t in cur.execute('SELECT region FROM main.region').fetchall() + ) + + for table, field_name in tables_with_regional_groups.items(): + if self.table_exists(table): + regions_and_groups.update( + t[0] for t in cur.execute(f'SELECT {field_name} FROM main.{table}').fetchall() + ) + + if None in regions_and_groups: + raise ValueError('A table has an empty entry for its region column.') + + list_of_groups = sorted((t,) for t in regions_and_groups) + self._load_component_data(data, model.regional_global_indices, list_of_groups) + + def _load_tech_group_members( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Loads members into the indexed set `tech_group_members`.""" + model = TemoaModel() + validator = self.viable_techs.members if self.viable_techs else None + for group_name, tech in filtered_data: + if validator is None or tech in validator: + store = data.get(model.tech_group_members.name, defaultdict(list)) + store[group_name].append(tech) # type: ignore[index] + data[model.tech_group_members.name] = store + + # --- Time-Related Components --- + def _load_time_season( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """ + Loads time_season as a flat ordered set of season names. + """ + model = TemoaModel() + if not filtered_data: + logger.warning('No time_season table found. Loading a single filler season "S".') + seasons_to_load: list[tuple[object, ...]] = [('S',)] + else: + seasons_to_load = list(filtered_data) + self._load_component_data(data, model.time_season, seasons_to_load) + + def _load_time_season_sequential( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """ + Composite loader for time_season_sequential and its associated index sets. + """ + model = TemoaModel() + if filtered_data: + seg_frac_data = [ + (row[0], row[2]) for row in filtered_data + ] # (seas_seq, segment_fraction) + self._load_component_data( + data, model.segment_fraction_per_sequential_season, seg_frac_data + ) + ordered_data = [row[0:2] for row in filtered_data] # (seas_seq, season) + self._load_component_data(data, model.ordered_season_sequential, ordered_data) + seq_seasons = [(row[0],) for row in filtered_data] # (seas_seq,) + self._load_component_data(data, model.time_season_sequential, seq_seasons) + + # --- Capacity and Cost Components --- + def _load_existing_capacity( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """ + Handles different queries for myopic vs. standard runs and also + populates the `tech_exist` set. + """ + model = TemoaModel() + cur = self.con.cursor() + mi = self.myopic_index + + rows_to_load = [] + if mi: + prev_period_res = cur.execute( + 'SELECT MAX(period) FROM time_period WHERE period < ?', (mi.base_year,) + ).fetchone() + prev_period = prev_period_res[0] if prev_period_res else -1 + rows_to_load = cur.execute( + 'SELECT region, tech, vintage, capacity FROM output_built_capacity WHERE ' + 'vintage <= ? AND scenario = ? ' + 'UNION SELECT region, tech, vintage, capacity FROM existing_capacity', + (prev_period, self.config.scenario), + ).fetchall() + elif self.table_exists('existing_capacity'): + rows_to_load = cur.execute( + 'SELECT region, tech, vintage, capacity FROM existing_capacity' + ).fetchall() + + self._load_component_data(data, model.existing_capacity, rows_to_load) + if rows_to_load: + tech_exist_data = sorted({(row[1],) for row in rows_to_load}) + self._load_component_data(data, model.tech_exist, tech_exist_data) + + # --- Singleton and Configuration-based Components --- + def _load_global_discount_rate( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Loads the required singleton global_discount_rate.""" + model = TemoaModel() + if filtered_data: + data[model.global_discount_rate.name] = {None: cast('float', filtered_data[0][0])} + else: + raise ValueError( + "Missing required parameter: 'global_discount_rate' not found in MetaDataReal " + 'table.' + ) + + def _load_default_loan_rate( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Loads the optional singleton default_loan_rate.""" + model = TemoaModel() + if filtered_data: + data[model.default_loan_rate.name] = {None: cast('float', filtered_data[0][0])} + + # --- Operational Constraints and Parameters --- + def _load_efficiency( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Loads the main efficiency parameter, which is pre-calculated.""" + model = TemoaModel() + self._load_component_data(data, model.efficiency, self.efficiency_values) + + def _load_linked_techs( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Provides critical error checking for linked_techs.""" + item = self.manifest_map['linked_techs'] + self._load_component_data(data, item.component, filtered_data) + if len(filtered_data) < len(raw_data): + missing = set(raw_data) - set(filtered_data) + valid_techs = self.viable_techs.members if self.viable_techs else set() + for entry in missing: + p_tech, d_tech = entry[1], entry[3] + if p_tech in valid_techs or d_tech in valid_techs: + msg = ( + 'A LinkedTech entry %s was invalidated, but one of its component ' + 'technologies ' + 'remains viable. This could lead to incorrect model behavior.' + ) + logger.error(msg, entry) + raise RuntimeError(msg % (entry,)) + + def _load_ramping_down( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Composite loader for ramp_down_hourly and its index set `tech_downramping`.""" + model = TemoaModel() + self._load_component_data(data, model.ramp_down_hourly, filtered_data) + if filtered_data: + tech_data = sorted({(row[1],) for row in filtered_data}) + tech_filtered = self._filter_data( + tech_data, self.manifest_map[model.tech_downramping.name], use_raw_data=False + ) + self._load_component_data(data, model.tech_downramping, tech_filtered) + + def _load_ramping_up( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Composite loader for ramp_up_hourly and its index set `tech_upramping`.""" + model = TemoaModel() + self._load_component_data(data, model.ramp_up_hourly, filtered_data) + if filtered_data: + tech_data = sorted({(row[1],) for row in filtered_data}) + tech_filtered = self._filter_data( + tech_data, self.manifest_map[model.tech_upramping.name], use_raw_data=False + ) + self._load_component_data(data, model.tech_upramping, tech_filtered) + + def _load_rps_requirement( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Handles deprecation warning for renewable_portfolio_standard.""" + model = TemoaModel() + self._load_component_data(data, model.renewable_portfolio_standard, filtered_data) + if filtered_data: + logger.warning( + 'The renewable_portfolio_standard constraint is deprecated. Use ' + 'limit_activity_share instead. ' + 'The constraint has been applied but this feature may be removed in the future.' + ) + + def _load_limit_tech_input_split( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Provides detailed warnings for filtered limit_tech_input_split data.""" + item = self.manifest_map['limit_tech_input_split'] + self._load_component_data(data, item.component, filtered_data) + if len(filtered_data) < len(raw_data): + missing = set(raw_data) - set(filtered_data) + for r, p, i, t, _, _ in sorted(missing, key=lambda x: (x[0], x[1], x[3], x[2])): + logger.warning( + 'Tech Input Split in region %s, period %d for tech %s with input %s ' + 'was removed because the path is invalid/orphaned. Review other warnings.', + r, + p, + t, + i, + ) + + def _load_limit_tech_input_split_annual( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Provides detailed warnings for filtered limit_tech_input_split_annual data.""" + item = self.manifest_map['limit_tech_input_split_annual'] + self._load_component_data(data, item.component, filtered_data) + if len(filtered_data) < len(raw_data): + missing = set(raw_data) - set(filtered_data) + for r, p, i, t, _, _ in sorted(missing, key=lambda x: (x[0], x[1], x[3], x[2])): + logger.warning( + 'Tech Input Split Annual in region %s, period %d for tech %s with input %s ' + 'was removed because the path is invalid/orphaned. Review other warnings.', + r, + p, + t, + i, + ) + + def _load_limit_tech_output_split( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Provides detailed warnings for filtered limit_tech_output_split data.""" + item = self.manifest_map['limit_tech_output_split'] + self._load_component_data(data, item.component, filtered_data) + if len(filtered_data) < len(raw_data): + missing = set(raw_data) - set(filtered_data) + for r, p, t, o, _, _ in sorted(missing, key=lambda x: (x[0], x[1], x[2], x[3])): + logger.warning( + 'Tech Output Split in region %s, period %d for tech %s with output %s ' + 'was removed because the path is invalid/orphaned. Review other warnings.', + r, + p, + t, + o, + ) + + def _load_limit_tech_output_split_annual( + self, + data: dict[str, object], + raw_data: Sequence[tuple[object, ...]], + filtered_data: Sequence[tuple[object, ...]], + ) -> None: + """Provides detailed warnings for filtered limit_tech_output_split_annual data.""" + item = self.manifest_map['limit_tech_output_split_annual'] + self._load_component_data(data, item.component, filtered_data) + if len(filtered_data) < len(raw_data): + missing = set(raw_data) - set(filtered_data) + for r, p, t, o, _, _ in sorted(missing, key=lambda x: (x[0], x[1], x[2], x[3])): + logger.warning( + 'Tech Output Split Annual in region %s, period %d for tech %s with output %s ' + 'was removed because the path is invalid/orphaned. Review other warnings.', + r, + p, + t, + o, + ) + + # ================================================================================= + # Finalizer Method + # ================================================================================= + + def load_param_idx_sets(self, data: dict[str, object]) -> dict[str, object]: + """ + Builds a dictionary of sparse sets used for indexing parameters. + This is a final data enhancement step that runs after all primary data + has been loaded. + + :param data: The main data dictionary. + :return: A dictionary of the new index sets to be added. + """ + model = TemoaModel() + param_idx_sets = { + model.cost_invest.name: model.cost_invest_rtv.name, + model.cost_emission.name: model.cost_emission_rpe.name, + model.demand.name: model.demand_constraint_rpc.name, + model.limit_emission.name: model.limit_emission_constraint_rpe.name, + model.limit_activity.name: model.limit_activity_constraint_rpt.name, + model.limit_seasonal_capacity_factor.name: ( + model.limit_seasonal_capacity_factor_constraint_rst.name + ), + model.limit_activity_share.name: model.limit_activity_share_constraint_rpgg.name, + model.limit_annual_capacity_factor.name: ( + model.limit_annual_capacity_factor_constraint_rtvo.name + ), + model.limit_capacity.name: model.limit_capacity_constraint_rpt.name, + model.limit_capacity_share.name: model.limit_capacity_share_constraint_rpgg.name, + model.limit_new_capacity.name: model.limit_new_capacity_constraint_rtv.name, + model.limit_new_capacity_share.name: ( + model.limit_new_capacity_share_constraint_rggv.name + ), + model.limit_resource.name: model.limit_resource_constraint_rt.name, + model.limit_storage_fraction.name: model.limit_storage_fraction_param_rsdt.name, + model.renewable_portfolio_standard.name: ( + model.renewable_portfolio_standard_constraint_rpg.name + ), + } + + res: dict[str, object] = {} + for p_name, s_name in param_idx_sets.items(): + param_data = data.get(p_name) + if isinstance(param_data, dict): + res[s_name] = list(param_data.keys()) + return res diff --git a/temoa/data_io/loader_manifest.py b/temoa/data_io/loader_manifest.py new file mode 100644 index 000000000..ad6ce930d --- /dev/null +++ b/temoa/data_io/loader_manifest.py @@ -0,0 +1,60 @@ +# temoa/data_io/loader_manifest.py +""" +Defines the data structure for the data loading manifest. + +This module contains the `LoadItem` dataclass, which serves as the schema for +the declarative manifest used by the `HybridLoader`. Each `LoadItem` instance +provides the loader with all the necessary metadata to fetch, validate, and +load a single Pyomo component (a `Set` or `Param`) from the database. + +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from pyomo.core import Param, Set + + type ComponentType = Set | Param +else: + ComponentType = Any + + +@dataclass +class LoadItem: + """ + Describes a single data component to load from the database. + + Attributes: + component: The target Pyomo `Set` or `Param` object to be loaded. + table: The name of the source table in the SQLite database. + columns: A list of column names to select from the table. The last + column is assumed to be the value for a `Param`. + validator_name: Optional. The name of a `ViableSet` attribute on the + `HybridLoader` instance, used for source-trace filtering. + validation_map: A tuple indicating which column indices in the data + correspond to the elements needing validation (e.g., region, tech). + where_clause: Optional. An SQL `WHERE` clause to apply when querying. + is_period_filtered: If True, the loader automatically adds a `WHERE` + clause to filter by the active periods in a myopic run. + is_table_required: If True, the loader will raise an error if the + table does not exist. + custom_loader_name: Optional. The name of a specialized method in + `HybridLoader` to handle non-standard loading logic for this component. + fallback_data: Optional. A list of default data tuples to use if the + table is missing or returns no data. + """ + + component: ComponentType + table: str + columns: list[str] + validator_name: str | None = None + validation_map: tuple[int, ...] = field(default_factory=tuple) + where_clause: str | None = None + is_period_filtered: bool = True + is_table_required: bool = True + custom_loader_name: str | None = None + fallback_data: list[tuple[object, ...]] | None = None + order_by: list[str] | None = None diff --git a/temoa/data_processing/DB_to_Excel.py b/temoa/data_processing/DB_to_Excel.py deleted file mode 100644 index dc265fcef..000000000 --- a/temoa/data_processing/DB_to_Excel.py +++ /dev/null @@ -1,235 +0,0 @@ -import getopt -import itertools -import re -import sqlite3 -import sys -from pathlib import Path - -import pandas as pd -from pyam import IamDataFrame - - -def make_excel(ifile, ofile: Path, scenario): - if ifile is None: - raise "You did not specify the input file, remember to use '-i' option" - print( - 'Use as :\n python DB_to_Excel.py -i (Optional -o )\n Use -h for help.' - ) - sys.exit(2) - else: - file_type = re.search(r'(\w+)\.(\w+)\b', ifile) # Extract the input filename and extension - if not file_type: - print('The file type %s is not recognized. Use a db file.' % ifile) - sys.exit(2) - if ofile is None: - ofile = file_type.group(1) - print('Look for output in %s_*.xls' % ofile) - - con = sqlite3.connect(ifile) - cur = con.cursor() # a database cursor is a control structure that enables traversal over the records in a database - con.text_factory = str # this ensures data is explored with the correct UTF-8 encoding - scenario = scenario.pop() - ofile = ofile.with_suffix('.xlsx') - writer = pd.ExcelWriter( - ofile, engine='xlsxwriter', engine_kwargs={'options': {'strings_to_formulas': False}} - ) - - workbook = writer.book - - header_format = workbook.add_format( - { - 'bold': True, - 'text_wrap': True, - 'align': 'left', - } - ) - - query = 'SELECT DISTINCT Efficiency.region, Efficiency.tech, Technology.sector FROM Efficiency \ - INNER JOIN Technology ON Efficiency.tech=Technology.tech' - all_techs = pd.read_sql_query(query, con) - - query = f"SELECT region, tech, sector, period, sum(capacity) as capacity FROM OutputNetCapacity WHERE scenario= '{scenario}' GROUP BY region, tech, sector, period" - df_capacity = pd.read_sql_query(query, con) - for sector in sorted(df_capacity['sector'].unique()): - df_capacity_sector = df_capacity[df_capacity['sector'] == sector] - df_capacity_sector = df_capacity_sector.drop(columns=['sector']).pivot_table( - values='capacity', index=['region', 'tech'], columns='period' - ) - df_capacity_sector.reset_index(inplace=True) - sector_techs = all_techs[all_techs['sector'] == sector] - df_capacity_sector = pd.merge( - sector_techs[['region', 'tech']], df_capacity_sector, on=['region', 'tech'], how='left' - ) - df_capacity_sector.rename(columns={'region': 'Region', 'tech': 'Technology'}, inplace=True) - df_capacity_sector.to_excel( - writer, sheet_name='Capacity_' + sector, index=False, startrow=1, header=False - ) - worksheet = writer.sheets['Capacity_' + sector] - worksheet.set_column('A:A', 10) - worksheet.set_column('B:B', 10) - for col, val in enumerate(df_capacity_sector.columns.values): - worksheet.write(0, col, val, header_format) - - query = ( - "SELECT region, tech, sector, period, sum(flow) as vflow_out FROM OutputFlowOut WHERE scenario='" - + scenario - + "' GROUP BY \ - region, tech, sector, period" - ) - df_activity = pd.read_sql_query(query, con) - for sector in sorted(df_activity['sector'].unique()): - df_activity_sector = df_activity[df_activity['sector'] == sector] - df_activity_sector = df_activity_sector.drop(columns=['sector']).pivot_table( - values='vflow_out', index=['region', 'tech'], columns='period' - ) - df_activity_sector.reset_index(inplace=True) - sector_techs = all_techs[all_techs['sector'] == sector] - df_activity_sector = pd.merge( - sector_techs[['region', 'tech']], df_activity_sector, on=['region', 'tech'], how='left' - ) - df_activity_sector.rename(columns={'region': 'Region', 'tech': 'Technology'}, inplace=True) - df_activity_sector.to_excel( - writer, sheet_name='Activity_' + sector, index=False, startrow=1, header=False - ) - worksheet = writer.sheets['Activity_' + sector] - worksheet.set_column('A:A', 10) - worksheet.set_column('B:B', 10) - for col, val in enumerate(df_activity_sector.columns.values): - worksheet.write(0, col, val, header_format) - - query = ( - 'SELECT DISTINCT EmissionActivity.region, EmissionActivity.tech, EmissionActivity.emis_comm, Technology.sector FROM EmissionActivity \ - INNER JOIN Technology ON EmissionActivity.tech=Technology.tech' - ) - all_emis_techs = pd.read_sql_query(query, con) - - query = ( - "SELECT region, tech, sector, period, emis_comm, sum(emission) as emissions FROM OutputEmission WHERE scenario='" - + scenario - + "' GROUP BY \ - region, tech, sector, period, emis_comm" - ) - df_emissions_raw = pd.read_sql_query(query, con) - if not df_emissions_raw.empty: - df_emissions = df_emissions_raw.pivot_table( - values='emissions', index=['region', 'tech', 'sector', 'emis_comm'], columns='period' - ) - df_emissions.reset_index(inplace=True) - df_emissions = pd.merge( - all_emis_techs, df_emissions, on=['region', 'tech', 'sector', 'emis_comm'], how='left' - ) - df_emissions.rename( - columns={ - 'region': 'Region', - 'tech': 'Technology', - 'emis_comm': 'Emission Commodity', - 'sector': 'Sector', - }, - inplace=True, - ) - df_emissions.to_excel(writer, sheet_name='Emissions', index=False, startrow=1, header=False) - worksheet = writer.sheets['Emissions'] - worksheet.set_column('A:A', 10) - worksheet.set_column('B:B', 10) - worksheet.set_column('C:C', 10) - worksheet.set_column('D:D', 20) - for col, val in enumerate(df_emissions.columns.values): - worksheet.write(0, col, val, header_format) - - query = ( - 'SELECT region, OutputCost.tech, Technology.sector, vintage, d_invest + d_var + d_fixed + d_emiss FROM OutputCost ' - ' JOIN main.Technology ON OutputCost.tech = main.Technology.tech ' - f" WHERE scenario = '{scenario}'" - ) - df_costs = pd.read_sql_query(query, con) - df_costs.columns = ['Region', 'Technology', 'Sector', 'Vintage', 'Cost'] - df_costs.to_excel(writer, sheet_name='Costs', index=False, startrow=1, header=False) - worksheet = writer.sheets['Costs'] - worksheet.set_column('A:A', 10) - worksheet.set_column('B:B', 10) - worksheet.set_column('C:C', 10) - worksheet.set_column('D:D', 30) - for col, val in enumerate(df_costs.columns): - worksheet.write(0, col, val, header_format) - - writer._save() - - # prepare results for IamDataFrame - df_emissions_raw['scenario'] = scenario - df_emissions_raw['unit'] = '?' - df_emissions_raw['variable'] = ( - 'Emissions|' + df_emissions_raw['emis_comm'] + '|' + df_emissions_raw['tech'] - ) - df_emissions_raw.rename(columns={'period': 'year', 'emissions': 'value'}, inplace=True) - - df_capacity['scenario'] = scenario - df_capacity['unit'] = '?' - df_capacity['variable'] = 'Capacity|' + df_capacity['sector'] + '|' + df_capacity['tech'] - df_capacity.rename(columns={'period': 'year', 'capacity': 'value'}, inplace=True) - - df_activity['scenario'] = scenario - df_activity['unit'] = '?' - df_activity['variable'] = 'Activity|' + df_activity['sector'] + '|' + df_activity['tech'] - df_activity.rename(columns={'period': 'year', 'vflow_out': 'value'}, inplace=True) - - # cast results to IamDataFrame and write to xlsx - columns = ['scenario', 'region', 'variable', 'year', 'value', 'unit'] - _results = pd.concat([df_emissions_raw[columns], df_activity[columns], df_capacity[columns]]) - df = IamDataFrame(_results, model='Temoa') - - emiss = df_emissions_raw['emis_comm'].unique() - sector = df_capacity['sector'].unique() - - # adding aggregates of emissions for each species - df.aggregate([f'Emissions|{q}' for q in emiss], append=True) - - # adding aggregates of activity/capacity for each sector - prod = itertools.product(['Activity', 'Capacity'], sector) - df.aggregate([f'{t}|{s}' for t, s in prod], append=True) - - # write IamDataFrame to xlsx - base_name = ofile.name.split('.')[0] - excel_pyam_filename = ofile.with_name(base_name + '_pyam.xlsx') - df.to_excel(excel_pyam_filename) - - cur.close() - con.close() - - -def get_data(inputs): - ifile = None - ofile = None - scenario = set() - - if inputs is None: - raise 'no arguments found' - - for opt, arg in inputs.items(): - if opt in ('-i', '--input'): - ifile = arg - elif opt in ('-o', '--output'): - ofile = arg - elif opt in ('-s', '--scenario'): - scenario.add(arg) - elif opt in ('-h', '--help'): - print( - 'Use as :\n python DB_to_Excel.py -i (Optional -o )\n Use -h for help.' - ) - sys.exit() - - make_excel(ifile, ofile, scenario) - - -if __name__ == '__main__': - try: - argv = sys.argv[1:] - opts, args = getopt.getopt(argv, 'hi:o:s:', ['help', 'input=', 'output=', 'scenario=']) - except getopt.GetoptError: - print( - "Something's Wrong. Use as :\n python DB_to_Excel.py -i (Optional -o )\n Use -h for help." - ) - sys.exit(2) - - print(opts) - - get_data(dict(opts)) diff --git a/temoa/data_processing/DatabaseUtil.py b/temoa/data_processing/DatabaseUtil.py deleted file mode 100644 index c2b5b7cce..000000000 --- a/temoa/data_processing/DatabaseUtil.py +++ /dev/null @@ -1,393 +0,0 @@ -import os -import re -import sqlite3 - -import deprecated -import pandas as pd - - -class DatabaseUtil(object): - def __init__(self, databasePath, scenario=None): - self.database = os.path.abspath(databasePath) - self.scenario = scenario - if not os.path.exists(self.database): - raise ValueError("The database file path doesn't exist") - - if self.isDataBaseFile(self.database): - try: - self.con = sqlite3.connect(self.database) - self.cur = self.con.cursor() - self.con.text_factory = ( - str # this ensures data is explored with the correct UTF-8 encoding - ) - except Exception: - raise ValueError('Unable to connect to database') - elif self.database.endswith('.dat'): - raise ValueError('Reading .dat files is no longer supported') - - def close(self): - if self.cur: - self.cur.close() - if self.con: - self.con.close() - - @staticmethod - def isDataBaseFile(file): - if file.endswith('.db') or file.endswith('.sqlite') or file.endswith('.sqlite3'): - return True - else: - return False - - @deprecated.deprecated('reading from .dat files no longer supported') - def readFromDatFile(self, inp_comm, inp_tech): - if self.cur is not None: - raise ValueError('Invalid Operation For Database file') - if inp_comm is None and inp_tech is None: - inp_comm = r'\w+' - inp_tech = r'\w+' - else: - if inp_comm is None: - inp_comm = r'\W+' - if inp_tech is None: - inp_tech = r'\W+' - - test2 = [] - eff_flag = False - with open(self.database) as f: - for line in f: - if eff_flag is False and re.search( - r'^\s*param\s+efficiency\s*[:][=]', line, flags=re.I - ): - # Search for the line param Efficiency := (The script recognizes the commodities specified in this section) - eff_flag = True - elif eff_flag: - line = re.sub(r'[#].*$', ' ', line) - if re.search(r'^\s*;\s*$', line): - break # Finish searching this section when encounter a ';' - if re.search(r'^\s+$', line): - continue - line = re.sub(r'^\s+|\s+$', '', line) - row = re.split(r'\s+', line) - if ( - not re.search(inp_comm, row[0]) - and not re.search(inp_comm, row[3]) - and not re.search(inp_tech, row[1]) - ): - continue - - test2.append(tuple(row)) - - result = pd.DataFrame( - test2, columns=['input_comm', 'tech', 'period', 'output_comm', 'flow'] - ) - return result[['input_comm', 'tech', 'output_comm']] - - def getTimePeridosForFlags(self, flags=[]): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - query = '' - if (flags is None) or (not flags): - query = 'SELECT period FROM TimePeriod' - else: - flag = flags[0] - query = "SELECT period FROM TimePeriod WHERE flag is '" + flag + "'" - for i in range(1, len(flags)): - query += " OR flag is '" + flags[i] + "'" - - self.cur.execute(query) - result = set() - for row in self.cur: - result.add(int(row[0])) - - return result - - def getTechnologiesForFlags(self, flags=[]): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - query = '' - if (flags is None) or (not flags): - query = 'SELECT tech FROM Technology' - else: - flag = flags[0] - query = "SELECT tech FROM Technology WHERE flag='" + flag + "'" - for i in range(1, len(flags)): - query += " OR flag='" + flags[i] + "'" - - result = set() - for row in self.cur.execute(query): - result.add(row[0]) - - return result - - # TODO: Merge this with next function (getExistingTechnologiesForCommodity) - def getCommoditiesAndTech(self, inp_comm, inp_tech, region): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - if inp_comm is None and inp_tech is None: - inp_comm = 'NOT NULL' - inp_tech = 'NOT NULL' - else: - if inp_comm is None: - inp_comm = 'NULL' - else: - inp_comm = "'" + inp_comm + "'" - if inp_tech is None: - inp_tech = 'NULL' - else: - inp_tech = "'" + inp_tech + "'" - - if region == None: - self.cur.execute( - 'SELECT input_comm, tech, output_comm FROM Efficiency WHERE input_comm is ' - + inp_comm - + ' or output_comm is ' - + inp_comm - + ' or tech is ' - + inp_tech - ) - else: - self.cur.execute( - "SELECT input_comm, tech, output_comm FROM Efficiency WHERE region LIKE '%" - + region - + "%' and (input_comm is " - + inp_comm - + ' or output_comm is ' - + inp_comm - + ' or tech is ' - + inp_tech - + ')' - ) - return pd.DataFrame(self.cur.fetchall(), columns=['input_comm', 'tech', 'output_comm']) - - def getExistingTechnologiesForCommodity(self, comm, region, comm_type='input'): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - query = '' - if comm_type == 'input': - query = "SELECT DISTINCT tech FROM Efficiency WHERE input_comm is '" + comm + "'" - else: - query = "SELECT DISTINCT tech FROM Efficiency WHERE output_comm is '" + comm + "'" - if region: - query += " AND region LIKE '%" + region + "%'" - - self.cur.execute(query) - result = pd.DataFrame(self.cur.fetchall(), columns=['tech']) - return result - - def getCommoditiesForFlags(self, flags=[]): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - query = '' - if (flags is None) or (not flags): - query = 'SELECT name FROM Commodity' - else: - flag = flags[0] - query = "SELECT name FROM Commodity WHERE flag is '" + flag + "'" - for i in range(1, len(flags)): - query += " OR flag is '" + flags[i] + "'" - - result = set() - for row in self.cur.execute(query): - result.add(row[0]) - - return result - - # comm_type can be 'input' or 'output' - def getCommoditiesByTechnology(self, region, comm_type='input'): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - query = '' - if comm_type == 'input': - query = 'SELECT DISTINCT input_comm, tech FROM Efficiency' - elif comm_type == 'output': - query = 'SELECT DISTINCT tech, output_comm FROM Efficiency' - else: - raise ValueError('Invalid commodity comm_type: can only be input or output') - - if region: - query += " WHERE region LIKE '%" + region + "%'" - result = set() - for row in self.cur.execute(query): - result.add((row[0], row[1])) - - return result - - def getCapacityForTechAndPeriod(self, tech=None, period=None, region=None): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - if self.scenario is None or self.scenario == '': - raise ValueError('For Output related queries, please set a scenario first') - - columns = [] - if tech is None: - columns.append('tech') - if period is None: - columns.append('period') - columns.append('sum(capacity)') - columns.append('region') - - query = 'SELECT ' + columns[0] - - for col in columns[1:]: - query += ', ' + col - - query += " FROM OutputNetCapacity WHERE scenario == '" + self.scenario + "'" - - if region: - query += " AND region LIKE '" + region + "%'" - if tech: - query += " AND tech is '" + tech + "'" - if period: - query += " AND period == '" + str(period) + "'" - - # sum over vintages - query += ' GROUP BY tech, region, period, sector;' - - self.cur.execute(query) - # quick hack...change the column name for capacity - new_columns = [] - for col in columns: - new_col = col.replace('sum(capacity)', 'capacity') - new_columns.append(new_col) - columns = new_columns - result = pd.DataFrame(self.cur.fetchall(), columns=columns) - if region is None: - mask = result['region'].str.contains('-') - # TODO: somebody needs to verify this halving of capacity...? - result.loc[mask, 'capacity'] /= 2 - - result.drop(columns=['region'], inplace=True) - if len(columns) == 2: - return result.sum() - else: - return result.groupby(by='tech').sum().reset_index() - - def getOutputFlowForPeriod(self, period, region, comm_type='input', commodity=None): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - if self.scenario is None or self.scenario == '': - raise ValueError('For Output related queries, please set a scenario first') - columns = [] - table = '' - col = '' - if comm_type == 'input': - table = 'OutputFlowIn' - if commodity is None: - columns.append('input_comm') - col = 'flow' - columns.append('tech') - if comm_type == 'output': - table = 'OutputFlowOut' - if commodity is None: - columns.append('output_comm') - col = 'flow' - - query = 'SELECT DISTINCT ' - for c in columns: - query += c + ', ' - query += ( - 'SUM(' + col + ') AS flow FROM ' + table + " WHERE scenario is '" + self.scenario + "'" - ) - if (region) and (comm_type == 'input'): - query += " AND region LIKE '" + region + "%'" - if (region) and (comm_type == 'output'): - query += " AND region LIKE '%" + region + "'" - query += " AND period is '" + str(period) + "' " - - query2 = ' GROUP BY tech' - if commodity is not None: - query += ' AND ' + comm_type + "_comm is '" + commodity + "'" - if comm_type == 'output': - query += " AND input_comm != 'ethos' " - else: - query2 += ', ' + comm_type + '_comm' - - query += query2 - columns.append('flow') - self.cur.execute(query) - result = pd.DataFrame(self.cur.fetchall(), columns=columns) - return result - - def getEmissionsActivityForPeriod(self, period, region): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - if self.scenario is None or self.scenario == '': - raise ValueError('For Output related queries, please set a scenario first') - query = ( - 'SELECT E.emis_comm, E.tech, SUM(E.activity*O.flow) FROM EmissionActivity E, OutputFlowOut O ' - + "WHERE E.input_comm == O.input_comm AND E.tech == O.tech AND E.vintage == O.vintage AND E.output_comm == O.output_comm AND O.scenario == '" - + self.scenario - + "' " - + "and O.period == '" - + str(period) - + "'" - ) - if region: - query += " AND E.region LIKE '%" + region + "%'" - query += ' GROUP BY E.tech, E.emis_comm' - self.cur.execute(query) - result = pd.DataFrame(self.cur.fetchall(), columns=['emis_comm', 'tech', 'emis_activity']) - return result - - def getCommodityWiseInputAndOutputFlow(self, tech, period, region): - if self.cur is None: - raise ValueError('Invalid Operation For dat file') - if self.scenario is None or self.scenario == '': - raise ValueError('For Output related queries, please set a scenario first') - - query = ( - "SELECT OF.input_comm, OF.output_comm, OF.vintage, OF.region,\ - SUM(OF.vflow_in) vflow_in, SUM(OFO.vflow_out) vflow_out, OC.capacity \ -FROM (SELECT region, scenario, sector, period, input_comm, tech, vintage, output_comm, sum(flow) AS vflow_in \ - FROM OutputFlowIn GROUP BY region, scenario, sector, period, input_comm, tech, vintage, output_comm) AS OF \ -INNER JOIN (SELECT region, scenario, sector, period, input_comm, tech, vintage, output_comm, sum(flow) AS vflow_out \ - FROM OutputFlowOut GROUP BY region, scenario, sector, period, input_comm, tech, vintage, output_comm) AS OFO \ -ON \ - OF.region = OFO.region AND \ - OF.scenario = OFO.scenario AND \ - OF.period = OFO.period AND \ - OF.tech = OFO.tech AND \ - OF.input_comm = OFO.input_comm AND \ - OF.vintage = OFO.vintage AND \ - OF.output_comm = OFO.output_comm \ -INNER JOIN \ - OutputNetCapacity OC \ -ON \ - OF.region = OC.region AND \ - OF.scenario = OC.scenario AND \ - OF.tech = OC.tech AND \ - OF.vintage = OC.vintage \ -WHERE \ - OF.period ='" - + str(period) - + "' AND \ - OF.tech is '" - + tech - + "' AND \ - OF.scenario is '" - + self.scenario - + "'" - ) - - if region: - query += " AND OF.region LIKE '%" + region + "%'" - - query += ' GROUP BY OF.region, OF.vintage, OF.input_comm, OF.output_comm' - - self.cur.execute(query) - result = pd.DataFrame( - self.cur.fetchall(), - columns=[ - 'input_comm', - 'output_comm', - 'vintage', - 'region', - 'flow_in', - 'flow_out', - 'capacity', - ], - ) - result = pd.DataFrame( - result.groupby(['input_comm', 'output_comm', 'vintage']).sum().reset_index() - ) - return result diff --git a/temoa/data_processing/GraphVizFormats.py b/temoa/data_processing/GraphVizFormats.py deleted file mode 100644 index 799363b40..000000000 --- a/temoa/data_processing/GraphVizFormats.py +++ /dev/null @@ -1,288 +0,0 @@ -# SVG Formats - -results_dot_fmt = """\ -strict digraph model { - label = "Results for %(period)s" - - rankdir = "LR" ; - smoothtype = "power_dist" ; - splines = "%(splinevar)s" ; - - node [ style="filled" ] ; - edge [ arrowhead="vee" ] ; - - subgraph unused_techs { - node [ - color = "%(unused_color)s", - fontcolor = "%(unusedfont_color)s", - shape = "box", - fontcolor = "%(font_color)s" - ] ; - - %(dtechs)s - } - - subgraph unused_energy_carriers { - node [ - color = "%(unused_color)s", - fontcolor = "%(unusedfont_color)s", - shape = "circle", - fillcolor = "%(fill_color)s" - ] ; - - %(dcarriers)s - } - - subgraph unused_emissions { - node [ - color = "%(unused_color)s", - fontcolor = "%(unusedfont_color)s", - shape = "circle", - fillcolor = "%(fill_color)s" - ] - - %(demissions)s - } - - subgraph in_use_techs { - node [ - color = "%(tech_color)s", - fontcolor = "%(usedfont_color)s", - shape = "box" - fontcolor = "%(font_color)s" - - ] ; - - %(etechs)s - } - - subgraph in_use_energy_carriers { - node [ - color = "%(commodity_color)s", - fontcolor = "%(usedfont_color)s", - shape = "circle", - fillcolor = "%(fill_color)s" - ] ; - - %(ecarriers)s - } - - subgraph in_use_emissions { - node [ - color = "%(commodity_color)s", - fontcolor = "%(usedfont_color)s", - shape = "circle", - fillcolor = "%(fill_color)s" - ] ; - - %(eemissions)s - } - - subgraph unused_flows { - edge [ color="%(unused_color)s" ] - - %(dflows)s - } - - subgraph in_use_flows { - subgraph inputs { - edge [ color="%(arrowheadin_color)s" ] ; - - %(eflowsi)s - } - - subgraph outputs { - edge [ color="%(arrowheadout_color)s" ] ; - - %(eflowso)s - } - } - - {rank = same; %(xnodes)s} -} -""" - - -tech_results_dot_fmt = """\ -strict digraph model { - label = "Results for %(inp_technology)s in %(period)s" ; - - compound = "True" ; - concentrate = "True"; - rankdir = "LR" ; - splines = "%(splinevar)s" ; - - node [ style="filled" ] ; - edge [ arrowhead="vee" ] ; - - subgraph cluster_vintages { - label = "Vintages\\nCapacity: %(total_cap).2f" ; - - href = "%(cluster_vintage_url)s" ; - style = "filled" - color = "%(sb_vpbackg_color)s" - - node [ color="%(sb_vp_color)s", shape="box", fontcolor="%(usedfont_color)s" ] ; - - %(vnodes)s - } - - subgraph energy_carriers { - node [ - color = "%(commodity_color)s", - fontcolor = "%(usedfont_color)s", - shape = "circle", - fillcolor = "%(fill_color)s" - ] ; - - %(enodes)s - } - - subgraph inputs { - edge [ color="%(arrowheadin_color)s" ] ; - - %(iedges)s - } - - subgraph outputs { - edge [ color="%(arrowheadout_color)s" ] ; - - %(oedges)s - } -} -""" - -slice_dot_fmt = """\ -strict digraph model { - label = "Activity split of process %(inp_technology)s, %(vintage)s in year %(period)s" ; - - compound = "True" ; - concentrate = "True"; - rankdir = "LR" ; - splines = "%(splinevar)s" ; - - node [ style="filled" ] ; - edge [ arrowhead="vee" ] ; - - subgraph cluster_slices { - label = "%(vintage)s Capacity: %(total_cap).2f" ; - - color = "%(vintage_cluster_color)s" ; - rank = "same" ; - style = "filled" ; - - node [ color="%(vintage_color)s", shape="box", fontcolor="%(usedfont_color)s" ] ; - - %(snodes)s - } - - subgraph energy_carriers { - node [ - color = "%(commodity_color)s", - fontcolor = "%(usedfont_color)s", - shape = "circle", - fillcolor = "%(fill_color)s" - ] ; - - %(enodes)s - } - - subgraph inputs { - edge [ color="%(input_color)s" ] ; - - %(iedges)s - } - - subgraph outputs { - edge [ color="%(output_color)s" ] ; - - %(oedges)s - } -} -""" - -commodity_dot_fmt = """\ -strict digraph result_commodity_%(inp_commodity)s { - label = "%(inp_commodity)s - %(period)s" ; - - compound = "True" ; - concentrate = "True" ; - rankdir = "LR" ; - splines = "True" ; - - node [ shape="box", style="filled", fontcolor="%(font_color)s" ] ; - edge [ - arrowhead = "vee", - fontsize = "8", - label = " ", - labelfloat = "False", - labelfontcolor = "lightgreen" - len = "2", - weight = "0.5", - ] ; - - %(resource_node)s - - subgraph used_techs { - node [ color="%(tech_color)s" ] ; - - %(used_nodes)s - } - - subgraph used_techs { - node [ color="%(unused_color)s" ] ; - - %(unused_nodes)s - } - - subgraph in_use_flows { - edge [ color="%(sb_arrow_color)s" ] ; - - %(used_edges)s - } - - subgraph unused_flows { - edge [ color="%(unused_color)s" ] ; - - %(unused_edges)s - } -} -""" - -quick_run_dot_fmt = """\ -strict digraph model { - rankdir = "LR" ; - - // Default node and edge attributes - node [ style="filled" ] ; - edge [ arrowhead="vee", labelfontcolor="lightgreen" ] ; - - // Define individual nodes - subgraph techs { - node [ color="%(tech_color)s", shape="box", fontcolor="%(font_color)s" ] ; - - %(tnodes)s - } - - subgraph energy_carriers { - node [ color="%(commodity_color)s", shape="circle", fillcolor="%(fill_color)s" ] ; - - %(enodes)s - } - - // Define edges and any specific edge attributes - subgraph inputs { - edge [ color="%(arrowheadin_color)s" ] ; - - %(iedges)s - } - - subgraph outputs { - edge [ color="%(arrowheadout_color)s" ] ; - - %(oedges)s - } - - {rank = same; %(snodes)s} -} -""" diff --git a/temoa/data_processing/GraphVizUtil.py b/temoa/data_processing/GraphVizUtil.py deleted file mode 100644 index 9871ed34c..000000000 --- a/temoa/data_processing/GraphVizUtil.py +++ /dev/null @@ -1,239 +0,0 @@ -import argparse - - -def processInput(args): - parser = argparse.ArgumentParser(description='Generate Output Plot') - parser.add_argument( - '-i', - '--input', - action='store', - dest='ifile', - help='Input Database Filename ', - required=True, - ) - parser.add_argument( - '-f', - '--format', - action='store', - dest='image_format', - help='Graphviz output format (Default: svg)', - default='svg', - ) - parser.add_argument( - '-c', - '--show_capacity', - action='store_true', - dest='show_capacity', - help='Whether capacity shows up in subgraphs (Default: not shown)', - default=False, - ) - parser.add_argument( - '-v', - '--splinevar', - action='store_true', - dest='splinevar', - help='Whether subgraph edges to be straight or curved (Default: Straight)', - default=False, - ) - parser.add_argument( - '-t', - '--graph_type', - action='store', - dest='graph_type', - help='Type of subgraph (Default: separate_vintages)', - choices=['separate_vintages', 'explicit_vintages'], - default='separate_vintages', - ) - parser.add_argument( - '-g', - '--gray', - action='store_true', - dest='grey_flag', - help='If specified, generates graph in graycale', - default=False, - ) - parser.add_argument( - '-n', - '--name', - action='store', - dest='quick_name', - help='Specify the extension you wish to give your quick run', - ) - parser.add_argument( - '-o', - '--output', - action='store', - dest='res_dir', - help='Optional output file path (to dump the images folder)', - default='./', - ) - - group1 = parser.add_mutually_exclusive_group() - group1.add_argument( - '-b', - '--technology', - action='store', - dest='inp_technology', - help='Technology for which graph to be generated', - ) - group1.add_argument( - '-a', - '--commodity', - action='store', - dest='inp_commodity', - help='Commodity for which graph to be generated', - ) - - parser.add_argument( - '-s', - '--scenario', - action='store', - dest='scenario_name', - help='Model run scenario name', - default=None, - ) - parser.add_argument( - '-y', - '--year', - action='store', - dest='period', - type=int, - help='The period for which the graph is to be generated (Used only for output plots)', - ) - parser.add_argument( - '-r', - '--region', - action='store', - dest='region', - help='The region for which the graph is to be generated', - default=None, - ) - - options = parser.parse_args(args) - - if bool(options.scenario_name) ^ bool(options.period): - parser.print_help() - raise ValueError('Scenario and input year must both be present or not present together') - - return vars(options) - - -def getColorConfig(grey_flag): - grey_flag = not (grey_flag) - kwargs = dict( - tech_color='darkseagreen' if grey_flag else 'black', - commodity_color='lightsteelblue' if grey_flag else 'black', - unused_color='powderblue' if grey_flag else 'gray75', - arrowheadout_color='forestgreen' if grey_flag else 'black', - arrowheadin_color='firebrick' if grey_flag else 'black', - usedfont_color='black', - unusedfont_color='chocolate' if grey_flag else 'gray75', - menu_color='hotpink', - home_color='gray75', - font_color='black' if grey_flag else 'white', - fill_color='lightsteelblue' if grey_flag else 'white', - # MODELDETAILED, - md_tech_color='hotpink', - sb_incom_color='lightsteelblue' if grey_flag else 'black', - sb_outcom_color='lawngreen' if grey_flag else 'black', - sb_vpbackg_color='lightgrey', - sb_vp_color='white', - sb_arrow_color='forestgreen' if grey_flag else 'black', - # SUBGRAPH 1 ARROW COLORS - color_list=( - 'red', - 'orange', - 'gold', - 'green', - 'blue', - 'purple', - 'hotpink', - 'cyan', - 'burlywood', - 'coral', - 'limegreen', - 'black', - 'brown', - ) - if grey_flag - else ('black', 'black'), - ) - return kwargs - - -def _getLen(key): - def wrapped(obj): - return len(obj[key]) - - return wrapped - - -def create_text_nodes(nodes, indent=1): - """\ -Return a set of text nodes in Graphviz DOT format, optimally padded for easier -reading and debugging. - -nodes: iterable of (id, attribute) node tuples - e.g. [(node1, attr1), (node2, attr2), ...] - -indent: integer, number of tabs with which to indent all Dot node lines -""" - if not nodes: - return '// no nodes in this section' - - # guarantee basic structure of nodes arg - assert len(nodes) == sum(1 for a, b in nodes) - - # Step 1: for alignment, get max item length in node list - maxl = max(map(_getLen(0), nodes)) + 2 # account for two extra quotes - - # Step 2: prepare a text format based on max node size that pads all - # lines with attributes - nfmt_attr = '{0:<%d} [ {1} ] ;' % maxl # node text format - nfmt_noa = '{0} ;' - - # Step 3: create each node, and place string representation in a set to - # guarantee uniqueness - q = '"%s"' # enforce quoting for all nodes - gviz = set(nfmt_attr.format(q % n, a) for n, a in nodes if a) - gviz.update(nfmt_noa.format(q % n) for n, a in nodes if not a) - - # Step 4: return a sorted version of nodes, as a single string - indent = '\n' + '\t' * indent - return indent.join(sorted(gviz)) - - -def create_text_edges(edges, indent=1): - """\ -Return a set of text edge definitions in Graphviz DOT format, optimally padded -for easier reading and debugging. - -edges: iterable of (from, to, attribute) edge tuples - e.g. [(inp1, tech1, attr1), (inp2, tech2, attr2), ...] - -indent: integer, number of tabs with which to indent all Dot edge lines -""" - if not edges: - return '// no edges in this section' - - # guarantee basic structure of edges arg - assert len(edges) == sum(1 for a, b, c in edges) - - # Step 1: for alignment, get max length of items on left and right side of - # graph operator token ('->') - maxl, maxr = max(map(_getLen(0), edges)), max(map(_getLen(1), edges)) - maxl += 2 # account for additional two quotes - maxr += 2 # account for additional two quotes - - # Step 2: prepare format to be "\n\tinp+PADDING -> out+PADDING [..." - efmt_attr = '{0:<%d} -> {1:<%d} [ {2} ] ;' % (maxl, maxr) # with attributes - efmt_noa = '{0:<%d} -> {1} ;' % maxl # no attributes - - # Step 3: add each edge to a set (to guarantee unique entries only) - q = '"%s"' # enforce quoting for all tokens - gviz = set(efmt_attr.format(q % i, q % t, a) for i, t, a in edges if a) - gviz.update(efmt_noa.format(q % i, q % t) for i, t, a in edges if not a) - - # Step 4: return a sorted version of the edges, as a single string - indent = '\n' + '\t' * indent - return indent.join(sorted(gviz)) diff --git a/temoa/data_processing/MakeGraphviz.py b/temoa/data_processing/MakeGraphviz.py deleted file mode 100644 index 82aa60e69..000000000 --- a/temoa/data_processing/MakeGraphviz.py +++ /dev/null @@ -1,460 +0,0 @@ -import os -import sys -from subprocess import call - -from DatabaseUtil import DatabaseUtil -from GraphVizFormats import ( - results_dot_fmt, - tech_results_dot_fmt, - commodity_dot_fmt, - quick_run_dot_fmt, -) -from GraphVizUtil import create_text_nodes, create_text_edges, getColorConfig, processInput - - -class GraphvizDiagramGenerator(object): - def __init__(self, dbFile, scenario=None, region=None, outDir='.', verbose=1): - self.dbFile = dbFile - self.qName = os.path.splitext(os.path.basename(self.dbFile))[0] - self.scenario = scenario - self.region = region - self.outDir = outDir - self.folder = {'results': 'whole_system', 'tech': 'processes', 'comm': 'commodities'} - self.verbose = verbose - self.colors = {} - - def connect(self): - self.dbUtil = DatabaseUtil(self.dbFile, self.scenario) - self.logger = open(os.path.join(self.outDir, 'graphviz.log'), 'w') - self.setGraphicOptions(False, False) - self.__log__('--------------------------------------') - self.__log__('GraphvizDiagramGenerator: connected') - if self.scenario: - outDir = self.qName + '_' + self.scenario + '_graphviz' - else: - outDir = self.qName + '_input_graphviz' - - self.outDir = os.path.join(self.outDir, outDir) - if not os.path.exists(self.outDir): - os.mkdir(self.outDir) - # os.chdir(self.outDir) - - def close(self): - self.dbUtil.close() - self.__log__('GraphvizDiagramGenerator: disconnected') - self.__log__('--------------------------------------') - self.logger.close() - # os.chdir('..') - - def __log__(self, msg): - if self.verbose == 1: - print(msg) - self.logger.write(msg + '\n') - - def __generateGraph__(self, dotFormat, dotArgs, outputName, outputFormat): - dotArgs.update(self.colors) - with open(outputName + '.dot', 'w') as f: - f.write(dotFormat % dotArgs) - cmd = ( - 'dot', - '-T' + outputFormat, - '-o' + outputName + '.' + outputFormat, - outputName + '.dot', - ) - call(cmd) - - def setGraphicOptions(self, greyFlag=None, splinevar=None): - if greyFlag is not None: - self.greyFlag = greyFlag - self.colors.update(getColorConfig(self.greyFlag)) - if splinevar is not None: - self.colors['splinevar'] = splinevar - self.__log__( - 'setGraphicOption: updated greyFlag = ' - + str(self.greyFlag) - + ' and splinevar = ' - + str(self.colors['splinevar']) - ) - - def CreateMainResultsDiagram(self, period, region, outputFormat='svg'): - self.__log__('CreateMainResultsDiagram: started with period = ' + str(period)) - - if not os.path.exists(os.path.join(self.outDir, self.folder['results'])): - os.makedirs(os.path.join(self.outDir, self.folder['results'])) - - outputName = os.path.join(self.folder['results'], 'results%s' % period) - if self.region: - outputName += '_' + self.region - outputName = os.path.join(self.outDir, outputName) - if self.greyFlag: - outputName += '.grey' - # if (os.path.exists(outputName + '.' + outputFormat)): - # self.__log__('CreateMainResultsDiagram: graph already exists at path, returning') - # return self.outDir, outputName + '.' + outputFormat - - time_exist = self.dbUtil.getTimePeridosForFlags(flags=['e']) - time_future = self.dbUtil.getTimePeridosForFlags(flags=['f']) - time_optimize = set(sorted(time_future)[:-1]) - - tech_all = self.dbUtil.getTechnologiesForFlags(flags=['r', 'p', 'pb', 'ps']) - - commodity_carrier = self.dbUtil.getCommoditiesForFlags(flags=['d', 'p']) - commodity_emissions = self.dbUtil.getCommoditiesForFlags(flags=['e']) - - Efficiency_Input = self.dbUtil.getCommoditiesByTechnology(region, comm_type='input') - Efficiency_Output = self.dbUtil.getCommoditiesByTechnology(region, comm_type='output') - - V_Cap2 = self.dbUtil.getCapacityForTechAndPeriod(period=period, region=region) - - EI2 = self.dbUtil.getOutputFlowForPeriod(period=period, region=region, comm_type='input') - EO2 = self.dbUtil.getOutputFlowForPeriod(period=period, region=region, comm_type='output') - - EmiO2 = self.dbUtil.getEmissionsActivityForPeriod(period=period, region=region) - - self.__log__('CreateMainResultsDiagram: database fetched successfully') - - tech_attr_fmt = 'label="%s\\nCapacity: %.2f", href="#", onclick="loadNextGraphvizGraph(\'results\', \'%s\', \'%s\')"' - # tech_attr_fmt = 'label="%%s\\nCapacity: %%.2f", href="results_%%s_%%s.%s"' - # tech_attr_fmt %= outputFormat - # commodity_fmt = 'href="../commodities/rc_%%s_%%s.%s"' % outputFormat - commodity_fmt = "href=\"#\", onclick=\"loadNextGraphvizGraph('results', '%s', '%s')\"" - flow_fmt = 'label="%.2f"' - - epsilon = 0.005 - - etechs, dtechs, ecarriers, xnodes = set(), set(), set(), set() - eemissions = set() - eflowsi, eflowso, dflows = set(), set(), set() # edges - usedc, usede = set(), set() # used carriers, used emissions - - V_Cap2.index = V_Cap2.tech - for tech in set(tech_all) - set(V_Cap2.tech): - dtechs.add((tech, None)) - - for i in range(len(V_Cap2)): - row = V_Cap2.iloc[i] - etechs.add( - (row['tech'], tech_attr_fmt % (row['tech'], row['capacity'], row['tech'], period)) - ) - # etechs.add( (row['tech'], tech_attr_fmt % (row['tech'], row['capacity'], row['tech'], period)) ) - - udflows = set() - for i in range(len(EI2)): - row = EI2.iloc[i] - if row['input_comm'] != 'ethos': - eflowsi.add((row['input_comm'], row['tech'], flow_fmt % row['flow'])) - ecarriers.add((row['input_comm'], commodity_fmt % (row['input_comm'], period))) - usedc.add(row['input_comm']) - else: - # check to see if this tech is in the unlim_cap set - tech = row['tech'] - if tech not in V_Cap2.tech: - cap = 99999 - else: - cap = V_Cap2.loc[row['tech']].capacity - xnodes.add((row['tech'], tech_attr_fmt % (row['tech'], cap, row['tech'], period))) - udflows.add((row['input_comm'], row['tech'])) - - for row in set(Efficiency_Input) - udflows: - if row[0] != 'ethos': - dflows.add((row[0], row[1], None)) - else: - xnodes.add((row[1], None)) - - udflows = set() - for i in range(len(EO2)): - row = EO2.iloc[i] - eflowso.add((row['tech'], row['output_comm'], flow_fmt % row['flow'])) - ecarriers.add((row['output_comm'], commodity_fmt % (row['output_comm'], period))) - usedc.add(row['output_comm']) - udflows.add((row['tech'], row['output_comm'])) - - for row in set(Efficiency_Output) - udflows: - dflows.add((row[0], row[1], None)) - - for i in range(len(EmiO2)): - row = EmiO2.iloc[i] - if row['emis_activity'] >= epsilon: - eflowso.add((row['tech'], row['emis_comm'], flow_fmt % row['emis_activity'])) - eemissions.add((row['emis_comm'], None)) - usede.add(row['emis_comm']) - - dcarriers = set() - demissions = set() - for cc in commodity_carrier: - if cc not in usedc and cc != 'ethos': - dcarriers.add((cc, None)) - for ee in commodity_emissions: - if ee not in usede: - demissions.add((ee, None)) - - self.__log__('CreateMainResultsDiagram: creating diagrams') - args = dict( - period=period, - splinevar=self.colors['splinevar'], - dtechs=create_text_nodes(dtechs, indent=2), - etechs=create_text_nodes(etechs, indent=2), - xnodes=create_text_nodes(xnodes, indent=2), - dcarriers=create_text_nodes(dcarriers, indent=2), - ecarriers=create_text_nodes(ecarriers, indent=2), - demissions=create_text_nodes(demissions, indent=2), - eemissions=create_text_nodes(eemissions, indent=2), - dflows=create_text_edges(dflows, indent=2), - eflowsi=create_text_edges(eflowsi, indent=3), - eflowso=create_text_edges(eflowso, indent=3), - ) - - self.__generateGraph__(results_dot_fmt, args, outputName, outputFormat) - self.__log__('CreateMainResultsDiagram: graph generated, returning') - return self.outDir, outputName + '.' + outputFormat - - # Needs some small fixing - cases where no input but output is there. # Check sample graphs - def CreateTechResultsDiagrams(self, period, region, tech, outputFormat='svg'): # tech results - self.__log__( - 'CreateTechResultsDiagrams: started with period = ' - + str(period) - + ' and tech = ' - + str(tech) - ) - - if not os.path.exists(os.path.join(self.outDir, self.folder['tech'])): - os.makedirs(os.path.join(self.outDir, self.folder['tech'])) - - outputName = os.path.join(self.folder['tech'], 'results_%s_%s' % (tech, period)) - if self.region: - outputName += '_' + self.region - outputName = os.path.join(self.outDir, outputName) - if self.greyFlag: - outputName += '.grey' - # if (os.path.exists(outputName + '.' + outputFormat)): - # self.__log__('CreateTechResultsDiagrams: graph already exists at path, returning') - # return self.outDir, outputName + '.' + outputFormat - - # enode_attr_fmt = 'href="../commodities/rc_%%s_%%s.%s"' % outputFormat - # vnode_attr_fmt = 'href="results_%%s_p%%sv%%s_segments.%s", ' % outputFormat - # vnode_attr_fmt += 'label="%s\\nCap: %.2f"' - enode_attr_fmt = "href=\"#\", onclick=\"loadNextGraphvizGraph('results', '%s', '%s')\"" - vnode_attr_fmt = "href=\"#\", onclick=\"loadNextGraphvizGraph('%s', '%s', '%s')\"" - vnode_attr_fmt += 'label="%s\\nCap: %.2f"' - - total_cap = self.dbUtil.getCapacityForTechAndPeriod(tech, period, region) - flows = self.dbUtil.getCommodityWiseInputAndOutputFlow(tech, period, region) - - self.__log__('CreateTechResultsDiagrams: database fetched successfully') - - enodes, vnodes, iedges, oedges = set(), set(), set(), set() - for i in range(len(flows)): - row = flows.iloc[i] - vnode = str(row['vintage']) - vnodes.add( - ( - vnode, - vnode_attr_fmt - % (tech, period, row['vintage'], row['vintage'], row['capacity']), - ) - ) - - if row['input_comm'] != 'ethos': - enodes.add((row['input_comm'], enode_attr_fmt % (row['input_comm'], period))) - iedges.add((row['input_comm'], vnode, 'label="%.2f"' % row['flow_in'])) - enodes.add((row['output_comm'], enode_attr_fmt % (row['output_comm'], period))) - oedges.add((vnode, row['output_comm'], 'label="%.2f"' % row['flow_out'])) - - # cluster_vintage_url = "results%s.%s" % (period, outputFormat) - cluster_vintage_url = '#' - - if vnodes: - self.__log__('CreateTechResultsDiagrams: creating diagrams') - args = dict( - cluster_vintage_url=cluster_vintage_url, - total_cap=total_cap, - inp_technology=tech, - period=period, - vnodes=create_text_nodes(vnodes, indent=2), - enodes=create_text_nodes(enodes, indent=2), - iedges=create_text_edges(iedges, indent=2), - oedges=create_text_edges(oedges, indent=2), - ) - self.__generateGraph__(tech_results_dot_fmt, args, outputName, outputFormat) - else: - self.__log__('CreateTechResultsDiagrams: nothing to create') - - self.__log__('CreateTechResultsDiagrams: graph generated, returning') - return self.outDir, outputName + '.' + outputFormat - - def CreateCommodityPartialResults(self, period, region, comm, outputFormat='svg'): - self.__log__( - 'CreateCommodityPartialResults: started with period = ' - + str(period) - + ' and comm = ' - + str(comm) - ) - - if not os.path.exists(os.path.join(self.outDir, self.folder['comm'])): - os.makedirs(os.path.join(self.outDir, self.folder['comm'])) - - outputName = os.path.join(self.folder['comm'], 'rc_%s_%s' % (comm, period)) - if self.region: - outputName += '_' + self.region - outputName = os.path.join(self.outDir, outputName) - if self.greyFlag: - outputName += '.grey' - # if (os.path.exists(outputName + '.' + outputFormat)): - # self.__log__('CreateCommodityPartialResults: graph already exists at path, returning') - # return self.outDir, outputName + '.' + outputFormat - - input_total = set( - self.dbUtil.getExistingTechnologiesForCommodity(comm, region, 'output')['tech'] - ) - output_total = set( - self.dbUtil.getExistingTechnologiesForCommodity(comm, region, 'input')['tech'] - ) - - flow_in = self.dbUtil.getOutputFlowForPeriod(period, region, 'input', comm) - otechs = set(flow_in['tech']) - - flow_out = self.dbUtil.getOutputFlowForPeriod(period, region, 'output', comm) - itechs = set(flow_out['tech']) - - self.__log__('CreateCommodityPartialResults: database fetched successfully') - - period_results_url_fmt = '../results/results%%s.%s' % outputFormat - # node_attr_fmt = 'href="../results/results_%%s_%%s.%s"' % outputFormat - # rc_node_fmt = 'color="%s", href="%s", shape="circle", fillcolor="%s", fontcolor="black"' - - node_attr_fmt = "href=\"#\", onclick=\"loadNextGraphvizGraph('results', '%s', '%s')\"" - rc_node_fmt = 'color="%s", href="%s", shape="circle", fillcolor="%s", fontcolor="black"' - - # url = period_results_url_fmt % period - url = '#' - enodes, dnodes, eedges, dedges = set(), set(), set(), set() - - rcnode = ( - (comm, rc_node_fmt % (self.colors['commodity_color'], url, self.colors['fill_color'])), - ) - - for i in range(len(flow_in)): - t = flow_in.iloc[i]['tech'] - f = flow_in.iloc[i]['flow'] - enodes.add((t, node_attr_fmt % (t, period))) - eedges.add((comm, t, 'label="%.2f"' % f)) - for t in output_total - otechs: - dnodes.add((t, None)) - dedges.add((comm, t, None)) - for i in range(len(flow_out)): - t = flow_out.iloc[i]['tech'] - f = flow_out.iloc[i]['flow'] - enodes.add((t, node_attr_fmt % (t, period))) - eedges.add((t, comm, 'label="%.2f"' % f)) - for t in input_total - itechs: - dnodes.add((t, None)) - dedges.add((t, comm, None)) - - self.__log__('CreateCommodityPartialResults: creating diagrams') - args = dict( - inp_commodity=comm, - period=period, - resource_node=create_text_nodes(rcnode), - used_nodes=create_text_nodes(enodes, indent=2), - unused_nodes=create_text_nodes(dnodes, indent=2), - used_edges=create_text_edges(eedges, indent=2), - unused_edges=create_text_edges(dedges, indent=2), - ) - self.__generateGraph__(commodity_dot_fmt, args, outputName, outputFormat) - self.__log__('CreateCommodityPartialResults: graph generated, returning') - return self.outDir, outputName + '.' + outputFormat - - # Function for generating the Input Graph - def createCompleteInputGraph(self, region, inp_tech=None, inp_comm=None, outputFormat='svg'): - self.__log__( - 'createCompleteInputGraph: started with inp_tech = ' - + str(inp_tech) - + ' and inp_comm = ' - + str(inp_comm) - ) - outputName = self.qName - - if inp_tech: - outputName += '_' + str(inp_tech) - if not os.path.exists(os.path.join(self.outDir, self.folder['tech'])): - os.makedirs(os.path.join(self.outDir, self.folder['tech'])) - outputName = os.path.join(self.folder['tech'], outputName) - elif inp_comm: - outputName += '_' + str(inp_comm) - if not os.path.exists(os.path.join(self.outDir, self.folder['comm'])): - os.makedirs(os.path.join(self.outDir, self.folder['comm'])) - outputName = os.path.join(self.folder['comm'], outputName) - else: - if not os.path.exists(os.path.join(self.outDir, self.folder['results'])): - os.makedirs(os.path.join(self.outDir, self.folder['results'])) - outputName = os.path.join(self.folder['results'], outputName) - - if self.region: - outputName += '_' + self.region - - outputName = os.path.join(self.outDir, outputName) - if self.greyFlag: - outputName += '.grey' - # if (os.path.exists(outputName + '.' + outputFormat)): - # self.__log__('createCompleteInputGraph: graph already exists at path, returning') - # return self.outDir, outputName + '.' + outputFormat - - nodes, tech, ltech, to_tech, from_tech = set(), set(), set(), set(), set() - - if DatabaseUtil.isDataBaseFile(self.dbFile): - res = self.dbUtil.getCommoditiesAndTech(inp_comm, inp_tech, region) - else: - res = self.dbUtil.readFromDatFile(inp_comm, inp_tech) - - self.__log__('createCompleteInputGraph: database fetched successfully') - # Create nodes and edges using the data frames from database - for i in range(len(res)): - row = res.iloc[i] - if row['input_comm'] != 'ethos': - nodes.add(row['input_comm']) - else: - ltech.add(row['tech']) - nodes.add(row['output_comm']) - tech.add(row['tech']) - - if row['input_comm'] != 'ethos': - to_tech.add('"%s"' % row['input_comm'] + '\t->\t"%s"' % row['tech']) - from_tech.add('"%s"' % row['tech'] + '\t->\t"%s"' % row['output_comm']) - - self.__log__('createCompleteInputGraph: creating diagrams') - - args = dict( - enodes=''.join('"%s";\n\t\t' % x for x in nodes), - tnodes=''.join('"%s";\n\t\t' % x for x in tech), - iedges=''.join('%s;\n\t\t' % x for x in to_tech), - oedges=''.join('%s;\n\t\t' % x for x in from_tech), - snodes=';'.join('"%s"' % x for x in ltech), - ) - self.__generateGraph__(quick_run_dot_fmt, args, outputName, outputFormat) - self.__log__('createCompleteInputGraph: graph generated, returning') - return self.outDir, outputName + '.' + outputFormat - - -if __name__ == '__main__': - input = processInput(sys.argv[1:]) - graphGen = GraphvizDiagramGenerator( - input['ifile'], input['scenario_name'], input['region'], outDir=input['res_dir'] - ) - graphGen.connect() - graphGen.setGraphicOptions(greyFlag=input['grey_flag'], splinevar=input['splinevar']) - if input['scenario_name'] is None: - res = graphGen.createCompleteInputGraph( - input['region'], input['inp_technology'], input['inp_commodity'] - ) - elif input['inp_technology'] is None and input['inp_commodity'] is None: - res = graphGen.CreateMainResultsDiagram(input['period'], input['region']) - elif input['inp_commodity'] is None: - res = graphGen.CreateTechResultsDiagrams( - input['period'], input['region'], input['inp_technology'] - ) - elif input['inp_technology'] is None: - res = graphGen.CreateCommodityPartialResults( - input['period'], input['region'], input['inp_commodity'] - ) - graphGen.close() - print('Check graph generated at ', res[1], ' and all results at ', res[0]) diff --git a/temoa/data_processing/MakeOutputPlots.py b/temoa/data_processing/MakeOutputPlots.py deleted file mode 100644 index c7b634136..000000000 --- a/temoa/data_processing/MakeOutputPlots.py +++ /dev/null @@ -1,424 +0,0 @@ -import sqlite3 -import sys - -import matplotlib - -matplotlib.use('Agg') -from matplotlib import pyplot as plt, cm as cmx, colors -import random -import os -import argparse - - -class OutputPlotGenerator: - def __init__(self, path_to_db, region, scenario, super_categories=False): - self.db_path = os.path.abspath(path_to_db) - if region == 'global': - self.region = '%' - else: - self.region = region - self.scenario = scenario - self.folder_name = ( - os.path.splitext(os.path.basename(path_to_db))[0] - + '_' - + region - + '_' - + scenario - + '_plots' - ) - # self.extractFromDatabase() - - def extractFromDatabase(self, mode): - """ - Based on the type of the plot being generated, extract data from the corresponding table from database - """ - con = sqlite3.connect(self.db_path) - cur = con.cursor() - if mode == 1: - cur.execute( - "SELECT sector, period, tech, capacity FROM OutputNetCapacity WHERE scenario == '" - + self.scenario - + "' AND region LIKE '" - + self.region - + "'" - ) - self.capacity_output = cur.fetchall() - self.capacity_output = [list(elem) for elem in self.capacity_output] - elif mode == 2: - cur.execute( - "SELECT sector, period, tech, SUM(flow) FROM OutputFlowOut WHERE scenario == '" - + self.scenario - + "' AND region LIKE '" - + self.region - + "' GROUP BY sector, period, tech" - ) - self.output_vflow = cur.fetchall() - self.output_vflow = [list(elem) for elem in self.output_vflow] - elif mode == 3: - cur.execute( - "SELECT sector, period, emis_comm, SUM(emission) FROM OutputEmission WHERE scenario == '" - + self.scenario - + "' AND region LIKE '" - + self.region - + "' GROUP BY sector, period, emis_comm" - ) - self.output_emissions = cur.fetchall() - self.output_emissions = [list(elem) for elem in self.output_emissions] - - cur.execute('SELECT tech, category FROM Technology') - self.tech_categories = cur.fetchall() - self.tech_categories = [[str(word) for word in t] for t in self.tech_categories] - con.close() - - def getSectors(self, type): - """ - Based on the type of the plot being generated, returns a list of sectors available in the database - """ - self.extractFromDatabase(type) - sectors = set() - - data = None - - if type == 1: - data = self.capacity_output - elif type == 2: - data = self.output_vflow - elif type == 3: - data = self.output_emissions - - for row in data: - sectors.add(row[0]) - - res = list(sectors) - res.insert(0, 'all') - return res - - def processData(self, inputData, sector, super_categories=False): - """ - Processes data for a particular sector to make it ready for plotting purposes - """ - periods = set() - techs = set() - - for row in inputData: - row[0] = str(row[0]) - row[1] = int(row[1]) - row[2] = str(row[2]) - row[3] = float(row[3]) - - tech_dict = dict(self.tech_categories) - if super_categories: - for row in inputData: - row[2] = tech_dict.get(row[2], row[2]) - - for row in inputData: - if row[0] == sector or sector == 'all': - periods.add(row[1]) # Reminder: indexing starts at 0 - techs.add(row[2]) - - periods = list(periods) - techs = list(techs) - periods.sort() - - output_values = dict() # Each row in a dictionary is a list - for tech in techs: - if tech == 'None' or tech == '': - continue - output_values[tech] = [0] * len(periods) # this just creates a blank table - for row in inputData: - if row[2] == 'None' or row[2] == '': - continue - if row[0] == sector or sector == 'all': - output_values[row[2]][periods.index(row[1])] += row[-1] - - output_values['periods'] = periods - return output_values - - def handleOutputPath(self, plot_type, sector, super_categories, output_dir): - outfile = plot_type + '_' + sector # +'_'+str(int(time.time()*1000))+'.png' - if super_categories: - outfile += '_merged' - outfile += '.png' - outfile2 = os.path.join(self.folder_name, outfile) - output_dir = os.path.join(output_dir, self.folder_name) - if not os.path.exists(output_dir): - os.makedirs(output_dir) - - self.output_file_name = os.path.join(output_dir, outfile) - - self.output_file_name = self.output_file_name.replace(' ', '') - return outfile2 - - def generatePlotForCapacity(self, sector, super_categories=False, output_dir='.'): - """ - Generates Plot for Capacity of a given sector - """ - - outfile2 = self.handleOutputPath('capacity', sector, super_categories, output_dir) - if os.path.exists(self.output_file_name): - print('not generating new capacity plot') - return outfile2 - - sectors = self.getSectors(1) - - if sector not in sectors: - return '' - - output_values = self.processData(self.capacity_output, sector, super_categories) - - if self.region == '%': - title = 'Capacity Plot for ' + sector + ' across all regions' - else: - title = 'Capacity Plot for ' + sector + ' sector in region ' + self.region - - self.makeStackedBarPlot(output_values, 'Years', 'Capacity ', 'periods', title) - - return outfile2 - - def generatePlotForOutputFlow(self, sector, super_categories=False, output_dir='.'): - """ - Generates Plot for Output Flow of a given sector - """ - outfile2 = self.handleOutputPath('flow', sector, super_categories, output_dir) - if os.path.exists(self.output_file_name): - print('not generating new flow plot') - return outfile2 - - sectors = self.getSectors(2) - if sector not in sectors: - return '' - - output_values = self.processData(self.output_vflow, sector, super_categories) - - if self.region == '%': - title = 'Output Flow Plot for ' + sector + ' across all regions' - else: - title = 'Output Flow Plot for ' + sector + ' sector in region ' + self.region - - self.makeStackedBarPlot(output_values, 'Years', 'Activity ', 'periods', title) - - return outfile2 - - def generatePlotForEmissions(self, sector, super_categories=False, output_dir='.'): - """ - Generates Plot for Emissions of a given sector - """ - outfile2 = self.handleOutputPath('emissions', sector, super_categories, output_dir) - if os.path.exists(self.output_file_name): - print('not generating new emissions plot') - return outfile2 - - sectors = self.getSectors(3) - if sector not in sectors: - return '' - - output_values = self.processData(self.output_emissions, sector, super_categories) - - if self.region == '%': - title = 'Emissions Plot for ' + sector + ' across all regions' - else: - title = 'Emissions Plot for ' + sector + ' sector in region ' + self.region - - self.make_line_plot(output_values.copy(), 'Emissions', title) - - return outfile2 - - """ - --------------------------- Plot Generation related functions -------------------------------------- - """ - - def get_random_color(self, pastel_factor=0.5): - return [ - (x + pastel_factor) / (1.0 + pastel_factor) - for x in [random.uniform(0, 1.0) for i in [1, 2, 3]] - ] - - def color_distance(self, c1, c2): - return sum([abs(x[0] - x[1]) for x in zip(c1, c2)]) - - def get_cmap(self, N): - """Returns a function that maps each index in 0, 1, ... N-1 to a distinct - RGB color.""" - color_norm = colors.Normalize(vmin=0, vmax=N - 1) - # More colormaps: https://matplotlib.org/examples/color/colormaps_reference.html - scalar_map = cmx.ScalarMappable(norm=color_norm, cmap='viridis') - - def map_index_to_rgb_color(index): - return scalar_map.to_rgba(index) - - return map_index_to_rgb_color - - def generate_new_color(self, existing_colors, pastel_factor=0.5): - max_distance = None - best_color = None - for i in range(0, 100): - color = self.get_random_color(pastel_factor=pastel_factor) - if not existing_colors: - return color - best_distance = min([self.color_distance(color, c) for c in existing_colors]) - if not max_distance or best_distance > max_distance: - max_distance = best_distance - best_color = color - return best_color - - def makeStackedBarPlot(self, data, xlabel, ylabel, xvar, title): - random.seed(10) - - handles = list() - xaxis = data[xvar] - data.pop('c', 0) - data.pop(xvar, 0) - stackedBars = data.keys() - colorMapForBars = dict() - colors = [] - plt.figure() - - cmap = self.get_cmap(len(stackedBars)) - for i in range(0, len(stackedBars)): - # colors.append(self.generate_new_color(colors,pastel_factor = 0.9)) - # colorMapForBars[data.keys()[i]]=colors[i] - colorMapForBars[list(data.keys())[i]] = cmap(i) - - width = min([xaxis[i + 1] - xaxis[i] for i in range(0, len(xaxis) - 1)]) / 2.0 - b = [0] * len(xaxis) - - # plt.figure() - - for bar in stackedBars: - h = plt.bar(xaxis, data[bar], width, bottom=b, color=colorMapForBars[bar]) - handles.append(h) - b = [b[j] + data[bar][j] for j in range(0, len(b))] - - plt.xlabel(xlabel) - plt.ylabel(ylabel) - # plt.xticks([width*0.5 + i for i in xaxis], [str(i) for i in xaxis]) - plt.xticks([i for i in xaxis], [str(i) for i in xaxis]) - plt.title(title) - lgd = plt.legend( - [h[0] for h in handles], stackedBars, bbox_to_anchor=(1.2, 1), fontsize=7.5 - ) - # plt.show() - plt.savefig(self.output_file_name, bbox_extra_artists=(lgd,), bbox_inches='tight') - - def make_line_plot(self, plot_var, label, title): - handles = list() - periods = plot_var['periods'] - plot_var.pop('periods', 0) - techs = plot_var.keys() - random.seed(10) - color_map = dict() - colors = [] - width = 1.5 - plt.figure() - - cmap = self.get_cmap(len(techs)) - for i in range(0, len(techs)): - # colors.append(self.generate_new_color(colors,pastel_factor = 0.9)) - # color_map[plot_var.keys()[i]]=colors[i] - color_map[plot_var.keys()[i]] = cmap(i) - - b = [0] * len(periods) - for tech in techs: - h = plt.plot(periods, plot_var[tech], color=color_map[tech], linestyle='--', marker='o') - handles.append(h) - - plt.xlabel('Years') - plt.ylabel(label) - # plt.xticks([i + width*0.5 for i in periods], [str(i) for i in periods]) - plt.xticks(periods) - plt.title(title) - lgd = plt.legend([h[0] for h in handles], techs, bbox_to_anchor=(1.2, 1), fontsize=7.5) - # plt.show() - plt.savefig(self.output_file_name, bbox_extra_artists=(lgd,), bbox_inches='tight') - - -# Function used for command line purposes. Parses arguments and then calls relevent functions. -def GeneratePlot(args): - parser = argparse.ArgumentParser(description='Generate Output Plot') - parser.add_argument( - '-i', - '--input', - action='store', - dest='input', - help='Input Database Filename ', - required=True, - ) - parser.add_argument( - '-r', - '--region', - action='store', - dest='region', - help="Region name, input 'global' if global results are desired", - required=True, - ) - parser.add_argument( - '-s', - '--scenario', - action='store', - dest='scenario', - help='Model run scenario name', - required=True, - ) - parser.add_argument( - '-p', - '--plot-type', - action='store', - dest='type', - help='Type of Plot to be generated', - choices=['capacity', 'flow', 'emissions'], - required=True, - ) - parser.add_argument( - '-c', - '--sector', - action='store', - dest='sector', - help='Sector for which plot to be generated', - required=True, - ) - parser.add_argument( - '-o', - '--output', - action='store', - dest='output_dir', - help='Output plot location', - default='./', - ) - parser.add_argument( - '--super', - action='store_true', - dest='super_categories', - help='Merge Technologies or not', - default=False, - ) - - options = parser.parse_args(args) - - result = OutputPlotGenerator( - options.input, options.region, options.scenario, options.super_categories - ) - error = '' - if options.type == 'capacity': - error = result.generatePlotForCapacity( - options.sector, options.super_categories, options.output_dir - ) - elif options.type == 'flow': - error = result.generatePlotForOutputFlow( - options.sector, options.super_categories, options.output_dir - ) - elif options.type == 'emissions': - error = result.generatePlotForEmissions( - options.sector, options.super_categories, options.output_dir - ) - - if error == '': - print("Error: The sector doesn't exist for the selected plot type and database") - else: - print( - 'Done. Look for output plot images in directory:' - + os.path.join(options.output_dir, error) - ) - - -if __name__ == '__main__': - GeneratePlot(sys.argv[1:]) diff --git a/temoa/data_processing/README.md b/temoa/data_processing/README.md index 341da5d0b..11634db7d 100644 --- a/temoa/data_processing/README.md +++ b/temoa/data_processing/README.md @@ -1,43 +1,19 @@ # Overview -This folder contains files used to manage Temoa input/output data. Included files are: +This folder contains files used to manage Temoa output data processing +> **⚠️ Note:** These tools have not been fully tested with Temoa v4.0. They may require updates or fixes. Please report any issues on [GitHub Issues](https://github.com/TemoaProject/temoa/issues). -1. `DB_to_Excel.py/` -Python script that queries database output tables to create an Excel file containing scenario-specific results. - -2. `Make_Graphviz.py/` -Python script that creates a Graphviz diagram for the database. -The most basic way to use graphviz is to view the full energy system map: -```$ python MakeGraphviz.py -i temoa_utopia.sqlite``` Other options include a capacitated -flow graph for a specific period: ```-i ../../data_files/utopia.sqlite -r utopia -s -c -y 2010``` -note how this input file passes a relative link up to the data_files directory, assuming standard location in the project - - -3. `Network_diagrams.ipynb/` -Notebook to interactively view network diagrams for a user-specified database. -Create and activate the Temoa environment, as follows: - - ```$ conda env create``` - - ```$ source activate temoa-py3``` - - Once the Temoa environment is created and activated, enable the following extensions from from the command line. - This will need to be done only once, before using notebooks within the Temoa environment. - - ```(temoa-py3) $ jupyter nbextension enable init_cell/main``` - - ```(temoa-py3) $ jupyter nbextension enable hide_input/main``` - - Once these extensions are enabled, navigate to the `temoa/data_processing/` folder and then open notebooks as follows. - - ```(temoa-py3) $ jupyter notebook``` - - Navigate to the `Network_diagrams.ipynb/` file and select technology/commodity options to interactively view their network diagrams. - The notebook also includes an interactive technology/commodity lookup tool. - The "Toggle selected cell input display" button (below and to the right of the Help menu) can be used to view hidden code cells. +## Available Tools +### 1. `db_to_excel.py` +**Status:** ⚠️ Untested in v4.0 +Python script that queries database output tables to create an Excel file containing scenario-specific results. +**Usage:** +```bash +uv run python temoa/data_processing/db_to_excel.py -i path/to/database.sqlite -s scenario_name +``` diff --git a/temoa/data_processing/db_query.py b/temoa/data_processing/db_query.py index 36449e55b..2a001daad 100644 --- a/temoa/data_processing/db_query.py +++ b/temoa/data_processing/db_query.py @@ -1,51 +1,86 @@ -# Dev Note: This module is unused and appears to be just a query executor. Retained for now. 12JUN2024 +# Dev Note: This module is unused and appears to be just a query executor. Retained for now. +# 12JUN2024 +""" +A command-line utility for sending a direct SQL query to a Temoa database. + +This script allows a user to specify a Temoa SQLite database and an SQL query +string to execute against it, printing the results to the console. +""" import getopt import re import sqlite3 import sys +from os import PathLike +from typing import Any + +def send_query(inp_f: str | PathLike[str], query_string: str) -> str: + """ + Connects to a database, executes a query, and returns the result as a string. -def send_query(inp_f, query_string): - db_result = [] + Args: + inp_f: The file path to the SQLite database. + query_string: The SQL query to execute. + + Returns: + A string containing the query results or an error message. + """ + db_result: list[tuple[Any, ...]] = [] try: con = sqlite3.connect(inp_f) - cur = con.cursor() # a database cursor is a control structure that enables traversal over the records in a database - con.text_factory = str # this ensures data is explored with the correct UTF-8 encoding + cur = con.cursor() + con.text_factory = str - print(inp_f) + print(f'Executing query on: {inp_f}') cur.execute(query_string) - for row in cur: - db_result.append(row) + db_result.extend(cur) cur.close() con.close() - return 'Query Result: %s' % ''.join(db_result) + # Convert list of tuples to a more readable string representation + result_str = '\n'.join(map(str, db_result)) + return f'Query Result:\n{result_str}' except sqlite3.Error as e: - print('Error in Query %s' % e.args[0]) - return 'Query Result: Error in Query %s' % e.args[0] + error_msg = f'Error in Query: {e.args[0]}' + print(error_msg) + return f'Query Result: {error_msg}' -def help_user(): +def help_user() -> None: + """Prints the help message for the script to the console.""" print( """Use as: - python db_query.py -i (or --input) - | -q (or --query) - | -h (or --help) """ + python db_query.py -i (or --input) + | -q (or --query) + | -h (or --help)""" ) -def get_flags(inputs): - inp_file = None - query_string = None +def get_flags(inputs: dict[str, str]) -> str | None: + """ + Parses command-line options and executes the database query. + + Args: + inputs: A dictionary of command-line options to their arguments. - if inputs is None: + Returns: + The result of the database query as a string, or None if no query was run. + + Raises: + TypeError: If no arguments are provided. + ValueError: If the input file is not specified or is not a valid database file. + """ + inp_file: str | None = None + query_string: str | None = None + + if not inputs: raise TypeError('no arguments found') for opt, arg in inputs.items(): - print('%s == %s' % (opt, arg)) + print(f'{opt} == {arg}') if opt in ('-i', '--input'): inp_file = arg @@ -56,15 +91,12 @@ def get_flags(inputs): sys.exit(2) if inp_file is None: - raise Exception('Input file not specified') - - file_ty = re.search(r'(\w+)\.(\w+)\b', inp_file) # Extract the input filename and extension + raise ValueError('Input file not specified') - if not file_ty: - raise 'The file type %s is not recognized. Please specify a database file.' % inp_file + file_ty = re.search(r'\.(\w+)$', inp_file) - elif file_ty.group(2) not in ('db', 'sqlite', 'sqlite3', 'sqlitedb'): - raise 'The file type %s is not recognized. Please specify a database file.' % inp_file + if not file_ty or file_ty.group(1) not in ('db', 'sqlite', 'sqlite3', 'sqlitedb'): + raise ValueError(f'The file type of "{inp_file}" is not a recognized database file.') if query_string is None: print('No query specified.') @@ -75,10 +107,12 @@ def get_flags(inputs): if __name__ == '__main__': try: - argv = sys.argv[1:] + argv: list[str] = sys.argv[1:] + opts: list[tuple[str, str]] + args: list[str] opts, args = getopt.getopt(argv, 'hi:q:', ['help', 'input=', 'query=']) - print(opts) + print(f'Options found: {opts}') except getopt.GetoptError: help_user() diff --git a/temoa/data_processing/db_to_excel.py b/temoa/data_processing/db_to_excel.py new file mode 100644 index 000000000..c5e4a4c1d --- /dev/null +++ b/temoa/data_processing/db_to_excel.py @@ -0,0 +1,278 @@ +""" +This script processes a Temoa database file (SQLite) and converts the data +into two Excel formats: one for human-readable analysis and another +compatible with the IAMC format for integrated assessment modeling. + +The script extracts capacity, activity, emissions, and cost data for a +specified scenario, formats it, and saves it to separate sheets in an +Excel workbook. It also creates an aggregated IAMC-compatible Excel file. + +Usage: + python db_to_excel.py -i -s [-o ] +""" + +import getopt +import itertools +import re +import sqlite3 +import sys +from pathlib import Path + +import pandas as pd +from pandas import DataFrame + + +def make_excel(ifile: str | None, ofile: Path | None, scenario: set[str]) -> None: + """ + Processes a Temoa database to produce human-readable and IAMC-format + Excel files. + """ + if ifile is None: + raise ValueError("You did not specify the input file. Remember to use the '-i' option.") + + file_match = re.search(r'(\w+)\.(\w+)\b', ifile) + if not file_match: + raise ValueError(f'The file type {ifile} is not recognized. Use a database file.') + + if ofile is None: + ofile = Path(file_match.group(1)) + print(f'Look for output in {ofile}_*.xlsx') + + con = sqlite3.connect(ifile) + scenario_name = scenario.pop() + ofile = ofile.with_suffix('.xlsx') + + writer = pd.ExcelWriter( + ofile, + engine='xlsxwriter', + engine_kwargs={'options': {'strings_to_formulas': False}}, + ) + workbook = writer.book + header_format = workbook.add_format({'bold': True, 'text_wrap': True, 'align': 'left'}) + + query_all_techs = """ + SELECT DISTINCT efficiency.region, efficiency.tech, Technology.sector + FROM efficiency + INNER JOIN Technology ON efficiency.tech = Technology.tech + """ + all_techs = pd.read_sql_query(query_all_techs, con) + + query_capacity = """ + SELECT region, tech, sector, period, SUM(capacity) as capacity + FROM output_net_capacity WHERE scenario = ? + GROUP BY region, tech, sector, period + """ + df_capacity = pd.read_sql_query(query_capacity, con, params=(scenario_name,)) + for sector in sorted(df_capacity['sector'].unique()): + df_sector = ( + df_capacity[df_capacity['sector'] == sector] + .drop(columns=['sector']) + .pivot_table(values='capacity', index=['region', 'tech'], columns='period') + .reset_index() + ) + sector_techs = all_techs[all_techs['sector'] == sector] + df_sector = pd.merge( + sector_techs[['region', 'tech']], + df_sector, + on=['region', 'tech'], + how='left', + ) + df_sector.rename(columns={'region': 'Region', 'tech': 'Technology'}, inplace=True) + sheet_name = f'Capacity_{sector}' + df_sector.to_excel(writer, sheet_name=sheet_name, index=False, startrow=1, header=False) + worksheet = writer.sheets[sheet_name] + worksheet.set_column('A:A', 10) + worksheet.set_column('B:B', 10) + for col, val in enumerate(df_sector.columns.values): + worksheet.write(0, col, val, header_format) + + query_activity = """ + SELECT region, tech, sector, period, SUM(flow) as vflow_out + FROM output_flow_out WHERE scenario = ? + GROUP BY region, tech, sector, period + """ + df_activity = pd.read_sql_query(query_activity, con, params=(scenario_name,)) + for sector in sorted(df_activity['sector'].unique()): + df_sector = ( + df_activity[df_activity['sector'] == sector] + .drop(columns=['sector']) + .pivot_table(values='vflow_out', index=['region', 'tech'], columns='period') + .reset_index() + ) + sector_techs = all_techs[all_techs['sector'] == sector] + df_sector = pd.merge( + sector_techs[['region', 'tech']], + df_sector, + on=['region', 'tech'], + how='left', + ) + df_sector.rename(columns={'region': 'Region', 'tech': 'Technology'}, inplace=True) + sheet_name = f'Activity_{sector}' + df_sector.to_excel(writer, sheet_name=sheet_name, index=False, startrow=1, header=False) + worksheet = writer.sheets[sheet_name] + worksheet.set_column('A:A', 10) + worksheet.set_column('B:B', 10) + for col, val in enumerate(df_sector.columns.values): + worksheet.write(0, col, val, header_format) + + query_all_emis = """ + SELECT DISTINCT ea.region, ea.tech, ea.emis_comm, t.sector + FROM emission_activity ea + INNER JOIN Technology t ON ea.tech = t.tech + """ + try: + all_emis_techs = pd.read_sql_query(query_all_emis, con) + except sqlite3.OperationalError: + all_emis_techs = pd.DataFrame() + + query_emissions = """ + SELECT region, tech, sector, period, emis_comm, SUM(emission) as emissions + FROM output_emission WHERE scenario = ? + GROUP BY region, tech, sector, period, emis_comm + """ + df_emissions_raw = pd.read_sql_query(query_emissions, con, params=(scenario_name,)) + if not df_emissions_raw.empty: + df_emissions = df_emissions_raw.pivot_table( + values='emissions', + index=['region', 'tech', 'sector', 'emis_comm'], + columns='period', + ).reset_index() + df_emissions = pd.merge( + all_emis_techs, + df_emissions, + on=['region', 'tech', 'sector', 'emis_comm'], + how='left', + ) + df_emissions.rename( + columns={ + 'region': 'Region', + 'tech': 'Technology', + 'emis_comm': 'Emission Commodity', + 'sector': 'Sector', + }, + inplace=True, + ) + df_emissions.to_excel(writer, sheet_name='Emissions', index=False, startrow=1, header=False) + worksheet = writer.sheets['Emissions'] + worksheet.set_column('A:A', 10) + worksheet.set_column('B:B', 10) + worksheet.set_column('C:C', 10) + worksheet.set_column('D:D', 20) + for col, val in enumerate(df_emissions.columns.values): + worksheet.write(0, col, val, header_format) + + query_costs = """ + SELECT region, oc.tech, t.sector, vintage, + d_invest + d_var + d_fixed + d_emiss as cost + FROM output_cost oc + JOIN Technology t ON oc.tech = t.tech + WHERE scenario = ? + """ + df_costs = pd.read_sql_query(query_costs, con, params=(scenario_name,)) + df_costs.columns = ['Region', 'Technology', 'Sector', 'Vintage', 'Cost'] + df_costs.to_excel(writer, sheet_name='Costs', index=False, startrow=1, header=False) + worksheet = writer.sheets['Costs'] + worksheet.set_column('A:A', 10) + worksheet.set_column('B:B', 10) + worksheet.set_column('C:C', 10) + worksheet.set_column('D:D', 30) + for col, val in enumerate(df_costs.columns): + worksheet.write(0, col, val, header_format) + + writer.close() + + df_emissions_raw['variable'] = ( + 'Emissions|' + df_emissions_raw['emis_comm'] + '|' + df_emissions_raw['tech'] + ) + df_emissions_iamc = df_emissions_raw.rename(columns={'period': 'year', 'emissions': 'value'}) + + df_capacity['variable'] = 'Capacity|' + df_capacity['sector'] + '|' + df_capacity['tech'] + df_capacity_iamc = df_capacity.rename(columns={'period': 'year', 'capacity': 'value'}) + + df_activity['variable'] = 'Activity|' + df_activity['sector'] + '|' + df_activity['tech'] + df_activity_iamc = df_activity.rename(columns={'period': 'year', 'vflow_out': 'value'}) + + iamc_columns = ['region', 'variable', 'year', 'value'] + df_iamc = pd.concat( + [ + df_emissions_iamc[iamc_columns], + df_activity_iamc[iamc_columns], + df_capacity_iamc[iamc_columns], + ], + ignore_index=True, + ) + df_iamc['unit'] = '?' + df_iamc['scenario'] = scenario_name + + aggregates_to_add: list[DataFrame] = [] + grouping_cols = ['scenario', 'region', 'year', 'unit'] + + emiss_types = df_emissions_raw['emis_comm'].unique() + for emiss in emiss_types: + new_variable_name = f'Emissions|{emiss}' + mask = df_iamc['variable'].str.startswith(f'{new_variable_name}|') + if mask.any(): + agg = df_iamc[mask].groupby(grouping_cols, as_index=False).sum(numeric_only=True) + agg['variable'] = new_variable_name + aggregates_to_add.append(agg) + + sector_types = df_capacity['sector'].unique() + for var_type, sector in itertools.product(['Activity', 'Capacity'], sector_types): + new_variable_name = f'{var_type}|{sector}' + mask = df_iamc['variable'].str.startswith(f'{new_variable_name}|') + if mask.any(): + agg = df_iamc[mask].groupby(grouping_cols, as_index=False).sum(numeric_only=True) + agg['variable'] = new_variable_name + aggregates_to_add.append(agg) + + if aggregates_to_add: + df_final_iamc = pd.concat([df_iamc, *aggregates_to_add], ignore_index=True) + else: + df_final_iamc = df_iamc + + df_final_iamc['model'] = 'Temoa' + final_cols = ['model', 'scenario', 'region', 'variable', 'unit', 'year', 'value'] + df_final_iamc = df_final_iamc[final_cols] + + base_name = ofile.stem + excel_pyam_filename = ofile.with_name(f'{base_name}_pyam.xlsx') + df_final_iamc.to_excel(excel_pyam_filename, index=False) + + con.close() + + +def get_data(inputs: dict[str, str]) -> None: + """Parses command-line arguments and calls the main excel creation function.""" + ifile = inputs.get('-i') or inputs.get('--input') + ofile_str = inputs.get('-o') or inputs.get('--output') + ofile = Path(ofile_str) if ofile_str else None + scenario = {s for k, s in inputs.items() if k in ('-s', '--scenario')} + + if not scenario: + raise ValueError("You must specify a scenario with the '-s' option.") + + make_excel(ifile, ofile, scenario) + + +if __name__ == '__main__': + try: + opts, _ = getopt.getopt( + sys.argv[1:], + 'hi:o:s:', + ['help', 'input=', 'output=', 'scenario='], + ) + except getopt.GetoptError as err: + print(err) + print('Use -h or --help for usage instructions.') + sys.exit(2) + + opts_dict = dict(opts) + if '-h' in opts_dict or '--help' in opts_dict: + print(__doc__) + sys.exit() + + try: + get_data(opts_dict) + except (ValueError, FileNotFoundError) as e: + print(f'Error: {e}') + sys.exit(2) diff --git a/data_files/temoa_schema_v3.sql b/temoa/db_schema/temoa_schema_v3.sql similarity index 99% rename from data_files/temoa_schema_v3.sql rename to temoa/db_schema/temoa_schema_v3.sql index 3c03e7b80..3d0d66faf 100644 --- a/data_files/temoa_schema_v3.sql +++ b/temoa/db_schema/temoa_schema_v3.sql @@ -917,5 +917,3 @@ CREATE TABLE IF NOT EXISTS OutputCost ); COMMIT; PRAGMA FOREIGN_KEYS = 1; - - diff --git a/data_files/temoa_schema_minimal_v3.sql b/temoa/db_schema/temoa_schema_v3_1.sql similarity index 52% rename from data_files/temoa_schema_minimal_v3.sql rename to temoa/db_schema/temoa_schema_v3_1.sql index 92438444c..ff555aa4d 100644 --- a/data_files/temoa_schema_minimal_v3.sql +++ b/temoa/db_schema/temoa_schema_v3_1.sql @@ -9,11 +9,9 @@ CREATE TABLE IF NOT EXISTS MetaData PRIMARY KEY (element) ); REPLACE INTO MetaData -VALUES ('myopic_base_year', 2000, 'Base Year for Myopic Analysis'); -REPLACE INTO MetaData VALUES ('DB_MAJOR', 3, 'DB major version number'); REPLACE INTO MetaData -VALUES ('DB_MINOR', 0, 'DB minor version number'); +VALUES ('DB_MINOR', 1, 'DB minor version number'); CREATE TABLE IF NOT EXISTS MetaDataReal ( @@ -41,18 +39,36 @@ CREATE TABLE IF NOT EXISTS OutputObjective objective_name TEXT, total_system_cost REAL ); +CREATE TABLE IF NOT EXISTS SeasonLabel +( + season TEXT PRIMARY KEY, + notes TEXT +); CREATE TABLE IF NOT EXISTS SectorLabel ( - sector TEXT, - PRIMARY KEY (sector) + sector TEXT PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS CapacityCredit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + credit REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage), + CHECK (credit >= 0 AND credit <= 1) ); - - CREATE TABLE IF NOT EXISTS CapacityFactorProcess ( region TEXT, - season TEXT - REFERENCES TimeSeason (season), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), tod TEXT REFERENCES TimeOfDay (tod), tech TEXT @@ -60,21 +76,23 @@ CREATE TABLE IF NOT EXISTS CapacityFactorProcess vintage INTEGER, factor REAL, notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), + PRIMARY KEY (region, period, season, tod, tech, vintage), CHECK (factor >= 0 AND factor <= 1) ); CREATE TABLE IF NOT EXISTS CapacityFactorTech ( region TEXT, + period INTEGER + REFERENCES TimePeriod (period), season TEXT - REFERENCES TimeSeason (season), + REFERENCES SeasonLabel (season), tod TEXT REFERENCES TimeOfDay (tod), tech TEXT REFERENCES Technology (tech), factor REAL, notes TEXT, - PRIMARY KEY (region, season, tod, tech), + PRIMARY KEY (region, period, season, tod, tech), CHECK (factor >= 0 AND factor <= 1) ); CREATE TABLE IF NOT EXISTS CapacityToActivity @@ -101,18 +119,38 @@ CREATE TABLE IF NOT EXISTS CommodityType description TEXT ); REPLACE INTO CommodityType +VALUES ('s', 'source commodity'); +REPLACE INTO CommodityType +VALUES ('a', 'annual commodity'); +REPLACE INTO CommodityType VALUES ('p', 'physical commodity'); REPLACE INTO CommodityType +VALUES ('d', 'demand commodity'); +REPLACE INTO CommodityType VALUES ('e', 'emissions commodity'); REPLACE INTO CommodityType -VALUES ('d', 'demand commodity'); +VALUES ('w', 'waste commodity'); REPLACE INTO CommodityType -VALUES ('s', 'source commodity'); - +VALUES ('wa', 'waste annual commodity'); +REPLACE INTO CommodityType +VALUES ('wp', 'waste physical commodity'); +CREATE TABLE IF NOT EXISTS ConstructionInput +( + region TEXT, + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage) +); CREATE TABLE IF NOT EXISTS CostEmission ( - region TEXT - REFERENCES Region (region), + region TEXT, period INTEGER REFERENCES TimePeriod (period), emis_comm TEXT NOT NULL @@ -177,27 +215,32 @@ CREATE TABLE IF NOT EXISTS Demand CREATE TABLE IF NOT EXISTS DemandSpecificDistribution ( region TEXT, - season TEXT - REFERENCES TimeSeason (season), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), tod TEXT REFERENCES TimeOfDay (tod), demand_name TEXT REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) + dsd REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, demand_name), + CHECK (dsd >= 0 AND dsd <= 1) ); -CREATE TABLE IF NOT EXISTS LoanRate +CREATE TABLE IF NOT EXISTS EndOfLifeOutput ( - region TEXT, - tech TEXT + region TEXT, + tech TEXT REFERENCES Technology (tech), - vintage INTEGER + vintage INTEGER REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) + output_comm TEXT + REFERENCES Commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage, output_comm) ); CREATE TABLE IF NOT EXISTS Efficiency ( @@ -215,6 +258,28 @@ CREATE TABLE IF NOT EXISTS Efficiency PRIMARY KEY (region, input_comm, tech, vintage, output_comm), CHECK (efficiency > 0) ); +CREATE TABLE IF NOT EXISTS EfficiencyVariable +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); CREATE TABLE IF NOT EXISTS EmissionActivity ( region TEXT, @@ -233,6 +298,34 @@ CREATE TABLE IF NOT EXISTS EmissionActivity notes TEXT, PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) ); +CREATE TABLE IF NOT EXISTS EmissionEmbodied +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS EmissionEndOfLife +( + region TEXT, + emis_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); CREATE TABLE IF NOT EXISTS ExistingCapacity ( region TEXT, @@ -245,15 +338,33 @@ CREATE TABLE IF NOT EXISTS ExistingCapacity notes TEXT, PRIMARY KEY (region, tech, vintage) ); - -CREATE TABLE IF NOT EXISTS LoanLifetimeTech +CREATE TABLE IF NOT EXISTS TechGroup +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS LoanLifetimeProcess ( region TEXT, tech TEXT REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), lifetime REAL, notes TEXT, - PRIMARY KEY (region, tech) + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS LoanRate +( + region TEXT, + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) ); CREATE TABLE IF NOT EXISTS LifetimeProcess ( @@ -275,69 +386,311 @@ CREATE TABLE IF NOT EXISTS LifetimeTech notes TEXT, PRIMARY KEY (region, tech) ); -CREATE TABLE IF NOT EXISTS LinkedTech +CREATE TABLE IF NOT EXISTS Operator ( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT + operator TEXT PRIMARY KEY, + notes TEXT +); +REPLACE INTO Operator VALUES('e','equal to'); +REPLACE INTO Operator VALUES('le','less than or equal to'); +REPLACE INTO Operator VALUES('ge','greater than or equal to'); +CREATE TABLE IF NOT EXISTS LimitGrowthCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitDegrowthCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitGrowthNewCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitDegrowthNewCapacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitGrowthNewCapacityDelta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitDegrowthNewCapacityDelta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitStorageLevelFraction +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) + vintage INTEGER + REFERENCES TimePeriod (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + fraction REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tod, tech, vintage, operator) ); -CREATE TABLE IF NOT EXISTS MaxActivity +CREATE TABLE IF NOT EXISTS LimitActivity ( region TEXT, period INTEGER REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + activity REAL, units TEXT, notes TEXT, - PRIMARY KEY (region, period, tech) + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitActivityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) ); -CREATE TABLE IF NOT EXISTS MaxCapacity +CREATE TABLE IF NOT EXISTS LimitAnnualCapacityFactor +( + region TEXT, + tech_or_group TEXT, + vintage INTEGER + REFERENCES TimePeriod (period), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY (region, tech_or_group, vintage, output_comm, operator), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS LimitCapacity ( region TEXT, period INTEGER REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + capacity REAL, units TEXT, notes TEXT, - PRIMARY KEY (region, period, tech) + PRIMARY KEY (region, period, tech_or_group, operator) ); - -CREATE TABLE IF NOT EXISTS MinActivity +CREATE TABLE IF NOT EXISTS LimitCapacityShare +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitNewCapacity ( region TEXT, - period INTEGER + tech_or_group TEXT, + vintage INTEGER REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + new_cap REAL, units TEXT, notes TEXT, - PRIMARY KEY (region, period, tech) + PRIMARY KEY (region, tech_or_group, vintage, operator) ); - -CREATE TABLE IF NOT EXISTS MinCapacity +CREATE TABLE IF NOT EXISTS LimitNewCapacityShare ( - region TEXT, - period INTEGER + region TEXT, + sub_group TEXT, + super_group TEXT, + vintage INTEGER REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, sub_group, super_group, vintage, operator) +); +CREATE TABLE IF NOT EXISTS LimitResource +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + cum_act REAL, units TEXT, notes TEXT, - PRIMARY KEY (region, period, tech) + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS LimitSeasonalCapacityFactor +( + region TEXT + REFERENCES Region (region), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY(region, period, season, tech, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechInputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechInputSplitAnnual +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + input_comm TEXT + REFERENCES Commodity (name), + tech TEXT + REFERENCES Technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechOutputSplit +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS LimitTechOutputSplitAnnual +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + tech TEXT + REFERENCES Technology (tech), + output_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS LimitEmission +( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), + emis_comm TEXT + REFERENCES Commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES Operator (operator), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm, operator) +); +CREATE TABLE IF NOT EXISTS LinkedTech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES Technology (tech), + emis_comm TEXT + REFERENCES Commodity (name), + driven_tech TEXT + REFERENCES Technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) ); - CREATE TABLE IF NOT EXISTS OutputCurtailment ( scenario TEXT, @@ -400,7 +753,8 @@ CREATE TABLE IF NOT EXISTS OutputRetiredCapacity REFERENCES Technology (tech), vintage INTEGER REFERENCES TimePeriod (period), - capacity REAL, + cap_eol REAL, + cap_early REAL, PRIMARY KEY (region, scenario, period, tech, vintage) ); CREATE TABLE IF NOT EXISTS OutputFlowIn @@ -411,8 +765,8 @@ CREATE TABLE IF NOT EXISTS OutputFlowIn REFERENCES SectorLabel (sector), period INTEGER REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), + season TEXT + REFERENCES SeasonLabel (season), tod TEXT REFERENCES TimeOfDay (tod), input_comm TEXT @@ -434,8 +788,8 @@ CREATE TABLE IF NOT EXISTS OutputFlowOut REFERENCES SectorLabel (sector), period INTEGER REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), + season TEXT + REFERENCES SeasonLabel (season), tod TEXT REFERENCES TimeOfDay (tod), input_comm TEXT @@ -449,27 +803,49 @@ CREATE TABLE IF NOT EXISTS OutputFlowOut flow REAL, PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) ); +CREATE TABLE IF NOT EXISTS OutputStorageLevel +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES SectorLabel (sector), + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), + tod TEXT + REFERENCES TimeOfDay (tod), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER + REFERENCES TimePeriod (period), + level REAL, + PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) +); CREATE TABLE IF NOT EXISTS PlanningReserveMargin ( region TEXT PRIMARY KEY REFERENCES Region (region), - margin REAL + margin REAL, + notes TEXT ); -CREATE TABLE IF NOT EXISTS RampDown +CREATE TABLE IF NOT EXISTS RampDownHourly ( region TEXT, tech TEXT REFERENCES Technology (tech), rate REAL, + notes TEXT, PRIMARY KEY (region, tech) ); -CREATE TABLE IF NOT EXISTS RampUp +CREATE TABLE IF NOT EXISTS RampUpHourly ( region TEXT, tech TEXT REFERENCES Technology (tech), rate REAL, + notes TEXT, PRIMARY KEY (region, tech) ); CREATE TABLE IF NOT EXISTS Region @@ -478,15 +854,32 @@ CREATE TABLE IF NOT EXISTS Region PRIMARY KEY, notes TEXT ); -CREATE TABLE IF NOT EXISTS TimeSegmentFraction +CREATE TABLE IF NOT EXISTS ReserveCapacityDerate ( + region TEXT, + period INTEGER + REFERENCES TimePeriod (period), season TEXT - REFERENCES TimeSeason (season), + REFERENCES SeasonLabel (season), + tech TEXT + REFERENCES Technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS TimeSegmentFraction +( + period INTEGER + REFERENCES TimePeriod (period), + season TEXT + REFERENCES SeasonLabel (season), tod TEXT REFERENCES TimeOfDay (tod), segfrac REAL, notes TEXT, - PRIMARY KEY (season, tod), + PRIMARY KEY (period, season, tod), CHECK (segfrac >= 0 AND segfrac <= 1) ); CREATE TABLE IF NOT EXISTS StorageDuration @@ -497,7 +890,18 @@ CREATE TABLE IF NOT EXISTS StorageDuration notes TEXT, PRIMARY KEY (region, tech) ); - +CREATE TABLE IF NOT EXISTS LifetimeSurvivalCurve +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech TEXT NOT NULL + REFERENCES Technology (tech), + vintage INTEGER NOT NULL + REFERENCES TimePeriod (period), + fraction REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); CREATE TABLE IF NOT EXISTS TechnologyType ( label TEXT @@ -505,53 +909,26 @@ CREATE TABLE IF NOT EXISTS TechnologyType description TEXT ); REPLACE INTO TechnologyType -VALUES ('r', 'resource technology'); -REPLACE INTO TechnologyType VALUES ('p', 'production technology'); REPLACE INTO TechnologyType VALUES ('pb', 'baseload production technology'); REPLACE INTO TechnologyType VALUES ('ps', 'storage production technology'); - -CREATE TABLE IF NOT EXISTS TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE IF NOT EXISTS TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE IF NOT EXISTS TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); +-- CREATE TABLE IF NOT EXISTS TimeNext +-- ( +-- period INTEGER +-- REFERENCES TimePeriod (period), +-- season TEXT +-- REFERENCES SeasonLabel (season), +-- tod TEXT +-- REFERENCES TimeOfDay (tod), +-- season_next TEXT +-- REFERENCES SeasonLabel (season), +-- tod_next TEXT +-- REFERENCES TimeOfDay (tod), +-- notes TEXT, +-- PRIMARY KEY (period, season, tod) +-- ); CREATE TABLE IF NOT EXISTS TimeOfDay ( sequence INTEGER UNIQUE, @@ -568,75 +945,37 @@ CREATE TABLE IF NOT EXISTS TimePeriod ); CREATE TABLE IF NOT EXISTS TimeSeason ( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -CREATE TABLE IF NOT EXISTS TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); - -CREATE TABLE IF NOT EXISTS MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE IF NOT EXISTS MaxNewCapacity -( - region TEXT, - period INTEGER + period INTEGER REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) + sequence INTEGER, + season TEXT + REFERENCES SeasonLabel (season), + notes TEXT, + PRIMARY KEY (period, sequence, season) ); - - -CREATE TABLE IF NOT EXISTS MinAnnualCapacityFactor +CREATE TABLE IF NOT EXISTS TimeSeasonSequential ( - region TEXT, - period INTEGER + period INTEGER REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) + sequence INTEGER, + seas_seq TEXT, + season TEXT + REFERENCES SeasonLabel (season), + num_days REAL NOT NULL, + notes TEXT, + PRIMARY KEY (period, sequence, seas_seq, season), + CHECK (num_days > 0) ); - -CREATE TABLE IF NOT EXISTS MinNewCapacity +CREATE TABLE IF NOT EXISTS TimePeriodType ( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) + label TEXT + PRIMARY KEY, + description TEXT ); - +REPLACE INTO TimePeriodType +VALUES('e', 'existing vintages'); +REPLACE INTO TimePeriodType +VALUES('f', 'future'); CREATE TABLE IF NOT EXISTS OutputEmission ( scenario TEXT, @@ -654,24 +993,29 @@ CREATE TABLE IF NOT EXISTS OutputEmission emission REAL, PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) ); - -CREATE TABLE IF NOT EXISTS EmissionLimit +CREATE TABLE IF NOT EXISTS RPSRequirement ( - region TEXT, - period INTEGER + region TEXT NOT NULL + REFERENCES Region (region), + period INTEGER NOT NULL REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) + tech_group TEXT NOT NULL + REFERENCES TechGroup (group_name), + requirement REAL NOT NULL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS TechGroupMember +( + group_name TEXT + REFERENCES TechGroup (group_name), + tech TEXT + REFERENCES Technology (tech), + PRIMARY KEY (group_name, tech) ); - CREATE TABLE IF NOT EXISTS Technology ( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, sector TEXT, category TEXT, sub_category TEXT, @@ -681,8 +1025,8 @@ CREATE TABLE IF NOT EXISTS Technology curtail INTEGER NOT NULL DEFAULT 0, retire INTEGER NOT NULL DEFAULT 0, flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, exchange INTEGER NOT NULL DEFAULT 0, + seas_stor INTEGER NOT NULL DEFAULT 0, description TEXT, FOREIGN KEY (flag) REFERENCES TechnologyType (label) ); @@ -690,9 +1034,10 @@ CREATE TABLE IF NOT EXISTS OutputCost ( scenario TEXT, region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, + sector TEXT REFERENCES SectorLabel (sector), + period INTEGER REFERENCES TimePeriod (period), + tech TEXT REFERENCES Technology (tech), + vintage INTEGER REFERENCES TimePeriod (period), d_invest REAL, d_fixed REAL, d_var REAL, diff --git a/temoa/db_schema/temoa_schema_v4.sql b/temoa/db_schema/temoa_schema_v4.sql new file mode 100644 index 000000000..77b146356 --- /dev/null +++ b/temoa/db_schema/temoa_schema_v4.sql @@ -0,0 +1,1074 @@ +PRAGMA foreign_keys = OFF; +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS metadata +( + element TEXT, + value INT, + notes TEXT, + PRIMARY KEY (element) +); +REPLACE INTO metadata +VALUES ('DB_MAJOR', 4, 'DB major version number'); +REPLACE INTO metadata +VALUES ('DB_MINOR', 0, 'DB minor version number'); + +CREATE TABLE IF NOT EXISTS metadata_real +( + element TEXT, + value REAL, + notes TEXT, + + PRIMARY KEY (element) +); +REPLACE INTO metadata_real +VALUES ('global_discount_rate', 0.05, 'Discount Rate for future costs'); +REPLACE INTO metadata_real +VALUES ('default_loan_rate', 0.05, 'Default Loan Rate if not specified in LoanRate table'); + +CREATE TABLE IF NOT EXISTS output_dual_variable +( + scenario TEXT, + constraint_name TEXT, + dual REAL, + PRIMARY KEY (constraint_name, scenario) +); +CREATE TABLE IF NOT EXISTS output_objective +( + scenario TEXT, + objective_name TEXT, + total_system_cost REAL +); +CREATE TABLE IF NOT EXISTS sector_label +( + sector TEXT PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS capacity_credit +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + credit REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage), + CHECK (credit >= 0 AND credit <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_factor_process +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_factor_tech +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS capacity_to_activity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + c2a REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS commodity +( + name TEXT + PRIMARY KEY, + flag TEXT + REFERENCES commodity_type (label), + description TEXT, + units TEXT +); +CREATE TABLE IF NOT EXISTS commodity_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO commodity_type +VALUES ('s', 'source commodity'); +REPLACE INTO commodity_type +VALUES ('a', 'annual commodity'); +REPLACE INTO commodity_type +VALUES ('p', 'physical commodity'); +REPLACE INTO commodity_type +VALUES ('d', 'demand commodity'); +REPLACE INTO commodity_type +VALUES ('e', 'emissions commodity'); +REPLACE INTO commodity_type +VALUES ('w', 'waste commodity'); +REPLACE INTO commodity_type +VALUES ('wa', 'waste annual commodity'); +REPLACE INTO commodity_type +VALUES ('wp', 'waste physical commodity'); +CREATE TABLE IF NOT EXISTS construction_input +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT NOT NULL + REFERENCES commodity (name), + cost REAL NOT NULL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE IF NOT EXISTS cost_fixed +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_invest +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS cost_variable +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS demand +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + commodity TEXT + REFERENCES commodity (name), + demand REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, commodity) +); +CREATE TABLE IF NOT EXISTS demand_specific_distribution +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + demand_name TEXT + REFERENCES commodity (name), + dsd REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, demand_name), + CHECK (dsd >= 0 AND dsd <= 1) +); +CREATE TABLE IF NOT EXISTS end_of_life_output +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS efficiency +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS efficiency_variable +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE IF NOT EXISTS emission_activity +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS emission_embodied +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS emission_end_of_life +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS existing_capacity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS tech_group +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS loan_lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS loan_rate +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE IF NOT EXISTS lifetime_tech +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS operator +( + operator TEXT PRIMARY KEY, + notes TEXT +); +REPLACE INTO operator VALUES('e','equal to'); +REPLACE INTO operator VALUES('le','less than or equal to'); +REPLACE INTO operator VALUES('ge','greater than or equal to'); +CREATE TABLE IF NOT EXISTS limit_growth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_growth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_growth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_degrowth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_storage_level_fraction +( + region TEXT, + season TEXT, + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + fraction REAL, + notes TEXT, + CHECK (fraction >= 0 AND fraction <= 1), + PRIMARY KEY(region, season, tod, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_activity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_activity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_annual_capacity_factor +( + region TEXT, + tech_or_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY (region, tech_or_group, vintage, output_comm, operator), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS limit_capacity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_capacity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_new_capacity +( + region TEXT, + tech_or_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, vintage, operator) +); +CREATE TABLE IF NOT EXISTS limit_new_capacity_share +( + region TEXT, + sub_group TEXT, + super_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, sub_group, super_group, vintage, operator) +); +CREATE TABLE IF NOT EXISTS limit_resource +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + cum_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_seasonal_capacity_factor +( + region TEXT + REFERENCES region (region), + season TEXT + REFERENCES time_season (season), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY(region, season, tech_or_group, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_input_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_input_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_output_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS limit_tech_output_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE IF NOT EXISTS limit_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm, operator) +); +CREATE TABLE IF NOT EXISTS linked_tech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES technology (tech), + emis_comm TEXT + REFERENCES commodity (name), + driven_tech TEXT + REFERENCES technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) +); +CREATE TABLE IF NOT EXISTS output_curtailment +( + scenario TEXT, + region TEXT, + sector TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + curtailment REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_net_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_built_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + PRIMARY KEY (region, scenario, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_retired_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cap_eol REAL, + cap_early REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS output_flow_in +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_flow_out +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE IF NOT EXISTS output_storage_level +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT, + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + level REAL, + units TEXT, + PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) +); +CREATE TABLE IF NOT EXISTS planning_reserve_margin +( + region TEXT + PRIMARY KEY + REFERENCES region (region), + margin REAL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS ramp_down_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS ramp_up_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS region +( + region TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE IF NOT EXISTS reserve_capacity_derate +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE IF NOT EXISTS storage_duration +( + region TEXT, + tech TEXT, + duration REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE IF NOT EXISTS lifetime_survival_curve +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + fraction REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE IF NOT EXISTS technology_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO technology_type +VALUES ('p', 'production technology'); +REPLACE INTO technology_type +VALUES ('pb', 'baseload production technology'); +REPLACE INTO technology_type +VALUES ('ps', 'storage production technology'); +-- CREATE TABLE IF NOT EXISTS time_manual +-- ( +-- season TEXT +-- REFERENCES time_season (season), +-- tod TEXT +-- REFERENCES time_of_day (tod), +-- season_next TEXT +-- REFERENCES time_season (season), +-- tod_next TEXT +-- REFERENCES time_of_day (tod), +-- notes TEXT, +-- PRIMARY KEY (season, tod) +-- ); +CREATE TABLE IF NOT EXISTS time_of_day +( + sequence INTEGER UNIQUE, + tod TEXT + PRIMARY KEY, + hours REAL NOT NULL DEFAULT 1, + notes TEXT, + CHECK (hours > 0) +); +CREATE TABLE IF NOT EXISTS time_period +( + sequence INTEGER UNIQUE, + period INTEGER + PRIMARY KEY, + flag TEXT + REFERENCES time_period_type (label) +); +CREATE TABLE IF NOT EXISTS time_period_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +REPLACE INTO time_period_type +VALUES('e', 'existing vintages'); +REPLACE INTO time_period_type +VALUES('f', 'future'); +CREATE TABLE IF NOT EXISTS output_emission +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + emission REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) +); +CREATE TABLE IF NOT EXISTS rps_requirement +( + region TEXT NOT NULL + REFERENCES region (region), + period INTEGER NOT NULL + REFERENCES time_period (period), + tech_group TEXT NOT NULL + REFERENCES tech_group (group_name), + requirement REAL NOT NULL, + notes TEXT +); +CREATE TABLE IF NOT EXISTS tech_group_member +( + group_name TEXT + REFERENCES tech_group (group_name), + tech TEXT + REFERENCES technology (tech), + PRIMARY KEY (group_name, tech) +); +CREATE TABLE IF NOT EXISTS technology +( + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, + sector TEXT, + category TEXT, + sub_category TEXT, + unlim_cap INTEGER NOT NULL DEFAULT 0, + annual INTEGER NOT NULL DEFAULT 0, + reserve INTEGER NOT NULL DEFAULT 0, + curtail INTEGER NOT NULL DEFAULT 0, + retire INTEGER NOT NULL DEFAULT 0, + flex INTEGER NOT NULL DEFAULT 0, + exchange INTEGER NOT NULL DEFAULT 0, + seas_stor INTEGER NOT NULL DEFAULT 0, + description TEXT, + FOREIGN KEY (flag) REFERENCES technology_type (label) +); +CREATE TABLE IF NOT EXISTS output_cost +( + scenario TEXT, + region TEXT, + sector TEXT REFERENCES sector_label (sector), + period INTEGER REFERENCES time_period (period), + tech TEXT REFERENCES technology (tech), + vintage INTEGER REFERENCES time_period (period), + d_invest REAL, + d_fixed REAL, + d_var REAL, + d_emiss REAL, + invest REAL, + fixed REAL, + var REAL, + emiss REAL, + units TEXT, + PRIMARY KEY (scenario, region, period, tech, vintage), + FOREIGN KEY (vintage) REFERENCES time_period (period), + FOREIGN KEY (tech) REFERENCES technology (tech) +); + +CREATE TABLE IF NOT EXISTS time_season +( + sequence INTEGER UNIQUE, + season TEXT, + segment_fraction REAL NOT NULL, + notes TEXT, + PRIMARY KEY (season), + CHECK (segment_fraction >= 0 AND segment_fraction <= 1) +); + +CREATE TABLE IF NOT EXISTS time_season_sequential +( + sequence INTEGER UNIQUE, + seas_seq TEXT, + season TEXT REFERENCES time_season(season), + segment_fraction REAL NOT NULL, + notes TEXT, + PRIMARY KEY (seas_seq), + CHECK (segment_fraction >= 0 AND segment_fraction <= 1) +); + +CREATE TABLE IF NOT EXISTS myopic_efficiency +( + base_year integer, + region text, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency real, + lifetime integer, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm) +); +-- for efficient searching by rtv: +CREATE INDEX IF NOT EXISTS region_tech_vintage ON myopic_efficiency (region, tech, vintage); + +CREATE TABLE IF NOT EXISTS output_flow_out_summary +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + PRIMARY KEY (scenario, region, period, input_comm, tech, vintage, output_comm) +); + +COMMIT; +PRAGMA foreign_keys = ON; diff --git a/temoa/extensions/README.txt b/temoa/extensions/README.txt deleted file mode 100644 index 20d60b8f6..000000000 --- a/temoa/extensions/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -Note that most of the "extensions" in this folder are legacy work that are currently not maintained. -Most need to be reworked and made compatible with the version 3 model and database structure. They are retained -for now as valuable work-product for future development. - -Working: -Myopic mode. This should be accessed via normal run and appropriate config file \ No newline at end of file diff --git a/temoa/extensions/breakeven/README.txt b/temoa/extensions/breakeven/README.txt deleted file mode 100644 index 339003393..000000000 --- a/temoa/extensions/breakeven/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -This extension is currently NOT maintained and is retained as a framework for possible future use. - -1 JUN 2024 \ No newline at end of file diff --git a/temoa/extensions/breakeven/breakeven.py b/temoa/extensions/breakeven/breakeven.py deleted file mode 100644 index 7f8459e75..000000000 --- a/temoa/extensions/breakeven/breakeven.py +++ /dev/null @@ -1,1114 +0,0 @@ -import os -import sys -from collections import defaultdict -from time import time - -import CplexSolverError -import cplex -import pandas as pd -from matplotlib import pyplot as plt -from openpyxl import Workbook -from pyomo.environ import * -from pyomo.opt import SolverFactory -from pyomo.repn import generate_canonical_repn -from temoa_model import temoa_create_model - - -def return_Temoa_model(): - model = temoa_create_model() - - model.dual = Suffix(direction=Suffix.IMPORT) - model.rc = Suffix(direction=Suffix.IMPORT) - model.slack = Suffix(direction=Suffix.IMPORT) - model.lrc = Suffix(direction=Suffix.IMPORT) - model.urc = Suffix(direction=Suffix.IMPORT) - return model - - -def return_Temoa_data(model, list_dat): - data = DataPortal(model=model) - for d in list_dat: - data.load(filename=d) - return data - - -def return_c_vector(block, unfixed): - # Note that this function is adapted function collect_linear_terms defined - # in pyomo/repn/collect.py. - - # - # Variables are constraints of block - # Constraints are unfixed variables of block and the parent model. - # - vnames = set() - for name, data in block.component_map(Constraint, active=True).items(): - vnames.add((name, data.is_indexed())) - cnames = set(unfixed) - for name, data in block.component_map(Var, active=True).items(): - cnames.add((name, data.is_indexed())) - # - A = {} - b_coef = {} - c_rhs = {} - c_sense = {} - d_sense = None - v_domain = {} - # - # Collect objective - # - for oname, odata in block.component_map(Objective, active=True).items(): - for ndx in odata: - if odata[ndx].sense == maximize: - o_terms = generate_canonical_repn(-1 * odata[ndx].expr, compute_values=False) - d_sense = minimize - else: - o_terms = generate_canonical_repn(odata[ndx].expr, compute_values=False) - d_sense = maximize - for i in range(len(o_terms.variables)): - c_rhs[ - o_terms.variables[i].parent_component().local_name, o_terms.variables[i].index() - ] = o_terms.linear[i] - # Stop after the first objective - break - return c_rhs - - -def coef_IC(instance, target_tech, target_year): - # This function returns coefficient associated with IC given t, v - t = target_tech - v = target_year - P_0 = min(instance.time_optimize) - P_e = instance.time_future.last() - GDR = value(instance.GlobalDiscountRate) - MPL = instance.ModelProcessLife - LLN = instance.LoanLifetimeProcess - x = 1 + GDR # convenience variable, nothing more. - period_available = set() - for p in instance.time_future: - if (p, t, v) in instance.CostFixed.keys(): - period_available.add(p) - c_i = ( - instance.CostInvest[t, v] - * instance.LoanAnnualize[t, v] - * (LLN[t, v] if not GDR else (x ** (P_0 - v + 1) * (1 - x ** (-value(LLN[t, v]))) / GDR)) - ) * ( - (1 - x ** (-min(value(instance.LifetimeProcess[t, v]), P_e - v))) - / (1 - x ** (-value(instance.LifetimeProcess[t, v]))) - ) - return value(c_i) - - -def coef_FC(instance, target_tech, target_year): - # This function returns coefficient associated with FC given t, v - t = target_tech - v = target_year - P_0 = min(instance.time_optimize) - P_e = instance.time_future.last() - GDR = value(instance.GlobalDiscountRate) - MPL = instance.ModelProcessLife - LLN = instance.LoanLifetimeProcess - x = 1 + GDR # convenience variable, nothing more. - period_available = set() - for p in instance.time_future: - if (p, t, v) in instance.CostFixed.keys(): - period_available.add(p) - - c_f = sum( - instance.CostFixed[p, t, v] - * ( - MPL[p, t, v] - if not GDR - else (x ** (P_0 - p + 1) * (1 - x ** (-value(MPL[p, t, v]))) / GDR) - ) - for p in period_available - ) - return value(c_f) - - -def validate_coef(c0, instance, target_tech, target_year): - # This function validates if c0 equals the correct coefficient of process - # (target_tech, target_year) - c_i = coef_IC(instance, target_tech, target_year) - c_f = coef_FC(instance, target_tech, target_year) - c = c_i + c_f - if value(c - c0) <= 1e-5: # Compatible with Pyomo 5.5 - return True - else: - return False - - -def sensitivity(dat, techs): - # This function performs break-even analysis for technologies specified in - # the argument techs. It uses suffix from Pyomo and returns the breakeven - # cost as screen outputs. Note that the Pyomo suffix sometimes returns - # anomalous values, and that's why I create another function, - # sensitivity_api() to use Python API for CPLEX. - - model = temoa_create_model() - - model.dual = Suffix(direction=Suffix.IMPORT) - model.rc = Suffix(direction=Suffix.IMPORT) - model.slack = Suffix(direction=Suffix.IMPORT) - model.lrc = Suffix(direction=Suffix.IMPORT) - model.urc = Suffix(direction=Suffix.IMPORT) - - data = DataPortal(model=model) - for d in dat: - data.load(filename=d) - instance = model.create_instance(data) - optimizer = SolverFactory('cplex') - optimizer.options['lpmethod'] = 1 # Use primal simplex - results = optimizer.solve(instance, suffixes=['dual', 'urc', 'slack', 'lrc']) - instance.solutions.load_from(results) - - coef_CAP = dict() - scal_CAP = dict() - # Break-even investment cost for this scenario, indexed by technology - years = list() - bic_s = dict() - ic_s = dict() # Raw investment costs for this scenario, indexed by tech - cap_s = dict() - for t in techs: - vintages = instance.vintage_optimize - P_0 = min(instance.time_optimize) - GDR = value(instance.GlobalDiscountRate) - MPL = instance.ModelProcessLife - LLN = instance.LoanLifetimeProcess - x = 1 + GDR # convenience variable, nothing more. - - bic_s[t] = list() - ic_s[t] = list() - cap_s[t] = list() - years = vintages.value - for v in vintages: - period_available = set() - for p in instance.time_future: - if (p, t, v) in instance.CostFixed.keys(): - period_available.add(p) - c_i = ( - instance.CostInvest[t, v] - * instance.LoanAnnualize[t, v] - * ( - LLN[t, v] - if not GDR - else (x ** (P_0 - v + 1) * (1 - x ** (-value(LLN[t, v]))) / GDR) - ) - ) - - c_s = (-1) * ( - value(instance.CostInvest[t, v]) - * value(instance.SalvageRate[t, v]) - / ( - 1 - if not GDR - else (1 + GDR) - ** (instance.time_future.last() - instance.time_future.first() - 1) - ) - ) - - c_f = sum( - instance.CostFixed[p, t, v] - * ( - MPL[p, t, v] - if not GDR - else (x ** (P_0 - p + 1) * (1 - x ** (-value(MPL[p, t, v]))) / GDR) - ) - for p in period_available - ) - - c = c_i + c_s + c_f - s = (c - instance.lrc[instance.V_Capacity[t, v]]) / c - coef_CAP[t, v] = c - scal_CAP[t, v] = s # Must reduce TO this percentage - bic_s[t].append(scal_CAP[t, v] * instance.CostInvest[t, v]) - ic_s[t].append(instance.CostInvest[t, v]) - cap_s[t].append(value(instance.V_Capacity[t, v])) - - # print("Tech\tVintage\tL. RC\tCoef\tU .RC\tScale\tBE IC\tBE FC\tIC\tFC\tCap") - print( - '{:>10s}\t{:>7s}\t{:>6s}\t{:>4s}\t{:>6s}\t{:>5s}\t{:>7s}\t{:>7s}\t{:>5s}\t{:>3s}\t{:>5s}'.format( - 'Tech', - 'Vintage', - 'L. RC', - 'Coef', - 'U. RC', - 'Scale', - 'BE IC', - 'BE FC', - 'IC', - 'FC', - 'Cap', - ) - ) - for v in vintages: - lrc = instance.lrc[instance.V_Capacity[t, v]] - urc = instance.urc[instance.V_Capacity[t, v]] - - # print("{:>s}\t{:>g}\t{:>.0f}\t{:>.0f}\t{:>.0f}\t{:>.3f}\t{:>.1f}\t{:>.1f}\t{:>.0f}\t{:>.0f}\t{:>.3f}".format() - print( - '{:>10s}\t{:>7g}\t{:>6.0f}\t{:>4.0f}\t{:>6.0f}\t{:>5.3f}\t{:>7.1f}\t{:>7.1f}\t{:>5.0f}\t{:>3.0f}\t{:>5.3f}'.format( - t, - v, - lrc, - coef_CAP[t, v], - urc, - scal_CAP[t, v], - scal_CAP[t, v] * instance.CostInvest[t, v], - scal_CAP[t, v] * instance.CostFixed[v, t, v], # Use the FC of the first period - instance.CostInvest[t, v], - instance.CostFixed[v, t, v], - value(instance.V_Capacity[t, v]), - ) - ) - - print('Dual and slack variables for emission caps:') - for e in instance.commodity_emissions: - for p in instance.time_optimize: - if (p, e) in instance.EmissionLimitConstraint: - print( - p, - e, - instance.dual[instance.EmissionLimitConstraint[p, e]], - '\t', - instance.slack[instance.EmissionLimitConstraint[p, e]], - ) - return years, bic_s, ic_s - - print('Dual and slack variables for Commodity Demand Constraints') - for c in instance.commodity_demand: - for p in instance.time_optimize: - for s in instance.time_season: - for tod in instance.time_of_day: - print( - p, - s, - tod, - instance.dual[instance.DemandConstraint[p, s, tod, c]], - instance.slack[instance.DemandConstraint[p, s, tod, c]], - ) - - -def sensitivity_api(instance, techs, algorithm=None): - # This code block realizes the same function as sensitivity(), however - # because I am using Python API for CPLEX here, it only works when the - # solver is CPLEX. I also updated the returned value and now it is a pandas - # DataFramework, which supports fast csv creation. - - instance.write('tmp.lp', io_options={'symbolic_solver_labels': True}) - c = cplex.Cplex('tmp.lp') - os.remove('tmp.lp') - c.set_results_stream(None) # Turn screen output off - - msg = '' - if algorithm: - if algorithm == 'o': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.auto) - elif algorithm == 'p': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.primal) - elif algorithm == 'd': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.dual) - elif algorithm == 'b': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.barrier) - c.parameters.barrier.crossover.set(c.parameters.barrier.crossover.values.none) - elif algorithm == 'h': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.barrier) - elif algorithm == 's': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.sifting) - elif algorithm == 'c': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.concurrent) - else: - raise ValueError('method must be one of "o", "p", "d", "b", "h", "s" or "c"') - - try: - c.solve() - except CplexSolverError: - print('Exception raised during solve') - return - - vintages = list(instance.vintage_optimize) - coef_CAP = dict() - scal_CAP = dict() - # Break-even investment cost for this scenario, indexed by technology - years = list() - bic_s = dict() - ic_s = dict() # Raw investment costs for this scenario, indexed by tech - cap_s = dict() - clb_s = dict() - cub_s = dict() - results = list() - for t in techs: - bic_s[t] = list() - ic_s[t] = list() - cap_s[t] = list() - for v in vintages: - target_var = 'V_Capacity(' + t + '_' + str(v) + ')' - c0 = c.objective.get_linear(target_var) - clb, cub = c.solution.sensitivity.objective( - target_var - ) # Coefficient lower bound, coefficient upper bound - if cub > 1e5: - cub = 0 # Infinity - clb_s[t, v], cub_s[t, v] = clb, cub - c_i = coef_IC(instance, t, v) - c_f = coef_FC(instance, t, v) - if not validate_coef(c0, instance, t, v): - print('Error: Check coefficients!') - sys.exit(0) - coef_CAP[t, v] = c0 - scal_CAP[t, v] = clb / c0 # Break-even cost 1: Scaling both IC and FC - alpha = c_i / value(instance.CostInvest[t, v]) - bic = (clb - c_f) / alpha # Break-even cost 2: Only decrease IC - bic_s[t].append(bic) - ic_s[t].append(value(instance.CostInvest[t, v])) - cap_s[t].append(c.solution.get_values(target_var)) - - print( - '{:>10s}\t{:>7s}\t{:>6s}\t{:>4s}\t{:>6s}\t{:>5s}\t{:>7s}\t{:>5s}\t{:>5s}'.format( - 'Tech', - 'Vintage', - 'L. CB', - 'Coef', - 'U. CB', - 'Scale', - 'BE IC', - 'IC', - 'Cap', - ) - ) - msg += '{:>10s}\t{:>7s}\t{:>6s}\t{:>4s}\t{:>6s}\t{:>5s}\t{:>7s}\t{:>5s}\t{:>5s}'.format( - 'Tech', - 'Vintage', - 'L. CB', - 'Coef', - 'U. CB', - 'Scale', - 'BE IC', - 'IC', - 'Cap', - ) - msg == '\n' - for v in vintages: - deployed = abs(cap_s[t][vintages.index(v)]) >= 1e-3 - tmp_beic_cs = ( - value(instance.CostInvest[t, v]) if deployed else bic_s[t][vintages.index(v)] - ) - tmp_bes_cs = 1 if deployed else scal_CAP[t, v] - row = { - 'algorithm': algorithm, - 'scenario': None, - 'technology': t, - 'vintage': v, - 'coef lower bound': clb_s[t, v], - 'coefficient': coef_CAP[t, v], - 'coef upper bound': cub_s[t, v], - 'scale': scal_CAP[t, v], - 'BE IC': bic_s[t][vintages.index(v)], - 'IC': value(instance.CostInvest[t, v]), - 'capacity': cap_s[t][vintages.index(v)], - 'BE IC (CS)': tmp_beic_cs, - 'scale (CS)': tmp_bes_cs, - } - results.append(row) - print( - '{:>10s}\t{:>7g}\t{:>6.0f}\t{:>4.0f}\t{:>6.0f}\t{:>5.3f}\t{:>7.1f}\t{:>5.0f}\t{:>5.3f}'.format( - t, - v, - clb_s[t, v], - coef_CAP[t, v], - cub_s[t, v], - scal_CAP[t, v], - bic_s[t][vintages.index(v)], - value(instance.CostInvest[t, v]), - cap_s[t][vintages.index(v)], - ) - ) - - msg += '{:>10s}\t{:>7g}\t{:>6.0f}\t{:>4.0f}\t{:>6.0f}\t{:>5.3f}\t{:>7.1f}\t{:>5.0f}\t{:>5.3f}'.format( - t, - v, - clb_s[t, v], - coef_CAP[t, v], - cub_s[t, v], - scal_CAP[t, v], - bic_s[t][vintages.index(v)], - value(instance.CostInvest[t, v]), - cap_s[t][vintages.index(v)], - ) - msg += '\n' - - return msg, pd.DataFrame(results) - - -def bin_search(tech, vintage, dat, eps=0.01, all_v=False): - # This code block performs breakeven analysis by brutal force in an - # iterative way. I did this because sometimes sensitivity() and - # sensitivity_api() return anomalous values. Note that this code block - # returns the absolutely correct breakeven costs, however, it is - # significantly more time-consuming, since it takes 8-9 instances to - # calculate the breakeven cost of just one technology of one vintage. - # Arguments are defined below: - # tech -> Target technology. - # vintage -> Target vintage. It is break-even when capacity in this year >= 0 - # dat -> A list of .dat files. - # eps -> Convergence tolerance - # all_v -> A flag used indicate the costs of which vintages are subject - # to change. If it is FALSE, then only the investment costs and fixed costs - # in the target vintage will be altered, otherwise all vintages are affected - # Note that, only the capacity of the target vintage will be monitored and - # be used to signal breakeven. - monitor_year = vintage - monitor_tech = tech - - t0 = time() - time_mark = lambda: time() - t0 - - model = return_Temoa_model() - optimizer = SolverFactory('cplex') - data = return_Temoa_data(model, dat) - instance = model.create_instance(data) - - time_optimize = [i for i in data['time_future']] - time_optimize.sort() - ic0 = dict() - fc0 = dict() - if all_v: - for v in time_optimize: - if (monitor_tech, v) in data['CostInvest']: - ic0[monitor_tech, v] = data['CostInvest'][monitor_tech, v] - for p in time_optimize: - if (p, monitor_tech, v) in data['CostFixed']: - fc0[p, monitor_tech, v] = data['CostFixed'][p, monitor_tech, v] - else: - ic0[monitor_tech, monitor_year] = data['CostInvest'][monitor_tech, monitor_year] - for p in time_optimize: - if (p, monitor_tech, monitor_year) in data['CostFixed']: - fc0[p, monitor_tech, monitor_year] = data['CostFixed'][ - p, monitor_tech, monitor_year - ] - - cap_target = 0 - scale_u = 1.0 - scale_l = 0.0 - - history = dict() - history['scale_u'] = [scale_u] - history['scale_l'] = [scale_l] - - counter = 0 - scale_this = scale_u # Starting scale - - print('Iteration # {} starts at {} s'.format(counter, time_mark())) - instance = model.create_instance(data) - instance.preprocess() - results = optimizer.solve(instance, suffixes=['dual', 'urc', 'slack', 'lrc']) - instance.solutions.load_from(results) - cap_target = value(instance.V_Capacity[monitor_tech, monitor_year]) - print('Iteration # {} solved at {} s'.format(counter, time_mark())) - print('Iteration # {}, scale: {:1.2f}, capacity: {} GW'.format(counter, scale_this, cap_target)) - if 1.0 - scale_this <= eps and cap_target > 0: - return scale_this - - while (scale_u - scale_l) >= eps and counter <= 20: - if cap_target <= 0: - scale_u = scale_this - history['scale_u'].append(scale_u) - else: - scale_l = scale_this - history['scale_l'].append(scale_l) - counter += 1 - - scale_this = (scale_u + scale_l) * 0.5 - for k in ic0: - data['CostInvest'][k] = scale_this * ic0[k] - for k in fc0: - data['CostFixed'][k] = scale_this * fc0[k] - - print('Iteration # {} starts at {} s'.format(counter, time_mark())) - instance = model.create_instance(data) - instance.preprocess() - results = optimizer.solve(instance, suffixes=['dual', 'urc', 'slack', 'lrc']) - instance.solutions.load_from(results) - cap_target = value(instance.V_Capacity[monitor_tech, monitor_year]) - print('Iteration # {} solved at {} s'.format(counter, time_mark())) - print( - 'Iteration # {}, scale: {:1.2f}, capacity: {} GW'.format( - counter, scale_this, cap_target - ) - ) - return (scale_u + scale_l) / 2.0 - - -def cplex_search(t, v, cplex_instance, eps=0.01, all_v=False): - # This code block performs breakeven analysis by brutal force in an - # iterative way. However, it differs from bin_search() in that this function - # saves instance creation time by modifying the LP model coefficients in the - # CPLEX instance directly. By contrast, bin_search() has to create a new lp - # file each iteration after model coefficients are updated, which is - # time-consuming since Pyomo is notorious slow in model instantiation and - # creation. Other than that, these two functions are the same. - # Arguments are defined below: - # tech -> Target technology. - # vintage -> Target vintage. It is break-even when capacity in this year >= 0 - # dat -> A list of .dat files. - # eps -> Convergence tolerance - # all_v -> A flag used indicate the costs of which vintages are subject - # to change. If it is FALSE, then only the investment costs and fixed costs - # in the target vintage will be altered, otherwise all vintages are affected - # Note that, only the capacity of the target vintage will be monitored and - # be used to signal breakeven. - - def return_row(c0, scale, capacity): - row = { - 'algorithm': cplex_instance.parameters.lpmethod, - 'scenario': None, - 'technology': t, - 'vintage': v, - 'coef lower bound': 'N/A', - 'coefficient': c0, - 'coef upper bound': 'N/A', - 'scale': scale, - 'BE IC': None, - 'BE FC': None, - 'IC': None, - 'FC': None, - 'capacity': capacity, - 'BE IC (CS)': 'N/A', - 'BE FC (CS)': 'N/A', - 'scale (CS)': 'N/A', - } - return row - - msg = '' - target_year = v - target_tech = t - target_var0 = 'V_Capacity(' + target_tech + '_' + str(target_year) + ')' - - t0 = time() - time_mark = lambda: time() - t0 - - c0 = cplex_instance.objective.get_linear(target_var0) # Original coefficient - cplex_instance.set_results_stream(None) - - scale_u = 1.0 - scale_l = 0.0 - - history = dict() - history['scale_u'] = [scale_u] - history['scale_l'] = [scale_l] - - counter = 0 - scale_this = scale_u # Starting scale - - print('Iteration # {} starts at {} s'.format(counter, time_mark())) - msg += 'Iteration # {} starts at {} s\n'.format(counter, time_mark()) - try: - cplex_instance.solve() - except CplexSolverError: - print('Exception raised during solve') - msg += 'Exception raised during solve\n' - return msg, None - cap_target0 = cplex_instance.solution.get_values(target_var0) - print('Iteration # {} solved at {} s'.format(counter, time_mark())) - msg += 'Iteration # {} solved at {} s\n'.format(counter, time_mark()) - print( - 'Iteration # {}, scale: {:1.2f}, capacity: {} GW'.format(counter, scale_this, cap_target0) - ) - if 1.0 - scale_this <= eps and cap_target0 > 0: - row = return_row(c0, scale_this, cap_target0) - return msg, pd.DataFrame([row]) - - cap_target = cap_target0 - while (scale_u - scale_l) >= eps and counter <= 20: - if cap_target <= 0: - scale_u = scale_this - history['scale_u'].append(scale_u) - else: - scale_l = scale_this - history['scale_l'].append(scale_l) - counter += 1 - - scale_this = (scale_u + scale_l) * 0.5 - cplex_instance.objective.set_linear(target_var0, scale_this * c0) - - print('Iteration # {} starts at {} s'.format(counter, time_mark())) - msg += 'Iteration # {} starts at {} s'.format(counter, time_mark()) - try: - cplex_instance.solve() - except CplexSolverError: - print('Exception raised during solve') - msg += 'Exception raised during solve\n' - return msg, None - cap_target = cplex_instance.solution.get_values(target_var0) - print('Iteration # {} solved at {} s'.format(counter, time_mark())) - msg += 'Iteration # {} solved at {} s\n'.format(counter, time_mark()) - print( - 'Iteration # {}, scale: {:1.2f}, capacity: {} GW'.format( - counter, scale_this, cap_target - ) - ) - row = return_row(c0, scale_this, cap_target0) - return msg, pd.DataFrame([row]) - - -def sen_range_api(tech, vintage, scales, list_dat): - # This function is adapted from CPLEX's example script lpex2.py - # It does the same thing as sen_range, but with CPLEX API for Python - - # Given a range of scaling factor for coefficient of a specific V_Capacity, - # returns objective value, reduced cost, capacity etc. for each scaling - # factor - - target_year = vintage - target_tech = tech - target_var0 = 'V_Capacity(' + target_tech + '_' + str(target_year) + ')' - algmap = { - 'primal simplex': 'p', - 'dual simplex': 'd', - 'barrier': 'h', # This is cross-over mode, since pure interior causes problems - 'default': 'o', - } # cplex definition - - t0 = time() - time_mark = lambda: time() - t0 - - model = return_Temoa_model() - data = return_Temoa_data(model, list_dat) - instance = model.create_instance(data) - - ic0 = data['CostInvest'][target_tech, target_year] - fc0 = data['CostFixed'][target_year, target_tech, target_year] - all_periods = data['time_future'] - - obj = dict() - cap = dict() - coef = dict() - bic = dict() - bfc = dict() - ic = dict() # Original IC - fc = dict() # Original FC - clb = dict() # Lower bound of objective coefficient - cub = dict() # Upper bound of objective coefficient - rc = dict() # Reduced cost - - for algorithm in ['barrier', 'dual simplex', 'primal simplex']: - print('Algorithm: {}'.format(algorithm)) - instance.write('tmp.lp', io_options={'symbolic_solver_labels': True}) - c = cplex.Cplex('tmp.lp') - os.remove('tmp.lp') - c.set_results_stream(None) # Turn screen output off - c0 = c.objective.get_linear(target_var0) - if not validate_coef(c0, instance, target_tech, target_year): - print('Error!') - sys.exit(0) - print('[{:>9.2f}] CPLEX model loaded.'.format(time_mark())) - - if algmap[algorithm] == 'o': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.auto) - elif algmap[algorithm] == 'p': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.primal) - elif algmap[algorithm] == 'd': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.dual) - elif algmap[algorithm] == 'b': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.barrier) - c.parameters.barrier.crossover.set(c.parameters.barrier.crossover.values.none) - elif algmap[algorithm] == 'h': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.barrier) - elif algmap[algorithm] == 's': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.sifting) - elif algmap[algorithm] == 'c': - c.parameters.lpmethod.set(c.parameters.lpmethod.values.concurrent) - else: - raise ValueError('method must be one of "o", "p", "d", "b", "h", "s" or "c"') - - obj_alg = list() - cap_alg = defaultdict(list) - coef_alg = defaultdict(list) - bic_alg = defaultdict(list) - bfc_alg = defaultdict(list) - ic_alg = defaultdict(list) - fc_alg = defaultdict(list) - clb_alg = defaultdict(list) - cub_alg = defaultdict(list) - rc_alg = defaultdict(list) - for s in scales: - print('[{:>9.2f}] Scale: {:>.3f} starts'.format(time_mark(), s)) - c.objective.set_linear(target_var0, s * c0) - - try: - c.solve() - except CplexSolverError: - print('Exception raised during solve') - return - - obj_alg.append(c.solution.get_objective_value()) - for y in instance.time_optimize: - key = str(y) - target_var = 'V_Capacity(' + target_tech + '_' + key + ')' - coefficient = c.objective.get_linear(target_var) - if y != target_year: - if not validate_coef(coefficient, instance, target_tech, y): - print('Error!') - sys.exit(0) - capacity = c.solution.get_values(target_var) - try: - # Out of some unknow reason, sometimes this function will - # fail even though the model is totally feasible. - # Notes: This function fails when cross-over is not selected - # when barrier algorithm is selected - c_bound = c.solution.sensitivity.objective(target_var) - s_be = c_bound[0] / coefficient # Break-even scale - except: - c_bound = [None, None] - s_be = None - cost_i = s * value(instance.CostInvest[target_tech, y]) - cost_f = s * value(instance.CostFixed[y, target_tech, y]) - - cap_alg[key].append(capacity) - coef_alg[key].append(coefficient) - ic_alg[key].append(cost_i) - fc_alg[key].append(cost_f) - if s_be: - bic_alg[key].append(s_be * cost_i) - bfc_alg[key].append(s_be * cost_f) - else: - bic_alg[key].append(None) - bfc_alg[key].append(None) - clb_alg[key].append(c_bound[0]) - cub_alg[key].append(c_bound[1]) - rc_alg[key].append(c.solution.get_reduced_costs(target_var)) - - obj[algorithm] = obj_alg - cap[algorithm] = cap_alg - coef[algorithm] = coef_alg - bic[algorithm] = bic_alg - bfc[algorithm] = bfc_alg - ic[algorithm] = ic_alg - fc[algorithm] = fc_alg - clb[algorithm] = clb_alg - cub[algorithm] = cub_alg - rc[algorithm] = rc_alg - - # Write to Excel spreadsheet - print('[{:>9.2f}] Saving to Excel spreadsheet'.format(time_mark())) - row_title = [ - 'scale', - 'obj', - 'cap', - 'clb', - 'coef', - 'cub', - 'bic (clb)', - 'bfc (clb)', - 'ic', - 'fc', - 'rc', - ] - wb = Workbook() - # for ws_title in cap_alg: - for year in all_periods: - ws_title = str(year) - if ws_title not in cap_alg: - continue - ws = wb.create_sheet(ws_title) - - row = [ - scales, - obj_alg, - cap_alg[ws_title], - clb_alg[ws_title], - coef_alg[ws_title], - cub_alg[ws_title], - bic_alg[ws_title], - bfc_alg[ws_title], - ic_alg[ws_title], - fc_alg[ws_title], - rc_alg[ws_title], - ] - - # Note Python starts from 0, but row number starts from 1 - for j in range(0, len(row_title)): - cell = ws.cell(row=1, column=j + 1) - cell.value = row_title[j] - for i in range(0, len(scales)): - for j in range(0, len(row_title)): - cell = ws.cell(row=i + 2, column=j + 1) - cell.value = row[j][i] - fname = '.'.join( - [target_tech, str(target_year)] - + [i[:-4] for i in list_dat] # Remove the .dat extension - + [algorithm] - ) # tech_name.year.dat_file_name.algorithm.xlsx - wb.save(fname + '.xlsx') - - -def sen_range(tech, vintage, scales, dat): - # Given a range of scaling factor for coefficient of a specific V_Capacity, - # returns objective value, reduced cost, capacity etc. for each scaling - # factor - from openpyxl import Workbook - - target_year = vintage - target_tech = tech - algmap = { - 'primal simplex': 1, - 'dual simplex': 2, - 'barrier': 4, - 'default': 0, - } # cplex definition - - t0 = time() - time_mark = lambda: time() - t0 - - model = return_Temoa_model() - data = return_Temoa_data(model, dat) - optimizer = SolverFactory('cplex') - - ic0 = data['CostInvest'][target_tech, target_year] - fc0 = data['CostFixed'][target_year, target_tech, target_year] - all_periods = data['time_future'] - - obj = dict() - cap = dict() - lrc = dict() - coef = dict() - urc = dict() - bic = dict() - bfc = dict() - ic = dict() # Original IC - fc = dict() # Original FC - - for algorithm in ['barrier', 'dual simplex', 'primal simplex']: - optimizer.options['lpmethod'] = algmap[algorithm] - print('Algorithm: {}'.format(algorithm)) - - obj_alg = list() - cap_alg = defaultdict(list) - lrc_alg = defaultdict(list) - coef_alg = defaultdict(list) - urc_alg = defaultdict(list) - bic_alg = defaultdict(list) - bfc_alg = defaultdict(list) - ic_alg = defaultdict(list) - fc_alg = defaultdict(list) - for s in scales: - print('[{:>9.2f}] Scale: {:>.3f} starts'.format(time_mark(), s)) - data['CostInvest'][target_tech, target_year] = s * ic0 - for y in data['time_future']: - if (y, target_tech, target_year) in data['CostFixed']: - data['CostFixed'][y, target_tech, target_year] = s * fc0 - instance = model.create_instance(data) - instance.preprocess() - results = optimizer.solve(instance, suffixes=['dual', 'urc', 'slack', 'lrc']) - instance.solutions.load_from(results) - - obj_alg.append(value(instance.TotalCost)) - for y in instance.time_optimize: - key = str(y) - c_vector = return_c_vector(instance, []) - coefficient = c_vector[('V_Capacity', (target_tech, y))] - capacity = value(instance.V_Capacity[target_tech, y]) - lower_rc = value(instance.lrc[instance.V_Capacity[target_tech, y]]) - upper_rc = value(instance.urc[instance.V_Capacity[target_tech, y]]) - cost_i = value(instance.CostInvest[target_tech, y]) - cost_f = value(instance.CostFixed[y, target_tech, y]) - s_be = (coefficient - lower_rc) / coefficient # Break-even scale - - cap_alg[key].append(capacity) - lrc_alg[key].append(lower_rc) - coef_alg[key].append(coefficient) - urc_alg[key].append(upper_rc) - ic_alg[key].append(cost_i) - fc_alg[key].append(cost_f) - bic_alg[key].append(s_be * cost_i) - bfc_alg[key].append(s_be * cost_f) - - obj[algorithm] = obj_alg - cap[algorithm] = cap_alg - lrc[algorithm] = lrc_alg - coef[algorithm] = coef_alg - urc[algorithm] = urc_alg - bic[algorithm] = bic_alg - bfc[algorithm] = bfc_alg - ic[algorithm] = ic_alg - fc[algorithm] = fc_alg - - # Write to Excel spreadsheet - print('[{:>9.2f}] Saving to Excel spreadsheet'.format(time_mark())) - row_title = ['scale', 'obj', 'cap', 'lrc', 'coef', 'urc', 'bic', 'bfc', 'ic', 'fc'] - wb = Workbook() - # for ws_title in cap_alg: - for year in all_periods: - ws_title = str(year) - if ws_title not in cap_alg: - continue - ws = wb.create_sheet(ws_title) - - row = [ - scales, - obj_alg, - cap_alg[ws_title], - lrc_alg[ws_title], - coef_alg[ws_title], - urc_alg[ws_title], - bic_alg[ws_title], - bfc_alg[ws_title], - ic_alg[ws_title], - fc_alg[ws_title], - ] - - # Note Python starts from 0, but row number starts from 1 - for j in range(0, len(row_title)): - c = ws.cell(row=1, column=j + 1) - c.value = row_title[j] - for i in range(0, len(scales)): - for j in range(0, len(row_title)): - c = ws.cell(row=i + 2, column=j + 1) - c.value = row[j][i] - fname = '.'.join( - [target_tech, str(target_year)] - + [i[:-4] for i in dat] # Remove the .dat extension - + [algorithm] - ) # tech_name.year.dat_file_name.algorithm.xlsx - wb.save(fname + '.xlsx') - - -def explore_Cost_marginal(dat): - model = temoa_create_model() - - model.dual = Suffix(direction=Suffix.IMPORT) - model.rc = Suffix(direction=Suffix.IMPORT) - model.slack = Suffix(direction=Suffix.IMPORT) - model.lrc = Suffix(direction=Suffix.IMPORT) - model.urc = Suffix(direction=Suffix.IMPORT) - - data = DataPortal(model=model) - for d in dat: - data.load(filename=d) - instance = model.create_instance(data) - - # Deactivate the DemandActivity constraint - # instance.DemandActivityConstraint.deactivate() - # instance.preprocess() - - optimizer = SolverFactory('cplex') - results = optimizer.solve(instance, keepfiles=True, suffixes=['dual', 'urc', 'slack', 'lrc']) - instance.solutions.load_from(results) - - print('Dual and slack variables for emission caps:') - for e in instance.commodity_emissions: - for p in instance.time_optimize: - if (p, e) in instance.EmissionLimitConstraint: - print( - p, - e, - instance.dual[instance.EmissionLimitConstraint[p, e]], - '\t', - instance.slack[instance.EmissionLimitConstraint[p, e]], - ) - - # print('Dual and slack variables for Commodity Demand Constraints') - # for c in instance.commodity_demand: - # for p in instance.time_optimize: - # for s in instance.time_season: - # for tod in instance.time_of_day: - # print(p, s, tod, instance.dual[instance.DemandConstraint[p,s,tod,c]], instance.slack[instance.DemandConstraint[p,s,tod,c]]) - - -def plot_breakeven(years, bic, ic): - # bic is a dictionary, ic is a list of the raw investment costs - # ic = [x, x, ..., x], the length of which equals to the length of years - # bic[scenario] = [x, x, x... x] where the length equals to the number - # of optimized periods. - sen_color_map = { - 'IC': [0.9, 0.9, 0.9], - 'LF': 'black', - 'R': 'black', - 'HF': 'black', - 'HD': 'black', - 'CPPLF': 'green', - 'CPP': 'green', - 'CPPHF': 'green', - 'CPPHD': 'green', - } - - sen_lstyle_map = { - 'IC': None, - 'LF': '--', - 'R': '-', - 'HF': '-.', - 'HD': ':', - 'CPPLF': '--', - 'CPP': '-', - 'CPPHF': '-.', - 'CPPHD': ':', - } - - sen_marker_map = { - 'IC': None, - 'LF': 's', - 'R': 's', - 'HF': 's', - 'HD': 's', - 'CPPLF': 's', - 'CPP': 's', - 'CPPHF': 's', - 'CPPHD': 's', - } - - scenarios = bic.keys() - h = plt.figure() - ax = plt.subplot(111) - ax.fill_between(years, 0, ic, facecolor=sen_color_map['IC']) - - for s in scenarios: - ax.plot( - years, - bic[s], - color=sen_color_map[s], - # marker = sen_marker_map[s], - linestyle=sen_lstyle_map[s], - ) - ax.yaxis.grid(True) - plt.ylabel('$/MWh') - plt.xlim((years[0] - 5, years[-1] + 5)) - return ax - - -def bin_search_and_range(): - def return_range(bs): - # Given break-even scaling factor, return an appropriate range - ub = None - lb = None - if bs >= 0.85: - lb = int(bs * 100) - 15 - ub = 100 - elif bs <= 0.15: - lb = 1 - ub = int(bs * 100) + 15 - else: - lb = int(bs * 100) - 15 - ub = int(bs * 100) + 15 - return [0.001 * i for i in range(lb * 10, ub * 10, 10)] - - list_file = ['reference.dat', 'NCupdated_noLeadTime.dat'] - list_tech = ['EBIOIGCC', 'EURNALWR15'] - monitor_vintage = 2020 - eps = 0.01 - for f in list_file: - for t in list_tech: - bs = bin_search(t, monitor_vintage, [f], eps) - sen_range(t, monitor_vintage, return_range(bs), [f]) - - -if __name__ == '__main__': - # sen_bin_search( - # 'ECOALIGCCS', - # 2020, - # ['reference.dat'], - # 0.01 - # ) - scales = [0.001 * i for i in range(250, 260, 10)] - sen_range('ECOALIGCCS', 2020, scales, ['reference.dat']) - # do_sensitivity_new() - # do_sensitivity_old() - # explore_Cost_marginal(['/afs/unity.ncsu.edu/users/b/bli6/TEMOA_NC/sql20170417/results/R/NCreference.R.dat']) diff --git a/temoa/extensions/get_comm_tech.py b/temoa/extensions/get_comm_tech.py index c4a36f8df..8c7167c28 100644 --- a/temoa/extensions/get_comm_tech.py +++ b/temoa/extensions/get_comm_tech.py @@ -1,34 +1,39 @@ +from __future__ import annotations + import getopt import os import re import sqlite3 import sys from collections import OrderedDict +from typing import Any -def get_tperiods(inp_f): +def get_tperiods(inp_f: str) -> dict[str, list[int]]: file_ty = re.search(r'(\w+)\.(\w+)\b', inp_f) # Extract the input filename and extension if not file_ty: - raise 'The file type %s is not recognized.' % inp_f + raise Exception(f'The file type {inp_f} is not recognized.') elif file_ty.group(2) not in ('db', 'sqlite', 'sqlite3', 'sqlitedb'): raise Exception('Please specify a database for finding scenarios') - periods_list = {} - periods_set = set() + periods_list: dict[str, list[int]] = {} con = sqlite3.connect(inp_f) - cur = con.cursor() # a database cursor is a control structure that enables traversal over the records in a database + cur = con.cursor() # a database cursor is a control structure that enables traversal over + # the records in a database con.text_factory = str # this ensures data is explored with the correct UTF-8 encoding print(inp_f) - cur.execute('SELECT DISTINCT scenario FROM OutputFlowOut') + cur.execute('SELECT DISTINCT scenario FROM output_flow_out') x = [] for row in cur: x.append(row[0]) for y in x: - cur.execute("SELECT DISTINCT period FROM OutputFlowOut WHERE scenario is '" + str(y) + "'") + cur.execute( + 'SELECT DISTINCT period FROM output_flow_out WHERE scenario = ?', (y,) + ) periods_list[y] = [] for per in cur: z = per[0] @@ -39,24 +44,24 @@ def get_tperiods(inp_f): return dict(OrderedDict(sorted(periods_list.items(), key=lambda x: x[1]))) -def get_scenario(inp_f): +def get_scenario(inp_f: str) -> dict[str, str]: file_ty = re.search(r'(\w+)\.(\w+)\b', inp_f) # Extract the input filename and extension if not file_ty: - raise 'The file type %s is not recognized.' % inp_f + raise Exception(f'The file type {inp_f} is not recognized.') elif file_ty.group(2) not in ('db', 'sqlite', 'sqlite3', 'sqlitedb'): raise Exception('Please specify a database for finding scenarios') - scene_list = {} - scene_set = set() + scene_list: dict[str, str] = {} con = sqlite3.connect(inp_f) - cur = con.cursor() # a database cursor is a control structure that enables traversal over the records in a database + cur = con.cursor() # a database cursor is a control structure that enables traversal over + # the records in a database con.text_factory = str # this ensures data is explored with the correct UTF-8 encoding print(inp_f) - cur.execute('SELECT DISTINCT scenario FROM OutputFlowOut') + cur.execute('SELECT DISTINCT scenario FROM output_flow_out') for row in cur: x = row[0] scene_list[x] = x @@ -66,14 +71,15 @@ def get_scenario(inp_f): return dict(OrderedDict(sorted(scene_list.items(), key=lambda x: x[1]))) -def get_comm(inp_f, db_dat): - comm_list = {} - comm_set = set() +def get_comm(inp_f: str, db_dat: bool) -> OrderedDict[str, str]: + comm_list: dict[str, str] = {} + comm_set: set[str] = set() is_query_empty = False if not db_dat: con = sqlite3.connect(inp_f) - cur = con.cursor() # a database cursor is a control structure that enables traversal over the records in a database + cur = con.cursor() # a database cursor is a control structure that enables traversal over + # the records in a database con.text_factory = str # this ensures data is explored with the correct UTF-8 encoding print(inp_f) @@ -87,7 +93,8 @@ def get_comm(inp_f, db_dat): if not is_query_empty: cur.execute( - 'SELECT input_comm FROM OutputFlowOut UNION SELECT output_comm FROM OutputFlowOut' + 'SELECT input_comm FROM output_flow_out UNION SELECT output_comm FROM ' + 'output_flow_out' ) for row in cur: @@ -103,25 +110,26 @@ def get_comm(inp_f, db_dat): with open(inp_f) as f: for line in f: if eff_flag is False and re.search( - '^\s*param\s+efficiency\s*[:][=]', line, flags=re.I + r'^\s*param\s+efficiency\s*[:][=]', line, flags=re.I ): - # Search for the line param Efficiency := (The script recognizes the commodities specified in this section) + # Search for the line param efficiency := (The script recognizes the + # commodities specified in this section) eff_flag = True elif eff_flag: line = re.sub('[#].*$', ' ', line) - if re.search('^\s*;\s*$', line): + if re.search(r'^\s*;\s*$', line): break # Finish searching this section when encounter a ';' - if re.search('^\s+$', line): + if re.search(r'^\s+$', line): continue - line = re.sub('^\s+|\s+$', '', line) - row = re.split('\s+', line) + line = re.sub(r'^\s+|\s+$', '', line) + row = re.split(r'\s+', line) if row[0] != 'ethos': comm_set.add(row[0]) comm_set.add(row[3]) if eff_flag is False: print( - 'Error: The Efficiency Parameters cannot be found in the specified file - ' + inp_f + 'Error: The efficiency Parameters cannot be found in the specified file - ' + inp_f ) sys.exit(2) @@ -131,14 +139,15 @@ def get_comm(inp_f, db_dat): return OrderedDict(sorted(comm_list.items(), key=lambda x: x[1])) -def get_tech(inp_f, db_dat): - tech_list = {} - tech_set = set() +def get_tech(inp_f: str, db_dat: bool) -> OrderedDict[str, str]: + tech_list: dict[str, str] = {} + tech_set: set[str] = set() is_query_empty = False if not db_dat: con = sqlite3.connect(inp_f) - cur = con.cursor() # a database cursor is a control structure that enables traversal over the records in a database + cur = con.cursor() # a database cursor is a control structure that enables traversal over + # the records in a database con.text_factory = str # this ensures data is explored with the correct UTF-8 encoding print(inp_f) @@ -150,7 +159,7 @@ def get_tech(inp_f, db_dat): tech_list[x] = x if not is_query_empty: - cur.execute('SELECT DISTINCT tech FROM OutputFlowOut') + cur.execute('SELECT DISTINCT tech FROM output_flow_out') for row in cur: x = row[0] @@ -164,23 +173,24 @@ def get_tech(inp_f, db_dat): with open(inp_f) as f: for line in f: if eff_flag is False and re.search( - '^\s*param\s+efficiency\s*[:][=]', line, flags=re.I + r'^\s*param\s+efficiency\s*[:][=]', line, flags=re.I ): - # Search for the line param Efficiency := (The script recognizes the commodities specified in this section) + # Search for the line param efficiency := (The script recognizes the + # commodities specified in this section) eff_flag = True elif eff_flag: line = re.sub('[#].*$', ' ', line) - if re.search('^\s*;\s*$', line): + if re.search(r'^\s*;\s*$', line): break # Finish searching this section when encounter a ';' - if re.search('^\s+$', line): + if re.search(r'^\s+$', line): continue - line = re.sub('^\s+|\s+$', '', line) - row = re.split('\s+', line) + line = re.sub(r'^\s+|\s+$', '', line) + row = re.split(r'\s+', line) tech_set.add(row[1]) if eff_flag is False: print( - 'Error: The Efficiency Parameters cannot be found in the specified file - ' + inp_f + 'Error: The efficiency Parameters cannot be found in the specified file - ' + inp_f ) sys.exit(2) @@ -190,13 +200,13 @@ def get_tech(inp_f, db_dat): return OrderedDict(sorted(tech_list.items(), key=lambda x: x[1])) -def is_db_overwritten(db_file, inp_dat_file): +def is_db_overwritten(db_file: str, inp_dat_file: str) -> bool: if os.path.basename(db_file) == '0': return False try: con = sqlite3.connect(db_file) - except: + except sqlite3.Error: return False cur = con.cursor() # A database cursor enables traversal over DB records con.text_factory = str # This ensures data is explored with UTF-8 encoding @@ -205,14 +215,15 @@ def is_db_overwritten(db_file, inp_dat_file): # IF output file is empty database. cur.execute('SELECT * FROM Technology') is_db_empty = False # False for empty db file - for elem in cur: + for _ in cur: is_db_empty = True # True for non-empty db file break - # This file could be schema with populated results from previous run. Or it could be a normal db file. + # This file could be schema with populated results from previous run. Or it could be a normal + # db file. if is_db_empty: cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='input_file';") does_input_file_table_exist = False - for i in cur: # This means that the 'input_file' table exists in db. + for _ in cur: # This means that the 'input_file' table exists in db. does_input_file_table_exist = True if does_input_file_table_exist: # This block distinguishes normal database from schema. # This is schema file. @@ -237,7 +248,7 @@ def is_db_overwritten(db_file, inp_dat_file): return False -def help_user(): +def help_user() -> None: print( """Use as: python get_comm_tech.py -i (or --input) @@ -249,8 +260,8 @@ def help_user(): ) -def get_info(inputs): - inp_file = None +def get_info(inputs: dict[str, str]) -> Any: + inp_file: str | None = None tech_flag = False comm_flag = False scene = False @@ -261,7 +272,7 @@ def get_info(inputs): raise Exception('no arguments found') for opt, arg in inputs.items(): - print('%s == %s' % (opt, arg)) + print(f'{opt} == {arg}') if opt in ('-i', '--input'): inp_file = arg @@ -297,7 +308,7 @@ def get_info(inputs): file_ty = re.search(r'(\w+)\.(\w+)\b', inp_file) # Extract the input filename and extension if not file_ty: - raise Exception('The file type {} is not recognized.'.format(file_ty)) + raise Exception(f'The file type {file_ty} is not recognized.') elif file_ty.group(2) in ('db', 'sqlite', 'sqlite3', 'sqlitedb'): db_or_dat = False @@ -307,8 +318,8 @@ def get_info(inputs): else: print( - 'The input file type %s is not recognized. Please specify a database or a text file.' - % inp_file + f'The input file type {inp_file} is not recognized. Please specify a database ' + 'or a text file.' ) sys.exit(2) diff --git a/temoa/extensions/method_of_morris/MM_README.md b/temoa/extensions/method_of_morris/MM_README.md index 2af27950e..f92a60101 100644 --- a/temoa/extensions/method_of_morris/MM_README.md +++ b/temoa/extensions/method_of_morris/MM_README.md @@ -1,56 +1,65 @@ # Method of Morris -### This document gives a _brief_ description of MM application and shows how to execute the provided example. +### This document gives a _brief_ description of MM application and shows how to execute the provided example ## Background -- MM is a Sensitivity Analysis (SA) technique that uses a unique Design Of Experiments (DOE) application to + +- MM is a Sensitivity Analysis (SA) technique that uses a unique Design Of Experiments (DOE) application to explore the input space. -- The MM techniques in Temoa utilize `SALib` which has fairly -thorough [documentation](https://salib.readthedocs.io/en/latest/api.html#method-of-morris) online, +- The MM techniques in Temoa utilize `SALib` which has fairly +thorough [documentation](https://salib.readthedocs.io/en/latest/api.html#method-of-morris) online, which also refers to relevant studies. ## Use + - The current MM setup in Temoa is capable of exploring input parameters from 3 tables: - - CostVariable - - CostInvest - - Efficiency -- The MM application will analyze effects of marked parameters (or groups) on the cost function + - cost_variable + - cost_invest + - efficiency +- The MM application will analyze effects of marked parameters (or groups) on the cost function (objective) and on co2 emissions specifically. - - It should be noted that co2 is _not_ optimized in any way, it is just the co2 emission + - It should be noted that co2 is _not_ optimized in any way, it is just the co2 emission for the optimal cost solution - - Further, the code is currently hard-coded to look for _exactly_ the string `co2` in the - `OutputEmissions` table. It is highly advisable to do a "regular" run on the data to ensure + - Further, the code is currently hard-coded to look for _exactly_ the string `co2` in the + `output_emissions` table. It is highly advisable to do a "regular" run on the data to ensure that `co2` is properly represented in the commodity and output tables. -- The basic sequence is: marking of input parameters (by user) and domains (through the `perturbation` +- The basic sequence is: marking of input parameters (by user) and domains (through the `perturbation` value in the `config`) -> make a MM Sample set -> run Temoa on the samples -> conduct MM analysis. -- In order to use MM in Temoa, the subject database needs to be augmented slightly to allow +- In order to use MM in Temoa, the subject database needs to be augmented slightly to allow relevant parameters to be marked for analysis. -- After Parameters are marked with group labels, the `morris` options should be set in the +- After Parameters are marked with group labels, the `morris` options should be set in the relevant `config.toml` file which can then be run. - Outputs are stored in the standard time-stamped output folder. ### Example: `morris_utopia` -1. Convert the `.sql` file in the `example_dbs` folder back to a database: -`data_files/example_dbs % sqlite3 morris_utopia.sqlite < morris_utopia.sql` +The files for this example must reside in the same directory as the extension code (`temoa/extensions/method_of_morris/`) because `morris.py` resolves them relative to its own path. + +1. **Prepare the database**: Obtain or create `morris_utopia.sql` and convert it to a SQLite database in the extension directory: -2. Observe the markings (3 groups) in the `MMAnalysis` columns in `CostVariable` and `Efficiency`. -3. Observe the `morris` configuration comments in the corresponding `morris_utopia.toml` file in `my_configs`. -4. Run the config as normal. + ```bash + # From the temoa/extensions/method_of_morris/ directory: + sqlite3 morris_utopia.sqlite < morris_utopia.sql + ``` + +2. **Ensure the configuration file is present**: Place `morris_utopia.toml` in the same directory. +3. Observe the markings (3 groups) in the `MMAnalysis` columns in `cost_variable` and `efficiency`. +4. Run the example from the extension directory. 5. MM analysis is reported on screen and in 2 csv files for the objective and `co2` in the Outputs folder -6. The DB will contain updated values (tagged by scenario name and "dash run") in `OutputObjective` and `OutputEmissions` -_only_ which might be of secondary value to the modeler. Other output tables are *not* updated. +6. The DB will contain updated values (tagged by scenario name and "dash run") in `output_objective` and `output_emissions` +_only_ which might be of secondary value to the modeler. Other output tables are _not_ updated. ### Preparing Other Databases (or modifying `morris_utopia`) -1. Augment all 3 of the tables (`CostInvest`, `CostInvest`, `Efficiency`) with a column (field) `MMAnalysis` + +1. Augment all 3 of the tables (`cost_invest`, `cost_invest`, `efficiency`) with a column (field) `MMAnalysis` that is `TEXT` data type, allowing NULL. 2. Add labels within that column for analysis. Note: - Labels that are repeated (in any table) are treated as a "Group" for MM purposes. - Labels that are used singly are a "group of 1". - - The number of runs will be: `(GROUPS + 1) x N` where `N` is the number of trajectories in the `config`. + - The number of runs will be: `(GROUPS + 1) x N` where `N` is the number of trajectories in the `config`. - Note: number runs is _independent_ of the total params marked (groups can be large and are varied together) - - The modeler need to consider how to group multiple vintages/periods for specific techs and/or group + - The modeler need to consider how to group multiple vintages/periods for specific techs and/or group related techs. 3. The modeler should consider the architecture used to run the model for cpu utilization. The number of cores to use is selectable in the `config` for parallel use. A `0` can be used to utilize all available cores diff --git a/temoa/extensions/method_of_morris/Method_of_Morris_README.txt b/temoa/extensions/method_of_morris/Method_of_Morris_README.txt index a2ed46e75..c1fb1d7da 100644 --- a/temoa/extensions/method_of_morris/Method_of_Morris_README.txt +++ b/temoa/extensions/method_of_morris/Method_of_Morris_README.txt @@ -1,7 +1,6 @@ - NOTE: The below README is OUTDATED but it describes the history of MM application. Current users should refer to the MM_README.md file in this directory for how-to and -walk-through on the example provided in data_files! +walk-through on the provided example files! ----------------------- Method of Morris README @@ -13,17 +12,17 @@ https://groups.google.com/forum/#!topic/temoa-project/SEqlvJOpnb0 1. Install SALib (pip install SALib). It's an open source python sensitivity analysis library. -2. Download the attached file and put it in your "temoa-energysystem" directory. Basically what this script does, is reading the baseline values of the parameters you want to do the sensitivity analysis on (say price of a specific fuel or technology), perturbing them by -+10% and creating text files which include all the parameters and their corresponding ranges of change (0.9*baseline=lower bound, 1.1*baseline=upper bound), sending the text files to SALib to generate the sampling matrix (param_values parameter in the script), running the model with the sampling matrix as many times as needed (you can control the number of the runs changing the N in line 141), reading the objective function values and CO2 emissions from the database for each run and sending their corresponding values to SALib to do the analysis. +2. Download the attached file and put it in your "temoa-energysystem" directory. Basically what this script does, is reading the baseline values of the parameters you want to do the sensitivity analysis on (say price of a specific fuel or technology), perturbing them by -+10% and creating text files which include all the parameters and their corresponding ranges of change (0.9*baseline=lower bound, 1.1*baseline=upper bound), sending the text files to SALib to generate the sampling matrix (param_values parameter in the script), running the model with the sampling matrix as many times as needed (you can control the number of the runs changing the N in line 141), reading the objective function values and CO2 emissions from the database for each run and sending their corresponding values to SALib to do the analysis. -3. Currently the sensitivity analysis can be done only on the parameters in the tables: CostInvest, CostVariable and Efficiency. You need to add a new column in your sqlite database for these tables. You do this by just executing these 3 lines: +3. Currently the sensitivity analysis can be done only on the parameters in the tables: cost_invest, cost_variable and efficiency. You need to add a new column in your sqlite database for these tables. You do this by just executing these 3 lines: -ALTER TABLE 'CostInvest' ADD 'MMAnalysis' -ALTER TABLE 'CostVariable' ADD 'MMAnalysis' -ALTER TABLE 'Efficiency' ADD 'MMAnalysis' +ALTER TABLE 'cost_invest' ADD 'MMAnalysis' +ALTER TABLE 'cost_variable' ADD 'MMAnalysis' +ALTER TABLE 'efficiency' ADD 'MMAnalysis' -4. Once this additional column is added for the 3 tables, you need to add names to the cell under MMAnalysis column which corresponds to the parameters you want to do the sensitivity analysis for. For example in Utopia case study, suppose you want to do the sensitivity analysis on gasoline, diesel and coal prices. +4. Once this additional column is added for the 3 tables, you need to add names to the cell under MMAnalysis column which corresponds to the parameters you want to do the sensitivity analysis for. For example in Utopia case study, suppose you want to do the sensitivity analysis on gasoline, diesel and coal prices. -Here, parameters with the same text in the MMAnalysis column are seen as "groups" of parameters and are changed simultaneously during the sampling process. If you don't want to use groups, you can put different names on each parameters and the MM only considers the sensitivity with respect to that specific variable. The former gives the sensitivity for a group of parameters. +Here, parameters with the same text in the MMAnalysis column are seen as "groups" of parameters and are changed simultaneously during the sampling process. If you don't want to use groups, you can put different names on each parameters and the MM only considers the sensitivity with respect to that specific variable. The former gives the sensitivity for a group of parameters. 5. Change your database name to "Method_of_Morris.sqlite" and like other databases ran by Temoa, put it in db_io/dbs directory. @@ -36,4 +35,4 @@ Other implementation-related issues you should know: 1. The scripts parallelizes all the cores in your machine when running the model. It might not be significant in small case studies for bigger models, using all the cores saves the time need to run the model. 2. It's very important that the reference to your input and output sqlite files in the config_sample file (--input=db_io/dbs/Method_of_Morris.sqlite and --output=db_io/dbs/Method_of_Morris.sqlite) are placed in the 14th and 21nd lines of config_sample file. 3. Since you are using sqlite and not .db, change all the ".db" texts in the attached script to ".sqlite". -4. I would suggest keeping num_levels=4, grid_jump=2. For this setting should be greater than 10. The total number of the runs is N*(number of groups+1). \ No newline at end of file +4. I would suggest keeping num_levels=4, grid_jump=2. For this setting should be greater than 10. The total number of the runs is N*(number of groups+1). diff --git a/temoa/extensions/method_of_morris/__init__.py b/temoa/extensions/method_of_morris/__init__.py index 3e289969b..e69de29bb 100644 --- a/temoa/extensions/method_of_morris/__init__.py +++ b/temoa/extensions/method_of_morris/__init__.py @@ -1,27 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/3/24 - -""" diff --git a/temoa/extensions/method_of_morris/morris.py b/temoa/extensions/method_of_morris/morris.py index 3b6841343..7619303bf 100644 --- a/temoa/extensions/method_of_morris/morris.py +++ b/temoa/extensions/method_of_morris/morris.py @@ -1,37 +1,38 @@ -# from __future__ import division -import time +from __future__ import annotations + +import csv +import sqlite3 +from importlib import resources from pathlib import Path +from typing import Any +from joblib import Parallel, delayed # type: ignore[import-untyped] +from numpy import array from pyomo.dataportal import DataPortal +from SALib.analyze import morris # type: ignore[import-untyped] +from SALib.sample.morris import sample # type: ignore[import-untyped] +from SALib.util import compute_groups_matrix, read_param_file # type: ignore[import-untyped] -from definitions import PROJECT_ROOT -from temoa.temoa_model import run_actions -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig - -start_time = time.time() -from joblib import Parallel, delayed -import sqlite3 -from numpy import array -from SALib.analyze import morris -from SALib.sample.morris import sample -from SALib.util import read_param_file, compute_groups_matrix +from temoa._internal import run_actions +from temoa._internal.table_writer import TableWriter +from temoa.core.config import TemoaConfig +from temoa.data_io.hybrid_loader import HybridLoader seed = 42 -def evaluate(param_names, param_values, data: dict, k): +def evaluate(param_names: dict[int, list[Any]], param_values: Any, + data: dict[str, Any], k: int) -> list[Any]: m = len(param_values) for j in range(0, m): names = param_names[j] match names[0]: - case 'CostInvest': + case 'cost_invest': data[names[0]][tuple(names[1:4])] = param_values[j] - case 'CostVariable': + case 'cost_variable': data[names[0]][tuple(names[1:5])] = param_values[j] - case 'Efficiency': + case 'efficiency': data[names[0]][tuple(names[1:6])] = param_values[j] case _: raise ValueError(f'Unrecognized parameter: {names[0]}') @@ -43,42 +44,52 @@ def evaluate(param_names, param_values, data: dict, k): if not status: raise RuntimeError('Bad solve during Method of Morris') table_writer = TableWriter(config) - table_writer.write_results(M=mdl) + table_writer.write_results(model=mdl) con = sqlite3.connect(db_file) cur = con.cursor() - cur.execute('SELECT * FROM OutputObjective') + cur.execute('SELECT * FROM output_objective') output_query = cur.fetchall() for row in output_query: - Y_OF = row[-1] - cur.execute("SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2'") + y_of = row[-1] + cur.execute("SELECT emis_comm, SUM(emission) FROM output_emission WHERE emis_comm='co2'") output_query = cur.fetchall() for row in output_query: - Y_CumulativeCO2 = row[-1] - Morris_Objectives = [] - Morris_Objectives.append(Y_OF) - Morris_Objectives.append(Y_CumulativeCO2) + y_cumulative_co2 = row[-1] + morris_objectives = [] + morris_objectives.append(y_of) + morris_objectives.append(y_cumulative_co2) con.close() - return Morris_Objectives + return morris_objectives -morris_root = Path(PROJECT_ROOT, 'temoa/extensions/method_of_morris') +morris_root = Path(__file__).parent perturbation_coefficient = 0.2 # minus plus 10% of the baseline values param_file = morris_root / 'm_params.txt' -db_file = Path(PROJECT_ROOT, 'data_files/untracked_data/morris/morris_utopia.sqlite') -config_path = Path(PROJECT_ROOT, 'data_files/untracked_data/morris/morris_utopia.toml') -with sqlite3.connect(db_file) as con: + +db_file = Path(__file__).parent / 'morris_utopia.sqlite' +config_path = Path(__file__).parent / 'morris_utopia.toml' + +if not db_file.exists() or not config_path.exists(): + raise FileNotFoundError( + "Example Morris data files not found in current directory. " + "Please see MM_README.md for instructions on how to generate " + "or provide the required 'morris_utopia.sqlite' and 'morris_utopia.toml'." + ) + +with sqlite3.connect(str(db_file)) as con: with open(param_file, 'w') as file: param_names = {} cur = con.cursor() cur.execute( - 'SELECT region, period, tech, vintage, cost, MMAnalysis FROM CostVariable WHERE MMAnalysis is not NULL' + 'SELECT region, period, tech, vintage, cost, MMAnalysis FROM cost_variable WHERE ' + 'MMAnalysis is not NULL' ) output_query = cur.fetchall() g1 = len(output_query) for i in range(0, len(output_query)): param_names[i] = [ - 'CostVariable', + 'cost_variable', *output_query[i][:4], 'cost_variable', ] @@ -93,12 +104,13 @@ def evaluate(param_names, param_values, data: dict, k): file.write('\n') cur.execute( - 'SELECT region, tech, vintage, cost, MMAnalysis FROM CostInvest WHERE MMAnalysis is not NULL' + 'SELECT region, tech, vintage, cost, MMAnalysis FROM cost_invest WHERE MMAnalysis is ' + 'not NULL' ) output_query = cur.fetchall() g2 = len(output_query) for i in range(0, len(output_query)): - param_names[i + g1] = ['CostInvest', *output_query[i][:3], 'cost_invest'] + param_names[i + g1] = ['cost_invest', *output_query[i][:3], 'cost_invest'] file.write('x' + str(i + g1)) file.write(' ') @@ -108,14 +120,16 @@ def evaluate(param_names, param_values, data: dict, k): file.write(' ') file.write(output_query[i][-1]) file.write('\n') + cur.execute( - 'SELECT DISTINCT region, input_comm, tech, vintage, output_comm, efficiency, MMAnalysis FROM Efficiency WHERE MMAnalysis is not NULL' + 'SELECT DISTINCT region, input_comm, tech, vintage, output_comm, efficiency, ' + 'MMAnalysis FROM efficiency WHERE MMAnalysis is not NULL' ) output_query = cur.fetchall() g3 = len(output_query) for i in range(0, len(output_query)): param_names[i + g1 + g2] = [ - 'Efficiency', + 'efficiency', *output_query[i][:5], 'efficiency', ] @@ -129,80 +143,76 @@ def evaluate(param_names, param_values, data: dict, k): file.write('\n') # load a data portal, retrieve the data dict for the problem - config = TemoaConfig.build_config(config_file=config_path, output_path='.') + config = TemoaConfig.build_config(config_file=config_path, output_path=Path('.')) loader = HybridLoader(db_connection=con, config=config) loader.load_data_portal() data = loader.data -problem = read_param_file(str(param_file), delimiter=' ') -param_values = sample( - problem, N=10, num_levels=8, optimal_trajectories=False, local_optimization=False, seed=seed -) -print(param_values) -print(param_names) -n = len(param_values) -# pull the data - -num_cores = 1 # multiprocessing.cpu_count() -Morris_Objectives = Parallel(n_jobs=num_cores)( - delayed(evaluate)(param_names, param_values[i, :], data, i) for i in range(0, n) -) -Morris_Objectives = array(Morris_Objectives) -print(Morris_Objectives) -Si_OF = morris.analyze( - problem, - param_values, - Morris_Objectives[:, 0], - conf_level=0.95, - print_to_console=False, - num_levels=8, - num_resamples=1000, - seed=seed + 1, -) - -Si_CumulativeCO2 = morris.analyze( - problem, - param_values, - Morris_Objectives[:, 1], - conf_level=0.95, - print_to_console=False, - num_levels=8, - num_resamples=1000, - seed=seed + 2, -) -num_vars = problem['num_vars'] -groups, unique_group_names = compute_groups_matrix(problem['groups']) -number_of_groups = len(unique_group_names) -print( - '{0:<30} {1:>10} {2:>10} {3:>15} {4:>10}'.format( - 'Parameter', 'Mu_Star', 'Mu', 'Mu_Star_Conf', 'Sigma' - ) -) -for j in list(range(number_of_groups)): - print( - '{0:30} {1:10.3f} {2:10.3f} {3:15.3f} {4:10.3f}'.format( - Si_OF['names'][j], - Si_OF['mu_star'][j], - Si_OF['mu'][j], - Si_OF['mu_star_conf'][j], - Si_OF['sigma'][j], + problem = read_param_file(str(param_file), delimiter=' ') + param_values = sample( + problem, N=10, num_levels=8, optimal_trajectories=False, local_optimization=False, seed=seed + ) + print(param_values) + print(param_names) + n = len(param_values) + + # pull the data + num_cores = 1 # multiprocessing.cpu_count() + Morris_Objectives = Parallel(n_jobs=num_cores)( + delayed(evaluate)(param_names, param_values[i, :], data, i) for i in range(0, n) + ) + + Morris_Objectives = array(Morris_Objectives) + print(Morris_Objectives) + si_of = morris.analyze( + problem, + param_values, + Morris_Objectives[:, 0], + conf_level=0.95, + print_to_console=False, + num_levels=8, + num_resamples=1000, + seed=seed + 1, ) - ) -import csv -line1 = Si_OF['mu_star'] -line2 = Si_OF['mu_star_conf'] -line3 = Si_CumulativeCO2['mu_star'] -line4 = Si_CumulativeCO2['mu_star_conf'] -with open('MMResults.csv', 'w') as f: - writer = csv.writer(f, delimiter=',') - writer.writerow(unique_group_names) - writer.writerow('Objective Function') - writer.writerow(line1) - writer.writerow(line2) - writer.writerow('Cumulative CO2 Emissions') - writer.writerow(line3) - writer.writerow(line4) - -f.close -print('--- %s seconds ---' % (time.time() - start_time)) + si_cumulative_co2 = morris.analyze( + problem, + param_values, + Morris_Objectives[:, 1], + conf_level=0.95, + print_to_console=False, + num_levels=8, + num_resamples=1000, + seed=seed + 2, + ) + groups, unique_group_names = compute_groups_matrix(problem['groups']) + number_of_groups = len(unique_group_names) + print( + '{:<30} {:>10} {:>10} {:>15} {:>10}'.format( + 'Parameter', 'Mu_Star', 'Mu', 'Mu_Star_Conf', 'Sigma' + ) + ) + for j in list(range(number_of_groups)): + print( + '{:30} {:10.3f} {:10.3f} {:15.3f} {:10.3f}'.format( + si_of['names'][j], + si_of['mu_star'][j], + si_of['mu'][j], + si_of['mu_star_conf'][j], + si_of['sigma'][j], + ) + ) + + line1 = si_of['mu_star'] + line2 = si_of['mu_star_conf'] + line3 = si_cumulative_co2['mu_star'] + line4 = si_cumulative_co2['mu_star_conf'] + with open('MMResults.csv', 'w') as f: + writer = csv.writer(f, delimiter=',') + writer.writerow(unique_group_names) + writer.writerow('Objective Function') + writer.writerow(line1) + writer.writerow(line2) + writer.writerow('Cumulative CO2 Emissions') + writer.writerow(line3) + writer.writerow(line4) diff --git a/temoa/extensions/method_of_morris/morris_evaluate.py b/temoa/extensions/method_of_morris/morris_evaluate.py index af75bdb6c..6104d80cf 100644 --- a/temoa/extensions/method_of_morris/morris_evaluate.py +++ b/temoa/extensions/method_of_morris/morris_evaluate.py @@ -1,45 +1,26 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/2/24 - -This module contains the core "evaluation" function for Method Of Morris. It needs to be isolated (outside -of class) to enable parallelization. +This module contains the core "evaluation" function for Method Of Morris. It needs to be isolated +(outside of class) to enable parallelization. """ +from __future__ import annotations + import logging import sqlite3 import sys from logging.handlers import QueueHandler +from typing import TYPE_CHECKING, Any from pyomo.dataportal import DataPortal -from temoa.temoa_model import run_actions -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig +from temoa._internal import run_actions +from temoa._internal.table_writer import TableWriter + +if TYPE_CHECKING: + from temoa.core.config import TemoaConfig -def configure_worker_logger(log_queue, log_level): + +def configure_worker_logger(log_queue: Any, log_level: int) -> logging.Logger: """configure the logger""" worker_logger = logging.getLogger('MM evaluate') if not worker_logger.hasHandlers(): @@ -54,7 +35,8 @@ def configure_worker_logger(log_queue, log_level): return worker_logger -def evaluate(param_info, mm_sample, data, i, config: TemoaConfig, log_queue, log_level): +def evaluate(param_info: dict[int, list[Any]], mm_sample: Any, data: dict[str, Any], + i: int, config: TemoaConfig, log_queue: Any, log_level: int) -> list[float]: """ Run model for params provided and return objective value and emission value Note: This function needs to be a static instance to enable the parallel @@ -73,19 +55,14 @@ def evaluate(param_info, mm_sample, data, i, config: TemoaConfig, log_queue, log log_entry = [''] for j in range(0, len(mm_sample)): param_name, *set_idx, _ = param_info[j] - set_idx = tuple(set_idx) + set_idx_tuple = tuple(set_idx) # tweak the parameter if data.get(param_name) is None: raise ValueError(f'Unrecognized parameter: {param_name}') - if data[param_name].get(set_idx) is None: + if data[param_name].get(set_idx_tuple) is None: raise ValueError('index mismatch from data read-in') - data[param_name][set_idx] = mm_sample[j] - setting_entry = 'run # %d: Setting param %s[%s] to value: %f' % ( - i + 1, - param_name, - set_idx, - mm_sample[j], - ) + data[param_name][set_idx_tuple] = mm_sample[j] + setting_entry = 'run # %d: Setting param %s[%s] to value: %f' log_entry.append(setting_entry) logger.debug('\n '.join(log_entry)) @@ -98,12 +75,12 @@ def evaluate(param_info, mm_sample, data, i, config: TemoaConfig, log_queue, log if not status: raise RuntimeError('Bad solve during Method of Morris') table_writer = TableWriter(config) - table_writer.write_mm_results(M=mdl, iteration=i) + table_writer.write_mm_results(model=mdl, iteration=i) con = sqlite3.connect(config.input_database) cur = con.cursor() scenario_name = config.scenario + f'-{i}' cur.execute( - 'SELECT total_system_cost FROM OutputObjective where scenario = ?', (scenario_name,) + 'SELECT total_system_cost FROM output_objective where scenario = ?', (scenario_name,) ) output_query = cur.fetchall() if len(output_query) > 1: @@ -111,23 +88,24 @@ def evaluate(param_info, mm_sample, data, i, config: TemoaConfig, log_queue, log 'Multiple outputs found in Objective table matching scenario name. Coding error.' ) else: - Y_OF = output_query[0][0] + y_of = output_query[0][0] if output_query[0][0] is not None else 0.0 cur.execute( - "SELECT SUM(emission) FROM OutputEmission WHERE emis_comm='co2' AND scenario=?", + "SELECT SUM(emission) FROM output_emission WHERE emis_comm='co2' AND scenario=?", (scenario_name,), ) output_query = cur.fetchall() if len(output_query) == 0: - Y_CumulativeCO2 = 0.0 + y_cumulative_co2 = 0.0 elif len(output_query) > 1: raise RuntimeError( - 'Multiple outputs found in OutputEmissions table matching scenario name. Coding error.' + 'Multiple outputs found in output_emissions table matching scenario name. Coding ' + 'error.' ) else: - Y_CumulativeCO2 = output_query[0][0] - morris_objectives = [float(Y_OF), float(Y_CumulativeCO2)] - logger.info('Finished MM evaluation # %d with OBJ value: %0.2f ', i + 1, Y_OF) + y_cumulative_co2 = output_query[0][0] if output_query[0][0] is not None else 0.0 + morris_objectives = [float(y_of), float(y_cumulative_co2)] + logger.info('Finished MM evaluation # %d with OBJ value: %0.2f ', i + 1, y_of) if not config.silent: - sys.stdout.write(f'Completed MM run {i+1}\n') + sys.stdout.write(f'Completed MM run {i + 1}\n') sys.stdout.flush() return morris_objectives diff --git a/temoa/extensions/method_of_morris/morris_sequencer.py b/temoa/extensions/method_of_morris/morris_sequencer.py index 670f10769..fd2c6ce5e 100644 --- a/temoa/extensions/method_of_morris/morris_sequencer.py +++ b/temoa/extensions/method_of_morris/morris_sequencer.py @@ -1,61 +1,40 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - -This code is modified from original work on Method of Morris framework -by Hadi Eshragi. Original morris.py file can be located in the energysystem -branch - -Modified/Refactored by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 5/30/24 - -An event sequencer to control the flow of a Method of Morris calculation. This code uses multiprocessing via -the joblib library - +An event sequencer to control the flow of a Method of Morris calculation. This code uses +multiprocessing via the joblib library """ +from __future__ import annotations + import csv import logging import multiprocessing import sqlite3 import sys import tomllib +from importlib import resources from logging.handlers import QueueListener -from pathlib import Path +from typing import TYPE_CHECKING, Any, cast -from SALib.analyze import morris -from SALib.sample.morris import sample -from SALib.util import read_param_file, compute_groups_matrix -from joblib import Parallel, delayed +from joblib import Parallel, delayed # type: ignore[import-untyped] from numpy import array +from SALib.analyze import morris # type: ignore[import-untyped] +from SALib.sample.morris import sample # type: ignore[import-untyped] +from SALib.util import compute_groups_matrix, read_param_file # type: ignore[import-untyped] -from definitions import PROJECT_ROOT, get_OUTPUT_PATH +from temoa._internal.table_writer import TableWriter +from temoa.data_io.hybrid_loader import HybridLoader from temoa.extensions.method_of_morris.morris_evaluate import evaluate -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig + +if TYPE_CHECKING: + from pathlib import Path + + from temoa.core.config import TemoaConfig + + logger = logging.getLogger(__name__) -solver_options_file = Path( - PROJECT_ROOT, 'temoa/extensions/method_of_morris/morris_solver_options.toml' +solver_options_file = ( + resources.files('temoa.extensions.method_of_morris') / 'morris_solver_options.toml' ) @@ -79,15 +58,17 @@ def __init__(self, config: TemoaConfig): config.save_excel = False if config.price_check: logger.warning( - 'Price check is disabled during Morris runs. If you wish to check costs, run "CHECK" on model before MM' + 'Price check is disabled during Morris runs. If you wish to check costs, run ' + '"CHECK" on model before MM' ) config.price_check = False self.config = config # read in the options try: - with open(solver_options_file, 'rb') as f: - all_options = tomllib.load(f) + with resources.as_file(solver_options_file) as path: + with open(path, 'rb') as f: + all_options = tomllib.load(f) s_options = all_options.get(self.config.solver_name, {}) logger.info('Using solver options: %s', s_options) @@ -97,15 +78,16 @@ def __init__(self, config: TemoaConfig): # output handling self.verbose = False # for troubleshooting - self.mm_output_folder = get_OUTPUT_PATH() / 'MM_outputs' + self.mm_output_folder = self.config.output_path / 'MM_outputs' self.mm_output_folder.mkdir(exist_ok=True) self.param_file: Path = self.mm_output_folder / 'params.csv' # MM Options + morris_inputs = config.morris_inputs or {} # the amount to perturb the marked params - pert = config.morris_inputs.get('perturbation') + pert = morris_inputs.get('perturbation') if pert: - self.mm_perturbation = pert + self.mm_perturbation = float(cast(str | float, pert)) logger.info('Morris perturbation: %0.2f', self.mm_perturbation) else: self.mm_perturbation = 0.10 @@ -113,9 +95,9 @@ def __init__(self, config: TemoaConfig): 'No value received for perturbation, using default: %0.2f', self.mm_perturbation ) - levels = config.morris_inputs.get('levels') + levels = morris_inputs.get('levels') if levels: - self.num_levels = levels + self.num_levels = int(cast('Any', levels)) logger.info('Morris levels: %d', self.num_levels) else: self.num_levels = ( @@ -123,37 +105,41 @@ def __init__(self, config: TemoaConfig): ) logger.warning('No value received for levels, using default: %d', self.num_levels) - traj = config.morris_inputs.get('trajectories') + traj = morris_inputs.get('trajectories') if traj: - self.trajectories = traj + self.trajectories = int(cast('Any', traj)) logger.info('Morris trajectories: %d', self.trajectories) else: self.trajectories = 4 # number of morris trajectories to generate logger.warning( 'No value received for trajectories, using default: %d', self.trajectories ) - # Note: Problem size (in general) is (Groups + 1) * trajectories see the SALib Dox (which aren't super) + # Note: Problem size (in general) is (Groups + 1) * trajectories see the SALib Dox (which + # aren't super) - seed = config.morris_inputs.get('seed') - self.seed = seed if seed else None + seed = morris_inputs.get('seed') + self.seed = int(cast('Any', seed)) if seed is not None else None logger.info('Morris Seed (None indicates system generated): %s', self.seed) self.conf_level = 0.95 # confidence level for mu_star analysis - self.num_cores = config.morris_inputs.get('cores', 0) + self.num_cores = morris_inputs.get('cores', 0) if self.num_cores == 0: self.num_cores = multiprocessing.cpu_count() logger.info('Morris number of cores: %d', self.num_cores) logger.info('Initialized Morris Sequencer') logger.info( - 'Currently, MM only logs ERROR level messages during model build, which is done repeatedly.' - ' If there are issues building the model, run Temoa in CHECK separately to get more detail on the model.' + 'Currently, MM only logs ERROR level messages during model build, which is done ' + 'repeatedly.' + ' If there are issues building the model, run Temoa in CHECK separately to get more ' + 'detail on the model.' ) - def start(self): + def start(self) -> Any: """ run the sequence of steps to do a MM analysis - 0. clear any prior results with this scenario name. this sequencer appends the DB, so start fresh + 0. clear any prior results with this scenario name. this sequencer appends the DB, so + start fresh 1. gather the parameters from items marked in the DB 2. build a data portal as a basis 3. use SALib to construct the sample @@ -166,7 +152,8 @@ def start(self): tw = TableWriter(config=self.config) tw.clear_scenario() - # 1. Gather param info from the DB and construct the param file, which will be basis of the 'problem' + # 1. Gather param info from the DB and construct the param file, which will be basis of + # the 'problem' param_names = self.gather_parameters() # 2. Use the loader to get raw access to the model's data (dictionary) @@ -214,7 +201,8 @@ def start(self): # 7. Return the cost objective Mu_Star for testing purposes... return cost_mu_star - def process_results(self, problem, mm_samples, morris_results): + def process_results(self, problem: dict[str, Any], mm_samples: Any, + morris_results: list[Any]) -> Any: """ Process the results of the runs on the mm_samples :param problem: the problem structure @@ -252,13 +240,13 @@ def process_results(self, problem, mm_samples, morris_results): for category in analysis.keys(): print(f'\nAnalysis of {category}:') print( - '{0:<30} {1:>10} {2:>10} {3:>20} {4:>10}'.format( + '{:<30} {:>10} {:>10} {:>20} {:>10}'.format( 'Parameter', 'Mu_Star', 'Mu', mu_star_conf_label, 'Sigma' ) ) for j in list(range(number_of_groups)): print( - '{0:30} {1:10.3f} {2:10.3f} {3:20.3f} {4:10.3f}'.format( + '{:30} {:10.3f} {:10.3f} {:20.3f} {:10.3f}'.format( analysis[category]['names'][j], analysis[category]['mu_star'][j], analysis[category]['mu'][j], @@ -285,7 +273,7 @@ def process_results(self, problem, mm_samples, morris_results): writer.writerow(row) return analysis['cost'] - def gather_parameters(self): + def gather_parameters(self) -> dict[int, list[Any]]: """ Scan the annotated DB tables for marked parameters and capture them in the parameters file. Also capture the names in the param_info data @@ -297,12 +285,13 @@ def gather_parameters(self): with open(self.param_file, 'w') as f: v_idx = 4 # index of variable to perturb raw = cur.execute( - 'SELECT region, period, tech, vintage, cost, MMAnalysis FROM CostVariable WHERE MMAnalysis IS NOT NULL' + 'SELECT region, period, tech, vintage, cost, MMAnalysis FROM cost_variable WHERE ' + 'MMAnalysis IS NOT NULL' ).fetchall() g1 = len(raw) for i in range(0, len(raw)): param_names[i] = [ - 'CostVariable', + 'cost_variable', *raw[i][:4], 'cost_variable', ] @@ -316,11 +305,12 @@ def gather_parameters(self): v_idx = 3 raw = cur.execute( - 'SELECT region, tech, vintage, cost, MMAnalysis FROM CostInvest WHERE MMAnalysis IS NOT NULL' + 'SELECT region, tech, vintage, cost, MMAnalysis FROM cost_invest WHERE MMAnalysis ' + 'IS NOT NULL' ).fetchall() g2 = len(raw) for i in range(0, len(raw)): - param_names[i + g1] = ['CostInvest', *raw[i][:3], 'cost_invest'] + param_names[i + g1] = ['cost_invest', *raw[i][:3], 'cost_invest'] iter = f'x{i + g1}' low = str(raw[i][v_idx] * (1 - self.mm_perturbation)) @@ -333,13 +323,13 @@ def gather_parameters(self): v_idx = 5 raw = cur.execute( 'SELECT DISTINCT region, input_comm, tech, vintage, output_comm, efficiency, ' - 'MMAnalysis FROM Efficiency WHERE MMAnalysis IS NOT NULL' + 'MMAnalysis FROM efficiency WHERE MMAnalysis IS NOT NULL' ).fetchall() g3 = len(raw) for i in range(0, len(raw)): param_names[i + g1 + g2] = [ - 'Efficiency', + 'efficiency', *raw[i][:5], 'efficiency', ] @@ -358,5 +348,5 @@ def gather_parameters(self): return param_names - def __del__(self): + def __del__(self) -> None: self.con.close() diff --git a/temoa/extensions/modeling_to_generate_alternatives/MGA Design.md b/temoa/extensions/modeling_to_generate_alternatives/MGA Design.md index 07251c2c9..1acfb3b3f 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/MGA Design.md +++ b/temoa/extensions/modeling_to_generate_alternatives/MGA Design.md @@ -1,29 +1,35 @@ # MGA Design ## Terms -### MGA Axis: -- The general description of the inputs to diversify. Default now is TECH CATEGORY ACTIVITY + +### MGA Axis + +- The general description of the inputs to diversify. Default now is TECH CATEGORY ACTIVITY implying that we will try to diversify activity of each labeled tech category in `Technology` table. - **DIMENSION**: number of categories. This defines the dimensionality of the convex hull - Notions for possible Axis: - Techs: All(?) techs - Sectors: Does this make sense? - Category/Subcategory: The Fundamental idea - - A selection of `TechGroup` from data + - A selection of `tech_group` from data - Emissions: Different types - Resilience: Some combo of storage & capacity factor & such -### `Axis Vector` + +### `Axis Vector` + - Directional unit vector within the axis space - **DIMENSION** = dimensionality of the MGA Axis - This is a possible exploration direction within the MGA Axis space - `Hull Norm`: An `Axis Vector` that is normal to a facet on the convex hull - Hull Norms are generated by the convex hull routine - Must be checked for redundancy (other nearly collinear instances), especially when re-creating the hull from points + ### `Model Vector` + - A vector of coefficients or model variables or both that represent the basis of the objective function - **DIMENSION**: The total number of variables chosen within the `Vector Manager` to capture output. This is `sample dimension`. - Generated by the `Vector Manager` -- `Variable Vector`: A `Model Vector` of model variables (only) selected for analysis. +- `Variable Vector`: A `Model Vector` of model variables (only) selected for analysis. - `Coefficient Vector`: A vector of scalar multipliers of the same length as the `Variable Vector`. - `Input Vector` - A `pyomo` expression of coefficients * variables from the above. @@ -31,7 +37,8 @@ implying that we will try to diversify activity of each labeled tech category in - `Output Vector` - A vector of variables (containing solution values) from an optimization run -#### Example: +#### Example + MGA Axis of "activity" with 2 categories: {`solar`,`wind`} An `Axis Vector` might be [0.75, 0.25] @@ -43,10 +50,12 @@ A `Coefficient Vector` might be: `[0.75/2, 0.75/2, 0.25/2, 0.25/2]` An `Input Vector` would be the sum of the dot product of variables and coefficients ### Point + - The outcome of an optimization represented in Axis space - Can be used to define points on the Convex Hull (if used) ### Weighting Scheme + - The logic that determines new `axis vectors` for exploration - To be implemented by the `Vector Manager` - Options: @@ -58,19 +67,19 @@ An `Input Vector` would be the sum of the dot product of variables and coefficie built and normal vectors are employed going forward. - Random (?)?: Random unit vectors in either the `Axis Vector` dimension or `Model Vector` dimension -## Implementation: +## Implementation + - User should specify an `Axis` in the config. This will enable the selection of a `Vector Manager` - User should specify a `Weighting Scheme` in the config that is appropriate for the `Axis` chosen. This setup is only sparsely built now, but allows a framework for follow-on development - User should verify the solver config data in the `solver_options.toml` file in the MGA pkg folder in the `extensions` package. -- User should ensure that the `Axis` is supported in the appropriate data table. - - For example, if using the default, we should mark/check categories in `Technology`. - - Techs that have no category will be processed but not be part of the `Objective` function. +- User should ensure that the `Axis` is supported in the appropriate data table. + - For example, if using the default, we should mark/check categories in `Technology`. + - Techs that have no category will be processed but not be part of the `Objective` function. - If using the cvx hull algorithm, realize that `2 x |Categories|` solves are necessary to establish a hull - -## Objectives & Ideas Notes: +## Objectives & Ideas Notes - Look at near-optimal solutions that are diverse - What are metrics? @@ -78,10 +87,11 @@ package. number of runs - Flow out? Cost? Emissions? - Almost certainly capacity built. **Capacity Built is implemented** and enumerated by dash-run -number in the `OutputBuiltCapacity` table. +number in the `output_built_capacity` table. - What are reasonable aggregation schemes to capture summary values? -## What kind of diversity: +## What kind of diversity + - How are outcomes diverse from each other? - Capacity of individual techs? (likely too granular) - Capacity of groups of techs @@ -108,7 +118,7 @@ number in the `OutputBuiltCapacity` table. - How much more in one direction? ## Outputs + - Perhaps save the "weighting" index to a table for each run to make individual points reproducible to get full output? - What do MGA output summary results look like...? - diff --git a/temoa/extensions/modeling_to_generate_alternatives/README.md b/temoa/extensions/modeling_to_generate_alternatives/README.md index bdf0a73ed..7b41c71e2 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/README.md +++ b/temoa/extensions/modeling_to_generate_alternatives/README.md @@ -1,32 +1,32 @@ ### Using MGA It is likely helpful to read the `MGA Design.md` file in this directory that describes some of the terms/ -analytic approach to MGA. MGA uses multi-processing to iteratively explore near-optimal solutions. +analytic approach to MGA. MGA uses multi-processing to iteratively explore near-optimal solutions. #### Outputs - The processing of results is left to the modeler and the results from runs are tagged iteratively in the Output tables in the database. -- Running MGA analysis also adds an additional `OutputFlowOutSummary` table which +- Running MGA analysis also adds an additional `output_flow_out_summary` table which summarizes flows at the period level (summarizing time of day and season) to manage the size of the output for larger models #### Setup - The Config file options - - cost_epsilon: The proportion to relax the minimal cost by to enable exploration - - iteration_limit: The max iterations to run. Note: Currently the process will run until either the + - cost_epsilon: The proportion to relax the minimal cost by to enable exploration + - iteration_limit: The max iterations to run. Note: Currently the process will run until either the iteration limit OR time limit is reached. If using hull expansion as the weighting scheme (default), realize that "the magic" of that doesn't start until the hull is built which takes 2x |categories| solves to produce. - - time_limit_hrs: Self explanatory. A backstop for long runs - - axis: The type of manager to use. Only `tech_category_activity` is currently implemented - - weighting: The type of weighting to use by the manager + - time_limit_hrs: Self explanatory. A backstop for long runs + - axis: The type of manager to use. Only `tech_category_activity` is currently implemented + - weighting: The type of weighting to use by the manager - The `MGA_solver_options.toml` file - Contains solver settings to optimize performance. These settings are used *after* the first solve, which sets the optimized cost. The "worker" solvers consume and use any options for the chosen solver from this file - - Contains the number of workers setting. Some balance must be considered between hardware resources and + - Contains the number of workers setting. Some balance must be considered between hardware resources and concurrency. Six (the current setting) seems an OK balance... - Large models consume fairly large memory footprint. It is possible to have `num_workers` + 2 models floating around either in solve, or waiting. - The number of workers should be balanced with number of cores per solve (if solver accepts that). For example, - with 6 workers and 20 threads/solve, this is a nice fit on 132 core servers, with a bit of slop. \ No newline at end of file + with 6 workers and 20 threads/solve, this is a nice fit on 132 core servers, with a bit of slop. diff --git a/temoa/extensions/modeling_to_generate_alternatives/__init__.py b/temoa/extensions/modeling_to_generate_alternatives/__init__.py index f79bdd36f..e69de29bb 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/__init__.py +++ b/temoa/extensions/modeling_to_generate_alternatives/__init__.py @@ -1,27 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/9/24 - -""" diff --git a/temoa/extensions/modeling_to_generate_alternatives/hull.py b/temoa/extensions/modeling_to_generate_alternatives/hull.py index d2eaa1ae4..d0f79c132 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/hull.py +++ b/temoa/extensions/modeling_to_generate_alternatives/hull.py @@ -1,42 +1,31 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/17/24 - A thin wrapper on Scipy's ConvexHull to make it more manageable """ +from __future__ import annotations + from logging import getLogger +from typing import Any import numpy as np -import scipy -from scipy.spatial import ConvexHull +from scipy.spatial import ConvexHull # type: ignore[import-untyped] logger = getLogger(__name__) class Hull: - def __init__(self, points: np.ndarray, **kwargs): + dim: int + cv_hull: Any + volume: float + seen_norms: np.ndarray | None + _valid_norms: np.ndarray | None + tolerance: float + norms_checked: int + norms_rejected: int + good_points: np.ndarray | None + all_points: np.ndarray | None + norm_index: int + + def __init__(self, points: np.ndarray, **kwargs: Any) -> None: """ Build the initial hull from array of points :param points: an array of points [points, hull dimension] @@ -83,34 +72,39 @@ def norms_available(self) -> int: @property def norm_rejection_proportion(self) -> float: + if self.norms_checked == 0: + return 0.0 return self.norms_rejected / self.norms_checked - def update(self): + def update(self) -> None: """ Update/rebuild the Hull based on new points. :return: """ - if self.all_points is None: + if self.all_points is None or len(self.all_points) == 0: return try: self.cv_hull = ConvexHull(self.all_points, qhull_options='Q12 QJ') - # Q12: Allow "wide" facets, which seems to happen with large disparity in scale in model - # QJ: option to "joggle" inputs if errors arise from singularities, etc. This seems to slow things down - # a moderate amount. - # Dev Note: After significant experiments with building new each time or allowing "incremental" - # additions to the hull, it appears more ROBUST to just rebuild. More frequent - # abnormal exits when trying to use incremental, and time difference is negligible - # for this few pts. + # Q12: Allow "wide" facets, which seems to happen with large disparity in scale in + # model + # QJ: option to "joggle" inputs if errors arise from singularities, etc. This seems + # to slow things down a moderate amount. + # Dev Note: After significant experiments with building new each time or allowing + # "incremental" additions to the hull, it appears more ROBUST to just + # rebuild. More frequent abnormal exits when trying to use incremental, and + # time difference is negligible for this few pts. self.good_points = self.cv_hull.points logger.info('Hull updated') self.volume = self.cv_hull.volume - except scipy.spatial._qhull.QhullError as e: + except Exception as e: + # scipy.spatial._qhull.QhullError may not be easily found by mypy logger.error( 'Attempt at hull construction from basis vectors failed.' - '\nMay be non-recoverable. Possibly try a set of random vectors to initialize the Hull.' + '\nMay be non-recoverable. Possibly try a set of random vectors to initialize the ' + 'Hull.' ) logger.error(e) - raise RuntimeError('Hull construction from vectors failed. See log file') + raise RuntimeError('Hull construction from vectors failed. See log file') from e # update the available norms from the new hull equations = self.cv_hull.equations[:, 0:-1] @@ -122,14 +116,14 @@ def update(self): else: self._valid_norms = np.vstack((self._valid_norms, norm)) - def add_point(self, point: np.ndarray): + def add_point(self, point: np.ndarray) -> None: if len(point) != self.dim: - logger.error( - 'Tried adding a point to hull (dim: %d) with wrong dimensions %d. Point: %s', - self.dim, - len(point), - point, + msg = ( + f'Tried adding a point to hull (dim: {self.dim}) with wrong dimensions {len(point)}. ' + f'Point: {point}' ) + logger.error(msg) + raise ValueError(msg) if self.all_points is None: self.all_points = np.atleast_2d(point) else: @@ -140,7 +134,7 @@ def get_norm(self) -> np.ndarray | None: pop a new direction norm from the stack :return: a new norm vector """ - if self.norm_index < len(self._valid_norms): + if self._valid_norms is not None and self.norm_index < len(self._valid_norms): if np.ndim(self._valid_norms) == 1: # only one on the stack res = self._valid_norms else: @@ -151,7 +145,7 @@ def get_norm(self) -> np.ndarray | None: def get_all_norms(self) -> np.ndarray: """Get a matrix of all unused new vectors""" - if self.norms_available > 0: + if self._valid_norms is not None and self.norms_available > 0: res = np.atleast_2d(self._valid_norms)[self.norm_index :, :] self.norm_index = len(self._valid_norms) return res diff --git a/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py b/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py index 8416f3903..3970c4e87 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py +++ b/temoa/extensions/modeling_to_generate_alternatives/manager_factory.py @@ -1,38 +1,19 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling +from __future__ import annotations -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/16/24 - -""" -import sqlite3 +from typing import TYPE_CHECKING, Any from temoa.extensions.modeling_to_generate_alternatives.mga_constants import MgaAxis, MgaWeighting from temoa.extensions.modeling_to_generate_alternatives.tech_activity_vector_manager import ( TechActivityVectorManager, ) -from temoa.extensions.modeling_to_generate_alternatives.vector_manager import VectorManager -from temoa.temoa_model.temoa_model import TemoaModel + +if TYPE_CHECKING: + import sqlite3 + + from temoa.core.model import TemoaModel + from temoa.extensions.modeling_to_generate_alternatives.vector_manager import VectorManager + + def get_manager( @@ -40,7 +21,7 @@ def get_manager( weighting: MgaWeighting, model: TemoaModel, con: sqlite3.Connection | None, - **kwargs, + **kwargs: Any, ) -> VectorManager: match axis: case MgaAxis.TECH_CATEGORY_ACTIVITY: @@ -48,6 +29,8 @@ def get_manager( raise NotImplementedError( 'TECH_CATEGORY_ACTIVITY is only implemented for HULL_EXPANSION' ) + if con is None: + raise ValueError('Connection is required for TECH_CATEGORY_ACTIVITY') return TechActivityVectorManager( base_model=model, conn=con, weighting=weighting, **kwargs ) diff --git a/temoa/extensions/modeling_to_generate_alternatives/mga_constants.py b/temoa/extensions/modeling_to_generate_alternatives/mga_constants.py index 973f8f07e..f5b2ed1e6 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/mga_constants.py +++ b/temoa/extensions/modeling_to_generate_alternatives/mga_constants.py @@ -1,30 +1,3 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/16/24 - -""" import enum from enum import Enum diff --git a/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py b/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py index d7f1d4c90..83b228fec 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py +++ b/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py @@ -1,68 +1,73 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/15/24 - -The purpose of this module is to perform top-level control over an MGA model run +Performs top-level control over an MGA model run """ +from __future__ import annotations + import logging import queue import sqlite3 import time import tomllib from datetime import datetime +from importlib import resources from logging import getLogger from multiprocessing import Queue -from pathlib import Path from queue import Empty +from typing import TYPE_CHECKING, Any, cast + +if TYPE_CHECKING: + from multiprocessing.process import BaseProcess + + from pyomo.contrib.solver.common.results import Results + from pyomo.dataportal import DataPortal + + from temoa.core.config import TemoaConfig + from temoa.core.model import TemoaModel + from temoa.extensions.modeling_to_generate_alternatives.vector_manager import VectorManager + + + + + import pyomo.environ as pyo -from pyomo.contrib.solver.results import Results -from pyomo.dataportal import DataPortal from pyomo.opt import check_optimal_termination -from definitions import get_OUTPUT_PATH, PROJECT_ROOT +from temoa._internal.run_actions import build_instance +from temoa._internal.table_writer import TableWriter +from temoa.components.costs import total_cost_rule +from temoa.data_io.hybrid_loader import HybridLoader from temoa.extensions.modeling_to_generate_alternatives.manager_factory import get_manager from temoa.extensions.modeling_to_generate_alternatives.mga_constants import MgaAxis, MgaWeighting -from temoa.extensions.modeling_to_generate_alternatives.vector_manager import VectorManager from temoa.extensions.modeling_to_generate_alternatives.worker import Worker -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.model_checking.pricing_check import price_checker -from temoa.temoa_model.run_actions import build_instance -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_model import TemoaModel -from temoa.temoa_model.temoa_rules import TotalCost_rule +from temoa.model_checking.pricing_check import price_checker logger = getLogger(__name__) -solver_options_path = Path( - PROJECT_ROOT, 'temoa/extensions/modeling_to_generate_alternatives/MGA_solver_options.toml' +solver_options_path = ( + resources.files('temoa.extensions.modeling_to_generate_alternatives') + / 'MGA_solver_options.toml' ) class MgaSequencer: + con: sqlite3.Connection + config: TemoaConfig + opt: Any + worker_solver_options: dict[str, Any] + internal_stop: bool + mga_axis: MgaAxis + mga_weighting: MgaWeighting + num_workers: int + iteration_limit: int + time_limit_hrs: float + cost_epsilon: float + solve_count: int + seen_instance_indices: set[int] + orig_label: str + writer: TableWriter + verbose: bool + def __init__(self, config: TemoaConfig): # PRELIMINARIES... # let's start with the assumption that input db = output db... this may change? @@ -87,8 +92,9 @@ def __init__(self, config: TemoaConfig): # read in the options try: - with open(solver_options_path, 'rb') as f: - all_options = tomllib.load(f) + with resources.as_file(solver_options_path) as path: + with open(path, 'rb') as f: + all_options = tomllib.load(f) s_options = all_options.get(self.config.solver_name, {}) logger.info('Using solver options: %s', s_options) @@ -103,27 +109,28 @@ def __init__(self, config: TemoaConfig): # some defaults, etc. self.internal_stop = False - axis_label = config.mga_inputs.get('axis', '').upper() + mga_inputs = self.config.mga_inputs or {} + axis_label = str(mga_inputs.get('axis', '')).upper() try: self.mga_axis = MgaAxis[axis_label] logger.info('MGA axis is set to %s.', self.mga_axis.name) except KeyError: logger.warning('No/bad MGA Axis specified. Using default: Activity by Tech Category') self.mga_axis = MgaAxis.TECH_CATEGORY_ACTIVITY - weighting_label = config.mga_inputs.get('weighting', '').upper() + weighting_label = str(mga_inputs.get('weighting', '')).upper() try: self.mga_weighting = MgaWeighting[weighting_label] logger.info('MGA weighting set to %s', self.mga_weighting.name) except KeyError: logger.warning('No/bad MGA Weighting specified. Using default: Hull Expansion') self.mga_weighting = MgaWeighting.HULL_EXPANSION - self.num_workers = all_options.get('num_workers', 1) + self.num_workers = int(cast(str | int, all_options.get('num_workers', 1))) logger.info('MGA workers are set to %s', self.num_workers) - self.iteration_limit = config.mga_inputs.get('iteration_limit', 20) + self.iteration_limit = int(cast(str | int, mga_inputs.get('iteration_limit', 20))) logger.info('Set MGA iteration limit to: %d', self.iteration_limit) - self.time_limit_hrs = config.mga_inputs.get('time_limit_hrs', 12) + self.time_limit_hrs = float(cast(str | float, mga_inputs.get('time_limit_hrs', 12))) logger.info('Set MGA time limit hours to: %0.1f', self.time_limit_hrs) - self.cost_epsilon = config.mga_inputs.get('cost_epsilon', 0.05) + self.cost_epsilon = float(cast(str | float, mga_inputs.get('cost_epsilon', 0.05))) logger.info('Set MGA cost (relaxation) epsilon to: %0.3f', self.cost_epsilon) # internal records @@ -142,7 +149,7 @@ def __init__(self, config: TemoaConfig): self.mga_weighting.name, ) - def start(self): + def start(self) -> None: """Run the sequencer""" # ==== basic sequence ==== # 1. Load the model data, which may involve filtering it down if source tracing @@ -174,7 +181,7 @@ def start(self): toc = datetime.now() elapsed = toc - tic self.solve_count += 1 - logger.info(f'Initial solve time: {elapsed.total_seconds():.4f}') + logger.info('Initial solve time: %0.4f', elapsed.total_seconds()) status = res.solver.termination_condition logger.debug('Termination condition: %s', status.name) if not check_optimal_termination(res): @@ -187,17 +194,17 @@ def start(self): self.writer.write_summary_flow(instance, iteration=0) # 3a. Capture cost and make it a constraint - tot_cost = pyo.value(instance.TotalCost) + tot_cost = pyo.value(instance.total_cost) logger.info('Completed initial solve with total cost: %0.2f', tot_cost) logger.info('Relaxing cost by fraction: %0.3f', self.cost_epsilon) # get hook on the expression generator for total cost... - cost_expression = TotalCost_rule(instance) + cost_expression = total_cost_rule(instance) instance.cost_cap = pyo.Constraint( expr=cost_expression <= (1 + self.cost_epsilon) * tot_cost ) # 3b. remove the old objective and prep for iterative solving - instance.del_component(instance.TotalCost) + instance.del_component(instance.total_cost) # 4. Instantiate the vector manager vector_manager: VectorManager = get_manager( @@ -207,34 +214,32 @@ def start(self): con=self.con, optimal_cost=tot_cost, cost_relaxation=self.cost_epsilon, + config=self.config, ) # 5. Set up the Workers num_workers = self.num_workers - work_queue = Queue(1) # restrict the queue to hold just 1 models in it max - result_queue = Queue( + work_queue: Queue[Any] = Queue(1) # restrict the queue to hold just 1 models in it max + result_queue: Queue[Any] = Queue( num_workers + 1 ) # must be able to hold a shutdown signal from all workers at once! - log_queue = Queue(50) + log_queue: Queue[Any] = Queue(50) # make workers - workers = [] - kwargs = { - 'solver_name': self.config.solver_name, - 'solver_options': self.worker_solver_options, - } + workers: list[BaseProcess] = [] # construct path for the solver logs - s_path = Path(get_OUTPUT_PATH(), 'solver_logs') + s_path = self.config.output_path / 'solver_logs' if not s_path.exists(): s_path.mkdir() - for i in range(num_workers): + for _ in range(num_workers): w = Worker( model_queue=work_queue, results_queue=result_queue, log_root_name=__name__, log_queue=log_queue, log_level=logging.INFO, + solver_name=self.config.solver_name, + solver_options=self.worker_solver_options, solver_log_path=s_path, - **kwargs, ) w.start() workers.append(w) @@ -258,7 +263,7 @@ def start(self): next_result = None # print('no result') if next_result is not None: - vector_manager.process_results(M=next_result) + vector_manager.process_results(model=next_result) self.process_solve_results(next_result) logger.info('Solve count: %d', self.solve_count) self.solve_count += 1 @@ -302,7 +307,7 @@ def start(self): next_result = None if next_result is not None and next_result != 'COYOTE': logger.debug('bagged a result post-shutdown') - vector_manager.process_results(M=next_result) + vector_manager.process_results(model=next_result) self.process_solve_results(next_result) logger.info('Solve count: %d', self.solve_count) self.solve_count += 1 @@ -318,8 +323,8 @@ def start(self): if empty == num_workers: break - for w in workers: - w.join() + for proc_join in workers: + proc_join.join() logger.debug('worker wrapped up...') log_queue.close() @@ -350,21 +355,27 @@ def solve_instance(self, instance: TemoaModel) -> bool: elapsed.total_seconds(), status.name, ) - return status == pyo.TerminationCondition.optimal + return status == pyo.TerminationCondition.optimal or \ + str(status) == 'convergenceCriteriaSatisfied' - def process_solve_results(self, instance: TemoaModel): + def process_solve_results(self, instance: TemoaModel) -> None: """write the results as required""" # get the instance number from the model name, if provided if '-' not in instance.name: raise ValueError( - 'Instance name does not appear to contain a -idx value. The manager should be tagging/updating this' + 'Instance name does not appear to contain a -idx value. The manager should be ' + 'tagging/updating this' ) idx = int(instance.name.split('-')[-1]) if idx in self.seen_instance_indices: raise ValueError('Instance index already seen. Likely coding error') self.seen_instance_indices.add(idx) - self.writer.write_capacity_tables(M=instance, iteration=idx) + self.writer.write_capacity_tables(model=instance, iteration=idx) self.writer.write_summary_flow(instance, iteration=idx) - def __del__(self): - self.con.close() + def __del__(self) -> None: + if hasattr(self, 'con') and self.con is not None: + try: + self.con.close() + except Exception: + pass diff --git a/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py b/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py index 8c23d8441..62f9a99a4 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py +++ b/temoa/extensions/modeling_to_generate_alternatives/tech_activity_vector_manager.py @@ -1,48 +1,26 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling +from __future__ import annotations -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/16/24 - -""" import queue -import sqlite3 from collections import defaultdict -from collections.abc import Iterator from logging import getLogger -from pathlib import Path from queue import Queue -from typing import Iterable +from typing import TYPE_CHECKING, Any import numpy as np from matplotlib import pyplot as plt -from pyomo.core import Expression, Var, value, Objective, quicksum +from pyomo.core import Expression, Objective, Var, quicksum, value -from definitions import get_OUTPUT_PATH from temoa.extensions.modeling_to_generate_alternatives.hull import Hull from temoa.extensions.modeling_to_generate_alternatives.mga_constants import MgaWeighting from temoa.extensions.modeling_to_generate_alternatives.vector_manager import VectorManager -from temoa.temoa_model.temoa_model import TemoaModel + +if TYPE_CHECKING: + import sqlite3 + from collections.abc import Iterable, Iterator, Mapping, Sequence + + from temoa.core.config import TemoaConfig + from temoa.core.model import TemoaModel + logger = getLogger(__name__) @@ -53,10 +31,10 @@ class DefaultItem: def __init__(self, name: str): self.name = name - def __str__(self): + def __str__(self) -> str: return self.name - def __repr__(self): + def __repr__(self) -> str: return self.name @@ -66,6 +44,23 @@ def __repr__(self): class TechActivityVectorManager(VectorManager): + completed_solves: int + conn: sqlite3.Connection + base_model: TemoaModel + optimal_cost: float + cost_relaxation: float + config: TemoaConfig + generation_index: int + category_mapping: dict[str | DefaultItem, list[str]] + technology_size: dict[str, int] + variable_index_mapping: dict[str, dict[str, list[tuple[Any, ...]]]] + coefficient_vector_queue: Queue[np.ndarray] + hull_points: np.ndarray | None + hull: Hull | None + basis_coefficients: Queue[np.ndarray] + hull_monitor: bool + perf_data: dict[int, float] + def __init__( self, conn: sqlite3.Connection, @@ -73,36 +68,38 @@ def __init__( weighting: MgaWeighting, optimal_cost: float, cost_relaxation: float, - ): + config: TemoaConfig, + ) -> None: self.completed_solves = 0 self.conn = conn self.base_model = base_model self.optimal_cost = optimal_cost self.cost_relaxation = cost_relaxation + self.config = config self.generation_index = 1 # index of how many models generated to couple inputs-outputs # {category : [technology, ...]} # the number of keys in this are the dimension of the hull - self.category_mapping: dict | None = None + self.category_mapping = defaultdict(list) # {technology: [number of associated variables, ...]} - self.technology_size: dict[str, int] = defaultdict(int) + self.technology_size = defaultdict(int) # in order to peel the data out of a solved model, we also need a rollup of the NAME # of the variable and indices in order... # {tech : {var_name : [indices, ...]}, ...} - self.variable_index_mapping: dict[str, dict[str, list]] = {} + self.variable_index_mapping = {} - self.coefficient_vector_queue: Queue[np.ndarray] = Queue() + self.coefficient_vector_queue = Queue() if weighting != MgaWeighting.HULL_EXPANSION: raise NotImplementedError( 'Tech Activity currently only works with Hull Expansion weighting' ) - self.hull_points: np.ndarray | None = None - self.hull: Hull | None = None + self.hull_points = None + self.hull = None self.initialize() - self.basis_coefficients: Queue[np.ndarray] = self._generate_basis_coefficients( + self.basis_coefficients = self._generate_basis_coefficients( self.category_mapping, self.technology_size ) @@ -116,7 +113,7 @@ def initialize(self) -> None: Fill the internal data stores from db and model :return: """ - self.basis_coefficients = [] + # self.basis_coefficients = [] # Removed inconsistent assignment techs_implemented = self.base_model.tech_all # some may have been culled by source tracing logger.debug('Initializing Technology Vectors data elements') raw = self.conn.execute('SELECT category, tech FROM Technology').fetchall() @@ -133,22 +130,22 @@ def initialize(self) -> None: logger.debug('Category %s members: %d', cat, len(self.category_mapping[cat])) # now pull the flow variables and map them - for idx in self.base_model.activeFlow_rpsditvo: + for idx in self.base_model.active_flow_rpsditvo or set(): tech = idx[5] self.technology_size[tech] += 1 - self.variable_index_mapping[tech][self.base_model.V_FlowOut.name].append(idx) - for idx in self.base_model.activeFlow_rpitvo: - tech = idx[3] + self.variable_index_mapping[tech][self.base_model.v_flow_out.name].append(idx) + for idx_annual in self.base_model.active_flow_rpitvo or set(): + tech = idx_annual[3] self.technology_size[tech] += 1 - self.variable_index_mapping[tech][self.base_model.V_FlowOutAnnual.name].append(idx) + self.variable_index_mapping[tech][self.base_model.v_flow_out_annual.name].append(idx_annual) logger.debug('Catalogued %d Technology Variables', sum(self.technology_size.values())) @property def expired(self) -> bool: return False # this Manager can always generate more... - def group_variable_names(self, tech) -> list[Var]: - return list(self.category_mapping.keys()) + def group_variable_names(self, tech: str) -> list[str]: + return list(self.variable_index_mapping.get(tech, {}).keys()) def random_input_vector_model(self) -> TemoaModel: new_model = self.base_model.clone() @@ -156,7 +153,7 @@ def random_input_vector_model(self) -> TemoaModel: var_vec = self.var_vector(new_model) coeffs = np.random.random(len(var_vec)) coeffs /= sum(coeffs) - obj_expr = quicksum(c * v for c, v in zip(coeffs, var_vec)) + obj_expr = quicksum(c * v for c, v in zip(coeffs, var_vec, strict=False)) new_model.obj = Objective(expr=obj_expr) return new_model @@ -176,13 +173,16 @@ def model_generator(self) -> Iterator[TemoaModel]: obj_vector = self._make_basis_objective_vector(new_model) # if asking for more, we *should* have enough data to create a good hull now... - while self.completed_solves <= 2 * len(self.category_mapping): - # some of the basis vectors must have "crashed" or timed out... - # supply random vectors until we have sufficient number of solved models to make hull - logger.info( - 'Adding random vectors to augment the basis. Some basis solves may have crashed...' - ) - yield self.random_input_vector_model() + if len(self.category_mapping) > 0: + while self.completed_solves <= 2 * len(self.category_mapping): + # some of the basis vectors must have "crashed" or timed out... + # supply random vectors until we have sufficient number of + # solved models to make hull + logger.info( + 'Adding random vectors to augment the basis.' + 'Some basis solves may have crashed...' + ) + yield self.random_input_vector_model() logger.info('Generating hull points') self.regenerate_hull() @@ -190,9 +190,9 @@ def model_generator(self) -> Iterator[TemoaModel]: while True: new_model = self.base_model.clone() new_model.name = self.new_model_name() - v = self._next_objective_vector(M=new_model) + v = self._next_objective_vector(model=new_model) if v is None: - yield None + return new_model.obj = Objective(expr=v) yield new_model @@ -203,19 +203,19 @@ def new_model_name(self) -> str: self.generation_index += 1 return new_name - def process_results(self, M: TemoaModel): + def process_results(self, model: TemoaModel) -> list[float]: """ retrieve the necessary variable values to make another hull point :param M: :return: None """ self.completed_solves += 1 - res = [] + res: list[float] = [] for cat in self.category_mapping: element = 0 for tech in self.category_mapping[cat]: for var_name in self.variable_index_mapping[tech]: - model_var = M.find_component(var_name) + model_var = model.find_component(var_name) if not isinstance(model_var, Var): raise RuntimeError('hooked a bad fish') element += sum( @@ -234,18 +234,19 @@ def process_results(self, M: TemoaModel): return res def stop_resolving(self) -> bool: - pass + return False @property - def groups(self) -> Iterable[str]: + def groups(self) -> Iterable[Any]: return self.category_mapping.keys() - def group_members(self, group) -> list[str]: + def group_members(self, group: str | DefaultItem) -> list[str]: return self.category_mapping.get(group, []) # noinspection PyTypeChecker - def _make_basis_objective_vector(self, M: TemoaModel) -> Iterable[Expression] | None: - """generator for basis vectors which will be the coefficients in the obj expression in the basis solves""" + def _make_basis_objective_vector(self, model: TemoaModel) -> Iterable[Expression] | None: + """generator for basis vectors which will be the coefficients in the obj expression in the + basis solves""" if self.basis_coefficients.empty(): return None try: @@ -254,15 +255,15 @@ def _make_basis_objective_vector(self, M: TemoaModel) -> Iterable[Expression] | return None # now we need to roll out a vector of the variables and pair them with coefficients... - vars = self.var_vector(M) + vars = self.var_vector(model) # verify a unit vector err = abs(abs(sum(coeffs)) - 1) assert err < 1e-6, 'unit vector size error' - expr = sum(c * v for v, c in zip(vars, coeffs) if c != 0) + expr = sum(c * v for v, c in zip(vars, coeffs, strict=False) if c != 0) return expr - def _next_objective_vector(self, M: TemoaModel) -> Expression | None: + def _next_objective_vector(self, model: TemoaModel) -> Expression | None: if self.coefficient_vector_queue.qsize() <= 3: logger.info('running low on input vectors... refreshing the vectors with new hull') self.regenerate_hull() @@ -271,29 +272,30 @@ def _next_objective_vector(self, M: TemoaModel) -> Expression | None: vector = self.coefficient_vector_queue.get() # translate the norm vector into coefficients - coeffs = [] + coeffs_list = [] for idx, cat in enumerate(self.category_mapping): for tech in self.category_mapping[cat]: reps = self.technology_size[tech] element = [ vector[idx], ] * reps - coeffs.extend(element) - coeffs = np.array(coeffs) + coeffs_list.extend(element) + coeffs = np.array(coeffs_list) coeffs /= np.sum(coeffs) # normalize - obj_vars = self.var_vector(M) + obj_vars = self.var_vector(model) assert len(obj_vars) == len(coeffs) - return quicksum(c * v for v, c in zip(obj_vars, coeffs)) + return quicksum(c * v for v, c in zip(obj_vars, coeffs, strict=False)) - def var_vector(self, M: TemoaModel) -> list[Var]: - """Produce a properly sequenced array of variables from the current model for use in obj vector""" + def var_vector(self, model: TemoaModel) -> list[Any]: + """Produce a properly sequenced array of variables from the current model for use in obj + vector""" res = [] for cat in self.category_mapping: for tech in self.category_mapping[cat]: for var_name in self.variable_index_mapping[tech]: - var = M.find_component(var_name) + var = model.find_component(var_name) if not isinstance(var, Var): raise RuntimeError( 'Failed to retrieve a named variable from the model: %s', var_name @@ -302,8 +304,11 @@ def var_vector(self, M: TemoaModel) -> list[Var]: res.append(var[idx]) return res - def regenerate_hull(self): + def regenerate_hull(self) -> None: """make the hull...""" + if self.hull_points is None: + logger.warning('Cannot regenerate hull: no points available') + return logger.debug('Generating the cvx hull from %d points', len(self.hull_points)) self.hull = Hull(self.hull_points) fresh_vecs = self.hull.get_all_norms() @@ -316,7 +321,7 @@ def regenerate_hull(self): ) self.load_normals(fresh_vecs) - def load_normals(self, normals: np.array): + def load_normals(self, normals: np.ndarray) -> None: for vector in normals: self.coefficient_vector_queue.put(vector) @@ -324,9 +329,13 @@ def input_vectors_available(self) -> int: return self.coefficient_vector_queue.qsize() @staticmethod - def _generate_basis_coefficients(category_mapping: dict, technology_size: dict) -> Queue: - # Sequentially build the coefficient vector in the order of the categories and associated techs - q = Queue() + def _generate_basis_coefficients( + category_mapping: Mapping[Any, Sequence[str]], + technology_size: Mapping[str, int], + ) -> Queue[np.ndarray]: + # Sequentially build the coefficient vector in the order of the categories and associated + # techs + q: Queue[np.ndarray] = Queue() for selected_cat in category_mapping: res = [] if selected_cat == default_cat: @@ -350,19 +359,20 @@ def _generate_basis_coefficients(category_mapping: dict, technology_size: dict) return q - def tracker(self): + def tracker(self) -> None: """ A little function to track the size of the hull, after it is built initially Note: This hull is a "throw away" and only used for volume calc, but it is pretty quick """ - if self.hull is not None: # don't try until after first hull is built + if self.hull is not None and \ + self.hull_points is not None: # don't try until after first hull is built hull = Hull(self.hull_points) volume = hull.volume - logger.info(f'Tracking hull at {volume}') + logger.info('Tracking hull at %0.2f', volume) self.perf_data.update({len(self.hull_points): volume}) - def finalize_tracker(self): - fout = Path(get_OUTPUT_PATH(), 'hull_performance.png') + def finalize_tracker(self) -> None: + fout = self.config.output_path / 'hull_performance.png' pts = sorted(self.perf_data.keys()) y = [self.perf_data[pt] for pt in pts] plt.plot(pts, y) diff --git a/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py b/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py index 9284e11fa..3b0d1ea27 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py +++ b/temoa/extensions/modeling_to_generate_alternatives/vector_manager.py @@ -1,36 +1,16 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/15/24 - An ABC to serve as a framework for future Vector Managers """ -import sqlite3 +from __future__ import annotations + from abc import ABC, abstractmethod -from collections.abc import Iterator, Iterable +from typing import TYPE_CHECKING, Any -from temoa.temoa_model.temoa_model import TemoaModel +if TYPE_CHECKING: + import sqlite3 + from collections.abc import Iterable, Iterator + + from temoa.core.model import TemoaModel class VectorManager(ABC): @@ -41,7 +21,7 @@ def __init__( base_model: TemoaModel, optimal_cost: float, cost_relaxation: float, - ): + ) -> None: """ Initialize a new manager :param conn: connection to the current database @@ -58,7 +38,7 @@ def groups(self) -> Iterable[str]: raise NotImplementedError() @abstractmethod - def group_members(self, group) -> list[str]: + def group_members(self, group: str) -> list[str]: """The members (by string name) in the group""" raise NotImplementedError() @@ -69,9 +49,10 @@ def expired(self) -> bool: Indicator that this manager has no more vectors to generate :return: True if expired """ + raise NotImplementedError() @abstractmethod - def group_variable_names(self, tech) -> list[str]: + def group_variable_names(self, tech: str) -> list[str]: """The variable NAMES associated with the individual group members""" raise NotImplementedError() @@ -86,10 +67,10 @@ def model_generator(self) -> Iterator[TemoaModel]: raise NotImplementedError('the manager subclass must implement instance_generator') @abstractmethod - def process_results(self, M: TemoaModel): + def process_results(self, model: TemoaModel) -> Any: raise NotImplementedError('the manager subclass must implement process_results') @abstractmethod - def finalize_tracker(self): + def finalize_tracker(self) -> None: """Finalize any tracker employed by the manager""" pass diff --git a/temoa/extensions/modeling_to_generate_alternatives/worker.py b/temoa/extensions/modeling_to_generate_alternatives/worker.py index 6632032bf..efb1c3ff6 100644 --- a/temoa/extensions/modeling_to_generate_alternatives/worker.py +++ b/temoa/extensions/modeling_to_generate_alternatives/worker.py @@ -1,82 +1,78 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 5/5/24 - Class to contain Workers that execute solves in separate processes - """ +from __future__ import annotations + import logging.handlers from datetime import datetime from logging import getLogger from multiprocessing import Process, Queue from pathlib import Path +from typing import TYPE_CHECKING, Any from pyomo.opt import SolverFactory, SolverResults, check_optimal_termination -from temoa.temoa_model.temoa_model import TemoaModel +if TYPE_CHECKING: + from temoa.core.model import TemoaModel verbose = False # for T/S or monitoring... class Worker(Process): - worker_idx = 1 + worker_idx: int = 1 + worker_number: int + model_queue: Queue[Any] + results_queue: Queue[Any] + log_queue: Queue[logging.LogRecord] + solver_name: str + solver_options: dict[str, Any] + solver_log_path: Path | None + opt: Any + log_root_name: str + log_level: int + solve_count: int def __init__( self, - model_queue: Queue, - results_queue: Queue, - log_root_name, - log_queue, - log_level, + model_queue: Queue[Any], + results_queue: Queue[Any], + log_root_name: str, + log_queue: Queue[logging.LogRecord], + log_level: int = logging.INFO, + solver_name: str = 'appsi_highs', + solver_options: dict[str, Any] | None = None, solver_log_path: Path | None = None, - **kwargs, ): - super(Worker, self).__init__(daemon=True) + super().__init__(daemon=True) self.worker_number = Worker.worker_idx Worker.worker_idx += 1 - self.model_queue: Queue = model_queue - self.results_queue: Queue = results_queue - self.solver_name = kwargs['solver_name'] - self.solver_options = kwargs['solver_options'] - self.opt = SolverFactory(self.solver_name, options=self.solver_options) + self.model_queue = model_queue + self.results_queue = results_queue self.log_queue = log_queue - self.log_level = log_level - self.root_logger_name = log_root_name + self.solver_name = solver_name + self.solver_options = solver_options or {} self.solver_log_path = solver_log_path + self.opt = None # Initialize in run() + + self.log_root_name = log_root_name + self.log_level = log_level self.solve_count = 0 - def run(self): - logger = getLogger('.'.join((self.root_logger_name, 'worker', str(self.worker_number)))) - logger.setLevel(self.log_level) - logger.propagate = ( - False # not propagating up the chain fixes issue on TRACE where we were getting dupes. - ) + def run(self) -> None: + logger: logging.Logger = getLogger('.'.join( + (self.log_root_name, 'worker', str(self.worker_number)))) + logger.propagate = False # prevent duplicate logs + # add a handler that pushes to the queue handler = logging.handlers.QueueHandler(self.log_queue) + logger.setLevel(self.log_level) logger.addHandler(handler) logger.info('Worker %d spun up', self.worker_number) + # Initialize the solver here in the child process to avoid pickling issues + # Note: We do not set the options here because appsi_highs does not accept options in + # __init__. We can set them later via self.opt.options + self.opt = SolverFactory(self.solver_name) + # update the solver options to pass in a log location while True: if self.solver_log_path: @@ -85,10 +81,10 @@ def run(self): self.solver_log_path, f'solver_log_{str(self.worker_number)}_{self.solve_count}.log', ) - log_location = str(log_location) + log_location_str = str(log_location) match self.solver_name: case 'gurobi': - self.solver_options.update({'LogFile': log_location}) + self.solver_options.update({'LogFile': log_location_str}) # case 'appsi_highs': # self.solver_options.update({'log_file': log_location}) case _: @@ -106,7 +102,7 @@ def run(self): tic = datetime.now() try: self.solve_count += 1 - res: SolverResults | None = self.opt.solve(model) + solve_res: SolverResults | None = self.opt.solve(model) except Exception as e: if verbose: @@ -117,12 +113,12 @@ def run(self): model.name, e, ) - res = None + solve_res = None toc = datetime.now() # guard against a bad "res" object... try: - good_solve = check_optimal_termination(res) + good_solve = check_optimal_termination(solve_res) if good_solve: self.results_queue.put(model) logger.info( @@ -133,9 +129,11 @@ def run(self): if verbose: print(f'Worker {self.worker_number} completed a successful solve') else: - status = res['Solver'].termination_condition - logger.info( - 'Worker %d did not solve. Results status: %s', self.worker_number, status - ) + if solve_res is not None: + status = solve_res['Solver'].termination_condition + logger.info( + 'Worker %d did not solve. Results status: %s', + self.worker_number, status + ) except AttributeError: pass diff --git a/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py b/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py index e1d51d3e2..08b688946 100644 --- a/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py +++ b/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py @@ -1,48 +1,42 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/11/24 - Simple analyzer--example only - """ +from importlib import resources from math import sqrt from pathlib import Path from sqlite3 import Connection from matplotlib import pyplot as plt -from definitions import PROJECT_ROOT - scenario_name = 'Purple Onion' # must match config file -db_path = Path(PROJECT_ROOT, 'data_files/example_dbs/utopia.sqlite') -with Connection(db_path) as conn: +# To run this example, ensure tutorial_database.sqlite is in your current directory. +# You can generate it using: temoa tutorial +# IMPORTANT: You must also run the model to populate results before analyzing: +# temoa run tutorial_config.toml +db_resource = 'tutorial_database.sqlite' + +if not Path(db_resource).exists(): + raise FileNotFoundError( + f"Database file '{db_resource}' not found. " + "Please run 'temoa tutorial' to create the base files." + ) + +with Connection(db_resource) as conn: cur = conn.cursor() + # Check if results exist before attempting to plot obj_values = cur.execute( - f"SELECT total_system_cost FROM OutputObjective WHERE scenario LIKE '{scenario_name}-%'" + "SELECT total_system_cost FROM output_objective WHERE scenario LIKE ?", + (f"{scenario_name}-%",) ).fetchall() - obj_values = tuple(t[0] for t in obj_values) -plt.hist(obj_values, bins=int(sqrt(len(obj_values)))) + if len(obj_values) == 0: + raise RuntimeError( + f"No results found for scenario '{scenario_name}-*' in '{db_resource}'. " + "Please run 'temoa run tutorial_config.toml' or run the tutorial model " + "to populate output_objective with results first." + ) + + obj_values_tuple = tuple(t[0] for t in obj_values) + +plt.hist(obj_values_tuple, bins=int(sqrt(len(obj_values_tuple)))) plt.show() diff --git a/temoa/extensions/monte_carlo/example_builds/scenario_maker.py b/temoa/extensions/monte_carlo/example_builds/scenario_maker.py index c93d53e1c..0e72dd483 100644 --- a/temoa/extensions/monte_carlo/example_builds/scenario_maker.py +++ b/temoa/extensions/monte_carlo/example_builds/scenario_maker.py @@ -1,28 +1,4 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/11/24 This file is intended to be a simple EXAMPLE for testing mainly of how one might make a set of runs for a Monte Carlo simulation. @@ -30,23 +6,25 @@ This scenario is based on Utopia with the following random variables: Cost of Imported Oil will have a normal distribution of *relative changes* applied in all periods -Residential Heating (RH) will have a similar distribution, with some negative correlation (seems logical that -there is some hidden price sensitivity, even though RH could be satisfied from electricity as well.) - -Additionally, we will assume there is an independent 20% chance that the govt will subsidize new nuclear -power by: - (a) subsidizing the cost of any Investment Cost by 40%, in the out-years of 2000, 2010 (but not 1990) +Residential Heating (RH) will have a similar distribution, with some negative correlation (seems +logical that there is some hidden price sensitivity, even though RH could be satisfied from +electricity as well.) + +Additionally, we will assume there is an independent 20% chance that the govt will subsidize new +nuclear power by: + (a) subsidizing the cost of any Investment Cost by 40%, in the out-years of 2000, 2010 (but not + 1990) (b) paying all fixed costs in the same years. Let's make a set of 500 runs and explore output """ + from pathlib import Path import matplotlib.pyplot as plt import numpy as np - -from definitions import PROJECT_ROOT +from typing import cast # distro for the related cost vars @@ -56,7 +34,8 @@ num_runs = 1000 cov = np.array([[0.4, -0.1], [-0.1, 0.1]]) price_devs = np.random.multivariate_normal([0, 0], cov, size=num_runs) -print(f'correlation check: {np.corrcoef(price_devs.T)[0, 1]}') +corr_matrix = cast(np.typing.NDArray[np.float64], np.corrcoef(price_devs.T)) +print(f'correlation check: {corr_matrix[0, 1]}') # verify with a peek... plt.plot(price_devs[:, 0], price_devs[:, 1], '.', alpha=0.5) @@ -68,16 +47,20 @@ nuc_dev = np.random.binomial(n=1, p=0.20, size=num_runs) * -0.4 # put it together... -file_loc = Path(PROJECT_ROOT) / 'data_files/monte_carlo/run_settings_2.csv' +file_loc = Path.cwd() / 'run_settings_2.csv' with open(file_loc, 'w') as f: f.write('run,param,index,mod,value,notes\n') for run_idx in range(num_runs): f.write( - f'{run_idx+1},CostVariable,*|*|IMPOIL1|*,r,{price_devs[run_idx, 0]},oil relative change\n' + f'{run_idx + 1},cost_variable,*|*|IMPOIL1|*,r,{price_devs[run_idx, 0]},oil relative ' + 'change\n' + ) + f.write( + f'{run_idx + 1},Demand,*|*|RH,r,{price_devs[run_idx, 1]},res heat relative change\n' ) - f.write(f'{run_idx+1},Demand,*|*|RH,r,{price_devs[run_idx, 1]},res heat relative change\n') f.write( - f'{run_idx+1},CostInvest,*|E21|2000/2010,r,{nuc_dev[run_idx]},nuclear invest relative discount\n' + f'{run_idx + 1},cost_invest,*|E21|2000/2010,r,{nuc_dev[run_idx]},nuclear invest ' + 'relative discount\n' ) if nuc_dev[run_idx] < 0: - f.write(f'{run_idx+1},CostFixed,*|*|E21|2000/2010,s,0.0,nuclear op cost covered\n') + f.write(f'{run_idx + 1},cost_fixed,*|*|E21|2000/2010,s,0.0,nuclear op cost covered\n') diff --git a/temoa/extensions/monte_carlo/make_deltas_table.sql b/temoa/extensions/monte_carlo/make_deltas_table.sql index cb2fc304b..0e4396239 100644 --- a/temoa/extensions/monte_carlo/make_deltas_table.sql +++ b/temoa/extensions/monte_carlo/make_deltas_table.sql @@ -1,6 +1,6 @@ BEGIN; -CREATE TABLE IF NOT EXISTS OutputMCDelta +CREATE TABLE IF NOT EXISTS output_mc_delta ( scenario TEXT NOT NULL, run INT NOT NULL, @@ -11,4 +11,4 @@ CREATE TABLE IF NOT EXISTS OutputMCDelta ); -COMMIT; \ No newline at end of file +COMMIT; diff --git a/temoa/extensions/monte_carlo/mc_run.py b/temoa/extensions/monte_carlo/mc_run.py index 33b2b6bab..7c2874a7a 100644 --- a/temoa/extensions/monte_carlo/mc_run.py +++ b/temoa/extensions/monte_carlo/mc_run.py @@ -1,42 +1,23 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/9/24 """ -from collections import namedtuple, defaultdict -from collections.abc import Generator +from __future__ import annotations + +from collections import defaultdict, namedtuple from itertools import product from logging import getLogger from pathlib import Path +from typing import TYPE_CHECKING, Any, cast + +from temoa.core.model import TemoaModel +from temoa.data_io.hybrid_loader import HybridLoader + +if TYPE_CHECKING: + from collections.abc import Generator -from pyomo.dataportal import DataPortal + from pyomo.dataportal import DataPortal -from definitions import PROJECT_ROOT -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_model import TemoaModel + from temoa.core.config import TemoaConfig logger = getLogger(__name__) @@ -46,13 +27,20 @@ """a record of a data element change, for an element acted on by a Tweak""" + + class Tweak: """ objects of this class represent individual tweaks to single (or wildcard) data elements for a Monte Carlo run """ - def __init__(self, param_name: str, indices: tuple, adjustment: str, value: float): + param_name: str + indices: tuple[Any, ...] + adjustment: str + value: float + + def __init__(self, param_name: str, indices: tuple[Any, ...], adjustment: str, value: float): if not isinstance(indices, tuple): raise TypeError('indices must be a tuple') if adjustment not in {'r', 'a', 's'}: @@ -65,8 +53,11 @@ def __init__(self, param_name: str, indices: tuple, adjustment: str, value: floa self.adjustment = adjustment self.value = value - def __repr__(self): - return f'' + def __repr__(self) -> str: + return ( + f'' + ) class TweakFactory: @@ -74,7 +65,9 @@ class TweakFactory: factor (likely a singleton) to manufacture Tweaks from input data """ - def __init__(self, data_store: dict): + val_data: dict[str, Any] + + def __init__(self, data_store: dict[str, Any]): """ make a new factor and use data_store as a validation tool :param data_store: the data dictionary holding the base values for the model @@ -82,7 +75,6 @@ def __init__(self, data_store: dict): if not isinstance(data_store, dict): raise TypeError('data_store must be a dict') self.val_data = data_store - tweak_dict: dict[int, list[Tweak]] = defaultdict(list) def make_tweaks(self, row_number: int, row: str) -> tuple[int, list[Tweak]]: """ @@ -96,9 +88,8 @@ def make_tweaks(self, row_number: int, row: str) -> tuple[int, list[Tweak]]: p_index = rd.indices.replace('(', '').replace(')', '') # remove any optional parens tokens = p_index.split('|') tokens = [t.strip() for t in tokens] - tweaks = [] # locate all 'multi' indices... - index_vals: dict[int, list] = defaultdict(list) + index_vals: dict[int, list[Any]] = defaultdict(list) for pos, token in enumerate(tokens): if '/' in token: # it is a multi-token sub_tokens = token.split('/') @@ -138,32 +129,35 @@ def row_parser(self, row_number: int, row: str) -> RowData: # check length if len(tokens) != 6: raise ValueError( - f'Incorrect number of tokens for row {row_number}. Did you omit notes / trailing comma for no notes or have a comma in your note?' + f'Error parsing line {row_number}. Did you omit notes / trailing comma for no ' + 'notes or have a comma in your note?' ) # convert the run number try: - tokens[0] = int(tokens[0]) - except ValueError: - raise ValueError(f'run number at row {row_number} must be an integer') + run_num = int(tokens[0]) + except ValueError as err: + raise ValueError(f'run number at row {row_number} must be an integer') from err # convert the value try: - tokens[-2] = float(tokens[-2]) - except ValueError: - raise ValueError('value at row {idx} must be numeric') - rd = RowData(*tokens) + val = float(tokens[-2]) + except ValueError as err: + raise ValueError(f'value at line {row_number} must be numeric') from err + rd = RowData(run_num, tokens[1], tokens[2], tokens[3], val, tokens[5]) # make other checks... if rd.param_name not in self.val_data: # the param name should be a key value in the data dictionary raise ValueError( - f'param_name at index: {row_number} is either invalid or not represented in the input dataset' + f'param_name at index: {row_number} is either invalid or not represented in the ' + 'input dataset' ) if rd.adjustment not in {'r', 'a', 's'}: raise ValueError(f'adjustment at index {row_number} must be either r/a/s') # check for no "empty" indices in the index if '||' in rd.indices: raise ValueError( - f'indices at index {row_number} cannot contain empty marker: ||. Did you mean to put in wildcard "*"?' + f'indices at index {row_number} cannot contain empty marker: ||. Did you mean to ' + 'put in wildcard "*"?' ) return rd @@ -173,11 +167,16 @@ class MCRun: A Container class to hold the data (and more?) to support a model build + run """ + scenario_name: str + run_index: int + data_store: dict[str, Any] + included_tweaks: dict[Tweak, list[ChangeRecord]] + def __init__( self, scenario_name: str, run_index: int, - data_store: dict, + data_store: dict[str, Any], included_tweaks: dict[Tweak, list[ChangeRecord]], ): self.scenario_name = scenario_name @@ -217,22 +216,39 @@ class MCRunFactory: They will hold the "data tweaks" gathered from input file for application to the base data """ - def __init__(self, config: TemoaConfig, data_store: dict): + config: TemoaConfig + data_store: dict[str, Any] + tweak_factory: TweakFactory + settings_file: Path + + def __init__(self, config: TemoaConfig, data_store: dict[str, Any]): self.config = config self.data_store = data_store self.tweak_factory = TweakFactory(data_store) - self.settings_file = PROJECT_ROOT / Path(self.config.monte_carlo_inputs['run_settings']) - def prescreen_input_file(self): + if not config.monte_carlo_inputs: + raise ValueError("Monte Carlo mode requires 'monte_carlo_inputs' in the configuration.") + + settings_path = config.monte_carlo_inputs.get('run_settings') + if not settings_path: + raise ValueError( + "Monte Carlo mode requires 'run_settings' path in 'monte_carlo_inputs'." + ) + + self.settings_file = Path(cast(str, settings_path)) + if not self.settings_file.exists(): + raise FileNotFoundError(f'Monte Carlo run settings file not found: {self.settings_file}') + + def prescreen_input_file(self) -> bool: """ read the input csv file and screen common errors :return: True if file passes, false otherwise with log entries """ - with open(self.settings_file, 'r') as f: + with open(self.settings_file) as f: header = f.readline().strip() - assert ( - header == 'run,param,index,mod,value,notes' - ), 'header should be: run,param,index,mod,value,notes' + assert header == 'run,param,index,mod,value,notes', ( + 'header should be: run,param,index,mod,value,notes' + ) current_run = -1 for idx, row in enumerate(f.readlines(), start=2): rd = self.tweak_factory.row_parser(idx, row) @@ -243,15 +259,15 @@ def prescreen_input_file(self): raise ValueError(f'Run sequence violation at row {idx}') elif current_run < rd.run: current_run = rd.run - logger.info(f'Pre-screen of data file: {self.settings_file} successful.') + logger.info('Pre-screen of data file: %s successful.', self.settings_file) return True - def _next_row_generator(self) -> Generator[tuple[int, str], None, None]: + def _next_row_generator(self) -> Generator[tuple[int, str]]: """ A generator to read lines from thr run settings file :return: """ - with open(self.settings_file, 'r') as f: + with open(self.settings_file) as f: # burn header f.readline() idx = 2 @@ -259,7 +275,7 @@ def _next_row_generator(self) -> Generator[tuple[int, str], None, None]: yield idx, line idx += 1 - def tweak_set_generator(self) -> tuple[int, list[Tweak]]: + def tweak_set_generator(self) -> Generator[tuple[int, list[Tweak]]]: """ generator for lists of tweaks per run :return: @@ -286,7 +302,9 @@ def tweak_set_generator(self) -> tuple[int, list[Tweak]]: current_run = run_number @staticmethod - def element_locator(data_store: dict, param: str, target_index: tuple) -> list[tuple]: + def element_locator( + data_store: dict[str, Any], param: str, target_index: tuple[Any, ...] + ) -> list[tuple[Any, ...]]: """ find the associated indices that match the index, which may contain wildcards @@ -307,13 +325,14 @@ def element_locator(data_store: dict, param: str, target_index: tuple) -> list[t first_index = tuple(param_data.keys())[0] if len(target_index) != len(first_index): raise ValueError( - f'length of search index {target_index} for parameter {param} does not match data ex: {first_index}' + f'length of search index {target_index} for parameter {param} does not match data ' + f'ex: {first_index}' ) raw_indices = param_data.keys() matches = [ k for k in raw_indices - if all((k[idx] == target_index[idx] for idx in non_wildcard_locs)) + if all(k[idx] == target_index[idx] for idx in non_wildcard_locs) ] return matches @@ -330,7 +349,7 @@ def _adjust_value(old_value: float, adjust_type: str, factor: float) -> float: raise ValueError(f'Unsupported adjustment type {adjust_type}') return res - def run_generator(self) -> Generator[MCRun, None, None]: + def run_generator(self) -> Generator[MCRun]: """ make a new MC Run, log problems with tweaks and write successful tweaks to the DB Output @@ -352,9 +371,10 @@ def run_generator(self) -> Generator[MCRun, None, None]: failed_tweaks.append(tweak) else: for index in matching_indices: - old_value = data_store.get(tweak.param_name)[index] + param_vals = data_store[tweak.param_name] + old_value = param_vals[index] new_value = self._adjust_value(old_value, tweak.adjustment, tweak.value) - data_store[tweak.param_name][index] = new_value + param_vals[index] = new_value good_tweaks[tweak].append( ChangeRecord(tweak.param_name, index, old_value, new_value) ) @@ -367,9 +387,10 @@ def run_generator(self) -> Generator[MCRun, None, None]: for tweak in failed_tweaks: logger.warning('Failed tweak: %s', tweak) - # skip the creation of the run if no tweaks were successful (it would just be the baseline run...) + # skip the creation of the run if no tweaks were successful (it would just be the + # baseline run...) if not good_tweaks: - logger.warning(f'Aborting run: {run}. No good tweaks found') + logger.warning('Aborting run: %s. No good tweaks found', run) continue mc_run = MCRun( scenario_name=self.config.scenario, diff --git a/temoa/extensions/monte_carlo/mc_sequencer.py b/temoa/extensions/monte_carlo/mc_sequencer.py index 80cffaef6..b770bbdab 100644 --- a/temoa/extensions/monte_carlo/mc_sequencer.py +++ b/temoa/extensions/monte_carlo/mc_sequencer.py @@ -1,55 +1,42 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/9/24 A sequencer for Monte Carlo Runs -. + """ +from __future__ import annotations + import logging import queue import sqlite3 import time import tomllib from datetime import datetime +from importlib import resources from logging import getLogger -from multiprocessing import Queue from pathlib import Path +from typing import TYPE_CHECKING, Any, cast -from pyomo.dataportal import DataPortal - -from definitions import PROJECT_ROOT, get_OUTPUT_PATH -from temoa.extensions.monte_carlo.mc_run import MCRunFactory +from temoa._internal.table_writer import TableWriter +from temoa.data_io.hybrid_loader import HybridLoader +from temoa.extensions.monte_carlo.mc_run import MCRun, MCRunFactory from temoa.extensions.monte_carlo.mc_worker import MCWorker -from temoa.temoa_model.data_brick import DataBrick -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig + +if TYPE_CHECKING: + from multiprocessing import Queue + from multiprocessing.process import BaseProcess + + from pyomo.dataportal import DataPortal + + from temoa._internal.data_brick import DataBrick + from temoa.core.config import TemoaConfig + + logger = getLogger(__name__) -solver_options_path = Path(PROJECT_ROOT, 'temoa/extensions/monte_carlo/MC_solver_options.toml') +solver_options_path = ( + resources.files('temoa.extensions.monte_carlo') / 'MC_solver_options.toml' +) class MCSequencer: @@ -57,18 +44,54 @@ class MCSequencer: A sequencer to control the steps in Monte Carlo run sequence """ + num_workers: int + worker_solver_options: dict[str, Any] + solve_count: int + seen_instance_indices: set[int] + orig_label: str + writer: TableWriter + verbose: bool + def __init__(self, config: TemoaConfig): self.config = config + # determine the path to the solver options file + custom_path = ( + self.config.monte_carlo_inputs.get('solver_options') + if self.config.monte_carlo_inputs + else None + ) + if custom_path: + options_file_path: Path | None = Path(cast('str', custom_path)) + # if the path is relative, make it relative to the config file location if possible + if ( + options_file_path + and not options_file_path.is_absolute() + and self.config.config_file + ): + options_file_path = self.config.config_file.parent / options_file_path + logger.info('Using custom Monte Carlo solver options from: %s', options_file_path) + else: + options_file_path = None + # read in the options + all_options: dict[str, Any] try: - with open(solver_options_path, 'rb') as f: - all_options = tomllib.load(f) + if options_file_path: + with open(options_file_path, 'rb') as f: + all_options = tomllib.load(f) + else: + with resources.as_file(solver_options_path) as path: + with open(path, 'rb') as f: + all_options = tomllib.load(f) s_options = all_options.get(self.config.solver_name, {}) logger.info('Using solver options: %s', s_options) except FileNotFoundError: - logger.warning('Unable to find solver options toml file. Using default options.') + if options_file_path: + logger.error('Unable to find custom solver options file at: %s', options_file_path) + else: + logger.warning('Unable to find default solver options toml file.') s_options = {} all_options = {} @@ -84,7 +107,7 @@ def __init__(self, config: TemoaConfig): self.writer = TableWriter(self.config) self.verbose = False # for troubleshooting - def start(self): + def start(self) -> None: """Run the sequencer""" # ==== basic sequence ==== # 1. Load the model data, which may involve filtering it down if source tracing @@ -94,7 +117,6 @@ def start(self): # 4. copy & modify the base data to make per-dataset runs # 5. farm out the runs to workers - start_time = datetime.now() # 0. Set up database for scenario self.writer.clear_scenario() @@ -104,52 +126,58 @@ def start(self): # 1. Load data with sqlite3.connect(self.config.input_database) as con: hybrid_loader = HybridLoader(db_connection=con, config=self.config) - data_store = hybrid_loader.create_data_dict(myopic_index=None) - mc_run = MCRunFactory(config=self.config, data_store=data_store) + data_store = hybrid_loader.create_data_dict(myopic_index=None) + mc_factory = MCRunFactory(config=self.config, data_store=data_store) # 2. Screen the input file - mc_run.prescreen_input_file() + mc_factory.prescreen_input_file() # 3. set up the run generator - run_gen = mc_run.run_generator() + run_gen = mc_factory.run_generator() # 4. Set up the workers + import multiprocessing + ctx = multiprocessing.get_context('spawn') + num_workers = self.num_workers - work_queue: Queue[tuple[str, DataPortal] | str] = Queue( + work_queue: Queue[tuple[str, DataPortal] | str] = ctx.Queue( num_workers + 1 - ) # must be able to hold all shutdowns at once (could be changed later to not lock on insertion...) - result_queue: Queue[DataBrick | str] = Queue( + ) # must be able to hold all shutdowns at once (could be changed later to not lock on + # insertion...) + result_queue: Queue[DataBrick | str] = ctx.Queue( num_workers + 1 ) # must be able to hold a shutdown signal from all workers at once! - log_queue = Queue() + log_queue: Queue[logging.LogRecord] = ctx.Queue() # make workers - workers = [] + workers: list[BaseProcess] = [] kwargs = { 'solver_name': self.config.solver_name, 'solver_options': self.worker_solver_options, } # construct path for the solver logs - s_path = Path(get_OUTPUT_PATH(), 'solver_logs') + s_path = self.config.output_path / 'solver_logs' if not s_path.exists(): s_path.mkdir() - for i in range(num_workers): + for _ in range(num_workers): w = MCWorker( dp_queue=work_queue, results_queue=result_queue, log_root_name=__name__, log_queue=log_queue, + solver_name=self.config.solver_name, + solver_options=self.worker_solver_options, log_level=logging.INFO, solver_log_path=s_path, - **kwargs, ) - w.start() - workers.append(w) + p: BaseProcess = ctx.Process(target=w.run, daemon=True) + p.start() + workers.append(p) # workers now running and waiting for jobs... # 6. Start the iterative solve process and let the manager run the show more_runs = True # pull the first instance - mc_run = next(run_gen) + mc_run: MCRun = next(run_gen) # capture the "tweaks" self.writer.write_tweaks(iteration=mc_run.run_index, change_records=mc_run.change_records) run_name, dp = mc_run.model_dp @@ -196,7 +224,7 @@ def start(self): next_result = None # print('no result') if next_result is not None: - self.process_solve_results(next_result) + self.process_solve_results(cast('DataBrick', next_result)) self.solve_count += 1 logger.info('Solve count: %d', self.solve_count) if self.verbose or not self.config.silent: @@ -247,7 +275,7 @@ def start(self): next_result = None if next_result is not None and next_result != 'COYOTE': logger.debug('bagged a result post-shutdown') - self.process_solve_results(next_result) + self.process_solve_results(cast('DataBrick', next_result)) self.solve_count += 1 logger.info('Solve count: %d', self.solve_count) if self.verbose or not self.config.silent: @@ -262,8 +290,8 @@ def start(self): if empty == num_workers: break - for w in workers: - w.join() + for proc in workers: + proc.join() logger.debug('worker wrapped up...') log_queue.close() @@ -280,12 +308,13 @@ def start(self): if self.verbose: print('result queue joined') - def process_solve_results(self, brick: DataBrick): + def process_solve_results(self, brick: DataBrick) -> None: """write the results as required""" # get the instance number from the model name, if provided if '-' not in brick.name: raise ValueError( - 'Instance name does not appear to contain a -idx value. The manager should be tagging/updating this' + 'Instance name does not appear to contain a -idx value. The manager should be ' + 'tagging/updating this' ) idx = int(brick.name.split('-')[-1]) if idx in self.seen_instance_indices: diff --git a/temoa/extensions/monte_carlo/mc_worker.py b/temoa/extensions/monte_carlo/mc_worker.py index 73cbf2496..7bf987ea9 100644 --- a/temoa/extensions/monte_carlo/mc_worker.py +++ b/temoa/extensions/monte_carlo/mc_worker.py @@ -1,81 +1,75 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 5/5/24 Class to contain Workers that execute solves in separate processes dev note: This class is derived from the original Worker class in MGA extension, but is just different enough -that it is a separate class. In future, it may make sense to re-combine these. RN, this worker will -ingest DataPortal objects to make new models. The MGA will (in future) likely just take in new obj functions +that it is a separate class. In future, it may make sense to re-combine these. RN, this worker +will ingest DataPortal objects to make new models. The MGA will (in future) likely just take in +new obj functions """ +from __future__ import annotations + import logging.handlers from datetime import datetime from logging import getLogger -from multiprocessing import Process, Queue -from pathlib import Path +from typing import TYPE_CHECKING, Any -from pyomo.dataportal import DataPortal from pyomo.opt import SolverFactory, SolverResults, check_optimal_termination -from temoa.temoa_model.data_brick import DataBrick, data_brick_factory -from temoa.temoa_model.temoa_model import TemoaModel +from temoa._internal.data_brick import DataBrick, data_brick_factory +from temoa.core.model import TemoaModel verbose = False # for T/S or monitoring... +if TYPE_CHECKING: + from multiprocessing import Queue + from pathlib import Path + -class MCWorker(Process): +class MCWorker: worker_idx = 1 + worker_number: int + dp_queue: Queue[Any] + results_queue: Queue[DataBrick | str] + solver_name: str + solver_options: dict[str, Any] + opt: Any + log_queue: Queue[logging.LogRecord] + log_level: int + root_logger_name: str + solver_log_path: Path | None + solve_count: int + def __init__( self, - dp_queue: Queue, - results_queue: Queue, - log_root_name, - log_queue, - log_level, + dp_queue: Queue[Any], + results_queue: Queue[DataBrick | str], + log_root_name: str, + log_queue: Queue[logging.LogRecord], + solver_name: str, + solver_options: dict[str, Any], + log_level: int = logging.INFO, solver_log_path: Path | None = None, - **kwargs, ): - super(MCWorker, self).__init__(daemon=True) self.worker_number = MCWorker.worker_idx MCWorker.worker_idx += 1 - self.dp_queue: Queue[DataPortal | str] = dp_queue - self.results_queue: Queue[DataBrick | str] = results_queue - self.solver_name = kwargs['solver_name'] - self.solver_options = kwargs['solver_options'] - self.opt = SolverFactory(self.solver_name, options=self.solver_options) + self.dp_queue = dp_queue + self.results_queue = results_queue + self.solver_name = solver_name + self.solver_options = solver_options + self.opt = None # Initialize in run() self.log_queue = log_queue self.log_level = log_level self.root_logger_name = log_root_name self.solver_log_path = solver_log_path self.solve_count = 0 - def run(self): - logger = getLogger('.'.join((self.root_logger_name, 'worker', str(self.worker_number)))) + def run(self) -> None: + msg = '.'.join((self.root_logger_name, 'worker', str(self.worker_number))) + logger: logging.Logger = getLogger(msg) logger.setLevel(self.log_level) logger.propagate = ( False # not propagating up the chain fixes issue on TRACE where we were getting dupes. @@ -84,34 +78,24 @@ def run(self): logger.addHandler(handler) logger.info('Worker %d spun up', self.worker_number) - # update the solver options to pass in a log location - while True: - if self.solver_log_path: - # add the solver log path to options, if one is provided - log_location = Path( - self.solver_log_path, - f'solver_log_{str(self.worker_number)}_{self.solve_count}.log', - ) - log_location = str(log_location) - match self.solver_name: - case 'gurobi': - self.solver_options.update({'LogFile': log_location}) - # case 'appsi_highs': - # self.solver_options.update({'log_file': log_location}) - case _: - pass - - self.opt.options = self.solver_options + # Initialize the solver here in the child process to avoid pickling issues + # Note: We do not set the options here because appsi_highs does not accept options in + # __init__. We can set them later via self.opt.options + self.opt = SolverFactory(self.solver_name) + while True: # wait for a DataPortal object to show up, then get to work tic = datetime.now() data = self.dp_queue.get() toc = datetime.now() + + # log data pull logger.debug( 'Worker %d waited for and pulled a DataPortal from work queue in %0.2f seconds', self.worker_number, (toc - tic).total_seconds(), ) + if data == 'ZEBRA': # shutdown signal if verbose: print(f'worker {self.worker_number} got shutdown signal') @@ -119,13 +103,44 @@ def run(self): self.results_queue.put('COYOTE') break name, dp = data + + # update the solver options + self.opt.options = self.solver_options + if self.solver_name.startswith('appsi_'): + for k, v in self.solver_options.items(): + try: + setattr(self.opt.config, k, v) + except (ValueError, AttributeError): + # Key not defined in ConfigDict, skip it + pass + + # Handle solver log path for appsi + if self.solver_log_path: + log_location = ( + self.solver_log_path + / f'solver_log_{self.worker_number}_{self.solve_count}.log' + ) + try: + self.opt.config.log_file = str(log_location) + except (ValueError, AttributeError): + pass + abstract_model = TemoaModel() model: TemoaModel = abstract_model.create_instance(data=dp) model.name = name # set the name from the input tic = datetime.now() try: self.solve_count += 1 - res: SolverResults | None = self.opt.solve(model) + solve_res: SolverResults | None + if self.solver_name.startswith('appsi_'): + # For appsi, we can set tee on the config + try: + self.opt.config.tee = True + except (ValueError, AttributeError): + pass + solve_res = self.opt.solve(model) + else: + solve_res = self.opt.solve(model, tee=True) except Exception as e: if verbose: @@ -136,12 +151,17 @@ def run(self): model.name, e, ) - res = None + # Try to get more info if it's a known solver error + if hasattr(self.opt, 'results') and self.opt.results: + logger.warning( + 'Solver results status: %s', self.opt.results.solver.termination_condition + ) + solve_res = None toc = datetime.now() # guard against a bad "res" object... try: - good_solve = check_optimal_termination(res) + good_solve = check_optimal_termination(solve_res) if good_solve: data_brick = data_brick_factory(model) self.results_queue.put(data_brick) @@ -153,10 +173,13 @@ def run(self): if verbose: print(f'Worker {self.worker_number} completed a successful solve') else: - status = res['Solver'].termination_condition - logger.info( - 'Worker %d did not solve. Results status: %s', self.worker_number, status - ) + if solve_res is not None: + status = solve_res['Solver'].termination_condition + logger.info( + 'Worker %d did not solve. Results status: %s', + self.worker_number, + status, + ) except AttributeError: pass logger.info('Worker %d finished', self.worker_number) diff --git a/temoa/extensions/myopic/__init__.py b/temoa/extensions/myopic/__init__.py index af2433005..e69de29bb 100644 --- a/temoa/extensions/myopic/__init__.py +++ b/temoa/extensions/myopic/__init__.py @@ -1,27 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 1/17/24 - -""" diff --git a/temoa/extensions/myopic/evolution_updater.py b/temoa/extensions/myopic/evolution_updater.py new file mode 100644 index 000000000..20808e79a --- /dev/null +++ b/temoa/extensions/myopic/evolution_updater.py @@ -0,0 +1,36 @@ +import sqlite3 +import logging +from temoa.extensions.myopic.myopic_index import MyopicIndex + +logger = logging.getLogger(__name__) + +def iterate( + idx: MyopicIndex | None = None, + prev_base_year: int | None = None, + last_instance_status: str | None = None, + db_con: sqlite3.Connection | None = None, + ) -> None: + """ + This function is called at the end of each myopic iteration, + after the results have been recorded to the myopic database. + You can use it to update your myopic database with any additional + information you want to track across iterations, or to implement + an evolving myopic approach where the model structure changes + across iterations based on some user-defined logic. + Parameters: + - idx (MyopicIndex): The index object for the current iteration, + containing information about the base year, view depth, etc. + - prev_base_year (int): The base year of the previous iteration. + - last_instance_status (str): The status of the last solved instance + ('optimal' meaning successful or 'roll_back' meaning solver failure + and rollback to previous iteration). + - db_con (sqlite3.Connection): A connection object to the myopic database, + which you can use to read/write data as needed. + """ + + assert idx is not None + logger.info(f"Running myopic iteration updater for base year {idx.base_year}") + + # Update your myopic database here. + + return diff --git a/temoa/extensions/myopic/make_myopic_tables.sql b/temoa/extensions/myopic/make_myopic_tables.sql index b72d891bc..00e88f71c 100644 --- a/temoa/extensions/myopic/make_myopic_tables.sql +++ b/temoa/extensions/myopic/make_myopic_tables.sql @@ -1,6 +1,6 @@ BEGIN; -CREATE TABLE IF NOT EXISTS MyopicEfficiency +CREATE TABLE IF NOT EXISTS myopic_efficiency ( base_year integer, region text, @@ -11,11 +11,11 @@ CREATE TABLE IF NOT EXISTS MyopicEfficiency efficiency real, lifetime integer, - FOREIGN KEY (tech) REFERENCES Technology (tech), + FOREIGN KEY (tech) REFERENCES technology (tech), PRIMARY KEY (region, input_comm, tech, vintage, output_comm) ); -- for efficient searching by rtv: -CREATE INDEX IF NOT EXISTS region_tech_vintage ON MyopicEfficiency (region, tech, vintage); +CREATE INDEX IF NOT EXISTS region_tech_vintage ON myopic_efficiency (region, tech, vintage); -COMMIT; \ No newline at end of file +COMMIT; diff --git a/temoa/extensions/myopic/myopic table notes.md b/temoa/extensions/myopic/myopic table notes.md index 36689f020..42626c96f 100644 --- a/temoa/extensions/myopic/myopic table notes.md +++ b/temoa/extensions/myopic/myopic table notes.md @@ -1,18 +1,17 @@ -Notes on Myopic +Notes on Myopic =============== -MyopicEfficiency Table +myopic_efficiency Table ---------------- -- Largely similar to baseline Efficiency table +- Largely similar to baseline efficiency table - Built sequentially during myopic run from capacity built, not built, or retired in previous period. This table -needs to be "actively maintained" during the run because it is the source of filtering for all other model elements. - - Notable actions: +needs to be "actively maintained" during the run because it is the source of filtering for all other model elements. + - Notable actions: - Items NOT built in previous myopic windows are removed in the subsequent iteration. During normal model runs, - the model will add entries from the normal Efficiency tables for consideration, and then in the subsequent + the model will add entries from the normal efficiency tables for consideration, and then in the subsequent iteration, it will houseclean and remove any that were not selected for build. - Items that become fully retired *during their normal lifespan* are also deleted upon retirement. - Uses base-year field (column) as reference to when added, and ``-1`` indicates original existing capacity -- Adds a computed Lifetime field, which is handy for future computations and used internally to screen active +- Adds a computed Lifetime field, which is handy for future computations and used internally to screen active technologies during data loading. - diff --git a/temoa/extensions/myopic/myopic_index.py b/temoa/extensions/myopic/myopic_index.py index 580197eb8..db7ac35bc 100644 --- a/temoa/extensions/myopic/myopic_index.py +++ b/temoa/extensions/myopic/myopic_index.py @@ -2,34 +2,6 @@ A simple frozen data structure to hold instance info on myopic runs """ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 1/18/24 - -""" - from dataclasses import dataclass @@ -44,7 +16,7 @@ class MyopicIndex: last_demand_year: int last_year: int - def __post_init__(self): + def __post_init__(self) -> None: if not self.base_year < self.step_year <= self.last_year: raise ValueError( f'Received a nonsense value for step_year: {self.step_year} with ' @@ -78,10 +50,10 @@ def __post_init__(self): The model generally expects one extra period after the last period to be optimized for the purpose of setting the last optimization interval length. So, when parsing out the "middle years" in myopic mode -we need to stop gathering demands in the 2nd year when we have a 3 year +we need to stop gathering demands in the 2nd year when we have a 3 year view depth. -If the myopic view depth is 1 period, and therefore, the step can +If the myopic view depth is 1 period, and therefore, the step can only be 1 period, then: FY = LDY (we just need data that FY period) SY = FY + 1 period (we are going to step 1 period) diff --git a/temoa/extensions/myopic/myopic_progress_mapper.py b/temoa/extensions/myopic/myopic_progress_mapper.py index 485095627..3dc3c2955 100644 --- a/temoa/extensions/myopic/myopic_progress_mapper.py +++ b/temoa/extensions/myopic/myopic_progress_mapper.py @@ -4,39 +4,13 @@ from datetime import datetime, timedelta -from temoa.extensions.myopic.myopic_index import MyopicIndex - -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 1/22/24 +from typing import Literal -""" +from temoa.extensions.myopic.myopic_index import MyopicIndex class MyopicProgressMapper: - def __init__(self, sorted_future_years: list): + def __init__(self, sorted_future_years: list[int]) -> None: self.leader = '--' self.trailer = ''.join(reversed(self.leader)) self.years = sorted_future_years @@ -46,7 +20,7 @@ def __init__(self, sorted_future_years: list): } self.hack: datetime = datetime.now() - def draw_header(self): + def draw_header(self) -> None: time_buffer = ' ' * 10 tot_length = len(self.years) * self.tag_width + 2 * len(self.years) print(time_buffer, end='') @@ -72,12 +46,24 @@ def draw_header(self): def timestamp(self) -> str: delta = datetime.now() - self.hack - return f'{int(delta.total_seconds()//3600):02d}:{int(delta.total_seconds()%3600//60):02d}:{int(delta.total_seconds())%60:02d} ' - - def report(self, mi: MyopicIndex, status): - if status not in {'load', 'solve', 'report', 'check'}: + return ( + f'Elapsed: {int(delta.total_seconds()//3600):02d}:' + f'{int(delta.total_seconds()%3600//60):02d}:{int(delta.total_seconds())%60:02d} ' + ) + + def report( + self, mi: MyopicIndex, status: Literal['load', 'solve', 'report', 'check', 'evolve'] + ) -> None: + if status not in {'load', 'solve', 'report', 'check', 'evolve'}: raise ValueError(f'bad status: {status} received in MyopicProgressMapper') + if status == 'evolve': + repeats = self.years.index(mi.last_demand_year) - self.years.index(mi.base_year) + 1 + print(self.timestamp(), end='') + print(' ' * self.pos[mi.base_year], end='') + for _ in range(repeats): + print('EVLV', end=' ' * (self.tag_width + 2 - 4)) # 4=length('EVLV') + if status == 'load': repeats = self.years.index(mi.last_demand_year) - self.years.index(mi.base_year) + 1 print(self.timestamp(), end='') diff --git a/temoa/extensions/myopic/myopic_sequencer.py b/temoa/extensions/myopic/myopic_sequencer.py index ebb026bda..fbee15c45 100644 --- a/temoa/extensions/myopic/myopic_sequencer.py +++ b/temoa/extensions/myopic/myopic_sequencer.py @@ -1,55 +1,31 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 1/15/24 - +This module provides the MyopicSequencer class, which orchestrates the +process of solving a sequence of myopic optimization problems. """ import logging import sqlite3 import sys from collections import deque +from importlib import resources, util from pathlib import Path -from sqlite3 import Connection -from sys import stderr as SE - -import definitions -from temoa.data_processing.DB_to_Excel import make_excel +from sqlite3 import Connection, Cursor +from typing import Any, cast + +from temoa._internal import run_actions +from temoa._internal.table_writer import TableWriter +from temoa.core.config import TemoaConfig +from temoa.core.model import TemoaModel +from temoa.data_io.hybrid_loader import HybridLoader +from temoa.data_processing.db_to_excel import make_excel from temoa.extensions.myopic.myopic_index import MyopicIndex from temoa.extensions.myopic.myopic_progress_mapper import MyopicProgressMapper -from temoa.temoa_model import run_actions -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.model_checking.pricing_check import price_checker -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_model import TemoaModel +from temoa.model_checking.pricing_check import price_checker +from temoa.utilities.sqlite_utils import tune_sqlite_connection logger = logging.getLogger(__name__) -table_script_file = Path( - definitions.PROJECT_ROOT, 'temoa/extensions/myopic', 'make_myopic_tables.sql' -) +table_script_file = resources.files('temoa.extensions.myopic') / 'make_myopic_tables.sql' class MyopicSequencer: @@ -59,40 +35,66 @@ class MyopicSequencer: # Tables that are cleaned of (scenario) data before run tables_with_scenario_reference = [ - 'OutputBuiltCapacity', - 'OutputCost', - 'OutputCurtailment', - 'OutputDualVariable', - 'OutputEmission', - 'OutputFlowIn', - 'OutputFlowOut', - 'OutputNetCapacity', - 'OutputObjective', - 'OutputRetiredCapacity', + 'output_built_capacity', + 'output_cost', + 'output_curtailment', + 'output_dual_variable', + 'output_emission', + 'output_flow_in', + 'output_flow_out', + 'output_net_capacity', + 'output_objective', + 'output_retired_capacity', + 'output_storage_level', ] tables_without_scenario_reference = [ - 'MyopicEfficiency', + 'myopic_efficiency', ] # Tables that may be cleaned by period during myopic run - # note: below excludes MyopicEfficiency, which is managed separately + # note: below excludes myopic_efficiency, which is managed separately tables_with_period = [ - 'OutputCost', - 'OutputCurtailment', - 'OutputEmission', - 'OutputFlowIn', - 'OutputFlowOut', - 'OutputNetCapacity', - 'OutputRetiredCapacity', + 'output_cost', + 'output_curtailment', + 'output_emission', + 'output_flow_in', + 'output_flow_out', + 'output_net_capacity', + 'output_retired_capacity', + 'output_storage_level', ] - def __init__(self, config: TemoaConfig | None): - self.capacity_epsilon = 1e-5 + capacity_epsilon: float + debugging: bool + optimization_periods: list[int] | None + instance_queue: deque[MyopicIndex] + progress_mapper: MyopicProgressMapper | None + config: TemoaConfig | None + # these are initialized during characterize_run() + output_con: sqlite3.Connection | None = None + cursor: sqlite3.Cursor | None = None + table_writer: TableWriter | None = None + view_depth: int | None = None + step_size: int | None = None + run_status: bool | None = None + evolving: bool + evolution_script: str + + def __init__(self, config: TemoaConfig | None) -> None: + # Minimum capacity (MW) to carry forward between myopic periods. + # Configurable via [myopic] capacity_threshold in TOML. + default_cap_threshold = 1e-3 + if config and config.myopic_inputs: + self.capacity_epsilon = float( + cast(Any, config.myopic_inputs.get('capacity_threshold', default_cap_threshold)) + ) + else: + self.capacity_epsilon = default_cap_threshold self.debugging = False - self.optimization_periods: list[int] | None = None - self.instance_queue: deque[MyopicIndex] = deque() # a LIFO queue - self.progress_mapper: MyopicProgressMapper | None = None + self.optimization_periods = None + self.instance_queue = deque() # a LIFO queue + self.progress_mapper = None self.config = config # allow a "shunt" (None) here so we can test parts of this by passing a None config if self.config: @@ -100,26 +102,28 @@ def __init__(self, config: TemoaConfig | None): self.cursor = self.output_con.cursor() self.table_writer = TableWriter(self.config) # break out what is needed from the config - myopic_options = config.myopic_inputs + myopic_options = config.myopic_inputs if config else None if not myopic_options: logger.error( 'The myopic mode was selected, but no options were received.\n %s', - config.myopic_inputs, + config.myopic_inputs if config else None, ) raise RuntimeError('No myopic options received. See log file.') else: - self.view_depth: int = myopic_options.get('view_depth') - if not isinstance(self.view_depth, int): - raise ValueError(f'view_depth is not an integer {self.view_depth}') - self.step_size: int = myopic_options.get('step_size') - if not isinstance(self.step_size, int): - raise ValueError(f'step_size is not an integer {self.step_size}') + self.view_depth = int(cast(Any, myopic_options.get('view_depth'))) + if not isinstance(self.view_depth, int) or self.view_depth < 1: + raise ValueError(f'view_depth is not a positive integer {self.view_depth}') + self.step_size = int(cast(Any, myopic_options.get('step_size'))) + if not isinstance(self.step_size, int) or self.step_size < 1: + raise ValueError(f'step_size is not a positive integer {self.step_size}') if self.step_size > self.view_depth: raise ValueError( f'the Myopic step size({self.step_size}) ' f'is larger than the view depth ({self.view_depth}). ' f'Check config' ) + self.evolving = bool(myopic_options.get('evolving')) + self.evolution_script = str(myopic_options.get('evolution_script') or '') else: # A None was passed for config and the caller is responsible for setting instance vars pass @@ -129,6 +133,8 @@ def get_connection(self) -> Connection: Get a connection to the output database :return: a database connection """ + if self.config is None: + raise RuntimeError('Config is not initialized') input_file = self.config.input_database output_db = self.config.output_database @@ -146,47 +152,44 @@ def get_connection(self) -> Connection: logger.info('Connected to database: %s', input_file) else: msg = ( - 'Myopic Mode processing only supports a single database (i.e. input_file = output_db).\n' + 'Myopic Mode processing only supports a single database (i.e. input_file = ' + 'output_db).\n' 'This is due to the linkage between input and output and the use of Myopic tables\n' - 'in the database to orchestrate the run. Either reset both input and output to point\n' - 'to the same db or make a copy to preserve the original and point both input/output\n' + 'in the database to orchestrate the run. Either reset both input and output to ' + 'point\n' + 'to the same db or make a copy to preserve the original and point both ' + 'input/output\n' 'to the copy.' ) - SE.write(msg) + sys.stderr.write(msg) logger.error('Run aborted. I/O database pointers are different') sys.exit(-1) + tune_sqlite_connection(con, self.config) + return con - def start(self): + def start(self) -> None: + if self.config is None: + raise RuntimeError('Config is not initialized') + # load up the instance queue self.characterize_run() # create the Myopic Output tables, if they don't already exist. - self.execute_script(table_script_file) + with resources.as_file(table_script_file) as script_path: + self.execute_script(script_path) # clear out the old riff-raff self.clear_old_results() - # start building the MyopicEfficiency table. + # start building the myopic_efficiency table. self.initialize_myopic_efficiency_table() - # start the fundamental control loop - # 1. get feedback from previous instance execution (optimal/infeasible/...) - # 2. decide what to do about it - # 3. pull the next instance from the queue (if !empty & if needed) - # 4. Update the MyopicEfficiency table (clean up history / add stuff now in visibility) - # 5. pull data for next run and filter it with source tracing - # 6. build instance - # 7. run checks (price check) on the model, if selected - # 8. run the model and assess - # 9. commit or back out any data as necessary - # 10. report findings - # 11. compact the db - last_instance_status = None # solve status last_base_year = None idx: MyopicIndex | None = None # just a type-hint + logger.info('Starting Myopic Sequence') # 1, 2, 3... while len(self.instance_queue) > 0: @@ -196,15 +199,20 @@ def start(self): elif last_instance_status == 'optimal': idx = self.instance_queue.pop() elif last_instance_status == 'roll_back': + if self.optimization_periods is None: + raise RuntimeError('optimization_periods not initialized') + if idx is None: + raise RuntimeError('idx is None') curr_start_idx = self.optimization_periods.index(idx.base_year) new_start_idx = curr_start_idx - 1 # back up 1 increment, expanding the window if new_start_idx < 0: logger.error('Failed myopic iteration. Cannot back up any further.') raise RuntimeError( - 'Myopic iteration failed during attempt to back up recursively before start of optimization ' - 'period.' + 'Myopic iteration failed during attempt to back up recursively before ' + 'start of optimization period.' ) - # roll back the start year by making a new index, increase the depth, keep the same last year + # roll back the start year by making a new index, increase the depth, keep the + # same last year base_year = self.optimization_periods[new_start_idx] idx = MyopicIndex( base_year=base_year, @@ -215,18 +223,28 @@ def start(self): else: raise RuntimeError('Illegal state in myopic iteration.') logger.info('Processing Myopic Index: %s', idx) - if not self.config.silent: - self.progress_mapper.report(idx, 'load') - # 4. update the MyopicEfficiency table so it is ready for the upcoming data pull. + # 4. If evolving, call the evolution script and pass it the myopic index and last instance status + if self.evolving and last_instance_status is not None: # don't evolve before first iteration (pointless) + self.run_evolution_script( + idx=idx, + last_base_year=last_base_year, + last_instance_status=last_instance_status, + con=self.output_con, + ) + + # 5. update the myopic_efficiency table so it is ready for the upcoming data pull. + if not self.config.silent and self.progress_mapper and idx: + self.progress_mapper.report(idx, 'load') self.update_myopic_efficiency_table(myopic_index=idx, prev_base=last_base_year) - # 5. pull the data + # 6. pull the data + assert self.output_con is not None # make a data loader data_loader = HybridLoader(self.output_con, self.config) data_portal = data_loader.load_data_portal(myopic_index=idx) - # 6. build + # 7. build instance = run_actions.build_instance( loaded_portal=data_portal, model_name=self.config.scenario, @@ -236,16 +254,16 @@ def start(self): / ''.join(('LP', str(idx.base_year))), # base year folder ) - # 7. Run checks... - if not self.config.silent: + # 8. Run checks... + if not self.config.silent and self.progress_mapper and idx: self.progress_mapper.report(idx, 'check') if self.config.price_check: good_prices = price_checker(instance) if not good_prices and not self.config.silent: print('\nWarning: Cost anomalies discovered. Check log file for details.') - # 8. Run the model and assess solve status - if not self.config.silent: + # 9. Run the model and assess solve status + if not self.config.silent and self.progress_mapper and idx: self.progress_mapper.report(idx, 'solve') model, results = run_actions.solve_instance( instance=instance, solver_name=self.config.solver_name, silent=True @@ -263,27 +281,40 @@ def start(self): logger.info('Completed myopic iteration on %s', idx) - # 9, 10. Update the output tables... - # first, clear any possible previous results that overlap, we might have been backtracking... - self.clear_results_after(idx.base_year) + # 10, 11. Update the output tables... + # first, clear any possible previous results that overlap, we might have been + # backtracking... + if idx: + self.clear_results_after(idx.base_year) # add the new results... - if not self.config.silent: + if not self.config.silent and self.progress_mapper and idx: self.progress_mapper.report(idx, 'report') # write results by appending. We have already cleared necessary items - self.table_writer.write_results(M=model, append=True) + assert self.table_writer is not None + self.table_writer.write_results(model=model, append=True) # handle side-case for writing duals if self.config.save_duals: self.table_writer.write_dual_variables(results=results, iteration=idx.base_year) # prep next loop - last_base_year = idx.base_year # update + last_base_year = idx.base_year if idx else last_base_year # update - # delete anything in the OutputObjective table, it is nonsensical... - self.output_con.execute('DELETE FROM OutputObjective WHERE 1') + # delete anything in the output_objective table, it is nonsensical... + assert self.output_con is not None + self.output_con.execute( + f'DELETE FROM output_objective WHERE scenario == "{self.config.scenario}"' + ) self.output_con.commit() - # 11. Compact the db... lots of writes/deletes leads to bloat - self.output_con.execute('VACUUM;') + + # Total system cost is, theoretically, sum of discounted costs from output_cost table + total_cost = self.get_current_total_cost(last_base_year if last_base_year is not None else 0) + + assert self.output_con is not None + self.output_con.execute( + f"INSERT INTO output_objective(scenario, objective_name, total_system_cost) VALUES('{self.config.scenario}', 'total_cost', {total_cost})" + ) + self.output_con.commit() if self.config.save_excel: temp_scenario = set() @@ -291,34 +322,48 @@ def start(self): excel_filename = self.config.output_path / self.config.scenario make_excel(str(self.config.output_database), excel_filename, temp_scenario) - def initialize_myopic_efficiency_table(self): + def get_current_total_cost(self, base_year: int) -> float: + assert self.output_con is not None + assert self.config is not None + total_cost = self.output_con.execute( + 'SELECT SUM(d_invest)+SUM(d_fixed)+SUM(d_var)+SUM(d_emiss) FROM output_cost ' + f'WHERE scenario == "{self.config.scenario}"' + ).fetchone()[0] + return float(total_cost) if total_cost is not None else 0.0 + + def initialize_myopic_efficiency_table(self) -> None: """ - create a new MyopicEfficiency table and pre-load it with all ExistingCapacity + create a new myopic_efficiency table and pre-load it with all existing_capacity :return: """ # clear out everything from previous runs - self.cursor.execute('DELETE FROM MyopicEfficiency WHERE 1') + assert self.cursor is not None + assert self.output_con is not None + self.cursor.execute('DELETE FROM myopic_efficiency WHERE 1') self.output_con.commit() # the -1 for base year is used to indicate "existing" for flag purposes # we will just use the "existing" flag in the orig db to set this up and capture # all values in those vintages as "existing" - # the "coalesce" is an if-else structure to pluck out the correct lifetime value, precedence left->right + # the "coalesce" is an if-else structure to pluck out the correct lifetime value, + # precedence left->right default_lifetime = TemoaModel.default_lifetime_tech query = ( - 'INSERT INTO MyopicEfficiency ' - ' SELECT -1, main.Efficiency.region, input_comm, Efficiency.tech, Efficiency.vintage, output_comm, efficiency, ' - f' coalesce(main.LifetimeProcess.lifetime, main.LifetimeTech.lifetime, {default_lifetime}) AS lifetime ' - ' FROM main.Efficiency ' - ' LEFT JOIN main.LifetimeProcess ' - ' ON main.Efficiency.tech = LifetimeProcess.tech ' - ' AND main.Efficiency.vintage = LifetimeProcess.vintage ' - ' AND main.Efficiency.region = LifetimeProcess.region ' - ' LEFT JOIN main.LifetimeTech ' - ' ON main.Efficiency.tech = main.LifetimeTech.tech ' - ' AND main.Efficiency.region = main.LifeTimeTech.region ' - ' JOIN TimePeriod ' - ' ON Efficiency.vintage = TimePeriod.period ' + 'INSERT INTO myopic_efficiency ' + ' SELECT -1, main.efficiency.region, input_comm, efficiency.tech, ' + 'efficiency.vintage, output_comm, efficiency, ' + f' coalesce(main.lifetime_process.lifetime, main.lifetime_tech.lifetime, ' + f'{default_lifetime}) AS lifetime ' + ' FROM main.efficiency ' + ' LEFT JOIN main.lifetime_process ' + ' ON main.efficiency.tech = lifetime_process.tech ' + ' AND main.efficiency.vintage = lifetime_process.vintage ' + ' AND main.efficiency.region = lifetime_process.region ' + ' LEFT JOIN main.lifetime_tech ' + ' ON main.efficiency.tech = main.lifetime_tech.tech ' + ' AND main.efficiency.region = main.lifetime_tech.region ' + ' JOIN time_period ' + ' ON efficiency.vintage = main.time_period.period ' " WHERE flag = 'e'" ) @@ -330,17 +375,66 @@ def initialize_myopic_efficiency_table(self): if self.debugging: q2 = ( "SELECT '-1', region, input_comm, tech, vintage, output_comm, efficiency " - 'FROM Efficiency ' - ' JOIN TimePeriod ' - ' ON Efficiency.vintage = TimePeriod.period ' + 'FROM efficiency ' + ' JOIN time_period ' + ' ON efficiency.vintage = time_period.period ' " WHERE flag = 'e'" ) res = self.cursor.execute(q2).fetchall() print(list(res)) - def update_myopic_efficiency_table(self, myopic_index: MyopicIndex, prev_base: int): + def run_evolution_script( + self, + idx: MyopicIndex | None, + last_base_year: int | None, + last_instance_status: str | None, + con: sqlite3.Connection | None + ) -> None: + """ + Run the evolution script to update the myopic database before the next iteration. + """ + + if not self.evolution_script: + logger.warning('Evolving myopic mode selected, but no evolution script provided.') + return + + # import the script as a module and call the iterate function + script_path = Path(self.evolution_script).expanduser() + if not script_path.is_file(): + msg = f"Myopic evolution script not found: {script_path}" + logger.error(msg) + raise FileNotFoundError(msg) + + spec = util.spec_from_file_location("evolution_script", script_path) + if spec is None or spec.loader is None: + msg = f"Could not load evolution script module spec from: {script_path}" + logger.error(msg) + raise RuntimeError(msg) + + evolution_module = util.module_from_spec(spec) + spec.loader.exec_module(evolution_module) + iterate = getattr(evolution_module, "iterate", None) + if not callable(iterate): + msg = f"Evolution script must define callable iterate(...): {script_path}" + logger.error(msg) + raise AttributeError(msg) + + if self.config and not self.config.silent: + if self.progress_mapper and idx: + self.progress_mapper.report(idx, 'evolve') + + iterate( + idx=idx, + prev_base_year=last_base_year, + last_instance_status=last_instance_status, + db_con=con, + ) + + def update_myopic_efficiency_table( + self, myopic_index: 'MyopicIndex', prev_base: int | None + ) -> None: """ - This function adds to the MyopicEfficiency table in the db with data specific + This function adds to the myopic_efficiency table in the db with data specific to the current MyopicIndex timeframe. Basically: prep it for the current iteration. :return: """ @@ -349,7 +443,7 @@ def update_myopic_efficiency_table(self, myopic_index: MyopicIndex, prev_base: i # efficiency table, we can bounce our other queries off of it to get accurate # data out of the DB, instead of dealing with it model-side - # We already captured the ExistingCapacity efficiency values when the table + # We already captured the existing_capacity efficiency values when the table # was initialized, so now we need to incrementally: # 0. Clear from base year forward: # REMOVE anything past the current base year that may have been added previously @@ -364,79 +458,89 @@ def update_myopic_efficiency_table(self, myopic_index: MyopicIndex, prev_base: i base = myopic_index.base_year last_demand_year = myopic_index.last_demand_year - logger.info('Starting update of MyopicEfficiency Table retaining [%s, %s)', prev_base, base) + logger.info( + 'Starting update of myopic_efficiency Table retaining [%s, %s)', prev_base, base + ) # 0. Clear any future things past the base year for housekeeping # ease with steps, depth, etc. These may have been added if we are stepping less # than the previous solve depth or if backtracking. + assert self.cursor is not None + assert self.output_con is not None self.cursor.execute( - 'DELETE FROM MyopicEfficiency WHERE MyopicEfficiency.vintage >= ?', (base,) + 'DELETE FROM myopic_efficiency WHERE myopic_efficiency.vintage >= ?', (base,) ) self.output_con.commit() # 1. Clean up stuff not implemented or retired by the last time period in previous step, # exempting unlim_cap techs (of course...who would forget that?) last_interval_end, flag = self.cursor.execute( - 'SELECT MAX(period), flag FROM main.TimePeriod WHERE period < ?', + 'SELECT MAX(period), flag FROM main.time_period WHERE period < ?', (myopic_index.base_year,), ).fetchone() - if flag == 'f': # the prior period should have an OutputNetCapacity entry + if flag == 'f': # the prior period should have an output_net_capacity entry # Delete anything that doesn't have capacity remaining at the end of last interval delete_qry = ( - 'DELETE FROM MyopicEfficiency ' + 'DELETE FROM myopic_efficiency ' 'WHERE (SELECT region, tech, vintage) ' - ' NOT IN (SELECT region, tech, vintage FROM OutputNetCapacity ' - ' WHERE period = ? AND scenario = ?) ' - 'AND tech not in (SELECT tech FROM Technology where unlim_cap > 0)' + ' NOT IN (SELECT region, tech, vintage FROM output_net_capacity ' + ' WHERE period = ? AND scenario = ? AND ABS(capacity) >= ?) ' + 'AND tech not in (SELECT tech FROM main.technology where unlim_cap > 0)' ) + scenario_name = self.config.scenario if self.config else None if self.debugging: debug_query = ( - 'SELECT * FROM MyopicEfficiency ' + 'SELECT * FROM myopic_efficiency ' 'WHERE (SELECT region, tech, vintage) ' - ' NOT IN (SELECT region, tech, vintage FROM OutputNetCapacity ' - ' WHERE period = ? AND scenario = ?) ' + ' NOT IN (SELECT region, tech, vintage FROM output_net_capacity ' + ' WHERE period = ? AND scenario = ? AND ABS(capacity) >= ?) ' 'AND tech not in (SELECT tech FROM Technology where unlim_cap > 0)' ) print('\n\n **** Removing these unused region-tech-vintage combos ****') removals = self.cursor.execute( - debug_query, (last_interval_end, self.config.scenario) + debug_query, + (last_interval_end, scenario_name, self.capacity_epsilon), ).fetchall() for i, removal in enumerate(removals): print(f'{i}. Removing: {removal}') - self.cursor.execute(delete_qry, (last_interval_end, self.config.scenario)) + + self.cursor.execute( + delete_qry, + (last_interval_end, scenario_name, self.capacity_epsilon), + ) self.output_con.commit() # 2. Add the new stuff now visible - # dev note: the `coalesce()` command is a nested if-else. The first hit wins, so it is priority: - # process lifetime > tech lifetime > lifetime default - lifetime = TemoaModel.default_lifetime_tech + # dev note: the `coalesce()` command is a nested if-else. The first hit wins, so it is + default_lifetime = TemoaModel.default_lifetime_tech query = ( - 'INSERT INTO MyopicEfficiency ' - f'SELECT {base}, Efficiency.region, input_comm, ' - ' Efficiency.tech, Efficiency.vintage, output_comm, efficiency, ' - f' coalesce(main.LifetimeProcess.lifetime, main.LifetimeTech.lifetime, {lifetime}) ' - f' AS lifetime ' - ' FROM main.Efficiency ' - ' LEFT JOIN main.LifetimeProcess ' - ' ON main.Efficiency.tech = LifetimeProcess.tech ' - ' AND main.Efficiency.vintage = LifetimeProcess.vintage ' - ' AND main.Efficiency.region = LifetimeProcess.region ' - ' LEFT JOIN main.LifetimeTech ' - ' ON main.Efficiency.tech = main.LifetimeTech.tech ' - ' AND main.Efficiency.region = main.LifeTimeTech.region ' - f' WHERE Efficiency.vintage >= {base}' - f' AND Efficiency.vintage <= {last_demand_year}' + 'INSERT INTO myopic_efficiency ' + f'SELECT {base}, efficiency.region, input_comm, ' + ' efficiency.tech, efficiency.vintage, output_comm, efficiency, ' + f' coalesce(main.lifetime_process.lifetime, main.lifetime_tech.lifetime, ' + f'{default_lifetime}) AS lifetime ' + ' FROM main.efficiency ' + ' LEFT JOIN main.lifetime_process ' + ' ON main.efficiency.tech = lifetime_process.tech ' + ' AND main.efficiency.vintage = lifetime_process.vintage ' + ' AND main.efficiency.region = lifetime_process.region ' + ' LEFT JOIN main.lifetime_tech ' + ' ON main.efficiency.tech = main.lifetime_tech.tech ' + ' AND main.efficiency.region = main.lifetime_tech.region ' + f' WHERE efficiency.vintage >= {base}' + f' AND efficiency.vintage <= {last_demand_year}' ) if self.debugging: - # note: the debug query below omits the lifetime computation for brevity, but is very useful without... + # note: the debug query below omits the lifetime computation for brevity, but is very + # useful without... raw = self.cursor.execute( f'SELECT {base}, region, input_comm, tech, vintage, output_comm, efficiency ' - 'FROM Efficiency ' - f' WHERE Efficiency.vintage >= {base}' - f' AND Efficiency.vintage <= {last_demand_year}' + 'FROM efficiency ' + f' WHERE efficiency.vintage >= {base}' + f' AND efficiency.vintage <= {last_demand_year}' ).fetchall() - print('\n\n **** adding to MyopicEfficiency table from newly visible techs ****') + print('\n\n **** adding to myopic_efficiency table from newly visible techs ****') for idx, t in enumerate(raw): print(idx, t) print() @@ -450,10 +554,11 @@ def characterize_run(self, future_periods: list[int] | None = None) -> None: :return: """ if not future_periods: - future_periods = self.cursor.execute( - "SELECT period FROM main.TimePeriod WHERE flag = 'f'" + assert self.cursor is not None + rows = self.cursor.execute( + "SELECT period FROM main.time_period WHERE flag = 'f'" ).fetchall() - future_periods = sorted(t[0] for t in future_periods) + future_periods = sorted(row[0] for row in rows) # set up the progress mapper self.progress_mapper = MyopicProgressMapper(future_periods) @@ -461,17 +566,41 @@ def characterize_run(self, future_periods: list[int] | None = None) -> None: self.progress_mapper.draw_header() # check that we have enough periods to do myopic run - # 2 iterations, excluding end year, will be via shortened depth, if reqd. - if len(future_periods) < 3: - logger.error('Not enough future years to run myopic mode: %d', len(future_periods)) - sys.exit(-1) + if self.view_depth is None: + raise RuntimeError('view_depth not initialized') + if len(future_periods) < self.view_depth + 1: + msg = ( + 'Not enough future periods for view depth. Need {} including end period. Got {}.' + ).format(self.view_depth+1, len(future_periods)) + logger.error(msg) + raise RuntimeError(msg) self.optimization_periods = future_periods.copy() - last_idx = len(future_periods) - 1 - for idx in range(0, len(future_periods[:-1]), self.step_size): - depth = min(self.view_depth, last_idx - idx) - step = min(self.step_size, last_idx - idx) + if self.step_size is None: + raise RuntimeError('step_size not initialized') + last_base_year = ((len(future_periods) - 2) // self.step_size) * self.step_size + base_years = list(range(0, last_base_year+1, self.step_size)) + if not self.evolving: + # Remove redundant iterations near end of horizon if not evolving + catch_Pe = [i for i in base_years if i + self.view_depth >= len(future_periods) - 1] + if len(catch_Pe) > 1: + # keep only one iteration that captures the end of the horizon + base_years = base_years[:-len(catch_Pe) + 1] + for n, idx in enumerate(base_years): + depth = min(self.view_depth, len(future_periods) - idx - 1) + if idx == base_years[-1]: + # last period, record the rest + step = depth + else: + # record to next base year + step = base_years[n+1] - idx if depth < 1: - break + msg = ( + 'Calculated MyopicIndex with non-positive depth. ' + 'This should never happen. Code error likely. ' + 'idx: {}, step: {}, depth: {}, future_periods: {}' + ).format(idx, step, depth, future_periods) + logger.error(msg) + raise RuntimeError(msg) myopic_idx = MyopicIndex( base_year=future_periods[idx], step_year=future_periods[idx + step], @@ -484,23 +613,27 @@ def characterize_run(self, future_periods: list[int] | None = None) -> None: logger.debug('Added myopic index %s', myopic_idx) logger.info('myopic run is divided into %d instances', len(self.instance_queue)) - def execute_script(self, script_file: Path): + def execute_script(self, script_file: Path) -> None: """ A utility to execute a sql script on the current db connection :return: """ - with open(script_file, 'r') as table_script: + with open(script_file) as table_script: sql_commands = table_script.read() logger.debug('Executing sql from file: %s on connection: %s', script_file, self.output_con) + assert self.cursor is not None + assert self.output_con is not None self.cursor.executescript(sql_commands) self.output_con.commit() - def clear_old_results(self): + def clear_old_results(self) -> None: """ Clear old results from tables :return: """ - scenario_name = self.config.scenario + assert self.cursor is not None + assert self.output_con is not None + scenario_name = self.config.scenario if self.config else None logger.debug('Deleting old results for scenario name %s', scenario_name) for table in self.tables_with_scenario_reference: try: @@ -508,23 +641,38 @@ def clear_old_results(self): f'DELETE FROM {table} WHERE scenario = ? OR scenario like ?', (scenario_name, f'{scenario_name}-%'), ) - except sqlite3.OperationalError: - SE.write(f'no scenario ref in table {table}\n') - raise sqlite3.OperationalError + except sqlite3.OperationalError as e: + sys.stderr.write(f'Could not clear scenario from table {table}.\n') + raise sqlite3.OperationalError from e for table in self.tables_without_scenario_reference: try: self.cursor.execute(f'DELETE FROM {table} WHERE 1') - except sqlite3.OperationalError: - SE.write(f'Failed to clear table {table}.\n') - raise sqlite3.OperationalError + except sqlite3.OperationalError as e: + sys.stderr.write(f'Failed to clear table {table}.\n') + raise sqlite3.OperationalError from e self.output_con.commit() - def clear_results_after(self, period): + def cleanup_outputs_by_period(self, base_year: int) -> None: + assert self.output_con is not None + assert self.cursor is not None + assert self.progress_mapper is not None + assert self.config is not None + self.progress_mapper.report( + MyopicIndex(base_year, 0, 0, 0), 'check' + ) # reusing 'check' for lack of a better 'cleanup' status + self.output_con.execute( + f'DELETE FROM output_objective WHERE scenario == "{self.config.scenario}"' + ) + self.output_con.commit() + + def clear_results_after(self, period: int) -> None: """ clear the results tables for the periods on/after the period specified :param period: the starting period to clear :return: """ + if self.optimization_periods is None: + raise RuntimeError('Optimization periods not initialized') if period not in self.optimization_periods: logger.error( 'Tried to clear period results for %s that is not in %s', @@ -534,24 +682,66 @@ def clear_results_after(self, period): raise ValueError(f'Trying to clear a year {period} that is not in the optimize periods') logger.debug('Clearing periods %s+ from output tables', period) + assert self.cursor is not None + assert self.output_con is not None for table in self.tables_with_period: + scenario_name = self.config.scenario if self.config else None try: self.cursor.execute( f'DELETE FROM {table} WHERE period >= (?) and scenario = (?)', - (period, self.config.scenario), + (period, scenario_name), ) - except sqlite3.OperationalError: - SE.write(f'Failed trying to clear periods from table {table}\n') - raise sqlite3.OperationalError + except sqlite3.OperationalError as e: + sys.stderr.write(f'Failed to clear periods from table {table}.\n') + raise sqlite3.OperationalError from e # special case... new capacity has vintage only... + scenario_name = self.config.scenario if self.config else None + self.cursor.execute( + 'DELETE FROM main.output_built_capacity WHERE ' + 'main.output_built_capacity.vintage >= (?) AND scenario = (?)', + (period, scenario_name), + ) + self.output_con.commit() + + def report_total_demand(self, mi: MyopicIndex) -> None: + assert self.output_con is not None + assert self.cursor is not None + self.cursor.execute( + "SELECT SUM(demand) FROM output_demand WHERE scenario='original'" + ) + self.output_con.commit() + + def write_myopic_efficiency(self, mi: MyopicIndex, status: str) -> None: + assert self.output_con is not None + assert self.cursor is not None + self.cursor.execute( + 'SELECT COUNT(*) FROM myopic_efficiency WHERE base_year=? AND step_year=?', + (mi.base_year, mi.step_year), + ) + if self.cursor.fetchone()[0] == 0: + self.cursor.execute( + 'INSERT INTO myopic_efficiency ' + '(base_year, step_year, last_demand_year, last_year, status) ' + 'VALUES (?, ?, ?, ?, ?)', + (mi.base_year, mi.step_year, mi.last_demand_year, mi.last_year, status), + ) + else: + self.cursor.execute( + 'UPDATE myopic_efficiency SET status=? WHERE base_year=? AND step_year=?', + (status, mi.base_year, mi.step_year), + ) + self.output_con.commit() + + def report_cumulative_capacity(self, mi: MyopicIndex) -> None: + assert self.output_con is not None + assert self.cursor is not None self.cursor.execute( - 'DELETE FROM main.OutputBuiltCapacity WHERE main.OutputBuiltCapacity.vintage >= (?) AND scenario = (?)', - (period, self.config.scenario), + "SELECT SUM(capacity) FROM output_capacity WHERE scenario='original'" ) self.output_con.commit() - def __del__(self): + def __del__(self) -> None: """ensure the connection is closed when destructor is called.""" if ( hasattr(self, 'output_con') and self.output_con is not None diff --git a/temoa/extensions/single_vector_mga/__init__.py b/temoa/extensions/single_vector_mga/__init__.py index 5cd29424f..e69de29bb 100644 --- a/temoa/extensions/single_vector_mga/__init__.py +++ b/temoa/extensions/single_vector_mga/__init__.py @@ -1,27 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/25/24 - -""" diff --git a/temoa/extensions/single_vector_mga/output_summary.py b/temoa/extensions/single_vector_mga/output_summary.py index 2caa33f9d..54e387b15 100644 --- a/temoa/extensions/single_vector_mga/output_summary.py +++ b/temoa/extensions/single_vector_mga/output_summary.py @@ -1,73 +1,55 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/26/24 - A tabular summation of the results from an SVMGA run """ +from __future__ import annotations + import sqlite3 -from sqlite3 import Connection +from typing import TYPE_CHECKING, Any, cast + +if TYPE_CHECKING: + from collections.abc import Iterable + + from temoa.core.config import TemoaConfig -import tabulate -from temoa.temoa_model.temoa_config import TemoaConfig +import tabulate # type: ignore[import-untyped] def summarize(config: TemoaConfig, orig_cost: float, option_cost: float) -> None: scenarios = (config.scenario + '-0', config.scenario + '-1') - emission_labels = config.svmga_inputs.get('emission_labels', []) - capacity_labels = config.svmga_inputs.get('capacity_labels', []) - activity_labels = config.svmga_inputs.get('activity_labels', []) + svmga_inputs = config.svmga_inputs or {} + emission_labels = svmga_inputs.get('emission_labels', []) + capacity_labels = svmga_inputs.get('capacity_labels', []) + activity_labels = svmga_inputs.get('activity_labels', []) conn = sqlite3.connect(config.output_database) - records = [['Category', 'Label', 'Original', 'Option', 'Delta [%]']] - delta = (option_cost - orig_cost) / orig_cost * 100 - records.append(['Cost', 'Total Cost', orig_cost, option_cost, delta]) + records: list[list[Any]] = [['Category', 'Label', 'Original', 'Option', 'Delta [%]']] + total_delta = (option_cost - orig_cost) / orig_cost * 100 + records.append(['Cost', 'Total Cost', orig_cost, option_cost, total_delta]) - for item in sorted(emission_labels): + for item in sorted(cast('Iterable[Any]', emission_labels)): orig = poll_emission( conn, scenarios[0], item, ) option = poll_emission(conn, scenarios[1], item) - delta = float((option - orig) / orig * 100) if all((orig, option)) else None - records.append(['Emission', item, orig, option, delta]) - for item in sorted(activity_labels): + row_delta = float((option - orig) / orig * 100) if orig != 0.0 else None + records.append(['Emission', item, orig, option, row_delta]) + for item in sorted(cast('Iterable[Any]', activity_labels)): orig = poll_activity(conn, scenarios[0], item) option = poll_activity(conn, scenarios[1], item) - delta = (option - orig) / orig * 100 if all((orig, option)) else None + row_delta = (option - orig) / orig * 100 if orig != 0.0 else None - records.append(['Activity', item, orig, option, delta]) + records.append(['Activity', item, orig, option, row_delta]) - for item in sorted(capacity_labels): + for item in sorted(cast('Iterable[Any]', capacity_labels)): orig = poll_capacity(conn, scenarios[0], item) option = poll_capacity(conn, scenarios[1], item) - delta = (option - orig) / orig * 100 if all((orig, option)) else None + row_delta = (option - orig) / orig * 100 if orig != 0.0 else None - records.append(['Capacity', item, orig, option, delta]) + records.append(['Capacity', item, orig, option, row_delta]) print() print(tabulate.tabulate(records, headers='firstrow', tablefmt='outline', floatfmt='.2f')) @@ -79,34 +61,34 @@ def summarize(config: TemoaConfig, orig_cost: float, option_cost: float) -> None conn.close() -def poll_emission(conn: Connection, scenario: str, label: str) -> float: +def poll_emission(conn: sqlite3.Connection, scenario: str, label: str) -> float: """ poll the output database of selected iteration for the given emission label total """ - raw = conn.execute( - 'SELECT sum(emission) FROM main.OutputEmission WHERE scenario=? AND emis_comm=?', + row = conn.execute( + 'SELECT sum(emission) FROM main.output_emission WHERE scenario=? AND emis_comm=?', (scenario, label), - ).fetchone()[0] - return raw + ).fetchone() + return float(row[0] if row and row[0] is not None else 0.0) -def poll_activity(conn: Connection, scenario: str, label: str) -> float: +def poll_activity(conn: sqlite3.Connection, scenario: str, label: str) -> float: """ poll the Flow Out activity for the given emission label total """ - raw = conn.execute( - 'SELECT sum(flow) FROM main.OutputFlowOut WHERE scenario=? AND tech=?', + row = conn.execute( + 'SELECT sum(flow) FROM main.output_flow_out WHERE scenario=? AND tech=?', (scenario, label), - ).fetchone()[0] - return raw + ).fetchone() + return float(row[0] if row and row[0] is not None else 0.0) -def poll_capacity(conn: Connection, scenario: str, label: str) -> float: +def poll_capacity(conn: sqlite3.Connection, scenario: str, label: str) -> float: """ poll the built capacity for the given emission label total """ - raw = conn.execute( - 'SELECT sum(capacity) FROM main.OutputBuiltCapacity WHERE scenario=? AND tech=?', + row = conn.execute( + 'SELECT sum(capacity) FROM main.output_built_capacity WHERE scenario=? AND tech=?', (scenario, label), - ).fetchone()[0] - return raw + ).fetchone() + return float(row[0] if row and row[0] is not None else 0.0) diff --git a/temoa/extensions/single_vector_mga/sv_mga_sequencer.py b/temoa/extensions/single_vector_mga/sv_mga_sequencer.py index a8aca3341..8498390bf 100644 --- a/temoa/extensions/single_vector_mga/sv_mga_sequencer.py +++ b/temoa/extensions/single_vector_mga/sv_mga_sequencer.py @@ -1,50 +1,30 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/25/24 Single-Vector MGA is a 2-stage solve process to look at an alternative solution with a relaxed cost The "single vector" distinguishes it from "regular" MGA in that this only uses 1 extra solve to look in one particular vector direction, characterized by keywords from an associated config file """ + import logging import sqlite3 import sys from collections.abc import Iterable +from typing import Any, cast, TYPE_CHECKING -from pyomo.core import value, Constraint, Expression, Objective -from pyomo.dataportal import DataPortal +from pyomo.core import Constraint, Expression, Objective, value from pyomo.opt import check_optimal_termination +from temoa._internal.run_actions import build_instance, handle_results, save_lp, solve_instance +from temoa._internal.table_writer import TableWriter +from temoa.components.costs import total_cost_rule +from temoa.core.config import TemoaConfig +from temoa.core.model import TemoaModel +from temoa.data_io.hybrid_loader import HybridLoader from temoa.extensions.single_vector_mga.output_summary import summarize -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.model_checking.pricing_check import price_checker -from temoa.temoa_model.run_actions import build_instance, solve_instance, handle_results, save_lp -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_model import TemoaModel -from temoa.temoa_model.temoa_rules import TotalCost_rule +from temoa.model_checking.pricing_check import price_checker + +if TYPE_CHECKING: + from pyomo.dataportal import DataPortal logger = logging.getLogger(__name__) @@ -69,12 +49,13 @@ def __init__(self, config: TemoaConfig): self.writer.clear_scenario() self.verbose = False # for troubleshooting - self.cost_epsilon = config.svmga_inputs.get('cost_epsilon', 0.05) + svmga_inputs = config.svmga_inputs or {} + self.cost_epsilon: float = float(svmga_inputs.get('cost_epsilon', 0.05)) # type: ignore[arg-type] logger.info('Set SVMGA cost (relaxation) epsilon to: %0.3f', self.cost_epsilon) logger.info('Initialized SVMGA sequencer.') - def start(self): + def start(self) -> None: """Run the sequencer... This should look pretty similar to 2 PF runs, back-to-back @@ -129,28 +110,35 @@ def start(self): handle_results(instance, results=res, config=self.config, append=False, iteration=0) # 3a. Capture cost and make it a constraint - tot_cost = value(instance.TotalCost) + tot_cost = value(instance.total_cost) logger.info('Completed initial solve with total cost: %0.2f', tot_cost) logger.info('Relaxing cost by fraction: %0.3f', self.cost_epsilon) # get hook on the expression generator for total cost... - cost_expression = TotalCost_rule(instance) + cost_expression = total_cost_rule(instance) instance.cost_cap = Constraint(expr=cost_expression <= (1 + self.cost_epsilon) * tot_cost) # 3b. remove the old objective - # instance.TotalCost.deactivate() - instance.del_component(instance.TotalCost) + # instance.total_cost.deactivate() + instance.del_component(instance.total_cost) # 4. Reconstruct the OBJ function... - emission_labels = self.config.svmga_inputs.get('emission_labels', []) - capacity_labels = self.config.svmga_inputs.get('capacity_labels', []) - activity_labels = self.config.svmga_inputs.get('activity_labels', []) + svmga_inputs = self.config.svmga_inputs or {} + emission_labels = svmga_inputs.get('emission_labels', []) + capacity_labels = svmga_inputs.get('capacity_labels', []) + activity_labels = svmga_inputs.get('activity_labels', []) new_obj = SvMgaSequencer.construct_obj( - instance, emission_labels, capacity_labels, activity_labels + instance, + cast('list[str]', emission_labels), + cast('list[str]', capacity_labels), + cast('list[str]', activity_labels), ) # check for an empty objective if isinstance(new_obj, int): # no variables found - msg = 'Construction of the alternative OBJ in SVMGA failed to locate any variables. Exiting' + msg = ( + 'Construction of the alternative OBJ in SVMGA failed to locate any variables. ' + 'Exiting' + ) logger.error(msg) print(msg) sys.exit(1) @@ -181,36 +169,43 @@ def start(self): logger.error('The baseline SVMGA solve failed. Terminating run.') raise RuntimeError('Baseline SVMGA solve failed. Terminating run.') logger.info( - 'Completed secondary solve with total cost: %0.2f', value(TotalCost_rule(instance)) + 'Completed secondary solve with total cost: %0.2f', value(total_cost_rule(instance)) ) # record the 1-solve in all tables handle_results(instance, results=res, config=self.config, append=True, iteration=1) if not self.config.silent: - summarize(self.config, tot_cost, value(TotalCost_rule(instance))) + summarize(self.config, tot_cost, value(total_cost_rule(instance))) @staticmethod - def flow_idxs_from_eac_idx(M: TemoaModel, reitvo: tuple) -> tuple[list[tuple], ...]: + def flow_idxs_from_eac_idx( + model: TemoaModel, reitvo: tuple[Any, ...] + ) -> tuple[list[tuple[Any, ...]], list[tuple[Any, ...]]]: """ From the emission index, expand to create the full list of possible flow indices for regular and annual flows. These may/may not be valid and must be screened for membership later """ r, _, i, t, v, o = reitvo - psd_set = [(p, s, d) for p in M.time_optimize for s in M.time_season for d in M.time_of_day] + psd_set = [ + (p, s, d) + for p in model.time_optimize + for s in model.time_season + for d in model.time_of_day + ] flow_idxs = [(r, *psd, i, t, v, o) for psd in psd_set] - annual_flow_idxs = [(r, p, i, t, v, o) for p in M.time_optimize] + annual_flow_idxs = [(r, p, i, t, v, o) for p in model.time_optimize] return flow_idxs, annual_flow_idxs @staticmethod def construct_obj( - M: TemoaModel, + model: TemoaModel, emission_labels: Iterable[str], capacity_labels: Iterable[str], activity_labels: Iterable[str], - verbose=True, + verbose: bool = True, ) -> Expression | int: """ Construct an alternative OBJ statement from the config data @@ -236,9 +231,12 @@ def construct_obj( categories_used += 1 if categories_used > 1: msg = ( - 'Warning: Using labels in multiple categories during SVMGA may lead to odd results.\n' - 'The catagories are not specifically designed to work together, but rather add flexibility.\n' - 'The new OBJ function will be an *unweighted* sum of everything found, so outputs in \n' + 'Warning: Using labels in multiple categories during SVMGA may lead to odd ' + 'results.\n' + 'The catagories are not specifically designed to work together, but rather add ' + 'flexibility.\n' + 'The new OBJ function will be an *unweighted* sum of everything found, so outputs ' + 'in \n' 'differing categories with vastly different scale may have odd interactions.' ) @@ -247,36 +245,38 @@ def construct_obj( # handle emissions... for label in emission_labels: - idxs = [idx for idx in M.EmissionActivity if idx[1] == label] + idxs = [idx for idx in model.emission_activity if idx[1] == label] logger.debug('Located %d items for emission label: %s', len(idxs), label) for idx in idxs: - # for each indexed item in EmissionActivity, we need to search both the regular + # for each indexed item in emission_activity, we need to search both the regular # flows and the annual flows. And, we need to sum across the "expanded" index # for both which includes period, season, tod or just period respectively - expanded_idxs, expanded_annual_idxs = SvMgaSequencer.flow_idxs_from_eac_idx(M, idx) + expanded_idxs, expanded_annual_idxs = SvMgaSequencer.flow_idxs_from_eac_idx( + model, idx + ) element = sum( - M.V_FlowOut[flow_idx] * M.EmissionActivity[idx] + model.v_flow_out[flow_idx] * model.emission_activity[idx] for flow_idx in expanded_idxs - if flow_idx in M.V_FlowOut + if flow_idx in model.v_flow_out ) expr += element annual_element = sum( - M.V_FlowOutAnnual[annual_flow_idx] * M.EmissionActivity[idx] + model.v_flow_out_annual[annual_flow_idx] * model.emission_activity[idx] for annual_flow_idx in expanded_annual_idxs - if annual_flow_idx in M.V_FlowOutAnnual + if annual_flow_idx in model.v_flow_out_annual ) expr += annual_element # handle activity... for label in activity_labels: - idxs = [idx for idx in M.V_FlowOut if idx[5] == label] + idxs = [idx for idx in model.v_flow_out if idx[5] == label] logger.debug('Located %d items for activity label: %s', len(idxs), label) - expr += sum(M.V_FlowOut[idx] for idx in idxs) + expr += sum(model.v_flow_out[idx] for idx in idxs) # handle capacity... for label in capacity_labels: - idxs = [idx for idx in M.V_Capacity if idx[2] == label] + idxs = [idx for idx in model.v_capacity if idx[2] == label] logger.debug('Located %d items for capacity label: %s', len(idxs), label) - expr += sum(M.V_Capacity[idx] for idx in idxs) + expr += sum(model.v_capacity[idx] for idx in idxs) return expr diff --git a/temoa/extensions/stochastics/EVPI.py b/temoa/extensions/stochastics/EVPI.py deleted file mode 100755 index 4732a337d..000000000 --- a/temoa/extensions/stochastics/EVPI.py +++ /dev/null @@ -1,261 +0,0 @@ -# This version is compatible with Pyomo 5.2 - -import os -import sys - -from pyomo.environ import * -from pyomo.opt import SolverFactory -from pyomo.pysp.ef import create_ef_instance -from pyomo.pysp.scenariotree.manager import ScenarioTreeManagerClientSerial - - -# To see detailed information about options -# for name in options.keys(): -# print(options.about(name)) - -# To see a more compact display of options -# options.display() - -# options.model_location = \ -# os.path.join(farmer_example_dir, 'models') -# options.scenario_tree_location = \ -# os.path.join(farmer_example_dir, 'scenariodata') - - -class DummyTemoaConfig: - pass - - -def compute_evpi(ef_result, pf_result): - pf = 0 - for i in range(0, len(pf_result['cost'])): - pf += pf_result['cd'][i] * pf_result['cost'][i] - return ef_result - pf - - -def solve_pf(p_model, p_data): - """ - solve_pf(p_model, p_data) -> dict() - Solves the model in perfect sight mode. - p_model -> string, the path to the model file. - p_data -> string, the path to the directory of data for the stochastic - model, where ScenarioStructure.dat should resides. - Returns a dictionary including the value of objective function for each - scenario and its conditional probability. - """ - - def return_obj(instance): - from pyomo.core import Objective - - obj = instance.component_objects(Objective, active=True) - obj_values = list() - for o in obj: - # See section 18.6.3 in Pyomo online doc - # https://taizilongxu.gitbooks.io/stackoverflow-about-python/content/59/README.html - method_obj = getattr(instance, str(o)) - obj_values.append(method_obj()) - # Assuming there is only one objective function - return obj_values[0] - - # Out-of-date for Pyomo 4.1 - # obj = instance.active_components(Objective) - # objs = obj.items()[0] - # obj_name, obj_value = objs[0], value(objs[1]()) - # return obj_value - - import sys - import os - from collections import deque, defaultdict - from pyomo.pysp.util.scenariomodels import scenario_tree_model - - (head, tail) = os.path.split(p_model) - sys.path.insert(0, head) - pwd = os.getcwd() - os.chdir(p_data) - - s2fp_dict = defaultdict(deque) # Scenario to 'file path' dictionary, .dat not included - s2cd_dict = defaultdict(float) # Scenario to conditonal density mapping - sStructure = scenario_tree_model.create_instance(filename='ScenarioStructure.dat') - - # The following code is borrowed from Kevin's temoa_lib.py - ########################################################################### - # Step 1: find the root node. PySP doesn't make this very easy ... - - # a child -> parent mapping, because every child has only one parent, but - # not vice-versa - ctpTree = dict() # Child to parent dict, one to one mapping - - to_process = deque() - to_process.extend(sStructure.Children.keys()) - while to_process: - node = to_process.pop() - if node in sStructure.Children: - # it's a parent! - new_nodes = set(sStructure.Children[node]) - to_process.extend(new_nodes) - ctpTree.update({n: node for n in new_nodes}) - - # parents - children - root_node = (set(ctpTree.values()) - set(ctpTree.keys())).pop() - - # ptcTree = defaultdict( list ) # Parent to child node, one to multiple mapping - # for c, p in ctpTree.iteritems(): - # ptcTree[ p ].append( c ) - # ptcTree = dict( ptcTree ) # be slightly defensive; catch any additions - - # leaf_nodes = set(ctpTree.keys()) - set(ctpTree.values()) - leaf_nodes = set(sStructure.ScenarioLeafNode.values()) # Try to hack Kevin's code - - scenario_nodes = dict() # Map from leafnode to 'node path' - for node in leaf_nodes: # e.g.: {Rs0s0: [R, Rs0, Rs0s0]} - s = deque() - scenario_nodes[node] = s - while node in ctpTree: - s.append(node) - node = ctpTree[node] - s.append(node) - s.reverse() - ########################################################################### - - for s in sStructure.Scenarios: - cp = 1.0 # Starting probability - for n in scenario_nodes[sStructure.ScenarioLeafNode[s]]: - cp = cp * sStructure.ConditionalProbability[n] - if not sStructure.ScenarioBasedData.value: - s2fp_dict[s].append(n + '.dat') - s2cd_dict[s] = cp - - if sStructure.ScenarioBasedData.value: - for s in sStructure.Scenarios: - s2fp_dict[s].append(s + '.dat') - # IP() - model_module = __import__(tail[:-3], globals(), locals()) - model = model_module.model - pf_result = {'cost': list(), 'cd': list()} - for s in sStructure.Scenarios: - pf_result['cd'].append(s2cd_dict[s]) - data = DataPortal(model=model) - for dat in s2fp_dict[s]: - data.load(filename=dat) - instance = model.create_instance(data) - optimizer = SolverFactory('cplex') - results = optimizer.solve(instance) - - instance.solutions.load_from(results) - # instance.load(results) - obj_val = return_obj(instance) - pf_result['cost'].append(obj_val) - sys.stdout.write('\nSolved .dat(s) {}\n'.format(s2fp_dict[s])) - sys.stdout.write(' Total cost: {}\n'.format(obj_val)) - os.chdir(pwd) - return pf_result - - -def solve_ef(p_model, p_data, dummy_temoa_options=None): - """ - solve_ef(p_model, p_data) -> objective value of the extensive form - Solves the model in stochastic mode. - p_model -> string, the path to the model file (ReferenceModel.py). - p_data -> string, the path to the directory of data for the stochastic - mdoel, where ScenarioStructure.dat should resides. - Returns a float point number of the value of objective function for the - stochastic program model. - """ - - options = ScenarioTreeManagerClientSerial.register_options() - - if os.path.basename(p_model) == 'ReferenceModel.py': - options.model_location = os.path.dirname(p_model) - else: - sys.stderr.write('\nModel file should be ReferenceModel.py. Exiting...\n') - sys.exit(1) - options.scenario_tree_location = p_data - - # using the 'with' block will automatically call - # manager.close() and gracefully shutdown - with ScenarioTreeManagerClientSerial(options) as manager: - manager.initialize() - - ef_instance = create_ef_instance(manager.scenario_tree, verbose_output=options.verbose) - - ef_instance.dual = Suffix(direction=Suffix.IMPORT) - - with SolverFactory('cplex') as opt: - ef_result = opt.solve(ef_instance) - - # Write to database - if dummy_temoa_options: - sys.path.append(options.model_location) - from pformat_results import pformat_results - from temoa_config import TemoaConfig - - temoa_options = TemoaConfig() - temoa_options.config = dummy_temoa_options.config - temoa_options.save_lp_file = dummy_temoa_options.save_lp_file - temoa_options.saveTEXTFILE = dummy_temoa_options.saveTEXTFILE - temoa_options.path_to_data = dummy_temoa_options.path_to_data - temoa_options.save_excel = dummy_temoa_options.save_excel - ef_result.solution.Status = 'feasible' # Assume it is feasible - for s in manager.scenario_tree.scenarios: - ins = s._instance - temoa_options.scenario = s.name - temoa_options.dot_dat = [ - os.path.join(options.scenario_tree_location, s.name + '.dat') - ] - temoa_options.output_file = os.path.join( - options.scenario_tree_location, dummy_temoa_options.output_file - ) - msg = '\nStoring results from scenario {} to database.\n'.format(s.name) - sys.stderr.write(msg) - formatted_results = pformat_results(ins, ef_result, temoa_options) - - ef_instance.solutions.store_to(ef_result) - ef_obj = value(ef_instance.EF_EXPECTED_COST.values()[0]) - return ef_obj - - -def do_test(p_model, p_data, temoa_config=None): - from time import time - - t0 = time() - timeit = lambda: time() - t0 - - if not isinstance(p_data, list): - p_data = [p_data] - for this_data in p_data: - sys.stderr.write('\nSolving perfect sight mode\n') - sys.stdout.write('-' * 25 + '\n') - pf_result = solve_pf(p_model, this_data) - msg = 'Time: {} s\n'.format(timeit()) - sys.stderr.write(msg) - - sys.stderr.write('\nSolving extensive form\n') - sys.stdout.write('-' * 25 + '\n') - ef_result = solve_ef(p_model, this_data, temoa_config) - - msg = '\nTime: {} s\n'.format(timeit()) - msg += 'runef objective value: {}\n'.format(ef_result) - msg += 'EVPI: {}\n'.format(compute_evpi(ef_result, pf_result)) - sys.stderr.write(msg) - - -if __name__ == '__main__': - # p_model = "/afs/unity.ncsu.edu/users/b/bli6/temoa/temoa_model" - # p_data = [ - # "/afs/unity.ncsu.edu/users/b/bli6/TEMOA_stochastic/NC/noIGCC-CP", - # "/afs/unity.ncsu.edu/users/b/bli6/TEMOA_stochastic/NC/noIGCC-noCP", - # "/afs/unity.ncsu.edu/users/b/bli6/TEMOA_stochastic/NC/IGCC-CP", - # "/afs/unity.ncsu.edu/users/b/bli6/TEMOA_stochastic/NC/IGCC-noCP", - # ] - # dummy_temoa_options = DummyTemoaConfig() - # dummy_temoa_options.config = None - # dummy_temoa_options.keepPyomoLP = False - # dummy_temoa_options.saveTEXTFILE = False - # dummy_temoa_options.path_to_data = None - # dummy_temoa_options.saveEXCEL = False - # dummy_temoa_options.output = "NCreference.db" - # do_test(p_model, p_data, dummy_temoa_options) - - p_model = '/mnt/disk2/nspatank/SS_2_H/For_Jeff/temoa_ssudan/temoa_model/ReferenceModel.py' - p_data = '/mnt/disk2/nspatank/SS_2_H/For_Jeff/temoa_ssudan/tools/S_Sudan' - do_test(p_model, p_data) diff --git a/temoa/extensions/stochastics/README.txt b/temoa/extensions/stochastics/README.txt deleted file mode 100644 index 339003393..000000000 --- a/temoa/extensions/stochastics/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -This extension is currently NOT maintained and is retained as a framework for possible future use. - -1 JUN 2024 \ No newline at end of file diff --git a/temoa/extensions/stochastics/ReferenceModel.py b/temoa/extensions/stochastics/ReferenceModel.py deleted file mode 120000 index 3e640644e..000000000 --- a/temoa/extensions/stochastics/ReferenceModel.py +++ /dev/null @@ -1 +0,0 @@ -temoa_stochastic.py \ No newline at end of file diff --git a/temoa/extensions/stochastics/VSS.py b/temoa/extensions/stochastics/VSS.py deleted file mode 100755 index f8e885779..000000000 --- a/temoa/extensions/stochastics/VSS.py +++ /dev/null @@ -1,313 +0,0 @@ -# Script to determine the value of the stochastic solution using Temoa -from pyomo.core import DataPortal -from pyomo.opt import SolverFactory -from pyomo.pysp.ef_vss import * -from pyomo.pysp.ef_writer_script_old import * - - -def organize_csv(): - # This function was imported from the EVPI script - from csv import reader, writer - from collections import OrderedDict - - rows = list() - tech = list() - node = list() - empty_row = [''] * 7 - with open('V_ActivityByPeriodAndTech.csv', 'rb') as f: - csv_reader = reader(f, dialect='excel') - for row in csv_reader: - rows.append(row + ['']) - - organized_rows = OrderedDict() - for row in rows: - this_tech = row[4] - if row[1] not in node: - node.append(row[1]) - if this_tech not in tech: - tech.append(this_tech) - organized_rows[this_tech] = [row] - else: - organized_rows[this_tech].append(row) - - for this_tech in tech: - for i in range(0, len(organized_rows[this_tech])): - if organized_rows[this_tech][i][1] != node[i]: - organized_rows[this_tech].insert(i, empty_row) - - # tech.sort() - with open('V_ActivityByPeriodAndTech_org.csv', 'wb') as f: - csv_writer = writer(f, dialect='excel') - for this_tech in organized_rows: - row = list() - for i in organized_rows[this_tech]: - row += i - csv_writer.writerow(row) - - -def my_ef_writer(scenario_tree): - # This function was imported from the EVPI script - from csv import writer - from collections import OrderedDict - - rows = dict() # Key is the variable's name - for stage in scenario_tree._stages: - stage_name = stage._name - for tree_node in stage._tree_nodes: - tree_node_name = tree_node._name - for var_id in sorted(tree_node._variable_ids): - var_name, index = tree_node._variable_ids[var_id] - row = [str(stage_name), str(tree_node_name), str(var_name)] - if isinstance(index, str): - row += [index] - else: - for i in index: - row += [str(i)] - row += [str(tree_node._solution[var_id])] - if var_name not in rows: - rows[var_name] = [row] - else: - rows[var_name].append(row) - - stage_cost_vardata = tree_node._cost_variable_datas[0][0] - obj = str(stage_cost_vardata.parent_component().name) - row = [ - str(stage_name), - str(tree_node_name), - str(obj), - str(stage_cost_vardata.index()), - str(stage_cost_vardata()), - ] - if obj not in rows: - rows[obj] = [row] - else: - rows[obj].append(row) - - for ofile in rows.keys(): - with open(ofile + '.csv', 'wb') as f: - csv_writer = writer(f, dialect='excel') - csv_writer.writerows(rows[ofile]) - - # To calculate V_Activity[p,t] - if 'V_ActivityByPeriodAndProcess' in rows: - V_Activity_ptv = rows['V_ActivityByPeriodAndProcess'] - V_Activity_pt = OrderedDict() - for row in V_Activity_ptv: - key = (row[0], row[1], row[2], row[3], row[4]) # (Stage, Node, var_name, p, t) - if key not in V_Activity_pt: - V_Activity_pt[key] = float(row[6]) - else: - V_Activity_pt[key] += float(row[6]) - - with open('V_ActivityByPeriodAndTech.csv', 'wb') as f: - csv_writer = writer(f, dialect='excel') - for key in V_Activity_pt.keys(): - row = list(key) + [V_Activity_pt[key]] - csv_writer.writerow(row) - - -def solve_ef(ef_options): - # This function solves a stochastic optimization problem via extensive form - # This function was imported from the EVPI script - import os - import sys - - sif = ScenarioTreeInstanceFactory( - ef_options.model_directory, ef_options.instance_directory, ef_options.verbose - ) - scenario_tree = GenerateScenarioTreeForEF(ef_options, sif) - ef = EFAlgorithmBuilder(ef_options, scenario_tree) - f = open(os.devnull, 'w') - sys.stdout = f - ef.solve() - # ef.save_solution() # This line saves the results into two csv files - sys.stdout = sys.__stdout__ - f.close() - sys.stderr.write('\nrunef output suppressed\n') - my_ef_writer(ef._scenario_tree) - root_node = ef._scenario_tree._stages[0]._tree_nodes[0] - # II() - return root_node.computeExpectedNodeCost() - - -def solve_ef_fix(ef_options, avg_instance): - # This function solves a stochastic optimization problem via extensive form - # where first stage decision variables are fixed at the optimal values from - # the deterministic model called here avg_instance - - import os - import sys - - sif = ScenarioTreeInstanceFactory( - ef_options.model_directory, ef_options.instance_directory, ef_options.verbose - ) - scenario_tree = GenerateScenarioTreeForEF(ef_options, sif) - ef = EFAlgorithmBuilder(ef_options, scenario_tree) - - time_fut = avg_instance.time_future.data() - techs = avg_instance.tech_all.data() - dV_Capacity = avg_instance.V_Capacity.get_values() # Getting dec vars that matters to be fixed - # dV_HydroStorage = avg_instance.V_HydroStorage.get_values() - - # Storing techs and future time periods in vector for easy access - vtime_fut = [0] * len(time_fut) - k = 0 - for iaux in time_fut: - vtime_fut[k] = iaux - k = k + 1 - - # Fixing Capacity values for first stage at the ef instance with values from the deterministic instance - for iaux1, iaux2 in dV_Capacity: - if iaux2 == vtime_fut[0]: - ef._binding_instance.S0s0s0.V_Capacity[iaux1, iaux2].fix(dV_Capacity[iaux1, iaux2]) - # ef._binding_instance.S0.V_Capacity[iaux1, iaux2].fix(3) #just for checking if fixing at one scen also fix in the other - ok for now - - # Fixing Hydro Storage values for first stage - # for iaux1 , iaux2 in dV_HydroStorage: - # if iaux2 == vtime_fut[0]: - # ef._binding_instance.S0s0.V_HydroStorage[iaux1, iaux2].fix(dV_HydroStorage[iaux1,iaux2]) - - f = open(os.devnull, 'w') - sys.stdout = f - ef.solve() - # ef.save_solution() # This line saves the results into two csv files - sys.stdout = sys.__stdout__ - f.close() - sys.stderr.write('\nrunef output suppressed\n') - my_ef_writer(ef._scenario_tree) - root_node = ef._scenario_tree._stages[0]._tree_nodes[0] - return root_node.computeExpectedNodeCost() - - -def solve_dm(p_model, p_data, opt_solver): - # This function solves a deterministic model with the inputs for - # uncertainty values represented by their average values at each stage - # We assume the ReferenceModel.dat as the average problem properly represented - # inside the stochastic folder - - def return_obj(instance): - from pyomo.core import Objective - - obj = instance.component_objects(Objective, active=True) - obj_values = list() - for o in obj: - # See section 18.6.3 in Pyomo online doc - method_obj = getattr(instance, str(o)) - obj_values.append(method_obj()) - # Assuming there is only one objective function - return obj_values[0] - - import sys - import os - - (head, tail) = os.path.split(p_model) - sys.path.insert(0, head) - pwd = os.getcwd() - os.chdir(p_data) - - model_module = __import__(tail[:-3], globals(), locals()) - model = model_module.model - dm_result = {'cost': list(), 'flowin': list(), 'flowout': list(), 'capacity': list()} - - data = DataPortal(model=model) - - dat = 'AverageModel.dat' # Loading the model from the data file - data.load(filename=dat) - - instance = model.create_instance( - data - ) # Defining the model instance with the data from .dat file - optimizer = SolverFactory(opt_solver) # Defining the optimization solver - results = optimizer.solve(instance) # Solving the optimization model - - instance.solutions.load_from(results) # Saving solutions in memory - - # Getting objective function values - obj_val = return_obj(instance) - dm_result['cost'].append(obj_val) - - # Writting to the Shell - sys.stdout.write('\nSolved deterministic model with uncertainty at average valures \n') - sys.stdout.write(' Total cost: {}\n'.format(obj_val)) - os.chdir(pwd) - return instance # Returning instance solved, values will be used later - - -def runEVPI(): - # EVPI_value = test_twotechs_vss_base() - EVPI_value = test_sudan_VSS() - return EVPI_value - - -def runECIU(): - import os - - # folder_string = "stochastic/twotechs_vss_base/" - folder_string = 'stochastic/utopia_vss/' - os.system('python temoa_model/ --eciu ' + folder_string) - - -def runVSS(): - # This is the main function. It calls 1) Extensive Form 2)Deterministic LP 3)Fixed Extensive Form - # After results of 1) and 3) are obtained it computes the VSS - # As input, this function requires the path of the stochastic folder and temoa_stochastic.py file - # It assumes that an instance named ReferenceModel.dat is located inside the stochastic folder - - from time import time - import sys - - sys.stderr.write('\nFinding the Value of the Stochastic Solution using Temoa\n') - - p_model = '/home/arqueiroz/SSudan/S1_2_H/temoa_model/temoa_stochastic.py' - p_data = '/home/arqueiroz/SSudan/S1_2_H/stochastic/S_Sudan_original_stoch_cap_cost_11' - optsolver = 'cplex' - - # --------------------- - # Solving the deterministic model with average values - # --------------------- - sys.stderr.write('\nSolving perfect sight with uncertainty at average values\n') - dm_instance = solve_dm( - p_model, p_data, optsolver - ) # Here we have all the information with respect to objfunc and decvars - - zdm_result = dm_instance.TotalCost.value - # --------------------- - # Solving the extensive model for the recoursive problem - # --------------------- - ef_args = ['-m', p_model, '-i', p_data, '--solver', optsolver, '--solve'] - ef_option_parser = construct_ef_writer_options_parser('runef [options]') - start_time = time() - (ef_options, args) = ef_option_parser.parse_args(args=ef_args) - - # II() - sys.stderr.write('\nSolving extensive form\n') - ef_result = solve_ef(ef_options) - - msg = '\nrunef time: {} s\n'.format(time() - start_time) - msg += 'runef objective value: {}\n'.format(ef_result) - sys.stderr.write(msg) - - # --------------------- - # Solving the extensive model fixing variables for stage 1 - # --------------------- - ef_result_fixed = solve_ef_fix(ef_options, dm_instance) - - # Compute the value of the stochastic solution (vss = z_eev - z_rp) - return ef_result, ef_result_fixed, zdm_result - - -if __name__ == '__main__': - vZrp, vZeev, vZdm = runVSS() - print('---------------------------------------------------------') - print('---------------------------------------------------------') - vEVPI = runEVPI() - print('---------------------------------------------------------') - print('---------------------------------------------------------') - # runECIU() - print('---------------------------------------------------------') - print('---------------------------------------------------------') - print('Zrp = ', vZrp) - print('Zeev = ', vZeev) - print('Zdm = ', vZdm) - print('EVPI = ', vEVPI) - print('VSS = ', vZeev - vZrp) diff --git a/temoa/extensions/stochastics/generate_scenario_tree-nonhomogenous.py b/temoa/extensions/stochastics/generate_scenario_tree-nonhomogenous.py deleted file mode 100755 index b422cfdb2..000000000 --- a/temoa/extensions/stochastics/generate_scenario_tree-nonhomogenous.py +++ /dev/null @@ -1,632 +0,0 @@ -#!/usr/bin/env pyomo_python - -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import os -import sys -from io import StringIO -from pprint import pformat -from shutil import copy as copyfile, rmtree - -from pyomo.core.base.sets import _SetProduct, _SetContainer - -SE = sys.stderr -instance = None - -node_count = 0 -stringify = lambda x: ', '.join(str(i) for i in x) - - -class Storage: - __slots__ = ('value', 'rate') # this saves a noticeable amount of memory - - def __str__(self): - return pformat(self.__dict__, indent=2) - - __repr__ = __str__ - - -class Param(object): - # will be common to all Parameters, so no sense in storing it N times - stochasticset = None - - # this saves a noticeable amount of memory, and mild decrease in time - __slots__ = ('items', 'name', 'spoint', 'param', 'my_keys', 'model_keys', 'skeys') - - def __init__(self, **kwargs): - # At the point someone is using this class, they probably know what - # they're doing, so intentionally die at this point if any of these - # items are not passed. They're all mandatory. - name = kwargs.pop('param') # parameter in question to modify - spoint = kwargs.pop('spoint') # stochastic point at which to do it - rates = kwargs.pop('rates') # how much to vary the parameter - pidx = int(kwargs.pop('stochastic_index')) - - param = getattr(instance, name) # intentionally die if not found. - - indices = tuple() - pindex = param.index() - - if isinstance(pindex, _SetProduct): - indices = param.sparse_keys() - skeys = lambda: (' '.join(str(i) for i in k) for k in self.model_keys) - - keys = param.sparse_keys() - f = lambda x: x[pidx] == spoint - r = lambda x: tuple(x[0:pidx] + x[pidx + 1 :]) - # reduce keys to remove stochastic parameter - - elif isinstance(pindex, _SetContainer): - # this is under sparse keys - indices = (param._index.name,) - skeys = lambda: (' '.join(str(i) for i in self.model_keys)) - - keys = param.sparse_keys() - f = lambda x: x[pidx] == spoint - r = lambda x: tuple(x[0:pidx] + x[pidx + 1 :]) - - # we filter out the spoint because it's inherently known by TreeNode, - # which "owns" /this/ Param - model_keys = filter(f, keys) - my_keys = map(r, model_keys) - - items = dict() - - for actual, mine in zip(model_keys, my_keys): - rate = 1 - for pattern, r in rates: - keys = pattern.split(',') - match = True - for p, t in zip(keys, mine): # "pattern", "test" - if '*' == p: - continue - if t != p: - match = False - break - if match: - rate = r - break - - items[mine] = Storage() - try: - items[mine].value = param[actual] # pulled from model - except ValueError: - items[mine].value = 0 - items[mine].rate = rate - - self.items = items - self.name = name - self.spoint = spoint - self.param = param - self.my_keys = my_keys # these keys are linked -- in the same - self.model_keys = model_keys # order -- for zip()-ability - self.skeys = skeys # for later, string keys - - def __iter__(self): - return self.items.__iter__() - - def __getitem__(self, i): - try: - return self.items[i] - except: - # it's likely the element did not exist, which hopefully means 0? - class _tmp: - rate = 0 - value = 0 - - return _tmp() - - def __str__(self): - x = '; '.join('(%s, %s)' % (self[i].value, self[i].rate) for i in self) - return 'Param(%s): %s' % (self.name, x) - - __repr__ = __str__ - - def as_ampl(self, comment=''): - pindex = self.param.index() - if comment: - comment = '# Decision: %s\n\n' % str(comment) - - keys = self.skeys() - if isinstance(keys, str): - keys = [keys] - - # Together, these functions return the length of a printed version of - # a number, in characters. They are used to make columns of data line up - # so one may have an easier time getting an overall sense of a data file. - def get_int_padding(v): - return len(str(int(v))) - - def get_str_padding(index): - def anonymous_function(obj): - val = obj[index] - return len(str(val)) - - return anonymous_function - - keys = tuple(tuple(i.split()) for i in keys) - vals = tuple(self[i].value for i in self.my_keys) - int_padding = max(map(get_int_padding, vals)) - str_padding = [max(map(get_str_padding(i), keys)) for i in range(len(keys[0]))] - str_format = ' %-{}s' * len(self.model_keys[0]) - str_format = str_format.format(*str_padding) - - format = '\n%%s %%%ds%%s' % int_padding - # works out to something like '\n %s %8d%-6s' - # index { val } - - data = StringIO() - data.write(comment + 'param %s :=' % self.name) - for actual_key, this_key in sorted(zip(self.model_keys, self.my_keys)): - v = self[this_key].value - int_part = str(int(abs(v))) - if int_part != str(abs(v)): - dec_part = str(abs(v))[len(int_part) :] - else: - dec_part = '' - - if v < 0: - int_part = '-%d' % int_part - index = str_format % tuple(actual_key) - data.write(format % (index, int_part, dec_part)) - data.write('\n\t;\n') - - # return comment + data - return data.getvalue() - - -class TreeNode(object): - __slots__ = ('name', 'parent', 'spoint', 'prob', 'params', 'bname', 'children') - - def __init__(self, *args, **kwargs): - # At the point someone is using this class, they probably know what - # they're doing, so intentionally die at this point if any of these - # items are not passed. They're all mandatory. - self.name = kwargs.pop('name') # name of /this/ node - self.parent = kwargs.pop('parent') - self.spoint = kwargs.pop('spoint') # stochastic point of node - self.prob = kwargs.pop('prob') # conditional probability of node - bname = kwargs.pop('filebase') # file name minus extension - types = kwargs.pop('types') # names of decisions - rates = kwargs.pop('rates') # rates at which to vary - sindices = kwargs.pop('stochastic_indices') - - params = rates.keys() - myparams = dict() - if self.name != 'HedgingStrategy': - for key, decisions in rates.items(): - paramkwargs = { - 'param': key, - 'rates': (), - 'spoint': self.spoint, - 'stochastic_index': sindices[key], - } - decision = '{}{}'.format(self.parent, self.name) - paramkwargs.update({'rates': decisions[decision]}) - - myparams[key] = Param(**paramkwargs) - - self.params = myparams - self.bname = bname - self.children = [] - - def addChild(self, node): - self.children.append(node) - - def __repr__(self): - x = self.name - if isinstance(self.name, tuple): - x = ', '.join(x) - return '%s(%s): ' % (self.spoint, x) + ', '.join(str(i) for i in self.params.values()) - - def __str__(self, indent=' ', space=''): - x = ''.join(i.__str__(indent, space + indent) for i in self.children) - - return space + repr(self) + '\n' + x - - def write_dat_files(self): - global node_count - - # Step 1: Write my own file, if necessary - if self.name != 'HedgingStrategy': - params = self.params.values() - data = params[0].as_ampl(self.name) - if len(params) > 1: - data += '\n' + '\n'.join(p.as_ampl() for p in params[1:]) - else: - data = '# Decision: HedgingStrategy (no change from R.dat)\n' - - with open(self.bname + '.dat', 'w') as f: - f.write(data) - - node_count += 1 - inform('\b' * (len(str(node_count - 1)) + 1) + str(node_count) + ' ') - - # Step 2: Tell my children to write their files - for c in self.children: - for p in self.params: - cp = c.params[p] - for key in self.params[p]: - cp[key].value = self.params[p][key].value * cp[key].rate - c.write_dat_files() - - def get_scenario_data(self): - nodes = [self.bname] - nodestage = [(self.bname, 's' + str(self.spoint))] - probability = [(self.bname, self.prob)] - scenarios = [] - children = [] - - if not self.children: - scenarios = [self.bname[2:]] - else: - children = [(self.bname, [c.bname for c in self.children])] - - for child in self.children: - s, n, ns, c, p = child.get_scenario_data() - scenarios += s - nodes += n - nodestage += ns - children += c - probability += p - - return scenarios, nodes, nodestage, children, probability - - -def write_scenario_file(stochasticset, tree): - ( - scenarios, - nodes, - nodestage, - children, - probability, - ) = tree.get_scenario_data() - - child_fmt = 'set Children[%s] :=\n %s\n\t;\n' - scenario_fmt = 'S%(i)s Rs%(i)s' - stages_fmt = 'set StageVariables[s{}] :=\n {}\n\t;' - stagecost_fmt = 's%s StochasticPointCost[%s]' - - leaves = '\n '.join(scenario_fmt % {'i': i} for i in scenarios) - nodes = '\n '.join(nodes) - nodestage = '\n '.join((' '.join(ns) for ns in nodestage)) - scenarios = 'S%s' % '\n S'.join(scenarios) - stagecost = '\n '.join(stagecost_fmt % (s, s) for s in stochasticset) - stages = '\n s'.join(str(se) for se in stochasticset) - - probability = '\n '.join((' '.join(str(i) for i in p) for p in probability)) - children = '\n'.join(child_fmt % (c[0], '\n '.join(c[1])) for c in children) - - # XXX: Temporary and absolute hack, that currently only works for Temoa - # models. The short of it is that this script was written prior to Temoa's - # implementation with sparse sets, so now we have to ensure that only the - # sparse sets are used: - - stage_var_sets = list() - for se in stochasticset: # se = "stochastic element" - flow_keys = [index for index in instance.V_FlowOut.keys() if index[0] == se] - processes = [(t, v) for p, s, d, i, t, v, o in flow_keys if v == se] - - stage_vars = list() - stage_vars.extend( - sorted('V_FlowIn[{},{},{},{},{},{},{}]'.format(*index) for index in flow_keys) - ) - stage_vars.extend( - sorted('V_FlowOut[{},{},{},{},{},{},{}]'.format(*index) for index in flow_keys) - ) - stage_vars.extend(sorted('V_Capacity[{},{}]'.format(*index) for index in processes)) - - stage_var_sets.append(stages_fmt.format(se, '\n '.join(stage_vars))) - stage_var_sets = '\n\n'.join(stage_var_sets) - - structure = """\ -set Stages := - s%(stages)s - ; - -set Scenarios := - %(scenarios)s - ; - -set Nodes := - %(nodes)s - ; - -%(children_sets)s - -%(stage_var_sets)s - -param NodeStage := - %(nodestage)s - ; - -param ConditionalProbability := - %(cond_prob)s - ; - -param ScenarioLeafNode := - %(leaves)s - ; - -param StageCostVariable := - %(stagecost)s - ; - -param ScenarioBasedData := False ; -""" - - structure %= dict( - stages=stages, - scenarios=scenarios, - nodes=nodes, - children_sets=children, - stage_var_sets=stage_var_sets, - nodestage=nodestage, - cond_prob=probability, - leaves=leaves, - stagecost=stagecost, - ) - - with open('ScenarioStructure.dat', 'w') as f: - f.write(structure) - - -def _create_tree(stochasticset, spoints, **kwargs): - name = kwargs.get('name') - bname = kwargs.get('bname') - parent = kwargs.get('parent') - prob = kwargs.get('prob') - cprob = kwargs.get('cprob') - - spoint = stochasticset.pop() # stochastic point, use of pop implies ordering - treekwargs = dict( - spoint=spoint, - name=name, - parent=parent, - types=kwargs.get('types'), - rates=kwargs.get('rates'), - filebase=bname, - prob=prob, - stochastic_indices=kwargs.get('stochastic_indices'), - ) - - node = TreeNode(**treekwargs) - global node_count - node_count += 1 - inform('\b' * (len(str(node_count - 1)) + 1) + str(node_count) + ' ') - - if spoint not in spoints: - kwargs.update( - name='HedgingStrategy', - parent=name, - bname='%ss0' % bname, - prob=1, - ) - node.addChild(_create_tree(stochasticset[:], spoints, **kwargs)) - elif stochasticset: - decisions = enumerate(cprob[name]) - bname = '%ss%%d' % bname # the format for the basename of the file - for enum, (d, prob) in decisions: - kwargs.update( - name=d, - parent=name, - bname=bname % enum, - prob=prob, - ) - node.addChild(_create_tree(stochasticset[:], spoints, **kwargs)) - - return node - - -def create_tree(stochasticset, spoints, opts): - types = opts.types - rates = opts.rates - cprob = opts.conditional_probability - - stochasticset.reverse() - spoints.sort() - spoints.reverse() - - kwargs = dict( - name='HedgingStrategy', - parent='', - bname='R', - types=types, - rates=rates, - cprob=cprob, - stochastic_indices=opts.stochastic_indices, - prob=1, # conditional probability, but root guaranteed to occur - ) - return _create_tree(stochasticset, spoints, **kwargs) - - -def inform(x): - global verbose - if verbose: - SE.write(x) - SE.flush() - - -def setup_directory(dname, force): - if os.path.exists(dname): - if os.path.isdir(dname): - files = os.listdir(dname) - if files and not force: - msg = ( - 'Not empty: {}\n\nIf you want to use this directory anyway, ' - "set 'force = True' in the options.py file." - ) - raise Warning(msg.format(dname)) - - # would be potentially useful to put this into a thread to speed up - # the process. like 'mv somedir to_del; rm -rf to_del &' - rmtree(dname) - os.mkdir(dname) - else: - msg = 'Error - already exists: {}' - raise NameError(msg.format(dname)) - else: - os.mkdir(dname) - - -def test_model_parameters(M, opts): - try: - getattr(M, opts.stochasticset) - except: - msg = ( - 'Whoops! The stochastic set is not available from the model. ' - 'Did you perhaps typo the name?\n' - ' Model name: {}\n' - ' Stochastic name: {}' - ) - raise ValueError(msg.format(M.name, opts.stochasticset)) - - try: - for pname in opts.rates: - param = getattr(M, pname) - except: - msg = ( - 'Whoops! Parameter not available from the model. Have you ' - 'perhaps typoed the name?\n' - ' Model name: {}\n' - ' Parameter name: {}' - ) - raise ValueError(msg.format(M.name, pname)) - - -def usage(): - SE.write( - """ -synopsis: pyomo_python {0} - -Example: pyomo_python {0} options/utopia_coal_vs_nuc.py - -For information about the options_to_import.py file, please see -options/README.txt -""".format(sys.argv[0]) - ) - - raise SystemExit - - -def main(): - from os import getcwd - from time import clock - - if len(sys.argv) < 2: - usage() - module_name = sys.argv[1][:-3].replace('/', '.') # remove the '.py' - - try: - __import__(module_name) - opts = sys.modules[module_name] - - except ImportError: - msg = 'Unable to import {}.\n\nRun this script with no arguments for ' 'more information.\n' - SE.write(msg.format(sys.argv[1])) - raise - - try: - opts.dirname - except AttributeError: - opts.dirname = module_name.split('.')[-1] - - global verbose - verbose = opts.verbose - - cwd = getcwd() - - begin = clock() - duration = lambda: clock() - begin - - inform('[ ] Setting up working directory (%s)' % opts.dirname) - setup_directory(opts.dirname, opts.force) - inform('\r[%6.2f\n' % duration()) - - inform('[ ] Import model definition (%s)' % opts.modelpath) - mp = opts.modelpath - modelbase = os.path.basename(mp)[:-3] - modeldir = os.path.abspath(os.path.dirname(mp)) - - sys.path.insert(0, modeldir) - _temp = __import__(modelbase, globals(), locals(), ('model',)) - M = _temp.model - del _temp - sys.path.pop(0) - - test_model_parameters(M, opts) - - inform('\r[%6.2f\n' % duration()) - - inform('[ ] Create concrete instance (%s)' % opts.dotdatpath) - ins = M.create(opts.dotdatpath) - inform('\r[%6.2f\n' % duration()) - - global instance - instance = ins - - inform('[ ] Collecting stochastic points from model (%s)' % M.name) - all_spoints = sorted(getattr(ins, opts.stochasticset).value) - try: - spoints = list(opts.stochastic_points) - except AttributeError: - spoints = all_spoints - - inform('\r[%6.2f\n' % duration()) - - # used for friendlier error checking - Param.stochasticset = opts.stochasticset - - os.chdir(opts.dirname) - inform('[ ] Building tree: ') - tree = create_tree(all_spoints[:], spoints[:], opts) # give an intentional copy - inform('\r[%6.2f\n' % duration()) - - global node_count - node_count = 0 - - inform('[ ] Writing scenario "dot dat" files: ') - tree.write_dat_files() - write_scenario_file(all_spoints, tree) - inform('\r[%6.2f] Writing scenario "dot dat" files\n' % duration()) - - os.chdir(cwd) - inform('[ ] Copying ReferenceModel.dat as scenario tree root') - copyfile(opts.dotdatpath, '%s/ReferenceModel.dat' % opts.dirname) - copyfile(opts.dotdatpath, '%s/R.dat' % opts.dirname) - inform('\r[%6.2f\n' % duration()) - - -if '__main__' == __name__: - try: - main() - except Exception as e: - if '--debug' in sys.argv: - raise - - msg = ( - '\n\nIf you need more verbose (potentially helpful) information ' - 'about this error, you can run this program again, and add the' - ' "--debug" command line flag.\n' - ) - msg = '\n\n' + str(e) + msg - SE.write(msg) diff --git a/temoa/extensions/stochastics/generate_scenario_tree.py b/temoa/extensions/stochastics/generate_scenario_tree.py deleted file mode 100755 index 960280652..000000000 --- a/temoa/extensions/stochastics/generate_scenario_tree.py +++ /dev/null @@ -1,642 +0,0 @@ -#!/usr/bin/env pyomo_python - -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import os -import sys -from io import StringIO -from pprint import pformat -from shutil import copy as copyfile, rmtree - -from pyomo.core.base.sets import _SetProduct, SimpleSet - -SE = sys.stderr -instance = None - -node_count = 0 -stringify = lambda x: ', '.join(str(i) for i in x) - - -class Storage: - __slots__ = ('value', 'rate') # this saves a noticeable amount of memory - - def __str__(self): - return pformat(self.__dict__, indent=2) - - __repr__ = __str__ - - -class Param(object): - # will be common to all Parameters, so no sense in storing it N times - stochasticset = None - - # this saves a noticeable amount of memory, and mild decrease in time - __slots__ = ('items', 'name', 'spoint', 'param', 'my_keys', 'model_keys', 'skeys') - - def __init__(self, **kwargs): - # At the point someone is using this class, they probably know what - # they're doing, so intentionally die at this point if any of these - # items are not passed. They're all mandatory. - name = kwargs.pop('param') # parameter in question to modify - spoint = kwargs.pop('spoint') # stochastic point at which to do it - rates = kwargs.pop('rates') # how much to vary the parameter - pidx = int(kwargs.pop('stochastic_index')) - - param = getattr(instance, name) # intentionally die if not found. - - indices = tuple() - pindex = param.index_set() - - if isinstance(pindex, _SetProduct): - getname = lambda x: x.name - indices = [getname(i) for i in pindex.set_tuple] - skeys = lambda: (' '.join(str(i) for i in k) for k in self.model_keys) - - keys = param.keys() - f = lambda x: x[pidx] == spoint - r = lambda x: tuple(x[0:pidx] + x[pidx + 1 :]) - # reduce keys to remove stochastic parameter - - elif isinstance(pindex, SimpleSet): - # this is under sparse keys - indices = (param._index.name,) - skeys = lambda: (' '.join(str(i) for i in self.model_keys)) - - keys = param.keys() - f = lambda x: x[pidx] == spoint - r = lambda x: tuple(x[0:pidx] + x[pidx + 1 :]) - - # we filter out the spoint because it's inherently known by TreeNode, - # which "owns" /this/ Param - model_keys = filter(f, keys) - my_keys = map(r, model_keys) - - items = dict() - - for actual, mine in zip(model_keys, my_keys): - rate = 1 - - for pattern, r in rates: - keys = pattern.split(',') - match = True - for p, t in zip(keys, mine): # "pattern", "test" - if '*' == p: - continue - if t != p: - match = False - break - if match: - rate = r - break - - items[mine] = Storage() - try: - items[mine].value = param[actual] # pulled from model - except ValueError: - items[mine].value = 0 - items[mine].rate = rate - - self.items = items - self.name = name - self.spoint = spoint - self.param = param - self.my_keys = my_keys # these keys are linked -- in the same - self.model_keys = model_keys # order -- for zip()-ability - self.skeys = skeys # for later, string keys - - def __iter__(self): - return self.items.__iter__() - - def __getitem__(self, i): - try: - return self.items[i] - except: - # it's likely the element did not exist, which hopefully means 0? - class _tmp: - rate = 0 - value = 0 - - return _tmp() - - def __str__(self): - x = '; '.join('(%s, %s)' % (self[i].value, self[i].rate) for i in self) - return 'Param(%s): %s' % (self.name, x) - - __repr__ = __str__ - - def as_ampl(self, comment=''): - pindex = self.param.index_set() - if comment: - comment = '# Decision: %s\n\n' % str(comment) - - keys = self.skeys() - if isinstance(keys, str): - keys = [keys] - - # Together, these functions return the length of a printed version of a - # number, in characters. They are used to make columns of data line up so - # one may have an easier time getting an overall sense of a data file. - def get_int_padding(v): - return len(str(int(v))) - - def get_str_padding(index): - def anonymous_function(obj): - val = obj[index] - return len(str(val)) - - return anonymous_function - - keys = tuple(tuple(i.split()) for i in keys) - vals = tuple(self[i].value for i in self.my_keys) - int_padding = max(map(get_int_padding, vals)) - str_padding = [max(map(get_str_padding(i), keys)) for i in range(len(keys[0]))] - str_format = ' %-{}s' * len(self.model_keys[0]) - str_format = str_format.format(*str_padding) - - format = '\n%%s %%%ds%%s' % int_padding - # works out to something like '\n %s %8d%-6s' - # index { val } - - data = StringIO() - data.write(comment + 'param %s :=' % self.name) - for actual_key, this_key in sorted(zip(self.model_keys, self.my_keys)): - v = self[this_key].value - int_part = str(int(abs(v))) - if int_part != str(abs(v)): - dec_part = str(abs(v))[len(int_part) :] - else: - dec_part = '' - - if v < 0: - int_part = '-%d' % int_part - index = str_format % tuple(actual_key) - data.write(format % (index, int_part, dec_part)) - data.write('\n\t;\n') - - # return comment + data - return data.getvalue() - - -class TreeNode(object): - __slots__ = ('name', 'spoint', 'prob', 'params', 'bname', 'children') - - def __init__(self, *args, **kwargs): - # At the point someone is using this class, they probably know what - # they're doing, so intentionally die at this point if any of these - # items are not passed. They're all mandatory. - self.name = kwargs.pop('name') # name of /this/ node - self.spoint = kwargs.pop('spoint') # stochastic point of node - self.prob = kwargs.pop('prob') # conditional probability of node - bname = kwargs.pop('filebase') # file name minus extension - types = kwargs.pop('types') # names of decisions - rates = kwargs.pop('rates') # rates at which to vary - sindices = kwargs.pop('stochastic_indices') - - params = rates.keys() - myparams = dict() - for key, decisions in rates.items(): - paramkwargs = { - 'param': key, - 'rates': (), - 'spoint': self.spoint, - 'stochastic_index': sindices[key], - } - if self.prob < 1: - paramkwargs.update({'rates': decisions[self.name]}) - - myparams[key] = Param(**paramkwargs) - - self.params = myparams - self.bname = bname - self.children = [] - - def addChild(self, node): - self.children.append(node) - - def __repr__(self): - x = self.name - if isinstance(self.name, tuple): - x = ', '.join(x) - return '%s(%s): ' % (self.spoint, x) + ', '.join(str(i) for i in self.params.values()) - - def __str__(self, indent=' ', space=''): - x = ''.join(i.__str__(indent, space + indent) for i in self.children) - - return space + repr(self) + '\n' + x - - def write_dat_files(self): - global node_count - - # Step 1: Write my own file, if necessary - if self.prob < 1: - params = self.params.values() - data = params[0].as_ampl(self.name) - if len(params) > 1: - data += '\n' + '\n'.join(p.as_ampl() for p in params[1:]) - else: - data = '# Decision: HedgingStrategy (no change from R.dat)\n' - - with open(self.bname + '.dat', 'w') as f: - f.write(data) - - node_count += 1 - inform('\b' * (len(str(node_count - 1)) + 1) + str(node_count) + ' ') - - # Step 2: Tell my children to write their files - for c in self.children: - for p in self.params: - cp = c.params[p] - for key in self.params[p]: - cp[key].value = self.params[p][key].value * cp[key].rate - c.write_dat_files() - - def get_scenario_data(self): - nodes = [self.bname] - nodestage = [(self.bname, 's' + str(self.spoint))] - probability = [(self.bname, self.prob)] - scenarios = [] - children = [] - - if not self.children: - scenarios = [self.bname[2:]] - else: - children = [(self.bname, [c.bname for c in self.children])] - - for child in self.children: - s, n, ns, c, p = child.get_scenario_data() - scenarios += s - nodes += n - nodestage += ns - children += c - probability += p - - return scenarios, nodes, nodestage, children, probability - - -def write_scenario_file(stochasticset, tree): - ( - scenarios, - nodes, - nodestage, - children, - probability, - ) = tree.get_scenario_data() - - child_fmt = 'set Children[%s] :=\n %s\n\t;\n' - scenario_fmt = 'S%(i)s Rs%(i)s' - stages_fmt = 'set StageVariables[s{}] :=\n {}\n\t;' - stagecost_fmt = 's%s StochasticPointCost[%s]' - - leaves = '\n '.join(scenario_fmt % {'i': i} for i in scenarios) - nodes = '\n '.join(nodes) - nodestage = '\n '.join((' '.join(ns) for ns in nodestage)) - scenarios = 'S%s' % '\n S'.join(scenarios) - stagecost = '\n '.join(stagecost_fmt % (s, s) for s in stochasticset) - stages = '\n s'.join(str(se) for se in stochasticset) - - probability = '\n '.join((' '.join(str(i) for i in p) for p in probability)) - children = '\n'.join(child_fmt % (c[0], '\n '.join(c[1])) for c in children) - - # XXX: Absolute hack, that currently only works for Temoa models. I have - # not yet thought about how to make this generic. Can it be done? - - stage_var_sets = list() - for se in stochasticset: # se = "stochastic element" - flow_keys = [index for index in instance.V_FlowOut.keys() if index[0] == se] - processes = [(t, v) for p, s, d, i, t, v, o in flow_keys if v == se] - - stage_vars = list() - stage_vars.extend( - sorted(set('V_FlowIn[{},{},{},{},{},{},{}]'.format(*index) for index in flow_keys)) - ) - stage_vars.extend( - sorted(set('V_FlowOut[{},{},{},{},{},{},{}]'.format(*index) for index in flow_keys)) - ) - stage_vars.extend(sorted(set('V_Capacity[{},{}]'.format(*index) for index in processes))) - - stage_var_sets.append(stages_fmt.format(se, '\n '.join(stage_vars))) - - stage_var_sets = '\n\n'.join(stage_var_sets) - - structure = """\ -set Stages := - s%(stages)s - ; - -set Scenarios := - %(scenarios)s - ; - -set Nodes := - %(nodes)s - ; - -%(children_sets)s - -%(stage_var_sets)s - -param NodeStage := - %(nodestage)s - ; - -param ConditionalProbability := - %(cond_prob)s - ; - -param ScenarioLeafNode := - %(leaves)s - ; - -param StageCostVariable := - %(stagecost)s - ; - -param ScenarioBasedData := False ; -""" - - structure %= dict( - stages=stages, - scenarios=scenarios, - nodes=nodes, - children_sets=children, - stage_var_sets=stage_var_sets, - nodestage=nodestage, - cond_prob=probability, - leaves=leaves, - stagecost=stagecost, - ) - - with open('ScenarioStructure.dat', 'w') as f: - f.write(structure) - - -def _create_tree(stochasticset, spoints, **kwargs): - name = kwargs.get('name') - bname = kwargs.get('bname') - prob = kwargs.get('prob') - cprob = kwargs.get('cprob') - decision_list = kwargs.get('decisions') - - try: - spoint = stochasticset.pop() # stochastic point, use of pop implies ordering - except: - SE.write( - '\nError: mismatch in specified stochastic set. Does ' - 'stochastic_points match the dat file?' - ) - raise - - treekwargs = dict( - spoint=spoint, - name=name, - types=kwargs.get('types'), - rates=kwargs.get('rates'), - filebase=bname, - prob=prob, - stochastic_indices=kwargs.get('stochastic_indices'), - ) - - node = TreeNode(**treekwargs) - global node_count - node_count += 1 - inform('\b' * (len(str(node_count - 1)) + 1) + str(node_count) + ' ') - - if spoint not in spoints: - kwargs.update( - name='HedgingStrategy', - bname='%ss0' % bname, - prob=1, - ) - node.addChild(_create_tree(stochasticset[:], spoints, **kwargs)) - elif stochasticset: - decisions = enumerate(decision_list) - bname = '%ss%%d' % bname # the format for the basename of the file - for enum, d in decisions: - kwargs.update( - name=d, - bname=bname % enum, - prob=cprob[d], - ) - node.addChild(_create_tree(stochasticset[:], spoints, **kwargs)) - - return node - - -def create_tree(stochasticset, spoints, opts): - types = opts.types - rates = opts.rates - cprob = opts.conditional_probability - - stochasticset.reverse() - spoints.sort() - spoints.reverse() - - kwargs = dict( - name='Root', - bname='R', - types=types, - rates=rates, - cprob=cprob, - decisions=types, - stochastic_indices=opts.stochastic_indices, - prob=1, # conditional probability, but root guaranteed to occur - ) - return _create_tree(stochasticset, spoints, **kwargs) - - -def inform(x): - global verbose - if verbose: - SE.write(x) - SE.flush() - - -def setup_directory(dname, force): - if os.path.exists(dname): - if os.path.isdir(dname): - files = os.listdir(dname) - if files and not force: - msg = ( - 'Not empty: {}\n\nIf you want to use this directory anyway, ' - "set 'force = True' in the options.py file." - ) - raise Warning(msg.format(dname)) - - # would be potentially useful to put this into a thread to speed up - # the process. like 'mv somedir to_del; rm -rf to_del &' - rmtree(dname) - os.mkdir(dname) - else: - msg = 'Error - already exists: {}' - raise NameError(msg.format(dname)) - else: - os.mkdir(dname) - - -def test_model_parameters(M, opts): - try: - getattr(M, opts.stochasticset) - except: - msg = ( - 'Whoops! The stochastic set is not available from the model. ' - 'Did you perhaps typo the name?\n' - ' Model name: {}\n' - ' Stochastic name: {}' - ) - raise ValueError(msg.format(M.name, opts.stochasticset)) - - try: - for pname in opts.rates: - param = getattr(M, pname) - except: - msg = ( - 'Whoops! Parameter not available from the model. Have you ' - 'perhaps typoed the name?\n' - ' Model name: {}\n' - ' Parameter name: {}' - ) - raise ValueError(msg.format(M.name, pname)) - - -def usage(): - SE.write( - """ -synopsis: pyomo_python {0} - -Example: pyomo_python {0} options/utopia_coal_vs_nuc.py - -For information about the options_to_import.py file, please see -options/README.txt -""".format(sys.argv[0]) - ) - - raise SystemExit - - -def main(): - from os import getcwd - from os.path import abspath, basename, dirname - from time import clock - - if len(sys.argv) < 2: - usage() - module_name = sys.argv[1][:-3].replace('/', '.') # remove the '.py' - - mbase = basename(module_name)[:-3] - mdir = abspath(dirname(module_name)) - sys.path.insert(0, mdir) - - try: - __import__(module_name) - opts = sys.modules[module_name] - sys.path.pop(0) - - except ImportError: - msg = 'Unable to import {}.\n\nRun this script with no arguments for ' 'more information.\n' - SE.write(msg.format(sys.argv[1])) - raise - - try: - opts.dirname - except AttributeError: - opts.dirname = module_name.split('.')[-1] - - global verbose - verbose = opts.verbose - - cwd = getcwd() - - begin = clock() - duration = lambda: clock() - begin - - inform('[ ] Setting up working directory (%s)' % opts.dirname) - setup_directory(opts.dirname, opts.force) - inform('\r[%6.2f\n' % duration()) - - inform('[ ] Import model definition (%s)' % opts.modelpath) - mp = opts.modelpath - modelbase = basename(mp)[:-3] - modeldir = abspath(dirname(mp)) - - sys.path.insert(0, modeldir) - _temp = __import__(modelbase, globals(), locals(), ('model',)) - M = _temp.model - del _temp - sys.path.pop(0) - - test_model_parameters(M, opts) - - inform('\r[%6.2f\n' % duration()) - - inform('[ ] Create concrete instance (%s)' % opts.dotdatpath) - ins = M.create(opts.dotdatpath) - inform('\r[%6.2f\n' % duration()) - - global instance - instance = ins - - inform('[ ] Collecting stochastic points from model (%s)' % M.name) - all_spoints = sorted(getattr(ins, opts.stochasticset).value) - try: - spoints = list(opts.stochastic_points) - except AttributeError: - spoints = all_spoints - - inform('\r[%6.2f\n' % duration()) - - # used for friendlier error checking - Param.stochasticset = opts.stochasticset - - os.chdir(opts.dirname) - inform('[ ] Building tree: ') - tree = create_tree(all_spoints[:], spoints[:], opts) # give an intentional copy - inform('\r[%6.2f\n' % duration()) - - global node_count - node_count = 0 - - inform('[ ] Writing scenario "dot dat" files: ') - tree.write_dat_files() - write_scenario_file(all_spoints, tree) - inform('\r[%6.2f] Writing scenario "dot dat" files\n' % duration()) - - os.chdir(cwd) - inform('[ ] Copying ReferenceModel.dat as scenario tree root') - copyfile(opts.dotdatpath, '%s/ReferenceModel.dat' % opts.dirname) - copyfile(opts.dotdatpath, '%s/R.dat' % opts.dirname) - inform('\r[%6.2f\n' % duration()) - - -if '__main__' == __name__: - try: - main() - except Exception as e: - if '--debug' in sys.argv: - raise - - msg = ( - '\n\nIf you need more verbose (potentially helpful) information ' - 'about this error, you can run this program again, and add the' - ' "--debug" command line flag.\n' - ) - msg = '\n\n' + str(e) + msg - SE.write(msg) diff --git a/temoa/extensions/stochastics/jobTemoa.pbs b/temoa/extensions/stochastics/jobTemoa.pbs deleted file mode 100644 index dfd5907c0..000000000 --- a/temoa/extensions/stochastics/jobTemoa.pbs +++ /dev/null @@ -1,61 +0,0 @@ -#! /bin/bash - -# This is a simple job submission script that can be adapted for your needs. -# At the very least, change the name of the program (line 10), -# number of nodes requested (line 16) and -# wallclock time requested (line 19) and -# your program arguments (line 48) - -# Name of the program (used to produce standard output and error logs - Eg. test.o### ) -#PBS -N test -# Combine both error and output logs -#PBS -j oe - -# Change nodes as desired below -# ppn refers to processes per node which is usually equal to number of cores (32 in our case) -#PBS -l nodes=2:ppn=32 - -# Wall clock time limit in hh:mm:ss format -#PBS -l walltime=72:00:00 - -# Available queues are short (5 min), low, medium and high priority. -# medium is appropriate for most users -#PBS -q long - -# If you wish to export environment variables, use both lines below -#PBS -V -# Just note that HYDRA_HOST_FILE shouldn't be set in this environment -unset HYDRA_HOST_FILE - -# If you wish to receive email, update email and -# remove first '#' from line below -# #PBS -M username@ncsu.edu - # a mail is sent when the job is aborted by the batch system. - # b mail is sent when the job begins execution. - # e mail is sent when the job terminates. -#PBS -m abe - -# Change to the program's directory -cd /home/arqueiroz/temoa/stochastic/usdatabase_64n -echo "Directory: $PBS_O_WORKDIR" -date -echo "Number of nodes is ${PBS_NUM_NODES}" -echo "Number of cores is ${PBS_NP}" -# If you wish to print the nodes on which your program ran, uncomment line below -# cat $PBS_NODEFILE - -# TODO: Replace cpi with your program and arguments -#mpiexec -n $PBS_NP -machinefile $PBS_NODEFILE ./cpi -export PYRO_NS_PORT=9096 -#mpiexec -n $PBS_NP -machinefile $PBS_NODEFILE \ -#-np 1 /home/arqueiroz/anaconda2/bin/pyomo_ns : \ -#-np 1 /home/arqueiroz/anaconda2/bin/dispatch_srvr : \ -#-np 3 /home/arqueiroz/anaconda2/bin/phsolverserver : \ -#-np 1 /home/arqueiroz/anaconda2/bin/runph --solver=cplex --solver-manager=phpyro --shutdown-pyro -m models -i scenariodata --default-rho=1.0 -mpiexec -np 1 /home/arqueiroz/anaconda2/bin/pyomo_ns -n localhost : -np 1 /home/arqueiroz/anaconda2/bin/dispatch_srvr localhost : -np 3 /home/arqueiroz/anaconda2/bin/phsolverserver : -np 1 /home/arqueiroz/anaconda2/bin/runph --solver=cplex --solver-manager=phpyro --shutdown-pyro --traceback -m ../../temoa_model/ -i ./ --default-rho=1 - -# mpiexec can detect number of available cores by default -# So above is equivalent to line below. But use the detailed version above, as that -# explicitly identifies number of cores etc. -# mpiexec ./cpi - diff --git a/temoa/extensions/stochastics/legacy_files/ef_writer_script_old.py b/temoa/extensions/stochastics/legacy_files/ef_writer_script_old.py deleted file mode 100644 index ffbe04d32..000000000 --- a/temoa/extensions/stochastics/legacy_files/ef_writer_script_old.py +++ /dev/null @@ -1,759 +0,0 @@ -# _________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright (c) 2014 Sandia Corporation. -# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, -# the U.S. Government retains certain rights in this software. -# This software is distributed under the BSD License. -# _________________________________________________________________________ - -import os -import random -import sys -import time -from optparse import OptionParser, OptionGroup - -import pyutilib.misc -from pyomo.core.base import maximize, minimize -from pyomo.opt.base import SolverFactory, PersistentSolver, ProblemFormat -from pyomo.opt.base.solvers import UnknownSolver -from pyomo.opt.parallel import SolverManagerFactory -from pyomo.pysp.ef import write_ef, create_ef_instance -from pyomo.pysp.scenariotree.instance_factory import ScenarioTreeInstanceFactory -from pyomo.pysp.solutionwriter import ISolutionWriterExtension -from pyomo.pysp.util.misc import launch_command -from pyomo.util import pyomo_command -from pyomo.util.plugin import ExtensionPoint -from pyutilib.pyro import shutdown_pyro_components - - -# -# utility method to construct an option parser for ef writer arguments -# - - -def construct_ef_writer_options_parser(usage_string): - solver_list = SolverFactory.services() - solver_list = sorted(filter(lambda x: '_' != x[0], solver_list)) - solver_help = ( - 'Specify the solver with which to solve the extensive form. The ' - 'following solver types are currently supported: %s; Default: cplex' - ) - solver_help %= ', '.join(solver_list) - - parser = OptionParser() - parser.usage = usage_string - - inputOpts = OptionGroup(parser, 'Input Options') - scenarioTreeOpts = OptionGroup(parser, 'Scenario Tree Options') - efOpts = OptionGroup(parser, 'EF Options') - ccOpts = OptionGroup(parser, 'Chance Constraint Options') - solverOpts = OptionGroup(parser, 'Solver Options') - outputOpts = OptionGroup(parser, 'Output Options') - otherOpts = OptionGroup(parser, 'Other Options') - parser.add_option_group(inputOpts) - parser.add_option_group(scenarioTreeOpts) - parser.add_option_group(efOpts) - parser.add_option_group(ccOpts) - parser.add_option_group(solverOpts) - parser.add_option_group(outputOpts) - parser.add_option_group(otherOpts) - - inputOpts.add_option( - '-i', - '--instance-directory', - help='The directory in which all instance (reference and scenario) definitions are stored. This option is required if no callback is found in the model file.', - action='store', - dest='instance_directory', - type='string', - default=None, - ) - inputOpts.add_option( - '-m', - '--model-directory', - help='The directory in which all model (reference and scenario) definitions are stored. Default is ".".', - action='store', - dest='model_directory', - type='string', - default='.', - ) - - def objective_sense_callback(option, opt_str, value, parser): - if value in ('min', 'minimize', minimize): - parser.values.objective_sense = minimize - elif value in ('max', 'maximize', maximize): - parser.values.objective_sense = maximize - else: - parser.values.objective_sense = None - - inputOpts.add_option( - '-o', - '--objective-sense-stage-based', - help='The objective sense to use for the auto-generated scenario instance objective, which is equal to the ' - 'sum of the scenario-tree stage costs. Default is None, indicating an Objective has been declared on the ' - 'reference model.', - action='callback', - dest='objective_sense', - type='choice', - choices=[maximize, 'max', 'maximize', minimize, 'min', 'minimize', None], - default=None, - callback=objective_sense_callback, - ) - - scenarioTreeOpts.add_option( - '--scenario-tree-seed', - help='The random seed associated with manipulation operations on the scenario tree (e.g., down-sampling). Default is None, indicating unassigned.', - action='store', - dest='scenario_tree_random_seed', - type='int', - default=random.getrandbits(100), - ) - scenarioTreeOpts.add_option( - '--scenario-tree-downsample-fraction', - help='The proportion of the scenarios in the scenario tree that are actually used. Specific scenarios are selected at random. Default is 1.0, indicating no down-sampling.', - action='store', - dest='scenario_tree_downsample_fraction', - type='float', - default=1.0, - ) - - efOpts.add_option( - '--cvar-weight', - help='The weight associated with the CVaR term in the risk-weighted objective formulation. Default is 1.0. If the weight is 0, then *only* a non-weighted CVaR cost will appear in the EF objective - the expected cost component will be dropped.', - action='store', - dest='cvar_weight', - type='float', - default=1.0, - ) - efOpts.add_option( - '--generate-weighted-cvar', - help='Add a weighted CVaR term to the primary objective', - action='store_true', - dest='generate_weighted_cvar', - default=False, - ) - efOpts.add_option( - '--risk-alpha', - help='The probability threshold associated with cvar (or any future) risk-oriented performance metrics. Default is 0.95.', - action='store', - dest='risk_alpha', - type='float', - default=0.95, - ) - - ccOpts.add_option( - '--cc-alpha', - help='The probability threshold associated with a chance constraint. The RHS will be one minus this value. Default is 0.', - action='store', - dest='cc_alpha', - type='float', - default=0.0, - ) - - ccOpts.add_option( - '--cc-indicator-var', - help='The name of the binary variable to be used to construct a chance constraint. Default is None, which indicates no chance constraint.', - action='store', - dest='cc_indicator_var', - type='string', - default=None, - ) - - solverOpts.add_option( - '--mipgap', - help='Specifies the mipgap for the EF solve.', - action='store', - dest='mipgap', - type='float', - default=None, - ) - solverOpts.add_option( - '--solve', - help='Following write of the extensive form model, solve it.', - action='store_true', - dest='solve_ef', - default=False, - ) - solverOpts.add_option( - '--solver', - help=solver_help, - action='store', - dest='solver_type', - type='string', - default='cplex', - ) - solverOpts.add_option( - '--solver-io', - help='The type of IO used to execute the solver. Different solvers support different types of IO, but the following are common options: lp - generate LP files, nl - generate NL files, python - direct Python interface, os - generate OSiL XML files.', - action='store', - dest='solver_io', - default=None, - ) - solverOpts.add_option( - '--solver-manager', - help='The type of solver manager used to coordinate scenario sub-problem solves. Default is serial.', - action='store', - dest='solver_manager_type', - type='string', - default='serial', - ) - solverOpts.add_option( - '--pyro-host', - help='The hostname to bind on when searching for a Pyro nameserver.', - action='store', - dest='pyro_host', - default=None, - ) - solverOpts.add_option( - '--pyro-port', - help='The port to bind on when searching for a Pyro nameserver.', - action='store', - dest='pyro_port', - type='int', - default=None, - ) - solverOpts.add_option( - '--solver-options', - help='Solver options for the extensive form problem.', - action='append', - dest='solver_options', - type='string', - default=[], - ) - solverOpts.add_option( - '--disable-warmstarts', - help='Disable warm-starts of EF solves. Default is False.', - action='store_true', - dest='disable_warmstarts', - default=False, - ) - solverOpts.add_option( - '--shutdown-pyro', - help='Shut down all Pyro-related components associated with the Pyro solver manager (if specified), including the dispatch server, name server, and any mip servers. Default is False.', - action='store_true', - dest='shutdown_pyro', - default=False, - ) - solverOpts.add_option( - '--shutdown-pyro-workers', - help='Shut down PH solver servers on exit, leaving dispatcher and nameserver running. Default is False.', - action='store_true', - dest='shutdown_pyro_workers', - default=False, - ) - - outputOpts.add_option( - '--output-file', - help="The name of the extensive form output file (currently only LP and NL file formats are supported). If the option name does not end in '.lp' or '.nl', then the output format will be determined by the value of the --solver-io option, and the appropriate ending suffix will be added to the name. Default is 'efout'.", - action='store', - dest='output_file', - type='string', - default='efout', - ) - outputOpts.add_option( - '--symbolic-solver-labels', - help='When interfacing with the solver, use symbol names derived from the model. For example, "my_special_variable[1_2_3]" instead of "v1". Useful for debugging. When using the ASL interface (--solver-io=nl), generates corresponding .row (constraints) and .col (variables) files. The ordering in these files provides a mapping from ASL index to symbolic model names.', - action='store_true', - dest='symbolic_solver_labels', - default=False, - ) - outputOpts.add_option( - '--output-solver-log', - help='Output solver log during the extensive form solve.', - action='store_true', - dest='output_solver_log', - default=False, - ) - outputOpts.add_option( - '--solution-writer', - help='The plugin invoked to write the scenario tree solution. Defaults to the empty list.', - action='append', - dest='solution_writer', - type='string', - default=[], - ) - outputOpts.add_option( - '--verbose', - help='Generate verbose output, beyond the usual status output. Default is False.', - action='store_true', - dest='verbose', - default=False, - ) - outputOpts.add_option( - '--output-times', - help='Output timing statistics for various EF components', - action='store_true', - dest='output_times', - default=False, - ) - outputOpts.add_option( - '--output-instance-construction-time', - help='Output timing statistics for instance construction (client-side only when using PHPyro', - action='store_true', - dest='output_instance_construction_time', - default=False, - ) - otherOpts.add_option( - '--disable-gc', - help='Disable the python garbage collecter. Default is False.', - action='store_true', - dest='disable_gc', - default=False, - ) - otherOpts.add_option( - '-k', - '--keep-solver-files', - help='Retain temporary input and output files for solve.', - action='store_true', - dest='keep_solver_files', - default=False, - ) - otherOpts.add_option( - '--profile', - help='Enable profiling of Python code. The value of this option is the number of functions that are summarized.', - action='store', - dest='profile', - type='int', - default=0, - ) - otherOpts.add_option( - '--traceback', - help='When an exception is thrown, show the entire call stack. Ignored if profiling is enabled. Default is False.', - action='store_true', - dest='traceback', - default=False, - ) - otherOpts.add_option( - '--compile-scenario-instances', - help='Replace all linear constraints on scenario instances with a more memory efficient sparse matrix representation. Default is False.', - action='store_true', - dest='compile_scenario_instances', - default=False, - ) - - return parser - - -def EF_DefaultOptions(): - parser = construct_ef_writer_options_parser('') - options, _ = parser.parse_args(['']) - return options - - -def GenerateScenarioTreeForEF(options, scenario_instance_factory, include_scenarios=None): - try: - scenario_tree = scenario_instance_factory.generate_scenario_tree( - include_scenarios=include_scenarios, - downsample_fraction=options.scenario_tree_downsample_fraction, - random_seed=options.scenario_tree_random_seed, - ) - - # - # print the input tree for validation/information purposes. - # - if options.verbose: - scenario_tree.pprint() - - # - # validate the tree prior to doing anything serious - # - if not scenario_tree.validate(): - raise RuntimeError('Scenario tree is invalid') - else: - if options.verbose: - print('Scenario tree is valid!') - - start_time = time.time() - - print('Constructing scenario tree instances') - instance_dictionary = scenario_instance_factory.construct_instances_for_scenario_tree( - scenario_tree, - output_instance_construction_time=options.output_instance_construction_time, - compile_scenario_instances=options.compile_scenario_instances, - ) - - if options.verbose or options.output_times: - print('Time to construct scenario instances=%.2f seconds' % (time.time() - start_time)) - - print('Linking instances into scenario tree') - start_time = time.time() - - # with the scenario instances now available, link the - # referenced objects directly into the scenario tree. - scenario_tree.linkInInstances( - instance_dictionary, objective_sense=options.objective_sense, create_variable_ids=True - ) - - if options.output_times: - print( - 'Time link scenario tree with instances=%.2f seconds' % (time.time() - start_time) - ) - - except: - if scenario_instance_factory is not None: - scenario_instance_factory.close() - print('Failed to initialize model and/or scenario tree data') - raise - - return scenario_tree - - -def CreateExtensiveFormInstance(options, scenario_tree): - start_time = time.time() - print('Creating extensive form instance') - - # then validate the associated parameters. - generate_weighted_cvar = False - cvar_weight = None - risk_alpha = None - if options.generate_weighted_cvar is True: - generate_weighted_cvar = True - cvar_weight = options.cvar_weight - risk_alpha = options.risk_alpha - - binding_instance = create_ef_instance( - scenario_tree, - verbose_output=options.verbose, - generate_weighted_cvar=generate_weighted_cvar, - cvar_weight=cvar_weight, - risk_alpha=risk_alpha, - cc_indicator_var_name=options.cc_indicator_var, - cc_alpha=options.cc_alpha, - ) - - if options.verbose or options.output_times: - print('Time to construct extensive form instance=%.2f seconds' % (time.time() - start_time)) - - return binding_instance - - -class ExtensiveFormAlgorithm(object): - def __init__( - self, - options, - binding_instance, - scenario_tree, - solver_manager, - solver, - solution_plugins=None, - ): - self._options = options - self._binding_instance = binding_instance - self._scenario_tree = scenario_tree - self._solver_manager = solver_manager - self._solver = solver - self._solution_plugins = solution_plugins - - def write(self): - start_time = time.time() - - output_filename = os.path.expanduser(self._options.output_file) - suf = os.path.splitext(output_filename)[1] - if suf not in ['.nl', '.lp']: - if self._solver.problem_format() == ProblemFormat.cpxlp: - output_filename += '.lp' - elif self._solver.problem_format() == ProblemFormat.nl: - output_filename += '.nl' - else: - raise ValueError( - 'Could not determine output file format. ' - 'No recognized ending suffix was provided ' - 'and no format was indicated was by the ' - '--solver-io option.' - ) - - start_time = time.time() - if self._options.verbose: - print('Starting to write extensive form') - - symbol_map = write_ef( - self._binding_instance, output_filename, self._options.symbolic_solver_labels - ) - - print('Extensive form written to file=' + output_filename) - if self._options.verbose or self._options.output_times: - print('Time to write output file=%.2f seconds' % (time.time() - start_time)) - - return output_filename, symbol_map - - def solve(self): - start_time = time.time() - print('Queuing extensive form solve') - - if isinstance(self._solver, PersistentSolver): - self._solver.compile_instance( - self._binding_instance, symbolic_solver_labels=self._options.symbolic_solver_labels - ) - - solve_kwds = {} - solve_kwds['load_solutions'] = False - if self._options.keep_solver_files: - solve_kwds['keepfiles'] = True - if self._options.symbolic_solver_labels: - solve_kwds['symbolic_solver_labels'] = True - if self._options.output_solver_log: - solve_kwds['tee'] = True - - if (not self._options.disable_warmstarts) and (self._solver.warm_start_capable()): - action_handle = self._solver_manager.queue( - self._binding_instance, opt=self._solver, warmstart=True, **solve_kwds - ) - else: - action_handle = self._solver_manager.queue( - self._binding_instance, opt=self._solver, **solve_kwds - ) - print('Waiting for extensive form solve') - results = self._solver_manager.wait_for(action_handle) - - if len(results.solution) == 0: - results.write() - raise RuntimeError('Solve failed; no solutions generated') - - print('Done with extensive form solve - loading results') - self._binding_instance.solutions.load_from(results) - - print('Storing solution in scenario tree') - self._scenario_tree.pullScenarioSolutionsFromInstances() - self._scenario_tree.snapshotSolutionFromScenarios() - # TODO - # self._scenario_tree.update_variable_statistics() - - if self._options.verbose or self._options.output_times: - print( - 'Time to solve and load results for the ' - 'extensive form=%.2f seconds' % (time.time() - start_time) - ) - - # print *the* metric of interest. - root_node = self._scenario_tree._stages[0]._tree_nodes[0] - print('') - print( - '********************************' - '********************************' - '*******************************' - ) - print( - '>>>THE EXPECTED SUM OF THE STAGE COST VARIABLES=' - + str(root_node.computeExpectedNodeCost()) - + '<<<' - ) - print( - '********************************' - '********************************' - '*******************************' - ) - - # handle output of solution from the scenario tree. - print('') - print('Extensive form solution:') - self._scenario_tree.pprintSolution() - print('') - print('Extensive form costs:') - self._scenario_tree.pprintCosts() - - def save_solution(self, label='ef'): - if self._solution_plugins is not None: - for plugin in self._solution_plugins: - plugin.write(self._scenario_tree, label) - - -def EFAlgorithmBuilder(options, scenario_tree): - solution_writer_plugins = ExtensionPoint(ISolutionWriterExtension) - for plugin in solution_writer_plugins: - plugin.disable() - - solution_plugins = [] - if len(options.solution_writer) > 0: - for this_extension in options.solution_writer: - if this_extension in sys.modules: - print( - 'User-defined EF solution writer module=' - + this_extension - + ' already imported - skipping' - ) - else: - print( - 'Trying to import user-defined EF ' 'solution writer module=' + this_extension - ) - # make sure "." is in the PATH. - original_path = list(sys.path) - sys.path.insert(0, '.') - pyutilib.misc.import_file(this_extension) - print('Module successfully loaded') - sys.path[:] = original_path # restore to what it was - - # now that we're sure the module is loaded, re-enable this - # specific plugin. recall that all plugins are disabled - # by default in phinit.py, for various reasons. if we want - # them to be picked up, we need to enable them explicitly. - import inspect - - module_to_find = this_extension - if module_to_find.rfind('.py'): - module_to_find = module_to_find.rstrip('.py') - if module_to_find.find('/') != -1: - module_to_find = module_to_find.split('/')[-1] - - for name, obj in inspect.getmembers(sys.modules[module_to_find], inspect.isclass): - import pyomo.util - - # the second condition gets around goofyness related to issubclass returning - # True when the obj is the same as the test class. - if issubclass(obj, pyomo.util.plugin.SingletonPlugin) and name != 'SingletonPlugin': - for plugin in solution_writer_plugins(all=True): - if isinstance(plugin, obj): - plugin.enable() - solution_plugins.append(plugin) - - ef_solver = SolverFactory(options.solver_type, solver_io=options.solver_io) - if isinstance(ef_solver, UnknownSolver): - raise ValueError( - 'Failed to create solver of type=' - + options.solver_type - + ' for use in extensive form solve' - ) - if len(options.worker_solver_options) > 0: - print('Initializing ef solver with options=' + str(options.worker_solver_options)) - ef_solver.set_options(''.join(options.worker_solver_options)) - if options.mipgap is not None: - if (options.mipgap < 0.0) or (options.mipgap > 1.0): - raise ValueError( - 'Value of the mipgap parameter for the EF ' - 'solve must be on the unit interval; ' - 'value specified=' + str(options.mipgap) - ) - ef_solver.options.mipgap = float(options.mipgap) - - ef_solver_manager = SolverManagerFactory( - options.solver_manager_type, host=options.pyro_host, port=options.pyro_port - ) - if ef_solver_manager is None: - raise ValueError( - 'Failed to create solver manager of type=' - + options.solver_type - + ' for use in extensive form solve' - ) - - binding_instance = CreateExtensiveFormInstance(options, scenario_tree) - - ef = ExtensiveFormAlgorithm( - options, - binding_instance, - scenario_tree, - ef_solver_manager, - ef_solver, - solution_plugins=solution_plugins, - ) - - return ef - - -def run_ef(options, ef): - if options.solve_ef: - retval = ef.solve() - ef.save_solution() - else: - retval = ef.write() - - return retval - - -def exec_runef(options): - import pyomo.solvers.plugins.smanager.phpyro - import pyomo.solvers.plugins.smanager.pyro - - start_time = time.time() - - if options.verbose: - print('Importing model and scenario tree files') - - scenario_instance_factory = ScenarioTreeInstanceFactory( - options.model_directory, options.instance_directory, options.verbose - ) - - if options.output_times: - print( - 'Time to import model and scenario tree structure files=%.2f seconds' - % (time.time() - start_time) - ) - - ef = None - try: - scenario_tree = GenerateScenarioTreeForEF(options, scenario_instance_factory) - - ef = EFAlgorithmBuilder(options, scenario_tree) - - run_ef(options, ef) - - finally: - if ef is not None: - if ef._solver_manager is not None: - if isinstance( - ef._solver_manager, pyomo.solvers.plugins.smanager.phpyro.SolverManager_PHPyro - ): - ef._solver_manager.release_servers(shutdown=option.shutdown_pyro_workers) - if isinstance( - ef._solver_manager, pyomo.solvers.plugins.smanager.pyro.SolverManager_Pyro - ): - if options.shutdown_pyro_workers: - ef._solver_manager.shutdown_workers() - ef._solver_manager.deactivate() - if ef._solver is not None: - ef._solver.deactivate() - - if ( - isinstance( - ef._solver_manager, pyomo.solvers.plugins.smanager.pyro.SolverManager_Pyro - ) - or isinstance( - ef._solver_manager, pyomo.solvers.plugins.smanager.phpyro.SolverManager_PHPyro - ) - ) and (options.shutdown_pyro): - print('Shutting down Pyro solver components') - shutdown_pyro_components( - host=options.pyro_host, port=options.pyro_port, num_retries=0 - ) - - if scenario_instance_factory is not None: - scenario_instance_factory.close() - - print('') - print('Total EF execution time=%.2f seconds' % (time.time() - start_time)) - print('') - - return 0 - - -def main(args=None): - # - # Top-level command that executes the runef command - # - - # - # Import plugins - # - - # - # Parse command-line options. - # - try: - options_parser = construct_ef_writer_options_parser('runef [options]') - (options, args) = options_parser.parse_args(args=args) - except SystemExit as _exc: - # the parser throws a system exit if "-h" is specified - # - catch it to exit gracefully. - return _exc.code - - return launch_command( - exec_runef, - options, - error_label='runef: ', - disable_gc=options.disable_gc, - profile_count=options.profile, - traceback=options.traceback, - ) - - -@pyomo_command('runef', 'Convert a SP tfo extensive form and optimize') -def EF_main(args=None): - return main(args=args) diff --git a/temoa/extensions/stochastics/legacy_files/scenariomodels.py b/temoa/extensions/stochastics/legacy_files/scenariomodels.py deleted file mode 100644 index 6b3893b09..000000000 --- a/temoa/extensions/stochastics/legacy_files/scenariomodels.py +++ /dev/null @@ -1,64 +0,0 @@ -# grab the pyomo modeling components. -from pyomo.environ import * - -scenario_tree_model = AbstractModel() - -# all set/parameter values are strings, representing the names of various entities/variables. - -scenario_tree_model.Stages = Set(ordered=True) -scenario_tree_model.Nodes = Set() - -scenario_tree_model.NodeStage = Param(scenario_tree_model.Nodes, within=scenario_tree_model.Stages) -scenario_tree_model.Children = Set( - scenario_tree_model.Nodes, within=scenario_tree_model.Nodes, ordered=True -) -scenario_tree_model.ConditionalProbability = Param(scenario_tree_model.Nodes) - -scenario_tree_model.Scenarios = Set(ordered=True) -scenario_tree_model.ScenarioLeafNode = Param( - scenario_tree_model.Scenarios, within=scenario_tree_model.Nodes -) - -scenario_tree_model.StageVariables = Set(scenario_tree_model.Stages) -scenario_tree_model.StageCostVariable = Param(scenario_tree_model.Stages) - -# scenario data can be populated in one of two ways. the first is "scenario-based", -# in which a single .dat file contains all of the data for each scenario. the .dat -# file prefix must correspond to the scenario name. the second is "node-based", -# in which a single .dat file contains only the data for each node in the scenario -# tree. the node-based method is more compact, but the scenario-based method is -# often more natural when parameter data is generated via simulation. the default -# is scenario-based. -scenario_tree_model.ScenarioBasedData = Param(within=Boolean, default=True, mutable=True) - -# do we bundle, and if so, how? -scenario_tree_model.Bundling = Param(within=Boolean, default=False, mutable=True) -scenario_tree_model.Bundles = Set() # bundle names -scenario_tree_model.BundleScenarios = Set(scenario_tree_model.Bundles) - - -# scenario_tree_model = AbstractModel() - -## all set/parameter values are strings, representing the names of various entities/variables. - -# scenario_tree_model.Stages = Set(ordered=True) -# scenario_tree_model.Nodes = Set() - -# scenario_tree_model.NodeStage = Param(scenario_tree_model.Nodes, within=scenario_tree_model.Stages) -# scenario_tree_model.Children = Set(scenario_tree_model.Nodes, within=scenario_tree_model.Nodes, ordered=True) -# scenario_tree_model.ConditionalProbability = Param(scenario_tree_model.Nodes) - -# scenario_tree_model.Scenarios = Set(ordered=True) -# scenario_tree_model.ScenarioLeafNode = Param(scenario_tree_model.Scenarios, within=scenario_tree_model.Nodes) - -# scenario_tree_model.StageVariables = Set(scenario_tree_model.Stages) -# scenario_tree_model.StageCostVariable = Param(scenario_tree_model.Stages) - -## scenario data can be populated in one of two ways. the first is "scenario-based", -## in which a single .dat file contains all of the data for each scenario. the .dat -## file prefix must correspond to the scenario name. the second is "node-based", -## in which a single .dat file contains only the data for each node in the scenario -## tree. the node-based method is more compact, but the scenario-based method is -## often more natural when parameter data is generated via simulation. the default -## is scenario-based. -# scenario_tree_model.ScenarioBasedData = Param(within=Boolean, default=True) diff --git a/temoa/extensions/stochastics/options/README.txt b/temoa/extensions/stochastics/options/README.txt deleted file mode 100644 index 91452d716..000000000 --- a/temoa/extensions/stochastics/options/README.txt +++ /dev/null @@ -1,67 +0,0 @@ -Stochastic Temoa Tree Generation Options Files -============================================== - -The options.py files in this directory describe stochastic trees as implemented -by the generate_scenario_tree.py script in the parent directory. These files -serve as a command line input to that script, specifying various items necessary -to create a generic scenario tree stucture and input dot dat files. - -The options.py files in this directory will generally be imported via the -generate_scenario_tree.py script in the parent directory. The script then -expects to be able to access the below items via the imported file. - -If any of the below is not clear, the other files in this directory can serve as -examples. If you do not have the files, the Temoa repository contains them all -at http://svn.temoaproject.org/trac/. Check "Browse Source". At the time of -this writing, the files are located in - -branches/stochastic/options/ - -Elements an options.py Should Specify -===================================== - -(str) dirname (optional) - This directory will be where all output files are placed. If not specified, - the name of the options file will be used as the default. - -(bool) verbose - Should the script give information about it's progress? - -(bool) force - If the dirname already exists, remove it before proceeding? - -(path) modelpath - Relative or absolute path of where to find the model - -(path) dotdatpath - Relative or absolute path of where to find the base LP dat file. - -(str) stochasticset - Within the model, the name of the stochastic set that indexes the parameters - to be rate-modified. - -(tuple) stochastic_points - Within the model, specifically /which/ items in the stochastic set are the - stochastic ones? For the parameters specified in types and rates, the ones - indexed by these points will be modified. Note that for useful output, this - item, if specified, needs at least two stochastic points, and the first one - will have a conditional probability of 1. - -(dict) stochastic_indices - For each parameter to modify, the numerical order of its stochastic index. - This is a 0-based, numerical specification. - -(tuple of strings) types - Each item in this tuple is the name of a decision branch from a node. However - many items specified here, are the number of branches each node in the event - tree will have. - -(dict) conditional_probability - This dict specifies the conditional probability of each branch. - -(dict of dicts of tuples) rates - This is a two-level dict that specifies each parameter to modify, and for each - branch in types, what to multiply against each index. Indices can be - explicitly spelled-out, or specified in a group via an asterisk. - ------ \ No newline at end of file diff --git a/temoa/extensions/stochastics/options/iew2012-nonhomogenized-markov.py b/temoa/extensions/stochastics/options/iew2012-nonhomogenized-markov.py deleted file mode 100644 index 3fdfd16c8..000000000 --- a/temoa/extensions/stochastics/options/iew2012-nonhomogenized-markov.py +++ /dev/null @@ -1,101 +0,0 @@ -verbose = True -force = True - -dirname = 'temoa_island_markoved' -modelpath = '../temoa_model.py' -dotdatpath = '../data_files/iew2012.dat' -stochasticset = 'time_optimize' -stochastic_points = (2020, 2025, 2030, 2035) -stochastic_indices = {'CostMarginal': 0} -types = ( - 'DDD', - 'DDU', - 'DUD', - 'DUU', - 'UDD', - 'UDU', - 'UUU', -) - -conditional_probability = dict( - HedgingStrategy=( - ('DDD', 13.0 / 57), - ('DDU', 1.0 / 57), - ('DUD', 12.0 / 57), - ('DUU', 14.0 / 57), - ('UDD', 3.0 / 57), - ('UDU', 2.0 / 57), - ('UUU', 12.0 / 57), - ), - DDD=( - ('DDD', 8.0 / 13), - ('DDU', 1.0 / 13), - ('DUD', 1.0 / 13), - ('DUU', 2.0 / 13), - ('UDD', 1.0 / 13), - ), - DDU=(('DDD', 1),), - DUD=(('DDD', 4.0 / 12), ('DUD', 7.0 / 12), ('DUU', 1.0 / 12)), - DUU=(('DUD', 3.0 / 14), ('DUU', 10.0 / 14), ('UUU', 1.0 / 14)), - UDD=(('UDD', 2.0 / 3), ('UUU', 1.0 / 3)), - UDU=(('UDU', 1),), - UUU=(('DUU', 1.0 / 12), ('UDU', 1.0 / 12), ('UUU', 10.0 / 12)), -) - -rates = { - 'CostMarginal': dict( - HedgingStrategyDDD=( - ('imp_coal', 1.06637), - ('imp_natgas,*', 0.88132), - ('imp_oil,*', 1.05582), - ), - HedgingStrategyDDU=( - ('imp_coal', 1.06637), - ('imp_natgas,*', 0.88132), - ('imp_oil,*', 1.05582), - ), - HedgingStrategyDUD=( - ('imp_coal', 1.06637), - ('imp_natgas,*', 0.88132), - ('imp_oil,*', 1.05582), - ), - HedgingStrategyDUU=( - ('imp_coal', 1.06637), - ('imp_natgas,*', 0.88132), - ('imp_oil,*', 1.05582), - ), - HedgingStrategyUDD=( - ('imp_coal', 1.06637), - ('imp_natgas,*', 0.88132), - ('imp_oil,*', 1.05582), - ), - HedgingStrategyUDU=( - ('imp_coal', 1.06637), - ('imp_natgas,*', 0.88132), - ('imp_oil,*', 1.05582), - ), - HedgingStrategyUUU=( - ('imp_coal', 1.06637), - ('imp_natgas,*', 0.88132), - ('imp_oil,*', 1.05582), - ), - DDDDDD=(('imp_coal,*', 0.95886), ('imp_natgas,*', 0.93415), ('imp_oil,*', 0.91485)), - DDDDDU=(('imp_coal,*', 0.94783), ('imp_natgas,*', 0.93143), ('imp_oil,*', 1.01982)), - DDDDUD=(('imp_coal,*', 0.95210), ('imp_natgas,*', 1.00764), ('imp_oil,*', 0.99502)), - DDDDUU=(('imp_coal,*', 0.95412), ('imp_natgas,*', 1.02703), ('imp_oil,*', 1.00843)), - DDDUDD=(('imp_coal,*', 1.03169), ('imp_natgas,*', 0.97375), ('imp_oil,*', 0.98145)), - DDUDDD=(('imp_coal,*', 0.95059), ('imp_natgas,*', 0.97436), ('imp_oil,*', 0.97492)), - DUDDDD=(('imp_coal,*', 0.95935), ('imp_natgas,*', 0.98153), ('imp_oil,*', 0.92444)), - DUDDUD=(('imp_coal,*', 0.96920), ('imp_natgas,*', 1.03930), ('imp_oil,*', 0.98458)), - DUDDUU=(('imp_coal,*', 0.97144), ('imp_natgas,*', 1.06985), ('imp_oil,*', 1.00170)), - DUUDUD=(('imp_coal,*', 0.96475), ('imp_natgas,*', 1.03622), ('imp_oil,*', 0.98506)), - DUUDUU=(('imp_coal,*', 0.97600), ('imp_natgas,*', 1.11657), ('imp_oil,*', 1.09607)), - DUUUUU=(('imp_coal,*', 1.01655), ('imp_natgas,*', 1.16025), ('imp_oil,*', 1.15942)), - UDDUDD=(('imp_coal,*', 1.05568), ('imp_natgas,*', 0.98233), ('imp_oil,*', 0.98602)), - UDDUUU=(('imp_coal,*', 1.07542), ('imp_natgas,*', 1.01397), ('imp_oil,*', 1.00729)), - UDUUDU=(('imp_coal,*', 1.06637), ('imp_natgas,*', 0.88132), ('imp_oil,*', 1.05582)), - UUUDUU=(('imp_coal,*', 0.98426), ('imp_natgas,*', 1.20240), ('imp_oil,*', 1.18666)), - UUUUDU=(('imp_coal,*', 1.08067), ('imp_natgas,*', 0.92897), ('imp_oil,*', 1.04542)), - UUUUUU=(('imp_coal,*', 1.10292), ('imp_natgas,*', 1.14894), ('imp_oil,*', 1.13962)), - ) -} diff --git a/temoa/extensions/stochastics/options/iew2012.py b/temoa/extensions/stochastics/options/iew2012.py deleted file mode 100644 index f56cef7c6..000000000 --- a/temoa/extensions/stochastics/options/iew2012.py +++ /dev/null @@ -1,92 +0,0 @@ -verbose = True -force = True - -dirname = 'temoa_island' -modelpath = '../temoa_model/temoa_model.py' -dotdatpath = '../data_files/iew2012.dat' -stochasticset = 'time_optimize' -stochastic_points = (2020, 2025, 2030, 2035) -stochastic_indices = {'Demand': 0, 'CostVariable': 0} -types = ( - 'DemD_NatD_OilD', - 'DemD_NatD_OilU', - 'DemD_NatU_OilD', - 'DemD_NatU_OilU', - 'DemU_NatD_OilD', - 'DemU_NatD_OilU', - 'DemU_NatU_OilD', - 'DemU_NatU_OilU', -) -conditional_probability = dict( - DemD_NatD_OilD=0.0952, - DemD_NatD_OilU=0.0238, - DemD_NatU_OilD=0.0952, - DemD_NatU_OilU=0.1429, - DemU_NatD_OilD=0.1429, - DemU_NatD_OilU=0.1667, - DemU_NatU_OilD=0.0952, - DemU_NatU_OilU=0.2381, -) - -rates = { - 'Demand': dict( - DemD_NatD_OilD=( - ('*,*,r_cooling', 0.95937), - ('*,*,r_heating', 0.95937), - ('*,*,r_lighting', 0.95937), - ('*,*,r_wheating', 0.95937), - ), - DemD_NatD_OilU=( - ('*,*,r_cooling', 0.78503), - ('*,*,r_heating', 0.78503), - ('*,*,r_lighting', 0.78503), - ('*,*,r_wheating', 0.78503), - ), - DemD_NatU_OilD=( - ('*,*,r_cooling', 0.92841), - ('*,*,r_heating', 0.92841), - ('*,*,r_lighting', 0.92841), - ('*,*,r_wheating', 0.92841), - ), - DemD_NatU_OilU=( - ('*,*,r_cooling', 0.90791), - ('*,*,r_heating', 0.90791), - ('*,*,r_lighting', 0.90791), - ('*,*,r_wheating', 0.90791), - ), - DemU_NatD_OilD=( - ('*,*,r_cooling', 1.18282), - ('*,*,r_heating', 1.18282), - ('*,*,r_lighting', 1.18282), - ('*,*,r_wheating', 1.18282), - ), - DemU_NatD_OilU=( - ('*,*,r_cooling', 1.20957), - ('*,*,r_heating', 1.20957), - ('*,*,r_lighting', 1.20957), - ('*,*,r_wheating', 1.20957), - ), - DemU_NatU_OilD=( - ('*,*,r_cooling', 1.20281), - ('*,*,r_heating', 1.20281), - ('*,*,r_lighting', 1.20281), - ('*,*,r_wheating', 1.20281), - ), - DemU_NatU_OilU=( - ('*,*,r_cooling', 1.12236), - ('*,*,r_heating', 1.12236), - ('*,*,r_lighting', 1.12236), - ('*,*,r_wheating', 1.12236), - ), - ), - 'CostVariable': dict( - DemD_NatD_OilD=(('imp_natgas,*', 0.73351), ('imp_oil,*', 0.15309)), - DemD_NatD_OilU=(('imp_natgas,*', 0.95164), ('imp_oil,*', 2.67004)), - DemD_NatU_OilD=(('imp_natgas,*', 1.68052), ('imp_oil,*', 0.55464)), - DemD_NatU_OilU=(('imp_natgas,*', 1.40218), ('imp_oil,*', 4.45265)), - DemU_NatD_OilD=(('imp_natgas,*', 0.74707), ('imp_oil,*', 0.62440)), - DemU_NatD_OilU=(('imp_natgas,*', 0.75098), ('imp_oil,*', 2.11862)), - DemU_NatU_OilD=(('imp_natgas,*', 1.37597), ('imp_oil,*', 0.66387)), - DemU_NatU_OilU=(('imp_natgas,*', 1.45544), ('imp_oil,*', 2.34730)), - ), -} diff --git a/temoa/extensions/stochastics/options/utopia_coal_vs_nuc.py b/temoa/extensions/stochastics/options/utopia_coal_vs_nuc.py deleted file mode 100644 index 197df5373..000000000 --- a/temoa/extensions/stochastics/options/utopia_coal_vs_nuc.py +++ /dev/null @@ -1,86 +0,0 @@ -verbose = True -force = True - -dirname = 'utopia_demand' -modelpath = '../temoa_model/temoa_model.py' -dotdatpath = '../data_files/utopia-15.dat' -stochasticset = 'time_optimize' -stochastic_indices = {'Demand': 0, 'CostInvest': 1} - -# CL, CA, CH = coal "[lower, average, high]" rate -# _Low, _Average, _High = demand "[low, average, high]" rate -types = ( - 'CL_Low', - 'CL_Average', - 'CL_High', - 'CA_Low', - 'CA_Average', - 'CA_High', - 'CH_Low', - 'CH_Average', - 'CH_High', -) - -# for this toy problem, all branches are equally likely, so 1/9 -conditional_probability = dict( - CL_Low=1.0 / 9, - CA_Low=1.0 / 9, - CH_Low=1.0 / 9, - CL_Average=1.0 / 9, - CA_Average=1.0 / 9, - CH_Average=1.0 / 9, - CL_High=1.0 / 9, - CA_High=1.0 / 9, - CH_High=1.0 / 9, -) -rates = { - 'Demand': dict( - CL_Low=(('*,*,RH', 0.951), ('*,*,RL', 0.951), ('*,*,TX', 0.951)), - CA_Low=(('*,*,RH', 0.951), ('*,*,RL', 0.951), ('*,*,TX', 0.951)), - CH_Low=(('*,*,RH', 0.951), ('*,*,RL', 0.951), ('*,*,TX', 0.951)), - CL_Average=(('*,*,RH', 1.105), ('*,*,RL', 1.105), ('*,*,TX', 1.105)), - CA_Average=(('*,*,RH', 1.105), ('*,*,RL', 1.105), ('*,*,TX', 1.105)), - CH_Average=(('*,*,RH', 1.105), ('*,*,RL', 1.105), ('*,*,TX', 1.105)), - CL_High=(('*,*,RH', 1.480), ('*,*,RL', 1.480), ('*,*,TX', 1.480)), - CA_High=(('*,*,RH', 1.480), ('*,*,RL', 1.480), ('*,*,TX', 1.480)), - CH_High=(('*,*,RH', 1.480), ('*,*,RL', 1.480), ('*,*,TX', 1.480)), - ), - 'CostInvest': dict( - CL_Low=( - ('E01,*', 1.8), - ('E21,*', 1.2), - ), - CL_Average=( - ('E01,*', 1.8), - ('E21,*', 1.2), - ), - CL_High=( - ('E01,*', 1.8), - ('E21,*', 1.2), - ), - CA_Low=( - ('E01,*', 2.0), - ('E21,*', 0.7), - ), - CA_Average=( - ('E01,*', 2.0), - ('E21,*', 0.7), - ), - CA_High=( - ('E01,*', 2.0), - ('E21,*', 0.7), - ), - CH_Low=( - ('E01,*', 2.2), - ('E21,*', 0.2), - ), - CH_Average=( - ('E01,*', 2.2), - ('E21,*', 0.2), - ), - CH_High=( - ('E01,*', 2.2), - ('E21,*', 0.2), - ), - ), -} diff --git a/temoa/extensions/stochastics/scenario_creator.py b/temoa/extensions/stochastics/scenario_creator.py new file mode 100644 index 000000000..a75e70430 --- /dev/null +++ b/temoa/extensions/stochastics/scenario_creator.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +import logging +import sqlite3 +from typing import TYPE_CHECKING, Any, cast +from collections.abc import Iterable + +from mpisppy.utils.sputils import attach_root_node # type: ignore[import-untyped] + +from temoa._internal.run_actions import build_instance +from temoa.components.costs import period_cost_rule +from temoa.data_io.hybrid_loader import HybridLoader + +if TYPE_CHECKING: + from temoa.core.config import TemoaConfig + from temoa.data_io.hybrid_loader import LoadItem + from temoa.extensions.stochastics.stochastic_config import StochasticConfig + +logger = logging.getLogger(__name__) + + +def scenario_creator(scenario_name: str, **kwargs: Any) -> Any: + """ + Creator for mpi-sppy scenarios. + + Args: + scenario_name (str): Name of the scenario to create. + **kwargs: Must contain 'temoa_config' and 'stoch_config'. + """ + if 'temoa_config' not in kwargs or 'stoch_config' not in kwargs: + raise ValueError("scenario_creator requires 'temoa_config' and 'stoch_config' in kwargs") + + temoa_config: TemoaConfig = kwargs['temoa_config'] + stoch_config: StochasticConfig = kwargs['stoch_config'] + + # 1. Load base data + try: + with sqlite3.connect(temoa_config.input_database) as con: + hybrid_loader = HybridLoader(db_connection=con, config=temoa_config) + data_dict = hybrid_loader.create_data_dict(myopic_index=None) + + # Build a map of table -> index columns from the manifest + # For each LoadItem, the index columns are all but the last one (which is the value) + table_index_map: dict[str, list[str]] = {} + for item in cast('Iterable[LoadItem]', hybrid_loader.manifest): + if item.table not in table_index_map and item.columns: + table_index_map[item.table] = list(item.columns[:-1]) + except Exception as e: + logger.exception('Failed to connect to database %s', temoa_config.input_database) + raise RuntimeError(f'Failed to connect to database {temoa_config.input_database}') from e + + # 2. Apply perturbations for this scenario + for p in stoch_config.perturbations: + if p.scenario != scenario_name: + continue + + target_param = cast(dict[Any, Any] | None, data_dict.get(p.table)) + if target_param is None: + logger.warning( + 'Table %s not found in data_dict for scenario %s', p.table, scenario_name + ) + continue + + # target_param is {(idx...): value} + # We need to find entries matching p.filter + index_cols = table_index_map.get(p.table) + if index_cols is None: + logger.warning( + 'Table %s not found in manifest; cannot map indices for scenario %s', + p.table, + scenario_name, + ) + continue + + for idx, current_val in list(target_param.items()): + # Map index tuple to names based on table manifest + # normalize idx to tuple if it is a single value + idx_tuple = idx if isinstance(idx, tuple) else (idx,) + index_map = dict(zip(index_cols, idx_tuple, strict=True)) + + # Check if filter matches + match = True + for filter_key, filter_val in p.filter.items(): + if index_map.get(filter_key) != filter_val: + match = False + break + + if match: + if p.action == 'multiply': + target_param[idx] = current_val * p.value + elif p.action == 'add': + target_param[idx] = current_val + p.value + elif p.action == 'set': + target_param[idx] = p.value + + # 3. Build instance + data_portal = HybridLoader.data_portal_from_data(data_dict) + instance = build_instance(data_portal, silent=True) + + # 4. Attach root node (Stage 1) + periods = sorted(instance.time_optimize) + first_period = periods[0] + + prob = stoch_config.scenarios.get(scenario_name) + if prob is None: + logger.warning( + "Scenario '%s' not found in stochastic config probabilities; defaulting to 1.0", + scenario_name, + ) + prob = 1.0 + instance._mpisppy_probability = prob + + # First stage variables: v_new_capacity[*, *, first_period] + first_stage_vars = [] + for r, t, p in instance.v_new_capacity: + if p == first_period: + first_stage_vars.append(instance.v_new_capacity[r, t, p]) + + if not first_stage_vars: + logger.error( + 'No first-stage variables (v_new_capacity for period %s) found for scenario %s. ' + 'Stochastic optimization requires at least one first-stage decision.', + first_period, + scenario_name, + ) + raise ValueError(f'No first-stage variables found for scenario {scenario_name}') + + # First stage cost: PeriodCost[first_period] + # We can use the period_cost_rule directly + first_stage_cost_expr = period_cost_rule(instance, first_period) + + # Attach root node + attach_root_node(instance, first_stage_cost_expr, first_stage_vars) + + return instance diff --git a/temoa/extensions/stochastics/stochastic_config.py b/temoa/extensions/stochastics/stochastic_config.py new file mode 100644 index 000000000..a46df1e32 --- /dev/null +++ b/temoa/extensions/stochastics/stochastic_config.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +import logging +import tomllib +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Self + +if TYPE_CHECKING: + from pathlib import Path + +logger = logging.getLogger(__name__) + + +@dataclass +class Perturbation: + scenario: str + table: str + filter: dict[str, Any] + action: str # 'multiply', 'add', 'set' + value: float + + def __post_init__(self) -> None: + allowed_actions = {'multiply', 'add', 'set'} + if self.action not in allowed_actions: + raise ValueError( + f"Invalid perturbation action '{self.action}'; must be one of {allowed_actions}" + ) + + +@dataclass +class StochasticConfig: + scenarios: dict[str, float] # scenario_name -> probability + perturbations: list[Perturbation] = field(default_factory=list) + solver_options: dict[str, Any] = field(default_factory=dict) + + + @classmethod + def from_toml(cls, path: Path) -> Self: + with open(path, 'rb') as f: + data = tomllib.load(f) + + scenarios_raw = data.get('scenarios', {}) + scenarios = {} + for name, val in scenarios_raw.items(): + if isinstance(val, dict): + scenarios[name] = float(val.get('probability', 1.0)) + else: + scenarios[name] = float(val) + + # Validate probability distribution + if scenarios: + total_prob = sum(scenarios.values()) + if not (0.99 <= total_prob <= 1.01): + logger.warning( + 'Stochastic scenario probabilities sum to %s; usually they should sum to ~1.0', + total_prob, + ) + + perturbations_data = data.get('perturbations', []) + perturbations = [] + for i, p in enumerate(perturbations_data): + try: + scenario_name = p['scenario'] + if scenario_name not in scenarios: + raise ValueError( + f'Perturbation at index {i} references nonexistent scenario: ' + f"'{scenario_name}'. Available scenarios: {list(scenarios.keys())}" + ) + + perturbations.append( + Perturbation( + scenario=scenario_name, + table=p['table'], + filter=p['filter'], + action=p.get('action', 'set'), + value=p['value'], + ) + ) + except KeyError as e: + raise ValueError(f'Perturbation at index {i} is missing required field: {e}') from e + + solver_options = data.get('solver_options', {}) + + # Validate at least one scenario exists + if not scenarios: + raise ValueError('Stochastic configuration must define at least one scenario') + + return cls( + scenarios=scenarios, + perturbations=perturbations, + solver_options=solver_options, + ) diff --git a/temoa/extensions/stochastics/stochastic_sequencer.py b/temoa/extensions/stochastics/stochastic_sequencer.py new file mode 100644 index 000000000..9950ee6cc --- /dev/null +++ b/temoa/extensions/stochastics/stochastic_sequencer.py @@ -0,0 +1,98 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +import pyomo.environ as pyo + +from temoa.extensions.stochastics.stochastic_config import StochasticConfig + +if TYPE_CHECKING: + from temoa.core.config import TemoaConfig + +logger = logging.getLogger(__name__) + + +class StochasticSequencer: + """ + Orchestrates a stochastic run using mpi-sppy. + """ + + def __init__(self, config: TemoaConfig) -> None: + self.config = config + self.objective_value: float | None = None + if not self.config.stochastic_config: + raise ValueError("Stochastic mode requires a 'stochastic_config' in the TOML.") + + sc_path = self.config.stochastic_config + if not sc_path.exists(): + raise ValueError(f'Stochastic config file not found: {sc_path}') + if not sc_path.is_file(): + raise ValueError(f'Stochastic config path is not a file: {sc_path}') + + try: + self.stoch_config = StochasticConfig.from_toml(sc_path) + except Exception as e: + logger.exception('Failed to load stochastic config from %s', sc_path) + raise ValueError(f'Error parsing stochastic config {sc_path}. Original error: {e}') from e + + def start(self) -> None: + """ + Execute the stochastic run. + """ + try: + from mpisppy.opt.ef import ExtensiveForm # type: ignore[import-untyped] + except ImportError as e: + logger.exception('mpi-sppy is not installed. Please install it to use stochastic mode.') + raise RuntimeError(f'mpi-sppy not found. Original error: {e}') from e + + from temoa.extensions.stochastics.scenario_creator import scenario_creator + + # Merge solver options from stoch_config + solver_options = self.stoch_config.solver_options.get(self.config.solver_name, {}) + + options = { + 'solver': self.config.solver_name, + } + + if not self.stoch_config.scenarios: + raise ValueError( + 'No scenarios found in stoch_config.scenarios; provide at least one scenario ' + 'before constructing the ExtensiveForm' + ) + + # For now, we only support Extensive Form (EF) + # We need to provide a list of scenario names to mpi-sppy + all_scenario_names = list(self.stoch_config.scenarios.keys()) + + scenario_creator_kwargs = { + 'temoa_config': self.config, + 'stoch_config': self.stoch_config, + } + + logger.info('Starting mpi-sppy Extensive Form (EF) solver...') + + ef = ExtensiveForm( + options, + all_scenario_names, + scenario_creator, + scenario_creator_kwargs=scenario_creator_kwargs, + ) + + results = ef.solve_extensive_form(solver_options=solver_options) + + # Check for optimal termination before accessing objective + if not pyo.check_optimal_termination(results): + termination = 'unknown' + if hasattr(results, 'solver'): + termination = getattr(results.solver, 'termination_condition', 'unknown') + + logger.error('Stochastic solve failed with termination condition: %s', termination) + raise RuntimeError(f'Stochastic solve failed: {termination}') + + obj_val = ef.get_objective_value() + self.objective_value = obj_val + logger.info('Stochastic Expected Value: %s', obj_val) + + # TODO: Integrate with TableWriter to save results to database + # This might require a modified handle_results or a new one for stochastics diff --git a/temoa/extensions/stochastics/stochastics_README.txt b/temoa/extensions/stochastics/stochastics_README.txt deleted file mode 100644 index 6ce971cf3..000000000 --- a/temoa/extensions/stochastics/stochastics_README.txt +++ /dev/null @@ -1,73 +0,0 @@ ------------------------------- -Stochastic Optimization README ------------------------------- - -(Solve a stochastic run and store results into a database using config file) -$ python temoa_model/temoa_stochastic.py --config=temoa_model/config_sample -# Note that to invoke the stochastic run, the "--input" flag must be the path -# to ScenarioStructure.dat, and "--output" flag is the path to the target -# database file, where the results will be stored. - -(Extensive Formulation or Deterministic Equivalent) -runef -m ../../temoa_model/ -i ./ --solver=glpk --solve >> out.txt - -(Progressive Hedging) -runph -m ../../temoa_model/ -i ./ --solver=ipopt --default-rho=1.0 - -(Solve a particular path in the tree as a linear program) -python ../../temoa_model/ R.dat Rs0.dat Rs0s2.dat - ------------------------------ -Stochastic Optimization Tools ------------------------------ - -(EVPI computation) -python test_EVPI.py - -(VSS computation) -python VSS.py - #(Information about how to setup a run of VSS): - #Lines 246 - 249 specify the path to the folders and the solver to be used. - #It is necessary to change these lines in order to properly point to the - #instance that you want to solve. The first one just points to the path of the - #temoa_stochastic.py file. The second one point to the folder of the instance - #where the scenario tree structure and all the scenarios are represented. - #p_model = '/home/arqueiroz/SSudan/S1_2_H/temoa_model/temoa_stochastic.py' - #p_data = '/home/arqueiroz/SSudan/S1_2_H/stochastic/S_Sudan_original_stoch_cap_cost_11' - #optsolver = 'cplex' - - #(Deterministic file with average values): - #Inside the stochastic folder where you want to run the VSS script it is necessary to - #manually create an input file to represent the uncertainty with average values. This - #file will be used to run the deterministic instance where we store information about the - #decisions on the first stage. - #The name of the input file is defined on line 195 - - #(Get info about decisions on the first stage): - #On line 147 - #ef._binding_instance.S0s0s0.V_Capacity[iaux1, iaux2].fix(dV_Capacity[iaux1,iaux2]) - #it is necessary to fix the first stage decisions from the deterministic model (with - #average values) when solving the stochastic program (with fixed values) that will - #be compared to the true stochastic program (without any fixed values) - #Note that the left portion of the equation is the one that is fixed. As for now - #it depends on the number of stages of the problem, for the one used here there is - #a total of four stages and we need to fix .S0s0s0.V_Capacity.fix if it was 3 stages we - #would make S0s0.V_Capacity.fix - - #Additional file for EVPI and VSS usage: - #pyomo version 4.3.11388 requires the addition - #of the file ef_writer_script_old.py within the installation - #of pyomo under anaconda. The correct path to add the - #file is: /anaconda/lib/python2.7/site-packages/pyomo/pysp - -(Generate Scenario Tree) -python generate_scenario_tree.py options/uc_tl_unlim.py - #Additional files needed for generate scenario tree script: - #pyomo version 4.3.11388 requires the addition - #of the file scenariomodels.py within the installation - #of pyomo under anaconda. The correct path to add the - #file is: /anaconda/lib/python2.7/site-packages/pyomo/pysp/util - - -(Script for Parallel runs of runph) (to be used on Neer super computer) -qsub jobTemoa.pbs \ No newline at end of file diff --git a/temoa/extensions/stochastics/temoa_stochastic.py b/temoa/extensions/stochastics/temoa_stochastic.py deleted file mode 100644 index 6a19cde82..000000000 --- a/temoa/extensions/stochastics/temoa_stochastic.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env python - -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" -import os -import sys - -from pformat_results import pformat_results -from pyomo.core.kernel.numvalue import value -from pyomo.environ import * -from pyomo.opt import SolverFactory -from pyomo.pysp.ef import create_ef_instance -from pyomo.pysp.scenariotree.manager import ScenarioTreeManagerClientSerial -from temoa_model import TemoaModel -from temoa_rules import PeriodCost_rule -from temoa_run import parse_args - - -def return_CP_and_path(p_data): - # return_CP_and_path(p_data) -> dict(), dict() - # This function reads the path to the instance directory (p_data) and - # returns conditional two dictionaries, the first one is the conditional - # probability of a scenario, the second one is the path to all files of a - # scenario. - from collections import deque, defaultdict - - # from pyomo.pysp.util.scenariomodels import scenario_tree_model - from pyomo.pysp.scenariotree.tree_structure_model import CreateAbstractScenarioTreeModel - - pwd = os.getcwd() - os.chdir(p_data) - - s2fp_dict = defaultdict(deque) # Scenario to 'file path' dictionary, .dat not included - s2cd_dict = defaultdict(float) # Scenario to conditonal density mapping - # sStructure = scenario_tree_model.create_instance( filename='ScenarioStructure.dat' ) - sStructure = CreateAbstractScenarioTreeModel().create_instance(filename='ScenarioStructure.dat') - - # The following code is borrowed from Kevin's temoa_lib.py - ########################################################################### - # Step 1: find the root node. PySP doesn't make this very easy ... - - # a child -> parent mapping, because every child has only one parent, but - # not vice-versa - ctpTree = dict() # Child to parent dict, one to one mapping - - to_process = deque() - to_process.extend(sStructure.Children.keys()) - while to_process: - node = to_process.pop() - if node in sStructure.Children: - # it's a parent! - new_nodes = set(sStructure.Children[node]) - to_process.extend(new_nodes) - ctpTree.update({n: node for n in new_nodes}) - - # parents - children - root_node = (set(ctpTree.values()) - set(ctpTree.keys())).pop() - - # ptcTree = defaultdict( list ) # Parent to child node, one to multiple mapping - # for c, p in ctpTree.items(): - # ptcTree[ p ].append( c ) - # ptcTree = dict( ptcTree ) # be slightly defensive; catch any additions - - # leaf_nodes = set(ctpTree.keys()) - set(ctpTree.values()) - # leaf_nodes = set(sStructure.ScenarioLeafNode.values()) # Try to hack Kevin's code - leaf_nodes = sStructure.ScenarioLeafNode.values() # Try to hack Kevin's code - leaf_nodes_names = list() - for n in leaf_nodes: - leaf_nodes_names.append(n.value) - leaf_nodes_names = set(leaf_nodes_names) - - scenario_nodes = dict() # Map from leafnode to 'node path' - for node in leaf_nodes_names: # e.g.: {Rs0s0: [R, Rs0, Rs0s0]} - s = deque() - scenario_nodes[node] = s - while node in ctpTree: - s.append(node) - node = ctpTree[node] - s.append(node) - s.reverse() - ########################################################################### - - for s in sStructure.Scenarios: - cp = 1.0 # Starting probability - for n in scenario_nodes[value(sStructure.ScenarioLeafNode[s])]: - cp = cp * value(sStructure.ConditionalProbability[n]) - if not sStructure.ScenarioBasedData.value: - s2fp_dict[s].append(n + '.dat') - s2cd_dict[s] = cp - - if sStructure.ScenarioBasedData.value: - for s in sStructure.Scenarios: - s2fp_dict[s].append(s + '.dat') - os.chdir(pwd) - return (s2cd_dict, s2fp_dict) - - -def solve_ef(p_model, p_data, temoa_options=None): - """ - solve_ef(p_model, p_data) -> objective value of the extensive form - Solves the model in stochastic mode. - p_model -> string, the path to the model file (ReferenceModel.py). - p_data -> string, the path to the directory of data for the stochastic - mdoel, where ScenarioStructure.dat should resides. - Returns a float point number of the value of objective function for the - stochastic program model. - """ - - options = ScenarioTreeManagerClientSerial.register_options() - - if os.path.basename(p_model) == 'ReferenceModel.py': - options.model_location = os.path.dirname(p_model) - else: - sys.stderr.write('\nModel file should be ReferenceModel.py. Exiting...\n') - sys.exit(1) - options.scenario_tree_location = p_data - - # using the 'with' block will automatically call - # manager.close() and gracefully shutdown - with ScenarioTreeManagerClientSerial(options) as manager: - manager.initialize() - - ef_instance = create_ef_instance(manager.scenario_tree, verbose_output=options.verbose) - - ef_instance.dual = Suffix(direction=Suffix.IMPORT) - - with SolverFactory(temoa_options.solver_name) as opt: - ef_result = opt.solve(ef_instance) - - # Write to database - if hasattr(temoa_options, 'output'): - sys.path.append(options.model_location) - - # from temoa_config import TemoaConfig - # temoa_options = TemoaConfig() - # temoa_options.config = temoa_options.config - # temoa_options.keepPyomoLP = temoa_options.keepPyomoLP - # temoa_options.saveTEXTFILE = temoa_options.saveTEXTFILE - # temoa_options.path_to_data = temoa_options.path_to_data - # temoa_options.saveEXCEL = temoa_options.saveEXCEL - ef_result.solution.Status = 'feasible' # Assume it is feasible - # Maybe there is a better solution using manager, but now it is a - # kludge to use return_CP_and_path() function - s2cd_dict, s2fp_dict = return_CP_and_path(p_data) - stochastic_run = temoa_options.scenario # Name of stochastic run - for s in manager.scenario_tree.scenarios: - ins = s._instance - temoa_options.scenario = '.'.join([stochastic_run, s.name]) - temoa_options.dot_dat = list() - for fname in s2fp_dict[s.name]: - temoa_options.dot_dat.append( - os.path.join(options.scenario_tree_location, fname) - ) - # temoa_options.output = os.path.join( - # options.scenario_tree_location, - # stochastic_output - # ) - msg = '\nStoring results from scenario {} to database.\n'.format(s.name) - sys.stderr.write(msg) - formatted_results = pformat_results(ins, ef_result, temoa_options) - - ef_instance.solutions.store_to(ef_result) - ef_obj = value(ef_instance.EF_EXPECTED_COST.values()[0]) - return ef_obj - - -def StochasticPointObjective_rule(M, p): - expr = M.StochasticPointCost[p] == PeriodCost_rule(M, p) - return expr - - -def Objective_rule(M): - return sum(M.StochasticPointCost[pp] for pp in M.time_optimize) - - -M = model = TemoaModel('TEMOA Stochastic') - -M.StochasticPointCost = Var(M.time_optimize, within=NonNegativeReals) -M.StochasticPointCostConstraint = Constraint(M.time_optimize, rule=StochasticPointObjective_rule) - -del M.TotalCost -M.TotalCost = Objective(rule=Objective_rule, sense=minimize) - -if __name__ == '__main__': - p_model = './ReferenceModel.py' - temoa_options, config_flag = parse_args() - p_dot_dat = temoa_options.dot_dat[0] # must be ScenarioStructure.dat - p_data = os.path.dirname(p_dot_dat) - print(p_model, p_data) - print(solve_ef(p_model, p_data, temoa_options)) diff --git a/temoa/model_checking/__init__.py b/temoa/model_checking/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/model_checking/commodity_graph.py b/temoa/model_checking/commodity_graph.py new file mode 100644 index 000000000..8275e0e1e --- /dev/null +++ b/temoa/model_checking/commodity_graph.py @@ -0,0 +1,327 @@ +from __future__ import annotations + +import logging +from collections import defaultdict +from typing import TYPE_CHECKING, Any, cast + +import networkx as nx + +from temoa.utilities.graph_utils import ( + calculate_initial_positions, + calculate_tech_graph_positions, +) +from temoa.utilities.visualizer import make_nx_graph, nx_to_vis + +if TYPE_CHECKING: + from collections.abc import Iterable + + from temoa.core.config import TemoaConfig + from temoa.model_checking.network_model_data import EdgeTuple, NetworkModelData + from temoa.types.core_types import Commodity, Period, Region, Sector, Technology + +logger = logging.getLogger(__name__) + + +def generate_technology_graph( + all_edges: Iterable[EdgeTuple], + source_commodities: set[Commodity], + demand_commodities: set[Commodity], + sector_colors: dict[Sector, str], +) -> nx.MultiDiGraph[str]: + """ + Generates a technology-centric graph with a pre-computed initial layout. + """ + tg: nx.MultiDiGraph[str] = nx.MultiDiGraph() + tech_positions = calculate_tech_graph_positions(all_edges) + + # Pass 1: Aggregate information for each unique technology. + tech_info: dict[str, dict[str, Any]] = defaultdict( + lambda: {'is_source': False, 'is_demand': False, 'sector': None} + ) + for tech_tuple in all_edges: + info = tech_info[tech_tuple.tech] + if tech_tuple.input_comm in source_commodities: + info['is_source'] = True + if tech_tuple.output_comm in demand_commodities: + info['is_demand'] = True + # Use the sector from the first tuple we see for a given tech + if not info['sector']: + info['sector'] = tech_tuple.sector + + # Pass 2: Create a single, correctly styled node for each unique technology. + for tech_name, info in tech_info.items(): + pos_attrs = tech_positions.get(cast('Technology', tech_name)) or {} + sector = info['sector'] + + color_obj: dict[str, str] = {} + if sector and (bg := sector_colors.get(sector)): + color_obj['background'] = bg + border_width = 1 + title = f'Tech: {tech_name}' + + # Apply styles based on the aggregated status + if info['is_source']: + color_obj['border'] = '#2ca02c' # Green border + border_width = 4 + title += '\nType: Source Technology' + if info['is_demand']: + color_obj['border'] = '#e377c2' # Magenta/Pink border + border_width = 4 + title += '\nType: Demand Technology' + + node_attrs: dict[str, Any] = { + 'label': tech_name, + 'title': title + (f'\nSector: {sector}' if sector else ''), + 'shape': 'box', + 'color': color_obj, + 'borderWidth': border_width, + **pos_attrs, + } + tg.add_node(tech_name, **node_attrs) + + # Create edges representing commodity flows + commodity_map: defaultdict[str, dict[str, set[str]]] = defaultdict( + lambda: {'producers': set(), 'consumers': set()} + ) + for tech_tuple in all_edges: + commodity_map[tech_tuple.output_comm]['producers'].add(tech_tuple.tech) + commodity_map[tech_tuple.input_comm]['consumers'].add(tech_tuple.tech) + + for commodity, roles in commodity_map.items(): + if commodity in source_commodities or commodity in demand_commodities: + continue + for producer in roles['producers']: + for consumer in roles['consumers']: + if producer != consumer: + tg.add_edge( + producer, + consumer, + label=commodity, + title=f'Commodity Flow: {commodity}', + arrows='to', + color='#555555', + ) + return tg + + +def generate_commodity_graph( + region: Region, + period: Period, + network_data: NetworkModelData, + demand_orphans: Iterable[EdgeTuple], + other_orphans: Iterable[EdgeTuple], + driven_techs: Iterable[EdgeTuple], +) -> tuple[nx.MultiDiGraph[str], dict[Sector, str]]: + """ + Generates the commodity-centric graph and its associated color scheme. + In this view, commodities are nodes and technologies are grouped into edges. + """ + all_edge_tuples = ( + set(network_data.available_techs.get((region, period), set())) + | set(demand_orphans) + | set(other_orphans) + | set(driven_techs) + ) + + # 1. Prepare sector-based mappings for coloring + unique_sectors = sorted({tech.sector for tech in all_edge_tuples if tech.sector}) + color_palette = [ + '#1f77b4', + '#ff7f0e', + '#2ca02c', + '#d62728', + '#9467bd', + '#8c564b', + '#e377c2', + '#7f7f7f', + '#bcbd22', + '#17becf', + ] + sector_colors = { + sector: color_palette[i % len(color_palette)] for i, sector in enumerate(unique_sectors) + } + default_color = '#A9A9A9' + + commodity_sector_counts: defaultdict[Commodity, defaultdict[Sector, int]] = defaultdict( + lambda: defaultdict(int) + ) + for tech in all_edge_tuples: + if tech.sector: + commodity_sector_counts[tech.input_comm][tech.sector] += 1 + commodity_sector_counts[tech.output_comm][tech.sector] += 1 + + commodity_to_primary_sector: dict[Commodity, Sector] = { + comm: max(counts, key=lambda k: counts[k]) + for comm, counts in commodity_sector_counts.items() + if counts + } + + # 2. Define node layers (1: source, 2: intermediate, 3: sink) + layer_map: dict[str, int] = dict.fromkeys( + network_data.physical_commodities, 2 + ) # all intermediates + for c in network_data.source_commodities.get((region, period), []): + layer_map[c] = 1 + for c in network_data.demand_commodities.get((region, period), []): + layer_map[c] = 3 + + node_positions = calculate_initial_positions( + layer_map, + commodity_to_primary_sector, + unique_sectors, + ) + + # 3. Prepare edge attributes with sector-based coloring + edge_attributes_map: dict[tuple[str, str, str, str | None], dict[str, Any]] = {} + all_connections: set[tuple[Commodity, Technology, Commodity, Sector | None]] = { + (edge_tuple.input_comm, edge_tuple.tech, edge_tuple.output_comm, edge_tuple.sector) + for edge_tuple in all_edge_tuples + } + + driven_names = {t.tech for t in driven_techs} + other_orphan_names = {t.tech for t in other_orphans} + demand_orphan_names = {t.tech for t in demand_orphans} + + driven_commodities: set[Commodity] = set() + other_orphan_commodities: set[Commodity] = set() + demand_orphan_commodities: set[Commodity] = set() + + for ic, tech_name, oc, sector in all_connections: + key = (ic, tech_name, oc, sector) + color = default_color + if sector: + color = sector_colors.get(sector, default_color) + + attrs: dict[str, Any] = { + 'color': color, + 'value': 1, # Default thickness + } + + tech_data = network_data.tech_data.get(tech_name, {}) + if tech_data.get('neg_cost', False): + attrs['value'] = 3 + + if tech_name in driven_names: + attrs.update({'color': '#1f77b4', 'value': 3, 'dashes': True}) + driven_commodities.update({ic, oc}) + elif tech_name in other_orphan_names: + attrs.update({'color': '#ff7f0e', 'value': 4, 'dashes': True}) + other_orphan_commodities.update({ic, oc}) + elif tech_name in demand_orphan_names: + attrs.update({'color': '#d62728', 'value': 6, 'dashes': True}) + demand_orphan_commodities.update({ic, oc}) + + edge_attributes_map[key] = attrs + + # 4. Create the NetworkX graph using the utility function + dg = make_nx_graph( + all_connections, + edge_attributes_map, + layer_map, + node_positions, + commodity_to_primary_sector, + driven_names, + other_orphan_names, + demand_orphan_names, + driven_commodities=driven_commodities, + other_orphan_commodities=other_orphan_commodities, + demand_orphan_commodities=demand_orphan_commodities, + ) + + return dg, sector_colors + + +def visualize_graph( + region: Region, + period: Period, + network_data: NetworkModelData, + demand_orphans: Iterable[EdgeTuple], + other_orphans: Iterable[EdgeTuple], + driven_techs: Iterable[EdgeTuple], + config: TemoaConfig, +) -> None: + """ + Generates and saves an interactive HTML file with two graph views if + config.plot_commodity_network is True. + """ + # 1. Check the configuration flag first. If false, do nothing. + if not config.plot_commodity_network: + logger.info("Skipping network graph generation because 'plot_commodity_network' is false.") + return + + # --- All generation logic now only runs if the flag is True --- + + # 2. Generate the primary (commodity-centric) graph and its color legend + commodity_graph, sector_colors = generate_commodity_graph( + region, period, network_data, demand_orphans, other_orphans, driven_techs + ) + + # 3. Collect all technology tuples needed for the secondary graph + all_techs_for_period = ( + set(network_data.available_techs.get((region, period), set())) + | set(demand_orphans) + | set(other_orphans) + | set(driven_techs) + ) + + # 4. Generate the secondary (technology-centric) graph + tech_graph = generate_technology_graph( + all_techs_for_period, + network_data.source_commodities.get((region, period), set()), + network_data.demand_commodities.get((region, period), set()), + sector_colors, + ) + + # 5. Define the style legend data + style_legend_map = [ + # Styles for the Commodity View + {'label': 'Connected to Demand Orphan', 'borderColor': '#d62728', 'borderWidth': 4}, + {'label': 'Connected to Other Orphan', 'borderColor': '#ff7f0e', 'borderWidth': 4}, + {'label': 'Connected to Driven Tech', 'borderColor': '#1f77b4', 'borderWidth': 4}, + # Styles for the Technology View + {'label': 'Source Technology', 'borderColor': '#2ca02c', 'borderWidth': 4}, + {'label': 'Demand Technology', 'borderColor': '#e377c2', 'borderWidth': 4}, + ] + + # 6. Create the interactive HTML visualization + output_file = config.output_path / f'Network_Graph_{region}_{period}.html' + unique_sectors = sorted(sector_colors) + + graph_path = nx_to_vis( + nx_graph=commodity_graph, + secondary_graph=tech_graph, + output_filename=output_file, + html_title=f'Network Graphs - {region} {period}', + sectors=unique_sectors, + color_legend_map=cast('dict[str, str]', sector_colors), + style_legend_map=style_legend_map, + show_browser=False, + ) + + if graph_path: + logger.info('Generated network graphs at: %s', graph_path) + else: + logger.error('Failed to generate network graphs') + + # 8. Perform cycle detection on the commodity graph + try: + count = 0 + limit = config.cycle_count_limit + length_limit = config.cycle_length_limit + + for cycle in nx.simple_cycles(G=commodity_graph): + if limit != -1 and count >= limit: + if limit > 0: + logger.warning('Cycle detection reached limit of %d cycles. Stopping.', limit) + else: + logger.error('Cycles detected but cycle_count_limit is 0. Stopping.') + break + + if len(cycle) < length_limit: + continue + + cycle_str = ' -> '.join(cycle) + f' -> {cycle[0]}' + logger.info('Cycle detected: %s', cycle_str) + count += 1 + except nx.NetworkXError as e: + logger.warning('NetworkXError during cycle detection: %s', e, exc_info=True) diff --git a/temoa/model_checking/commodity_network.py b/temoa/model_checking/commodity_network.py new file mode 100644 index 000000000..10a4ca9b6 --- /dev/null +++ b/temoa/model_checking/commodity_network.py @@ -0,0 +1,340 @@ +""" +This module provides the CommodityNetwork class, a tool for analyzing and +validating the structure of an energy system model for a specific region +and time period within the Temoa framework. + +The primary purpose is to perform a "source trace" analysis to ensure that all +technologies supplying a demand are fully connected back to a designated source +commodity (e.g., crude oil, natural gas, solar radiation). This process +identifies two types of modeling errors: +1. Demand-Side Orphans: Technologies that are part of a chain serving a demand + but are not connected to a valid source. +2. Other Orphans: Technologies that are not connected to any demand chain at all. + +The analysis is performed using a depth first search on a directed graph where +commodities are nodes and technologies are edges. +""" + +from collections import defaultdict +from logging import getLogger + +from temoa.model_checking.network_model_data import EdgeTuple, NetworkModelData +from temoa.types.core_types import Commodity, Period, Region, Technology + +logger = getLogger(__name__) + +# Represents a technology link: (input_commodity, tech_name) +type TechLink = tuple[Commodity, Technology] +# Represents a pair of linked technologies: (driver_tech, driven_tech) +type LinkedTechPair = tuple[Technology, Technology] +# Represents a full connection: (input_commodity, tech_name, output_commodity) +type TechConnection = tuple[Commodity, Technology, Commodity] +# Adjacency dict mapping: {output_comm: set of (input_comm, tech_name)} +type ForwardConnections = dict[Commodity, set[TechLink]] +# Adjacency dict mapping: {input_comm: set of (output_comm, tech_name)} +type ReverseConnections = dict[Commodity, set[TechLink]] + + +class CommodityNetwork: + """ + Holds and analyzes the commodity-technology network for a specific region and period. + + This class is blind to vintage; it determines network validity based on connections + independent of vintage. It is the responsibility of the manager to apply these + findings across available vintages. + """ + + def __init__(self, region: Region, period: Period, model_data: NetworkModelData) -> None: + """ + Initializes and builds the network for a given region and period. + + :param region: The region to analyze. + :param period: The period to analyze. + :param model_data: A NetworkModelData object containing all model connections. + """ + self.region = region + self.period = period + self.model_data = model_data + + if not self.model_data.source_commodities: + msg = ( + "No source commodities found. Have they been marked with 's' " + 'in the commodities table?' + ) + logger.error(msg) + raise ValueError(msg) + + if not self.model_data.demand_commodities[self.region, self.period]: + msg = ( + f'No demand commodities discovered in region {self.region} ' + f'period {self.period}. Ignore this if this was intentional.' + ) + logger.info(msg) + + # Final results of the analysis + self.good_connections: set[TechConnection] = set() + self.demand_orphans: set[TechConnection] = set() + self.other_orphans: set[TechConnection] = set() + + # Internal state for the analysis + self.tech_inputs: dict[Technology, set[Commodity]] = defaultdict(set) + self.tech_outputs: dict[Technology, set[Commodity]] = defaultdict(set) + self.connections: ForwardConnections = defaultdict(set) + self.viable_linked_tech: set[LinkedTechPair] = set() + + self._load_connections() + self.prescreen_linked_tech() + + def _load_connections(self) -> None: + """Populate internal connection structures from model_data.""" + self.tech_inputs.clear() + self.tech_outputs.clear() + self.connections.clear() + + edge_tuples = self.model_data.available_techs[self.region, self.period] + for edge_tuple in edge_tuples: + self.connections[edge_tuple.output_comm].add((edge_tuple.input_comm, edge_tuple.tech)) + self.tech_inputs[edge_tuple.tech].add(edge_tuple.input_comm) + self.tech_outputs[edge_tuple.tech].add(edge_tuple.output_comm) + + def reload(self, connections: set[EdgeTuple]) -> None: + """ + Reload the network model with a new set of connections. + + :param connections: A set of connections (Tech objects) to evaluate. + """ + # Overwrite the model data's available techs for this region/period + self.model_data.available_techs[self.region, self.period] = connections + # Clear all existing state and reload + self.good_connections.clear() + self.demand_orphans.clear() + self.other_orphans.clear() + self.viable_linked_tech.clear() + self._load_connections() + + def remove_tech_by_name(self, tech_name: Technology) -> None: + """Remove all connections associated with a given technology name.""" + self.tech_inputs.pop(tech_name, None) + self.tech_outputs.pop(tech_name, None) + + removed_connections: set[TechConnection] = set() + # Use list() to avoid issues with modifying the dict during iteration + for oc, links in list(self.connections.items()): + removals = {link for link in links if link[1] == tech_name} + if removals: + links -= removals + for ic, name in removals: + removed_connections.add((ic, name, oc)) + if not links: + self.connections.pop(oc, None) + + self.other_orphans.update(removed_connections) + for r in removed_connections: + logger.debug('Removed %s via by-name removal', r) + + def prescreen_linked_tech(self) -> None: + """ + Screen linked techs to ensure both members of a pair are available. + If not, the stray member is removed from the network analysis. + """ + for r, driver, _, driven in self.model_data.available_linked_techs: + if r == self.region: + driver_exists = driver in self.tech_outputs + driven_exists = driven in self.tech_outputs + + if driver_exists and driven_exists: + logger.debug( + 'Both %s and %s are available in region %s, period %s to establish link', + driver, + driven, + self.region, + self.period, + ) + self.viable_linked_tech.add((driver, driven)) + elif driver_exists and not driven_exists: + logger.info( + 'No driven linked tech for driver %s in region %s, period %s. Driver ' + 'REMOVED.', + driver, + self.region, + self.period, + ) + self.remove_tech_by_name(driver) + elif not driver_exists and driven_exists: + logger.warning( + 'Driven linked tech %s has no active driver in region %s, period %s. ' + 'Driven tech REMOVED.', + driven, + self.region, + self.period, + ) + self.remove_tech_by_name(driven) + + def analyze_network(self) -> None: + """ + Analyzes the network to find valid and orphaned connections. + + This process may be iterative if linked technologies are found to be invalid, + as removing one requires removing its partner and re-running the analysis. + """ + while True: + demand_nodes = ( + self.model_data.demand_commodities[self.region, self.period] + | self.model_data.waste_commodities[self.region, self.period] + ) + source_nodes = self.model_data.source_commodities[self.region, self.period] + + ( + discovered_sources, + reverse_connex, + ) = self._trace_backward_from_demands( + start_nodes=demand_nodes, + end_nodes=source_nodes, + connections=self.connections, + ) + + self.good_connections = self._trace_forward_from_sources( + start_nodes=discovered_sources, connections=reverse_connex + ) + + observed_tech_names = {tech for _, tech, _ in self.good_connections} + sour_links = { + link + for link in self.viable_linked_tech + if not (link[0] in observed_tech_names and link[1] in observed_tech_names) + } + + if not sour_links: + break # Analysis is stable, exit loop. + + for driver, driven in sour_links: + logger.warning( + 'Both members of link (%s, %s) are not valid. Removing both in region %s, ' + 'period %s.', + driver, + driven, + self.region, + self.period, + ) + self.remove_tech_by_name(driver) + self.remove_tech_by_name(driven) + self.viable_linked_tech -= sour_links + + all_connections = { + (ic, name, oc) for oc, links in self.connections.items() for ic, name in links + } + demand_reachable_connections = { + (ic, name, oc) for ic, links in reverse_connex.items() for oc, name in links + } + + self.demand_orphans = demand_reachable_connections - self.good_connections + self.other_orphans |= all_connections - demand_reachable_connections + + self._log_orphans() + + def _trace_backward_from_demands( + self, + start_nodes: set[Commodity], + end_nodes: set[Commodity], + connections: ForwardConnections, + ) -> tuple[set[Commodity], ReverseConnections]: + """Iterative DFS tracing backward from demand nodes.""" + stack = list(start_nodes) + visited_nodes = set() + discovered_sources = set() + reachable_subgraph: ReverseConnections = defaultdict(set) + + while stack: + current_oc = stack.pop() + if current_oc in visited_nodes: + continue + visited_nodes.add(current_oc) + + for ic, tech in connections.get(current_oc, set()): + reachable_subgraph[ic].add((current_oc, tech)) + if ic in end_nodes: + discovered_sources.add(ic) + if ic not in visited_nodes: + stack.append(ic) + + return discovered_sources, reachable_subgraph + + def _trace_forward_from_sources( + self, start_nodes: set[Commodity], connections: ReverseConnections + ) -> set[TechConnection]: + """Iterative DFS tracing forward from discovered source nodes.""" + stack = list(start_nodes) + visited_nodes = set(start_nodes) + good_connections: set[TechConnection] = set() + + while stack: + current_ic = stack.pop() + + for oc, tech in connections.get(current_ic, set()): + good_connections.add((current_ic, tech, oc)) + if oc not in visited_nodes: + visited_nodes.add(oc) + stack.append(oc) + + return good_connections + + def _log_orphans(self) -> None: + """Helper to log discovered orphaned processes.""" + if self.other_orphans: + logger.info( + "Source tracing revealed %s 'other' (non-demand) orphaned processes in region %s, " + 'period %s.', + len(self.other_orphans), + self.region, + self.period, + ) + for orphan in sorted(self.other_orphans, key=lambda x: x[1]): + logger.info('Discovered orphaned process: %s', orphan) + + if self.demand_orphans: + logger.info( + 'Source tracing revealed %s demand-side orphaned processes in region %s, ' + 'period %s.', + len(self.demand_orphans), + self.region, + self.period, + ) + for orphan in sorted(self.demand_orphans, key=lambda x: x[1]): + logger.info('Discovered orphaned process: %s', orphan) + + def get_valid_tech(self) -> set[EdgeTuple]: + """Returns the set of Tech objects that are part of a valid connection.""" + return { + edge_tuple + for edge_tuple in self.model_data.available_techs[self.region, self.period] + if (edge_tuple.input_comm, edge_tuple.tech, edge_tuple.output_comm) + in self.good_connections + } + + def get_demand_side_orphans(self) -> set[EdgeTuple]: + """Returns Tech objects for demand-side orphans.""" + return { + edge_tuple + for edge_tuple in self.model_data.available_techs[self.region, self.period] + if (edge_tuple.input_comm, edge_tuple.tech, edge_tuple.output_comm) + in self.demand_orphans + } + + def get_other_orphans(self) -> set[EdgeTuple]: + """Returns Tech objects for non-demand-side orphans.""" + return { + edge_tuple + for edge_tuple in self.model_data.available_techs[self.region, self.period] + if (edge_tuple.input_comm, edge_tuple.tech, edge_tuple.output_comm) + in self.other_orphans + } + + def unsupported_demands(self) -> set[Commodity]: + """ + Finds demand commodities that are not supplied by any valid connection. + + :return: A set of unsupported demand commodity names. + """ + supported_demands = {oc for (_, _, oc) in self.good_connections} + all_demands = self.model_data.demand_commodities[self.region, self.period] + return all_demands - supported_demands diff --git a/temoa/model_checking/commodity_network_manager.py b/temoa/model_checking/commodity_network_manager.py new file mode 100644 index 000000000..b3eb91dc6 --- /dev/null +++ b/temoa/model_checking/commodity_network_manager.py @@ -0,0 +1,254 @@ +""" +This module provides the CommodityNetworkManager, which orchestrates the network +analysis across all regions and time periods of a Temoa model. + +Its primary responsibility is to identify and filter out "orphaned" technologies— +those that are not part of a valid, complete supply chain from a source commodity +to a demand commodity. It iteratively analyzes the model until a stable, fully- +connected network is achieved, and then generates data filters that can be used +by other parts of the Temoa framework to ensure only valid data is used. +""" + +from collections import defaultdict +from collections.abc import Iterable +from logging import getLogger +from typing import Any, cast + +from temoa.core.config import TemoaConfig +from temoa.model_checking.commodity_graph import visualize_graph +from temoa.model_checking.commodity_network import CommodityNetwork +from temoa.model_checking.element_checker import ViableSet +from temoa.model_checking.network_model_data import EdgeTuple, NetworkModelData +from temoa.types.core_types import Period, Region + +logger = getLogger(__name__) + +# Type alias for clarity in dictionary keys +type RegionPeriodKey = tuple[Region, Period] + + +class CommodityNetworkManager: + """ + Manages the iterative network analysis for all regions across a set of periods. + """ + + def __init__(self, periods: Iterable[str | int], network_data: NetworkModelData) -> None: + self.analyzed: bool = False + self.periods: list[Period] = sorted([Period(int(p)) for p in periods]) + self.orig_data: NetworkModelData = network_data + self.filtered_data: NetworkModelData | None = None + self.regions: set[Region] | None = None + + # Store a deep copy of the original connections for graphing purposes + self.orig_tech = {k: v.copy() for k, v in network_data.available_techs.items()} + # Final collections of all orphans found, organized by (region, period) + self.demand_orphans: dict[RegionPeriodKey, set[EdgeTuple]] = defaultdict(set) + self.other_orphans: dict[RegionPeriodKey, set[EdgeTuple]] = defaultdict(set) + + def _analyze_region(self, region: Region, data: NetworkModelData) -> None: + """ + Iteratively analyzes a region's network until no new orphans are found. + + This process is iterative because removing an orphan technology in one + period (e.g., its last available vintage) can have cascading effects, + potentially orphaning dependent technologies in earlier periods. The loop + continues until a pass over all periods finds no new orphans, signifying + a stable, valid network. + """ + + for period in self.periods: + cn = CommodityNetwork(region=region, period=period, model_data=data) + cn.analyze_network() + + # Log any demands that are not fully supported + for commodity in cn.unsupported_demands(): + logger.warning( + 'Demand %s is not supported back to a source in region %s, period %d', + commodity, + region, + period, + ) + + # Add to the main collections, ensuring no duplicates + self.demand_orphans[region, period].update(cn.get_demand_side_orphans()) + self.other_orphans[region, period].update(cn.get_other_orphans()) + + def analyze_network(self) -> bool: + """ + Analyze all regions in the model. + + Note: By design, this excludes inter-regional exchange technologies, + which would require a more complex, multi-region analysis. + + :return: True if the model is "clean" (no orphans found), False otherwise. + """ + self.filtered_data = self.orig_data.clone() + # Identify regions to analyze (excluding exchange pseudo-regions) + self.regions = set({r for (r, p) in self.orig_data.available_techs if '-' not in r}) + + for region in self.regions: + logger.info('Starting network analysis for region %s', region) + self._analyze_region(region, data=self.filtered_data) + + self.analyzed = True + orphans_found = any(self.demand_orphans.values()) or any(self.other_orphans.values()) + return not orphans_found + + def build_filters(self, tech_groups: defaultdict[str, set[str]]) -> dict[str, ViableSet]: + """ + Constructs ViableSet filters based on the valid technologies remaining + after the network analysis is complete. + """ + if not self.analyzed or self.filtered_data is None: + raise RuntimeError('analyze_network() must be called before build_filters().') + + # Use defaultdicts to easily collect unique elements + valid_elements: defaultdict[str, set[Any]] = defaultdict(set) + + for (_r, p), edge_tuples in self.filtered_data.available_techs.items(): + if not edge_tuples: + continue + for edge_tuple in edge_tuples: + valid_elements['ritvo'].add( + ( + edge_tuple.region, + edge_tuple.input_comm, + edge_tuple.tech, + edge_tuple.vintage, + edge_tuple.output_comm, + ) + ) + valid_elements['rtvo'].add( + ( + edge_tuple.region, + edge_tuple.tech, + edge_tuple.vintage, + edge_tuple.output_comm, + ) + ) + valid_elements['rpt'].add((edge_tuple.region, p, edge_tuple.tech)) + valid_elements['rtv'].add((edge_tuple.region, edge_tuple.tech, edge_tuple.vintage)) + valid_elements['rt'].add((edge_tuple.region, edge_tuple.tech)) + valid_elements['rpit'].add( + (edge_tuple.region, p, edge_tuple.input_comm, edge_tuple.tech) + ) + valid_elements['rpto'].add( + (edge_tuple.region, p, edge_tuple.tech, edge_tuple.output_comm) + ) + valid_elements['rtv_eol'].add( + (edge_tuple.region, edge_tuple.input_comm, edge_tuple.vintage) + ) + valid_elements['t'].add(edge_tuple.tech) + valid_elements['v'].add(edge_tuple.vintage) + valid_elements['ic'].add(edge_tuple.input_comm) + valid_elements['oc'].add(edge_tuple.output_comm) + + if cast('int', p) == cast('int', edge_tuple.vintage): + valid_elements['rtv_new'].add( + (edge_tuple.region, edge_tuple.tech, edge_tuple.vintage) + ) + + for tech_group in tech_groups.get(edge_tuple.tech, {}): + valid_elements['rtvo'].add( + ( + edge_tuple.region, + tech_group, + edge_tuple.vintage, + edge_tuple.output_comm, + ) + ) + valid_elements['rpt'].add((edge_tuple.region, p, tech_group)) + valid_elements['rtv'].add((edge_tuple.region, tech_group, edge_tuple.vintage)) + valid_elements['rt'].add((edge_tuple.region, tech_group)) + valid_elements['rpit'].add( + (edge_tuple.region, p, edge_tuple.input_comm, tech_group) + ) + valid_elements['rpto'].add( + (edge_tuple.region, p, tech_group, edge_tuple.output_comm) + ) + + if cast('int', p) == cast('int', edge_tuple.vintage): + valid_elements['rtv_new'].add( + (edge_tuple.region, tech_group, edge_tuple.vintage) + ) + + # Good processes that we dont want in the network diagram + for r, p, t, v in self.orig_data.silent_rptv: + valid_elements['rpt'].add((r, p, t)) + valid_elements['rtv'].add((r, t, v)) + valid_elements['rt'].add((r, t)) + valid_elements['t'].add(t) + valid_elements['v'].add(v) + + if cast('int', p) == cast('int', v): + valid_elements['rtv_new'].add((r, t, v)) + + return { + 'ritvo': ViableSet( + elements=valid_elements['ritvo'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rtvo': ViableSet( + elements=valid_elements['rtvo'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rtv_new': ViableSet( + elements=valid_elements['rtv_new'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rtv': ViableSet( + elements=valid_elements['rtv'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rpt': ViableSet( + elements=valid_elements['rpt'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rt': ViableSet( + elements=valid_elements['rt'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rpit': ViableSet( + elements=valid_elements['rpit'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rpto': ViableSet( + elements=valid_elements['rpto'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 'rtv_eol': ViableSet( + elements=valid_elements['rtv_eol'], + exception_loc=0, + exception_vals=ViableSet.REGION_REGEXES, + ), + 't': ViableSet(elements=valid_elements['t']), + 'v': ViableSet(elements=valid_elements['v']), + 'ic': ViableSet(elements=valid_elements['ic']), + 'oc': ViableSet(elements=valid_elements['oc']), + } + + def analyze_graphs(self, config: TemoaConfig) -> None: + """ + Generates and saves visual graphs of the network for each region and period. + """ + if not self.analyzed or self.regions is None: + raise RuntimeError('analyze_network() must be called before analyze_graphs().') + for region in self.regions: + for period in self.periods: + visualize_graph( + region, + period, + network_data=self.orig_data, + demand_orphans=self.demand_orphans[region, period], + other_orphans=self.other_orphans[region, period], + driven_techs=self.orig_data.get_driven_techs(region, period), + config=config, + ) diff --git a/temoa/model_checking/element_checker.py b/temoa/model_checking/element_checker.py new file mode 100644 index 000000000..6551ad838 --- /dev/null +++ b/temoa/model_checking/element_checker.py @@ -0,0 +1,249 @@ +""" +Class to hold members of "validation sets" used by loader to validate elements as they are +read in to the DataPortal or other structure. Motivation is to contain the values AND +any extra validation information in one instance. + +""" + +import re +from collections.abc import Iterable, Sequence +from operator import itemgetter +from typing import ClassVar, Self + +type ValidationPrimitive = str | int | float | None +type ValidationElement = tuple[ValidationPrimitive, ...] +type InputElement = ValidationPrimitive | ValidationElement + + +class ViableSet: + """ + Manages a set of valid elements and rules for "exception-based" matches. + + This class is designed for filtering data where an element is considered + valid if it either: + 1. Exactly matches a pre-defined tuple of values. + 2. Matches a pre-defined tuple in all positions *except* for one, where + the value at that "exception position" matches a given regex pattern. + + Example: + - Core elements: {('a', 1), ('a', 2)} + - Exception: location 0, regexes {r'dog', r'cat'} + + This will match: + - ('a', 1), ('a', 2) (exact matches) + - ('dog', 1), ('cat', 1) (exception matches, since the non-exception part, (1,), is valid) + - ('dog', 2), ('cat', 2) (exception matches, since the non-exception part, (2,), is valid) + + This will NOT match: + - ('a', 4) (non-exception part (4,) is not in the valid set) + - ('cat', 3) (non-exception part (3,) is not in the valid set) + """ + + # Stored for reference; these are examples of common automatic approvals. + REGION_REGEXES: ClassVar[list[str]] = [ + r'\+', # any grouping with a plus sign + r'^global\Z', # the exact word 'global' with no leader/trailer + ] + + def __init__( + self, + elements: Iterable[InputElement], + exception_loc: int | None = None, + exception_vals: Iterable[str] | None = None, + ) -> None: + """ + Constructs a ViableSet instance. + + Args: + elements: The core elements to match against. + exception_loc: The index within an element to apply exception regexes. + exception_vals: An iterable of regex patterns to match against. + """ + self._elements: set[ValidationElement] = {self._tupleize(el) for el in elements} + self.dim: int = self._calculate_dim() + self._exception_loc: int | None = None + self._exceptions: frozenset[str] = frozenset() + self.non_excepted_items: set[ValidationElement] | None = set() + # Cache for compiled regex patterns + self._compiled_val_exceptions: list[re.Pattern[str]] = [] + + if exception_loc is not None and exception_vals is not None: + self.set_val_exceptions(exception_loc, exception_vals) + + @staticmethod + def _tupleize(element: InputElement) -> ValidationElement: + """Ensures an element is a tuple.""" + return element if isinstance(element, tuple) else (element,) + + def _calculate_dim(self) -> int: + """Calculates the dimension of the elements.""" + if not self._elements: + return 0 + # Get an arbitrary element to determine the dimension + return len(next(iter(self._elements))) + + def _update_internals(self) -> None: + """ + Updates internal lookup sets based on current elements and exceptions. + This pre-calculates values for efficient filtering. + """ + self.dim = self._calculate_dim() + if self._exception_loc is None or not self._exceptions: + self.non_excepted_items = set() + return + + # Locations of items *not* at the exception index + non_excepted_locs = [i for i in range(self.dim) if i != self._exception_loc] + + if not non_excepted_locs: + # This occurs if dim is 1 and exception_loc is 0. + # There are no other items to validate against. + self.non_excepted_items = None + else: + getter = itemgetter(*non_excepted_locs) + self.non_excepted_items = {self._tupleize(getter(t)) for t in self._elements} + + @property + def exception_loc(self) -> int | None: + return self._exception_loc + + @property + def val_exceptions(self) -> frozenset[str]: + return self._exceptions + + def set_val_exceptions(self, exception_loc: int, exception_vals: Iterable[str]) -> Self: + """ + Sets or updates the validation exceptions. + + Args: + exception_loc: The index for the exception. + exception_vals: An iterable of regex patterns. + + Returns: + The instance (self) to allow for method chaining. + """ + if not (isinstance(exception_loc, int) and exception_loc >= 0): + raise ValueError('exception_loc must be a non-negative integer') + + self._exception_loc = exception_loc + # Use a frozenset for immutability and performance + self._exceptions = frozenset(exception_vals) + self._compiled_val_exceptions = [re.compile(p) for p in self._exceptions] + self._update_internals() + return self + + @property + def member_tuples(self) -> set[ValidationElement]: + """The elements of the membership set as tuples.""" + return self._elements + + @member_tuples.setter + def member_tuples(self, elements: Iterable[InputElement]) -> None: + """Sets the core elements, ensuring they are stored as tuples.""" + self._elements = {self._tupleize(el) for el in elements} + self._update_internals() + + @property + def members(self) -> set[ValidationElement] | set[ValidationPrimitive]: + """ + The members of the validation set. + Unwraps single-element tuples for convenience. + """ + if self.dim > 1: + # This branch returns set[ValidationElement] + return self.member_tuples + + # This branch returns set[ValidationPrimitive] + return {t[0] for t in self.member_tuples} + + +# dev note: The reason for this filtering construct is to allow passage of items that either +# match the basic 'valid' elements exactly OR match the exception regex in one +# position and match all other elements. The use case is for regions, where we +# want to match explicit regions exactly, but also match 'global' and +# region groups where we have a '+' sign in the name without having to +# create all the possible permutations. An alternate (rejected) approach +# would be to re-create the region groups for something like 'global' to the +# actually legal combinations on-the-fly from data, which would be more complex +# and mask the intent of the original data. +def filter_elements( + values: Sequence[tuple[ValidationPrimitive, ...]], + validation: ViableSet, + value_locations: tuple[int, ...], +) -> list[tuple[ValidationPrimitive, ...]]: + """ + Filters a sequence of elements based on the rules in a ViableSet. + + Args: + values: The sequence of data tuples to filter. + validation: The ViableSet instance containing the validation rules. + value_locations: A tuple of indices that maps a data tuple from `values` + to the format expected by `validation`. + + Returns: + A list of the items from `values` that passed validation. + + Example: + - A data item is `('USA', 'other', 'cars', 2020, 1.5)` + - `validation` expects `(region, tech, vintage)` + - `value_locations` would be `(0, 2, 3)` to extract ('USA', 'cars', 2020) + """ + if not isinstance(validation, ViableSet): + raise TypeError("'validation' must be an instance of ViableSet") + + if validation.dim > 0 and validation.dim != len(value_locations): + raise ValueError( + 'The number of value_locations must match the dimensionality of the validation set.' + ) + + # Use the cached compiled regexes. Fall back to compiling for backward compatibility. + exception_regexes = getattr( + validation, '_compiled_val_exceptions', [re.compile(p) for p in validation.val_exceptions] + ) + + # Pre-build itemgetters for performance + full_element_getter = itemgetter(*value_locations) + + # --- Simplified path for no exceptions --- + if not exception_regexes: + return [ + item + for item in values + if validation._tupleize(full_element_getter(item)) in validation.member_tuples + ] + + assert validation.exception_loc is not None # for mypy type checking + + # --- Main logic for filtering with exceptions --- + non_exempt_item_locs: list[int] = [ + loc for i, loc in enumerate(value_locations) if i != validation.exception_loc + ] + non_exempt_getter = itemgetter(*non_exempt_item_locs) if non_exempt_item_locs else None + + filtered_list = [] + for item in values: + element_to_check = validation._tupleize(full_element_getter(item)) + + # 1. Check for a direct match + if element_to_check in validation.member_tuples: + filtered_list.append(item) + continue + + # 2. Check for an exception-based match + if validation.non_excepted_items is None: # dim=1 case + pass + elif non_exempt_getter: + non_exempt_part = validation._tupleize(non_exempt_getter(item)) + if non_exempt_part not in validation.non_excepted_items: + continue + + # Check if the value at the exception location matches any regex + exception_loc_in_item = value_locations[validation.exception_loc] + value_at_exception_loc = str(item[exception_loc_in_item]) + + for pattern in exception_regexes: + if pattern.search(value_at_exception_loc): + filtered_list.append(item) + break + + return filtered_list diff --git a/temoa/model_checking/network_model_data.py b/temoa/model_checking/network_model_data.py new file mode 100644 index 000000000..1ff4e4488 --- /dev/null +++ b/temoa/model_checking/network_model_data.py @@ -0,0 +1,560 @@ +""" +The purpose of this module is to build an Object to hold all of the network data for the entire +model in a usable format for the commodity_network_manager to use in building the individual +network. +""" + +from __future__ import annotations + +import copy +import logging +import sqlite3 +from collections import defaultdict +from dataclasses import dataclass, field +from itertools import chain +from typing import TYPE_CHECKING, NamedTuple, Self, TypedDict, cast, overload + +import deprecated +from pyomo.core.base import ConcreteModel + +from temoa.core.model import TemoaModel +from temoa.types.core_types import ParameterValue + +if TYPE_CHECKING: + from collections.abc import Callable + + from temoa.extensions.myopic.myopic_index import MyopicIndex + from temoa.types import Commodity, Period, Region, Sector, Technology, Vintage + + +# --- Type Definitions --- +class EdgeTuple(NamedTuple): + region: Region + input_comm: Commodity + tech: Technology + vintage: Vintage + output_comm: Commodity + lifetime: int | None = None + sector: Sector | None = None + + +class LinkedTechTuple(NamedTuple): + region: Region + driver: Technology + emission: Commodity + driven: Technology + + +type TechAttributeValue = ParameterValue +type DbConnection = sqlite3.Connection +type ModelBlock = TemoaModel | ConcreteModel + + +class BasicData(TypedDict): + """Defines the shape of the data returned by _fetch_basic_data.""" + + tech_uncap: set[Technology] + tech_retire: set[Technology] + tech_survival_curve: set[tuple[Region, Technology, Vintage]] + periods: list[Period] + period_length: dict[Period, int] + physical_commodities: set[Commodity] + waste_commodities_all: set[Commodity] + source_commodities_all: set[Commodity] + demand_commodities: defaultdict[tuple[Region, Period], set[Commodity]] + + +class LookupData(TypedDict): + """Defines the shape of the data returned by _fetch_lookup_data.""" + + eol: defaultdict[tuple[Region, Technology, Vintage, int], list[Commodity]] + eeol: set[tuple[Region, Technology, Vintage, int]] + exs_cap: defaultdict[tuple[Region, Technology, Vintage], float] + construction: list[tuple[Region, Commodity, Technology, Vintage]] + linked: set[tuple[Region, Technology, Commodity, Technology]] + neg_cost_techs: set[Technology] + + +logger = logging.getLogger(__name__) + + +# --- Data Structure --- +@dataclass +class NetworkModelData: + """A simple encapsulation of data needed for the Commodity Network using a dataclass.""" + + demand_commodities: defaultdict[tuple[Region, Period], set[Commodity]] = field( + default_factory=lambda: defaultdict(set) + ) + waste_commodities: defaultdict[tuple[Region, Period], set[Commodity]] = field( + default_factory=lambda: defaultdict(set) + ) + capacity_commodities: set[Commodity] = field(default_factory=set) + exchange_commodities: set[Commodity] = field(default_factory=set) + source_commodities: defaultdict[tuple[Region, Period], set[Commodity]] = field( + default_factory=lambda: defaultdict(set) + ) + physical_commodities: set[Commodity] = field(default_factory=set) + available_techs: defaultdict[tuple[Region, Period], set[EdgeTuple]] = field( + default_factory=lambda: defaultdict(set) + ) + available_linked_techs: set[LinkedTechTuple] = field(default_factory=set) + tech_data: defaultdict[Technology, dict[str, TechAttributeValue]] = field( + default_factory=lambda: defaultdict(dict) + ) + silent_rptv: set[tuple[Region, Period, Technology, Vintage]] = field(default_factory=set) + + def __post_init__(self) -> None: + """Validate data consistency after initialization.""" + for (r, _), techs in self.available_techs.items(): + for tech in techs: + if tech.region != r: + raise ValueError( + f'Improperly constructed set of techs for region {r}, tech: {tech}' + ) + + def clone(self) -> Self: + """Create a deep copy of the current object.""" + return copy.deepcopy(self) + + def update_tech_data(self, tech: Technology, element: str, value: TechAttributeValue) -> None: + """Update a data element for a tech.""" + self.tech_data[tech][element] = value + + def get_driven_techs(self, region: Region, period: Period) -> set[EdgeTuple]: + """Identifies all linked techs by name from the linked tech names.""" + driven_tech_names = {lt.driven for lt in self.available_linked_techs} + return { + efficiencyTuple + for efficiencyTuple in self.available_techs.get((region, period), set()) + if efficiencyTuple.tech in driven_tech_names + } + + def __str__(self) -> str: + return ( + f'physical commodities: {len(self.physical_commodities)}, ' + f'demand commodities: {len({c for s in self.demand_commodities.values() for c in s})}, ' + f'source commodities: {len({c for s in self.source_commodities.values() for c in s})}, ' + f'available techs: {len(tuple(chain(*self.available_techs.values())))}, ' + f'linked techs: {len(self.available_linked_techs)}' + ) + + +# --- Builder Factory --- +@overload +def build(data: DbConnection, myopic_index: MyopicIndex | None = ...) -> NetworkModelData: ... +@overload +def build(data: ModelBlock, *args: object, **kwargs: object) -> NetworkModelData: ... +def build(data: ModelBlock | DbConnection, *args: object, **kwargs: object) -> NetworkModelData: + """Factory function to dispatch to the correct builder based on data type.""" + builder = _get_builder(data) + return builder(data, *args, **kwargs) + + +def _get_builder(data: ModelBlock | DbConnection) -> Callable[..., NetworkModelData]: + """Selects the appropriate builder function based on the input data type.""" + if isinstance(data, TemoaModel | ConcreteModel): + return _build_from_model + if isinstance(data, sqlite3.Connection): + return _build_from_db + raise NotImplementedError(f'Cannot build NetworkModelData from type: {type(data)}') + + +# --- Builder Implementations --- +@deprecated.deprecated('no longer supported... build from db connection instead') +def _build_from_model( + model: TemoaModel, myopic_index: MyopicIndex | None = None +) -> NetworkModelData: + """Build a NetworkModelData from a TemoaModel.""" + if myopic_index is not None: + raise NotImplementedError('Cannot build network data from model using a myopic_index') + + dem_com = defaultdict(set) + for r, p, d in model.demand.sparse_keys(): + dem_com[r, p].add(d) + + techs: defaultdict[tuple[Region, Period], set[EdgeTuple]] = defaultdict(set) + if model.active_flow_rpsditvo is not None: + for r, p, _s, _d, ic, tech, v, oc in model.active_flow_rpsditvo: + techs[r, p].add(EdgeTuple(r, ic, tech, v, oc)) + if model.active_flow_rpitvo is not None: + for r, p, ic, tech, v, oc in model.active_flow_rpitvo: + techs[r, p].add(EdgeTuple(r, ic, tech, v, oc)) + + linked_techs = { + LinkedTechTuple(r, driver, emission, driven) + for r, driver, emission, driven in model.linked_techs.sparse_keys() + } + + res = NetworkModelData( + physical_commodities=set(model.commodity_all), + demand_commodities=dem_com, + available_techs=techs, + available_linked_techs=linked_techs, + ) + logger.debug('built network data: %s', res) + return res + + +def _fetch_basic_data(cur: sqlite3.Cursor) -> BasicData: + """Fetches simple, required tables and parameters from the DB.""" + tech_uncap = { + t[0] for t in cur.execute('SELECT tech FROM technology WHERE unlim_cap==1').fetchall() + } + tech_retire = { + t[0] for t in cur.execute('SELECT tech FROM technology WHERE retire==1').fetchall() + } + tech_survival_curve = set( + cur.execute('SELECT DISTINCT region, tech, vintage FROM lifetime_survival_curve').fetchall() + ) + + periods_full = sorted( + p[0] for p in cur.execute('SELECT period FROM time_period WHERE flag = "f"').fetchall() + ) + periods = periods_full[:-1] + period_length = { + periods_full[i]: periods_full[i + 1] - periods_full[i] for i in range(len(periods_full) - 1) + } + + physical_commodities = { + c[0] + for c in cur.execute( + "SELECT name FROM main.commodity WHERE flag LIKE '%p%' OR flag = 's' OR flag LIKE '%a%'" + ).fetchall() + } + waste_commodities_all = { + c[0] for c in cur.execute("SELECT name FROM commodity WHERE flag LIKE '%w%'").fetchall() + } + source_commodities_all = { + c[0] for c in cur.execute("SELECT name FROM commodity WHERE flag = 's'").fetchall() + } + + demand_commodities: defaultdict[tuple[Region, Period], set[Commodity]] = defaultdict(set) + for r, p, d in cur.execute('SELECT region, period, commodity FROM main.demand').fetchall(): + demand_commodities[r, p].add(d) + + return BasicData( + tech_uncap=tech_uncap, + tech_retire=tech_retire, + tech_survival_curve=tech_survival_curve, + periods=periods, + period_length=period_length, + physical_commodities=physical_commodities, + waste_commodities_all=waste_commodities_all, + source_commodities_all=source_commodities_all, + demand_commodities=demand_commodities, + ) + + +def _fetch_all_tech_definitions( + cur: sqlite3.Cursor, myopic_index: MyopicIndex | None +) -> list[ + tuple[Region, Commodity, Technology, Vintage, Commodity, int] + | tuple[Region, Commodity, Technology, Vintage, Commodity, int, Sector] +]: + """Fetches the main block of technology efficiency and lifetime data.""" + default_lifetime = TemoaModel.default_lifetime_tech + table = 'myopic_efficiency' if myopic_index else 'efficiency' + + # Check if Technology table has sector column + try: + cur.execute('SELECT sector FROM technology LIMIT 1') + has_sector = True + except sqlite3.OperationalError: + has_sector = False + except (sqlite3.DatabaseError, AttributeError): + # For atypical cursors/mocks without schema + has_sector = False + + if has_sector: + query = f""" + SELECT + eff.region, eff.input_comm, eff.tech, eff.vintage, eff.output_comm, + COALESCE(lp.lifetime, lt.lifetime, ?) AS lifetime, + COALESCE(tech_dim.sector, 'Other') AS sector + FROM main.{table} AS eff + LEFT JOIN main.lifetime_process AS lp ON eff.tech = lp.tech AND eff.vintage = lp.vintage + AND eff.region = lp.region + LEFT JOIN main.lifetime_tech AS lt ON eff.tech = lt.tech AND eff.region = lt.region + LEFT JOIN main.technology AS tech_dim ON eff.tech = tech_dim.tech + JOIN main.time_period AS tp ON eff.vintage = tp.period + """ + else: + query = f""" + SELECT + eff.region, eff.input_comm, eff.tech, eff.vintage, eff.output_comm, + COALESCE(lp.lifetime, lt.lifetime, ?) AS lifetime + FROM main.{table} AS eff + LEFT JOIN main.lifetime_process AS lp ON eff.tech = lp.tech AND eff.vintage = lp.vintage + AND eff.region = lp.region + LEFT JOIN main.lifetime_tech AS lt ON eff.tech = lt.tech AND eff.region = lt.region + JOIN main.time_period AS tp ON eff.vintage = tp.period + """ + cursor = cur.execute(query, (default_lifetime,)) + return cursor.fetchall() + + +def _fetch_lookup_data(cur: sqlite3.Cursor) -> LookupData: + """Fetches data from all optional tables to avoid N+1 queries.""" + lookups = LookupData( + eol=defaultdict(list), + eeol=set(), + construction=[], + exs_cap=defaultdict(float), + linked=set(), + neg_cost_techs=set(), + ) + + try: + query = """ + SELECT + eol.region, eol.tech, eol.vintage, eol.output_comm, + COALESCE(lp.lifetime, lt.lifetime, ?) AS lifetime + FROM end_of_life_output AS eol + LEFT JOIN main.lifetime_process AS lp ON eol.tech = lp.tech + AND eol.vintage = lp.vintage + AND eol.region = lp.region + LEFT JOIN main.lifetime_tech AS lt ON eol.tech = lt.tech AND eol.region = lt.region + """ + for r, tech, v, oc, life in cur.execute( + query, (TemoaModel.default_lifetime_tech,) + ).fetchall(): + lookups['eol'][(r, tech, v, life)].append(oc) + except sqlite3.OperationalError: + logger.warning('Table end_of_life_output not found, skipping.') + + try: + query = """ + SELECT + eeol.region, eeol.tech, eeol.vintage, + COALESCE(lp.lifetime, lt.lifetime, ?) AS lifetime + FROM emission_end_of_life AS eeol + LEFT JOIN main.lifetime_process AS lp ON eeol.tech = lp.tech + AND eeol.vintage = lp.vintage + AND eeol.region = lp.region + LEFT JOIN main.lifetime_tech AS lt ON eeol.tech = lt.tech AND eeol.region = lt.region + """ + for r, tech, v, life in cur.execute(query, (TemoaModel.default_lifetime_tech,)).fetchall(): + lookups['eeol'].add((r, tech, v, life)) + except sqlite3.OperationalError: + logger.warning('Table emission_end_of_life not found, skipping.') + + try: + lookups['construction'] = cur.execute( + 'SELECT region, input_comm, tech, vintage FROM construction_input' + ).fetchall() + except sqlite3.OperationalError: + logger.warning('Table construction_input not found, skipping.') + + try: + for r, tech, v, capacity in cur.execute( + 'SELECT region, tech, vintage, capacity FROM existing_capacity' + ).fetchall(): + lookups['exs_cap'][(r, tech, v)] = capacity + except sqlite3.OperationalError: + logger.warning('Table existing_capacity not found, skipping.') + + try: + lookups['linked'] = set( + cur.execute( + 'SELECT primary_region, primary_tech, emis_comm, driven_tech FROM main.linked_tech' + ).fetchall() + ) + except sqlite3.OperationalError: + logger.warning('Table linked_tech not found, skipping.') + + try: + lookups['neg_cost_techs'] = { + tech + for (tech,) in cur.execute( + 'SELECT DISTINCT tech FROM cost_variable WHERE cost < 0' + ).fetchall() + } + except sqlite3.OperationalError: + logger.warning('Table cost_variable not found, skipping.') + + return lookups + + +def _build_from_db(con: DbConnection, myopic_index: MyopicIndex | None = None) -> NetworkModelData: + """Build NetworkModelData object from a sqlite database.""" + cur = con.cursor() + res = NetworkModelData() + + # --- 1. Fetch all data from DB --- + basic_data = _fetch_basic_data(cur) + raw_techs = _fetch_all_tech_definitions(cur, myopic_index) + lookup_data = _fetch_lookup_data(cur) + + res.physical_commodities = basic_data['physical_commodities'] + res.demand_commodities = basic_data['demand_commodities'] + + periods: list[Period] = basic_data['periods'] + if myopic_index: + periods = [ + p for p in periods if myopic_index.base_year <= p <= myopic_index.last_demand_year + ] + + living_techs: set[Technology] = set() + living_rtv: set[tuple[Region, Technology, Vintage]] = set() + + # --- 2. Process technologies --- + for tech_data in raw_techs: + logger.debug(tech_data) + if len(tech_data) == 7: # Has sector + r, ic, tech, v, oc, lifetime, sector = tech_data + else: # No sector + r, ic, tech, v, oc, lifetime = tech_data + sector = None + + for p in periods: + if v <= p < v + lifetime: + living_techs.add(tech) + living_rtv.add((r, tech, v)) + if '-' in r and r.count('-') == 1: # Inter-regional transfer + r1, r2 = (cast('Region', reg) for reg in r.split('-', 1)) + source_comm, dest_comm = ( + cast('Commodity', f'{ic} ({r1})'), + cast('Commodity', f'{oc} ({r2})'), + ) + res.available_techs[r2, p].add( + EdgeTuple( + region=r2, + input_comm=source_comm, + tech=tech, + vintage=v, + output_comm=oc, + lifetime=lifetime, + sector=sector, + ) + ) + res.available_techs[r1, p].add( + EdgeTuple( + region=r1, + input_comm=ic, + tech=tech, + vintage=v, + output_comm=dest_comm, + lifetime=lifetime, + sector=sector, + ) + ) + res.available_techs[r, p].add( + EdgeTuple( + region=r, + input_comm=ic, + tech=tech, + vintage=v, + output_comm=oc, + lifetime=lifetime, + sector=sector, + ) + ) + res.source_commodities[r2, p].add(source_comm) + res.demand_commodities[r1, p].add(dest_comm) + res.physical_commodities.update([source_comm, dest_comm]) + res.exchange_commodities.update([source_comm, dest_comm]) + else: # Standard technology + res.available_techs[r, p].add( + EdgeTuple( + region=r, + input_comm=ic, + tech=tech, + vintage=v, + output_comm=oc, + lifetime=lifetime, + sector=sector, + ) + ) + if ic in basic_data['source_commodities_all']: + res.source_commodities[r, p].add(ic) + if oc in basic_data['waste_commodities_all']: + res.waste_commodities[r, p].add(oc) + + for r, tech, v, lifetime in lookup_data['eol']: + if tech in basic_data['tech_uncap']: + # No capacity to retire + continue + for p in periods: + # retires bang on start of horizon or survives into planning periods + is_p0_eol = ( + (p == periods[0]) + and (v + lifetime == p) + and lookup_data['exs_cap'].get((r, tech, v), 0) > 0 + ) + is_living = (r, tech, v) in living_rtv + if not (is_p0_eol or is_living): + continue + + is_natural_eol = p <= v + lifetime < p + basic_data['period_length'][p] + is_retireable = ( + tech in basic_data['tech_retire'] + and v < p <= v + lifetime - basic_data['period_length'][p] + ) + has_survival = (r, tech, v) in basic_data[ + 'tech_survival_curve' + ] and v <= p <= v + lifetime + + if is_natural_eol or is_retireable or has_survival: + for eol_oc in lookup_data['eol'][(r, tech, v, lifetime)]: + res.available_techs[r, p].add( + EdgeTuple( + region=r, + input_comm=cast('Commodity', tech), + tech=tech, + vintage=v, + output_comm=eol_oc, + lifetime=lifetime, + sector=cast('Sector', 'other'), + ) + ) + res.source_commodities[r, p].add(cast('Commodity', tech)) + res.capacity_commodities.add(cast('Commodity', tech)) + if eol_oc in basic_data['waste_commodities_all']: + res.waste_commodities[r, p].add(eol_oc) + + for r, tech, v, lifetime in lookup_data['eeol']: + if tech in basic_data['tech_uncap']: + # No capacity to retire + continue + # retires bang on start of horizon or survives into planning periods + if v + lifetime == periods[0] and lookup_data['exs_cap'].get((r, tech, v), 0) > 0: + res.silent_rptv.add((r, periods[0], tech, v)) + + # --- 3. Process Construction --- + for r, ic, tech, v in lookup_data['construction']: + if tech in basic_data['tech_uncap']: + # No capacity to construct + continue + if cast('Period', v) not in periods: + continue + construction_lifetime = basic_data['period_length'].get( + cast('Period', v), cast('Period', 1) + ) + res.available_techs[r, cast('Period', v)].add( + EdgeTuple( + region=r, + input_comm=ic, + tech=tech, + vintage=v, + output_comm=cast( + 'Commodity', tech + ), # commodity is kind of input to the capacity of the technology/vice versa + lifetime=construction_lifetime, + sector=cast('Sector', 'other'), + ) + ) + res.demand_commodities[r, cast('Period', v)].add(cast('Commodity', tech)) + res.capacity_commodities.add(cast('Commodity', tech)) + + # --- 4. Process Linked Techs and Other Metadata --- + res.available_linked_techs = { + LinkedTechTuple(r, driver, emiss, driven) + for r, driver, emiss, driven in lookup_data['linked'] + if driver in living_techs and driven in living_techs + } + for tech in lookup_data['neg_cost_techs']: + res.update_tech_data(tech=tech, element='neg_cost', value=True) + + logger.debug('built network data: %s', res) + return res diff --git a/temoa/temoa_model/model_checking/pricing_check.py b/temoa/model_checking/pricing_check.py similarity index 77% rename from temoa/temoa_model/model_checking/pricing_check.py rename to temoa/model_checking/pricing_check.py index 872b71c3e..03e9a4935 100644 --- a/temoa/temoa_model/model_checking/pricing_check.py +++ b/temoa/model_checking/pricing_check.py @@ -4,7 +4,7 @@ Intent is to identify several possible errors. Note: These will need to be enhanced as this will likely generate many false positives initially. -1. Technologies that have an entry in Efficiency table that have no corresponding +1. Technologies that have an entry in efficiency table that have no corresponding (or inconsistent) fixed-cost / inv cost pairs. The primary motivation is that things without either an FC or IC have no downward pressure on activity in the model, which is regulated by cost @@ -14,56 +14,36 @@ 3. Technologies that any entry for a fixed or variable cost, but do not extend through all years in the tech_lifetime - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/5/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ +from __future__ import annotations + from collections import defaultdict from logging import getLogger from typing import TYPE_CHECKING +from pyomo.environ import value + if TYPE_CHECKING: - from temoa.temoa_model.temoa_model import TemoaModel + from temoa.core.model import TemoaModel logger = getLogger(__name__) -def price_checker(M: 'TemoaModel') -> bool: +def price_checker(model: TemoaModel) -> bool: """ Check the cost data for common errors :param M: :return: True if "clean" (no warnings), else False """ - logger.info('Started price checking model: %s', M.name) + logger.info('Started price checking model: %s', model.name) warnings = False # flag # some sets for x-checking registered_inv_costs = { - (region, tech, vintage) for (region, tech, vintage) in M.CostInvest.sparse_iterkeys() + (region, tech, vintage) for (region, tech, vintage) in model.cost_invest.sparse_keys() } efficiency_rtv = { - (region, tech, vintage) for (region, _, tech, vintage, __) in M.Efficiency.sparse_iterkeys() + (region, tech, vintage) for (region, _, tech, vintage, __) in model.efficiency.sparse_keys() } sorted_efficiency_rtv = sorted(efficiency_rtv, key=lambda rtv: (rtv[1], rtv[0], rtv[2])) @@ -77,11 +57,11 @@ def price_checker(M: 'TemoaModel') -> bool: # var costs for the period = vintage year base_year_variable_cost_rtv = set() - for r, p, t, v in M.CostFixed: + for r, p, t, v in model.cost_fixed.sparse_keys(): fixed_costs[r, t, v].add(p) if p == v: base_year_fixed_cost_rtv.add((r, t, v)) - for r, p, t, v in M.CostVariable: + for r, p, t, v in model.cost_variable.sparse_keys(): var_costs[r, t, v].add(p) if p == v: base_year_variable_cost_rtv.add((r, t, v)) @@ -100,16 +80,16 @@ def price_checker(M: 'TemoaModel') -> bool: # Check 1 looks for missing (1a) and inconsistent (1b) fixed cost - investment cost pairings logger.debug(' Starting price check #1a') # Check 1a: Look for "missing" FC/IC (no fixed or investment cost) based on what is in the - # Efficiency set + # efficiency set techs_without_fc_or_ic = set() # pull the details... for region, tech, vintage in sorted_efficiency_rtv: # disregard "unrestricted capacity" technologies that should NOT have a fixed/invest cost - if tech in M.tech_uncap: + if tech in model.tech_uncap: continue # disregard vintages that are not in the optimization period, their capacity decisions # are already made and the lack of fixed/invest cost is non-impactful - if vintage not in M.time_optimize: + if vintage not in model.time_optimize: continue has_fc = (region, tech, vintage) in fixed_costs @@ -117,8 +97,11 @@ def price_checker(M: 'TemoaModel') -> bool: if not any((has_fc, has_ic)): logger.warning( - f'Check 1a (detail): tech {tech} of vintage {vintage} in region {region} does not ' - f'have a Fixed Cost or Investment Cost component' + 'Check 1a (detail): tech with capacity %s of vintage %s in region %s does not ' + 'have a Fixed Cost or Investment Cost component', + tech, + vintage, + region, ) techs_without_fc_or_ic.add(tech) warnings = True @@ -133,23 +116,23 @@ def price_checker(M: 'TemoaModel') -> bool: if missing_fc: missing_techs = defaultdict(set) for r, t, v in missing_fc: - if v in M.time_optimize: + if v in model.time_optimize: missing_techs[t].add((r, v)) for t in missing_techs: # get set of fixed cost for all {rtv} if the tech matches - compaprable_fc = sorted(filter(lambda x: x[1] == t, base_year_fixed_cost_rtv)) + comparable_fc = sorted(filter(lambda x: x[1] == t, base_year_fixed_cost_rtv)) err = None - if compaprable_fc: + if comparable_fc: err = ( f'Check 1b:\ntech {t} has Fixed Cost in some vintage/regions for ' f'the base (vintage) year, but not all:\n' ) err += ' missing (r, v):\n' - for r, v in sorted(missing_techs.get(t)): + for r, v in sorted(missing_techs[t]): err += f' ({r}, {v})\n' err += ' available (r, v):\n' - for r, tt, v in compaprable_fc: - err += f' ({r}, {v}): {M.CostFixed[r, v, tt, v]}\n' + for r, tt, v in comparable_fc: + err += f' ({r}, {v}): {model.cost_fixed[r, v, tt, v]}\n' if err: logger.warning(err) warnings = True @@ -161,7 +144,7 @@ def price_checker(M: 'TemoaModel') -> bool: if missing_ic: missing_techs = defaultdict(set) for r, t, v in missing_ic: - if v in M.time_optimize: + if v in model.time_optimize: missing_techs[t].add((r, v)) for t in missing_techs: compaprable_ic = sorted(filter(lambda x: x[1] == t, registered_inv_costs)) @@ -171,11 +154,11 @@ def price_checker(M: 'TemoaModel') -> bool: f'check 1b:\ntech {t} has Investment Cost in some vintage/regions but not all\n' ) err += ' missing (r, v):\n' - for r, v in sorted(missing_techs.get(t)): + for r, v in sorted(missing_techs[t]): err += f' ({r}, {v})\n' err += ' available (r, v):\n' for r, tt, v in compaprable_ic: - err += f' ({r}, {v}): {M.CostInvest[r, tt, v]}\n' + err += f' ({r}, {v}): {model.cost_invest[r, tt, v]}\n' if err: logger.warning(err) warnings = True @@ -196,7 +179,8 @@ def price_checker(M: 'TemoaModel') -> bool: if missing_fixed_costs: logger.warning( 'Check 2: The following have registered variable costs in ' - 'the periods listed and at least 1 fixed cost, but not fixed & var in all periods: %s', + 'the periods listed and at least 1 fixed cost, but not fixed & var in all periods: ' + '%s', missing_fixed_costs, ) warnings = True @@ -222,13 +206,15 @@ def price_checker(M: 'TemoaModel') -> bool: logger.debug(' Starting price check #3') for region, tech, vintage in sorted_efficiency_rtv: # skip resources - if tech in M.tech_resource: - continue + # devnote: this feels like an OEO specific use case and not generally applicable. + # also, the tech_resource set isn't used ANYWHERE else + # if tech in M.tech_resource: + # continue # get the lifetime of the tech, or default - lifetime = M.LifetimeProcess[region, tech, vintage] + lifetime = value(model.lifetime_process[region, tech, vintage]) # get all applicable future periods that should be priced for this item - expected_periods = {p for p in M.time_optimize if vintage <= p < vintage + lifetime} + expected_periods = {p for p in model.time_optimize if vintage <= p < vintage + lifetime} missing_fixed_costs = ( expected_periods - fixed_costs[region, tech, vintage] if fixed_costs[region, tech, vintage] @@ -263,41 +249,45 @@ def price_checker(M: 'TemoaModel') -> bool: ) warnings = True # continue by checking the uncap techs... - warnings &= check_tech_uncap(M) + if not check_tech_uncap(model): + warnings = True logger.info('Finished Price Checking Build Action') return not warnings -def check_tech_uncap(M: 'TemoaModel') -> bool: +def check_tech_uncap(model: TemoaModel) -> bool: """ Check that the tech_uncap set members... 1. do not have fixed or invest costs - 2. Either have no Var cost, or a Var cost in every year of their lifespan (similar to check #3 above) - 3. Are not in the MaxCapacity/MinCapacity parameters + 2. Either have no Var cost, or a Var cost in every year of their lifespan (similar to check #3 + above) + 3. Are not in the limit_capacity parameters :param M: :return: True if "clean" (no warnings), else False """ - if len(M.tech_uncap) == 0: + if len(model.tech_uncap) == 0: return True logger.debug('starting price check #4: uncapacitated techs') efficiency_rtv = { (region, tech, vintage) - for (region, _, tech, vintage, __) in M.Efficiency.sparse_iterkeys() - if tech in M.tech_uncap + for (region, _, tech, vintage, __) in model.efficiency.sparse_keys() + if tech in model.tech_uncap } - fixed_cost_periods = {(r, t, v): p for r, p, t, v in M.CostFixed} + fixed_cost_periods = {(r, t, v): p for r, p, t, v in model.cost_fixed.sparse_keys()} rtv_with_fixed_cost = efficiency_rtv & set(fixed_cost_periods.keys()) if rtv_with_fixed_cost: logger.error( - 'The following technologies are labeled as unlimited capacity, but have a FIXED cost in periods' + 'The following technologies are labeled as unlimited capacity, but have a FIXED cost ' + 'in ' + 'periods' ) for rtv in rtv_with_fixed_cost: logger.error('%s: %s', rtv, fixed_cost_periods[rtv]) - rtv_with_invest_cost = efficiency_rtv & set(M.CostInvest.keys()) + rtv_with_invest_cost = efficiency_rtv & set(model.cost_invest.sparse_keys()) if rtv_with_invest_cost: logger.error( 'The following technologies are labeled as unlimited capacity, but have an INVEST cost' @@ -307,19 +297,21 @@ def check_tech_uncap(M: 'TemoaModel') -> bool: var_cost_periods = defaultdict(set) # by starting from the cost side, we will naturally omit anything with NO var costs at all. - for r, p, t, v in M.CostVariable.sparse_iterkeys(): + for r, p, t, v in model.cost_variable.sparse_keys(): if (r, t, v) in efficiency_rtv: var_cost_periods[(r, t, v)].add(p) # use it to check for all/none var costs in viable periods - all_periods = M.time_optimize + all_periods = model.time_optimize bad_var_costs = False for r, t, v in var_cost_periods: - lifetime = M.LifetimeProcess[r, t, v] + lifetime = model.lifetime_process[r, t, v] expected_periods = {p for p in all_periods if v <= p < v + lifetime} missing_periods = expected_periods - var_cost_periods[r, t, v] if missing_periods: logger.warning( - 'Unlimited capacity tech %s has some Variable costs, but is missing cost in periods: %s', + 'Unlimited capacity tech %s has some Variable costs, but is missing cost in ' + 'periods: ' + '%s', t, missing_periods, ) @@ -327,7 +319,9 @@ def check_tech_uncap(M: 'TemoaModel') -> bool: extra_periods = var_cost_periods[r, t, v] - expected_periods if extra_periods: logger.warning( - 'Unlimited capacity region-tech-vintage %s-%s-%s has some variable costs outside of its ' + 'Unlimited capacity region-tech-vintage %s-%s-%s has some variable costs outside ' + 'of ' + 'its ' 'lifespan: %s', r, t, @@ -335,10 +329,10 @@ def check_tech_uncap(M: 'TemoaModel') -> bool: extra_periods, ) - capacity_params = (M.MaxCapacity, M.MinCapacity, M.ExistingCapacity) + capacity_params = (model.existing_capacity,) bad_cap_entries = False for param in capacity_params: - bad_entries = {(r, t, v) for r, t, v in param.keys() if t in M.tech_uncap} + bad_entries = {(r, t, v) for r, t, v in param.sparse_keys() if t in model.tech_uncap} if bad_entries: for entry in bad_entries: logger.error( diff --git a/temoa/model_checking/unit_checking/__init__.py b/temoa/model_checking/unit_checking/__init__.py new file mode 100644 index 000000000..0db3c443f --- /dev/null +++ b/temoa/model_checking/unit_checking/__init__.py @@ -0,0 +1,22 @@ +from importlib import resources as importlib_resources + +from pint import UnitRegistry +from pint.errors import DefinitionSyntaxError + +# UnitRegistry is generic but doesn't require type args at instantiation +ureg: UnitRegistry = UnitRegistry() # type: ignore[type-arg] + +# Load custom unit definitions from the package resources +_resource_path = 'temoa.model_checking.unit_checking/temoa_units.txt' +try: + data = importlib_resources.files('temoa.model_checking.unit_checking').joinpath( + 'temoa_units.txt' + ) + # Ensure we have a real filesystem path (handles zipped resources too) + with importlib_resources.as_file(data) as path: + ureg.load_definitions(path) +except (FileNotFoundError, OSError, DefinitionSyntaxError) as exc: + raise RuntimeError( + f'Failed to load custom Temoa unit definitions from {_resource_path!r}. ' + 'This may indicate a broken installation or missing resource file.' + ) from exc diff --git a/temoa/model_checking/unit_checking/common.py b/temoa/model_checking/unit_checking/common.py new file mode 100644 index 000000000..f4c024a99 --- /dev/null +++ b/temoa/model_checking/unit_checking/common.py @@ -0,0 +1,172 @@ +""" +common elements used within Unit Checking +""" + +from collections.abc import Sequence +from dataclasses import dataclass +from enum import Enum +from typing import NamedTuple + +input_tables_with_units = [ + 'capacity_to_activity', + 'commodity', + 'construction_input', + 'cost_emission', + 'cost_fixed', + 'cost_invest', + 'cost_variable', + 'demand', + 'efficiency', + 'emission_activity', + 'emission_embodied', + 'emission_end_of_life', + 'end_of_life_output', + 'existing_capacity', + 'lifetime_process', + 'lifetime_tech', + 'loan_lifetime_process', + 'limit_activity', + 'limit_capacity', + # Growth/degrowth tables use 'seed_units' column, not 'units' - handle separately + # 'limit_degrowth_capacity', + # 'limit_degrowth_new_capacity', + # 'limit_degrowth_new_capacity_delta', + 'limit_emission', + # 'limit_growth_capacity', + # 'limit_growth_new_capacity', + # 'limit_growth_new_capacity_delta', + 'limit_new_capacity', + 'limit_resource', +] + +output_tables_with_units = [ + 'output_built_capacity', + 'output_cost', + 'output_curtailment', + 'output_emission', + 'output_flow_in', + 'output_flow_out', + 'output_net_capacity', + 'output_retired_capacity', + 'output_storage_level', +] + +# Combined list for backward compatibility +tables_with_units = input_tables_with_units + output_tables_with_units + +ratio_capture_tables = { + 'efficiency', + # 'emission_activity', # Not using ratio format in v4 + 'cost_emission', + 'cost_fixed', + 'cost_invest', + 'cost_variable', +} +"""Tables that require ratio capture in form "units / (other units)" """ + +commodity_based_tables = [ + 'demand', +] + +# Group tables Not Yet Implemented... would need to gather by group name and tech, etc. +activity_based_tables = [ + 'limit_activity', + # 'limit_activity' with groups - NYI +] +"""Tables that should have units equivalent to the commodity's native units""" + +# dev note: The "grouped" functions below are not yet implemented / future work. +# They are (to date) seldom used. Implementing would require grouping by group name, +# ensuring all techs in group are same... +capacity_based_tables = [ + 'existing_capacity', + 'limit_capacity', + 'limit_new_capacity', + # Group-based capacity limits - NYI +] +"""Tables that require conversion via capacity_to_activity to reach the native units""" + +period_based_tables = [ + 'lifetime_process', + 'lifetime_tech', + 'loan_lifetime_process', +] +"""Tables that align to the time period, presumably 'years'""" + + +# we need to delineate whether the units are commodity-referenced or tech-referenced +# and if they are "capacity based" so... +# format: (table_name, commodity field name (None if 'tech' based), +# capacity-based, period-based ) +class CostTableData(NamedTuple): + """A named tuple for the cost tables + important properties""" + + table_name: str + commodity_reference: str | None + capacity_based: bool + period_based: bool + + +cost_based_tables = [ + CostTableData( + table_name='cost_invest', commodity_reference=None, capacity_based=True, period_based=False + ), + CostTableData( + table_name='cost_emission', + commodity_reference='emis_comm', + capacity_based=False, + period_based=False, + ), + CostTableData( + table_name='cost_fixed', commodity_reference=None, capacity_based=True, period_based=True + ), + CostTableData( + table_name='cost_variable', + commodity_reference=None, + capacity_based=False, + period_based=False, + ), +] +"""Tables that have cost units and their properties""" + + +class RelationType(Enum): + ACTIVITY = 1 + CAPACITY = 2 + COMMODITY = 3 + + +@dataclass(frozen=True) +class UnitsFormat: + format: str + groups: int + + +# any gathering of letters and allowed symbols which are "*" and "_" +# with end lead/trail spaces trimmed +# We include numbers here for cases where there is an exponent in the units like "meter^2" +# the units *may* be parenthesized arbitrarily. See the unit tests for examples. +SINGLE_ELEMENT = UnitsFormat(format=r'^\s*([A-Za-z0-9\*\^\_\s\/\(\)]+?)\s*$', groups=1) + +# any fractional expression using the same pattern above with the denominator +# IN PARENTHESES this modification of above REQUIRES a parenthetical expression +# after the slash to isolate the denominator. see the unit tests for examples. +RATIO_ELEMENT = UnitsFormat( + format=r'^\s*([A-Za-z0-9\*\/\^\_\s]+?)\s*\/\s*\(\s*([A-Za-z0-9\*\^\/\(\)\_\s]+?)\s*\)\s*$', + groups=2, +) +"""Format for a units ratio. re will return the first group as the numerator +and the second as the denominator""" + +ACCEPTABLE_CHARACTERS = r'^\s*([A-Za-z0-9\*\^\_\s\/\(\)]+?)\s*$' + + +def consolidate_lines(line_nums: Sequence[int]) -> str: + """A little sand wedge function to prevent lists of many line numbers + and maxing at 5 or 5 + 'more'""" + listed_lines = ( + ', '.join(str(t) for t in line_nums) + if len(line_nums) < 5 + else f'{", ".join(str(t) for t in line_nums[:5])}, ... +{len(line_nums) - 5} more' + ) + return listed_lines diff --git a/temoa/model_checking/unit_checking/entry_checker.py b/temoa/model_checking/unit_checking/entry_checker.py new file mode 100644 index 000000000..effe07cb2 --- /dev/null +++ b/temoa/model_checking/unit_checking/entry_checker.py @@ -0,0 +1,76 @@ +""" +module to check all units entries in database for... + (1) existence :) + (2) general format (e.g. as a singleton or a ratio expression like Lumens / (Watt)) + (3) membership in units registry + +""" + +import logging +import re +import sqlite3 +from collections import defaultdict + +from pint import UndefinedUnitError, Unit + +from temoa.model_checking.unit_checking import ureg +from temoa.model_checking.unit_checking.common import ( + UnitsFormat, +) + +logger = logging.getLogger(__name__) + + +def validate_units_format( + expr: str, unit_format: UnitsFormat +) -> tuple[bool, tuple[str, ...] | None]: + """ + validate against the format + return boolean for validity and tuple of elements if valid + """ + if not expr: + return False, None + elements = re.search(unit_format.format, expr) + if elements: + return True, tuple(elements.groups()) + return False, None + + +def validate_units_expression(expr: str) -> tuple[bool, Unit | None]: + """ + validate an entry against the units registry + :param expr: the expression to validate + :return: tuple of the validity and the converted expression + """ + try: + units = ureg.parse_units(expr) + return True, units + except UndefinedUnitError: + return False, None + + +def gather_from_table(conn: sqlite3.Connection, table: str) -> dict[str, list[int]]: + """gather all unique "units" entries from a table and collect the row indices""" + + res = defaultdict(list) + with conn: + cur = conn.cursor() + + try: + cur.execute(f'SELECT units FROM {table}') + except sqlite3.OperationalError as exc: + # e.g. "no such column: units" for a table that hasn't been upgraded + logger.error( + 'Table %s does not contain a "units" column required for units checking: %s', + table, + exc, + ) + # Let the caller decide how to treat this (e.g., mark Check 2 as failed) + return {} + + for idx, result in enumerate(cur.fetchall(), start=1): + # note: this will put in "blank" entries which is OK, we want to mark blank rows too + entry = result[0] + res[entry].append(idx) + + return res diff --git a/temoa/model_checking/unit_checking/relations_checker.py b/temoa/model_checking/unit_checking/relations_checker.py new file mode 100644 index 000000000..522c2087b --- /dev/null +++ b/temoa/model_checking/unit_checking/relations_checker.py @@ -0,0 +1,522 @@ +""" +A systematic check of expected relationships between tables to ensure units are consistent + +""" + +import dataclasses +import logging +import sqlite3 +from collections import defaultdict +from collections.abc import Iterable + +from pint.registry import Unit + +from temoa.model_checking.unit_checking import ureg +from temoa.model_checking.unit_checking.common import ( + RATIO_ELEMENT, + SINGLE_ELEMENT, + CostTableData, + RelationType, + activity_based_tables, + capacity_based_tables, + commodity_based_tables, + consolidate_lines, +) +from temoa.model_checking.unit_checking.entry_checker import ( + validate_units_expression, + validate_units_format, +) + +logger = logging.getLogger(__name__) + + +def make_commodity_lut(conn: sqlite3.Connection) -> dict[str, Unit]: + """Get a dictionary of the units for each commodity entry""" + res: dict[str, Unit] = {} + cursor = conn.cursor() + query = 'SELECT name, units FROM commodity' + cursor.execute(query) + rows = cursor.fetchall() + for comm, units in rows: + valid, group = validate_units_format(units, SINGLE_ELEMENT) + if valid and group is not None: + valid, unit_obj = validate_units_expression(group[0]) + if valid and unit_obj is not None: + res[comm] = unit_obj + return res + + +def make_c2a_lut(conn: sqlite3.Connection) -> dict[str, Unit]: + """Get a dictionary of the units for each capacity to activity entry""" + res: dict[str, Unit] = {} + cursor = conn.cursor() + query = 'SELECT tech, units FROM capacity_to_activity' + cursor.execute(query) + rows = cursor.fetchall() + for tech, units in rows: + valid, group = validate_units_format(units, SINGLE_ELEMENT) + if valid and group is not None: + valid, unit_obj = validate_units_expression(group[0]) + if valid and unit_obj is not None: + res[tech] = unit_obj + return res + + +@dataclasses.dataclass(frozen=True) +class IOUnits: + input_units: Unit + output_units: Unit + + +def check_efficiency_table( + conn: sqlite3.Connection, comm_units: dict[str, Unit] +) -> tuple[dict[str, IOUnits], list[str]]: + """ + Check the technology units for Efficiency table entries + + Returns a dictionary of technology : IOUnits and a list of error messages + + """ + + query = 'SELECT tech, input_comm, output_comm, units FROM efficiency' + rows = conn.execute(query).fetchall() + res: dict[str, IOUnits] = {} + error_msgs = [] + invalid_rows = [] + for idx, (tech, ic, oc, units) in enumerate(rows, start=1): + input_units: Unit | None = None + output_units: Unit | None = None + valid, located_units = validate_units_format(units, RATIO_ELEMENT) + if valid and located_units is not None and len(located_units) >= 2: + valid, output_units = validate_units_expression(located_units[0]) + if valid and located_units is not None and len(located_units) >= 2: + valid, input_units = validate_units_expression(located_units[1]) + if not valid or input_units is None or output_units is None: + invalid_rows.append(idx) + # we give up early. The specifics of why this failed should be evident in earlier tests + continue + + # check that our tech matches the units of the connected commodities + expected_input = comm_units.get(ic) + expected_output = comm_units.get(oc) + if expected_input is None or expected_output is None: + invalid_rows.append(idx) + logger.warning( + 'Missing commodity units for input_comm=%s or output_comm=%s in efficiency row %d', + ic, + oc, + idx, + ) + continue + + invalid_input_flag = input_units != expected_input + invalid_output_flag = output_units != expected_output + if invalid_input_flag or invalid_output_flag: + logger.warning( + 'Efficiency units conflict with associated commodity for Technology %s near row %d', + tech, + idx, + ) + msg = ( + f'\n Expected: {f"{ic} [{expected_input}]":^25} ----> ' + f'{tech:^20} ----> {f"{oc} [{expected_output}]": ^25}' + ) + if invalid_input_flag: + msg += f'\n Invalid input units: {input_units}' + if invalid_output_flag: + msg += f'\n Invalid output units: {output_units}' + error_msgs.append(msg) + + # check that the output of this technology is consistent in units with + # other instances of same tech + if tech in res: + if res[tech].output_units != output_units: + logger.warning( + 'Efficiency units conflict with same-name tech for Technology %s near row %d', + tech, + idx, + ) + msg = ( + f'\n Found: {f"{ic} [{input_units}]":^25} ----> ' + f'{tech:^20} ----> {f"{oc} [{output_units}]": ^25}' + ) + msg += f'\n Conflicting output units: {res[tech].output_units} vs {output_units}' + error_msgs.append(msg) + + else: + res[tech] = IOUnits(input_units, output_units) + + # we gather all non-processed rows in one statement here due to size of table + # vs. individual reporting + if invalid_rows: + listed_lines = consolidate_lines(invalid_rows) + line_error_msg = f'Non-processed rows (see earlier tests): {listed_lines}' + error_msgs.append(line_error_msg) + + return res, error_msgs + + +def _column_exists(conn: sqlite3.Connection, table: str, column: str) -> bool: + """Check if a column exists in a table.""" + try: + cursor = conn.execute(f'PRAGMA table_info({table})') + columns = [row[1] for row in cursor.fetchall()] + return column in columns + except sqlite3.Error: + return False + + +def check_inter_table_relations( + conn: sqlite3.Connection, + table_name: str, + tech_lut: dict[str, IOUnits], + comm_lut: dict[str, Unit], + relation_type: RelationType, +) -> list[str]: + """Check the tech and units in the given table vs. baseline (expected) values for the tech. + + Fixed: Made SQL queries more robust to handle: + - Missing columns (e.g., 'region' may not exist in all tables) + - Missing tables (e.g., some databases may not have all limit tables) + - Schema variations between v3.1 and v4.0 + """ + # Validate table_name against known safe tables + valid_tables = activity_based_tables + capacity_based_tables + commodity_based_tables + if table_name not in valid_tables: + raise ValueError(f'Invalid table name: {table_name}') + + grouped_errors: defaultdict[str, list[int]] = defaultdict(list) + + # Build query based on relation type, with robustness checks + match relation_type: + case RelationType.CAPACITY: + # Check if required tables and columns exist + has_c2a = _column_exists(conn, 'capacity_to_activity', 'tech') + has_region = _column_exists(conn, table_name, 'region') + + # Some tables use 'tech_or_group' instead of 'tech' (e.g., limit tables) + tech_column = 'tech' if _column_exists(conn, table_name, 'tech') else 'tech_or_group' + + if has_c2a: + # Use LEFT JOIN to handle missing matches gracefully + join_condition = f'{table_name}.{tech_column} = ca.tech' + if has_region: + join_condition += f' AND {table_name}.region = ca.region' + + query = ( + f'SELECT {table_name}.{tech_column}, {table_name}.units, ca.units ' + f'FROM {table_name} ' + f'LEFT JOIN capacity_to_activity ca ON {join_condition}' + ) + else: + # Fallback: no C2A table available, just check the table itself + query = f'SELECT {tech_column}, units, NULL FROM {table_name}' + logger.warning( + 'capacity_to_activity table not available for %s, skipping C2A verification', + table_name, + ) + case RelationType.ACTIVITY: + # Activity tables may also have tech_or_group + tech_column = 'tech' if _column_exists(conn, table_name, 'tech') else 'tech_or_group' + query = f'SELECT {tech_column}, units, NULL FROM {table_name}' + case RelationType.COMMODITY: + query = f'SELECT commodity, units, NULL FROM {table_name}' + case _: + raise ValueError(f'Unexpected relation type: {relation_type}') + + try: + rows = conn.execute(query).fetchall() + except sqlite3.OperationalError as _: + # Log the error but don't fail the entire check + logger.exception('failed to process query: %s when processing table %s', query, table_name) + msg = ( + f'Failed to process table {table_name} due to SQL error. ' + f'This may indicate missing columns or incompatible schema. ' + f'See log for details.' + ) + return [msg] + + # process the rows + for idx, (tech_or_comm, table_units, c2a_units) in enumerate(rows, start=1): + expected_units = None + match relation_type: + case RelationType.CAPACITY: + io_units = tech_lut.get(tech_or_comm) + if not io_units: + grouped_errors[ + f'Unprocessed row (missing reference for tech ' + f'"{tech_or_comm}" --see earlier tests)' + ].append(idx) + continue + expected_units = io_units.output_units + case RelationType.ACTIVITY: + io_units = tech_lut.get(tech_or_comm) + if not io_units: + grouped_errors[ + f'Unprocessed row (missing reference for tech ' + f'"{tech_or_comm}" --see earlier tests)' + ].append(idx) + continue + expected_units = io_units.output_units + case RelationType.COMMODITY: + expected_units = comm_lut.get(tech_or_comm) + case _: + raise ValueError(f'Unexpected relation type: {relation_type}') + if not expected_units: + entity = 'commodity' if relation_type is RelationType.COMMODITY else 'tech' + grouped_errors[ + f'Unprocessed row (missing reference for {entity} "{tech_or_comm}"' + ].append(idx) + + continue + + # validate the units in the table... + entry_format_valid, units_data = validate_units_format(table_units, SINGLE_ELEMENT) + if entry_format_valid and units_data is not None and len(units_data) >= 1: + _is_valid, valid_table_units = validate_units_expression(units_data[0]) + else: + valid_table_units = None + + # validate the c2a units, if needed + if c2a_units: + c2a_valid, units_data = validate_units_format(c2a_units, SINGLE_ELEMENT) + if c2a_valid and units_data is not None and len(units_data) >= 1: + # further ensure the conversion is valid and retain the appropriate units object + _is_valid, valid_c2a_units = validate_units_expression(units_data[0]) + if not valid_c2a_units: + grouped_errors[ + f'Invalid units or unit format for c2a table: {c2a_units}' + ].append(idx) + continue + else: + grouped_errors[f'Invalid units or unit format for c2a table: {c2a_units}'].append( + idx + ) + continue + else: + valid_c2a_units = None + + if not valid_table_units: + grouped_errors[f'Invalid units or unit format: {table_units}'].append(idx) + continue + + # if we have valid c2a units, combine them to get the units of activity + if valid_c2a_units: + res_units = valid_table_units * (valid_c2a_units * ureg.year) + else: + res_units = valid_table_units + + # check that the res_units match the expectation from the tech + if expected_units != res_units: + label = f'Units do not match expectation for tech/comm: {tech_or_comm}' + conversions = [] + if valid_c2a_units: + conversions.append(f'C2A Factor: {valid_c2a_units}') + conversions.append(f'Nominal Period: {ureg.year}') + detail = _ding_label( + table_entry=table_units, + focus=f'Converted Measure: {valid_table_units}', + conversions=conversions, + result=res_units, + expectation=expected_units, + ) + msg = label + detail + '\n' + grouped_errors[msg].append(idx) + + # gather into list format + res = [] + for msg, line_nums in grouped_errors.items(): + res.append(f'{msg} at rows: {consolidate_lines(line_nums)}') + + return res + + +def _ding_label( + table_entry: str, + focus: str, + result: Unit | None, + expectation: Unit | None, + conversions: list[str] | None = None, +) -> str: + """Make a standardized 'ding' label to use in error reporting""" + res = [''] + res.append(f'| Table Entry: {table_entry}') + res.append(f'| Focused Portion: {focus}') + if conversions: + for conversion in conversions: + res.append(f'| Conversion: {conversion}') + res.append(f'| Result: {result}') + res.append(f'| Expectation: {expectation}') + return '\n '.join(res) + + +def check_cost_tables( + conn: sqlite3.Connection, + cost_tables: Iterable[CostTableData], + tech_lut: dict[str, IOUnits], + c2a_lut: dict[str, Unit], + commodity_lut: dict[str, Unit], +) -> list[str]: + """ + Check all cost tables for (a) alignment of units to tech output (the denominator) + and (b) 100% commonality in the cost units (numerator) + Note: we'll *assume* the first passing entry in the first table establishes + the common cost units and check for consistency + """ + common_cost_unit = None # Expectation: MUSD. Something with a prefix and currency dimension + error_msgs = [] + for ct in cost_tables: + table_grouped_errors = defaultdict(list) + if ct.commodity_reference and ct.capacity_based: + raise ValueError( + f'Table that is "capacity based" {ct.table_name} flagged as ' + 'having commodity field--expecting tech field. Check data.' + ) + query = ( + f'SELECT {ct.commodity_reference if ct.commodity_reference else "tech"}, ' + f'units FROM {ct.table_name}' + ) + try: + rows = conn.execute(query).fetchall() + except sqlite3.OperationalError: + logger.exception( + 'failed to process query: %s when processing table %s', query, ct.table_name + ) + msg = f'Failed to process table {ct.table_name}. See log for failed query.' + error_msgs.append(msg) + continue + for idx, (tech, raw_units_expression) in enumerate(rows, start=1): + # convert to pint expression + cost_units, measure_units = None, None + # screen for empty/missing raw inputs + if not raw_units_expression: + label = f'{ct.table_name}: Unprocessed row (missing units): {raw_units_expression}' + table_grouped_errors[label].append(idx) + continue + valid, elements = validate_units_format(raw_units_expression, RATIO_ELEMENT) + if valid and elements is not None and len(elements) >= 2: + cost_valid, cost_units = validate_units_expression(elements[0]) + units_valid, measure_units = validate_units_expression(elements[1]) + else: + cost_valid, units_valid = False, False + if not (cost_valid and units_valid): + label = ( + f'{ct.table_name}: Unprocessed row ' + f'(invalid units--see earlier tests): {raw_units_expression}' + ) + table_grouped_errors[label].append(idx) + continue + + # Test 1: Look for cost commonality + # extract the cost units + if not cost_units: + label = ( + f'{ct.table_name}: Unprocessed row ' + f'(missing cost units): {raw_units_expression}' + ) + table_grouped_errors[label].append(idx) + continue + + # Get cost unit object + # cost_units is already a pint Unit object from validate_units_expression(elements[0]) + cost_unit_obj = cost_units + + # Check for currency dimension + if '[currency]' not in cost_unit_obj.dimensionality: + label = ( + f'{ct.table_name}: Cost units must have currency dimension. ' + f'Found: {cost_unit_obj}' + ) + table_grouped_errors[label].append(idx) + continue + + # Initialize common_cost_unit on first valid row + if common_cost_unit is None: + common_cost_unit = cost_unit_obj + # No need to continue here, as we still need to check measure units + else: + # Validate subsequent rows against the established common unit + if cost_unit_obj != common_cost_unit: + # Try to see if they're equivalent but differently expressed + try: + # Attempt conversion to check if they're compatible + (1.0 * cost_unit_obj).to(common_cost_unit) + except (ValueError, AttributeError, TypeError) as e: + # Not compatible - this is an error + label = ( + f'{ct.table_name}: Inconsistent cost units: {cost_unit_obj} ' + f'does not match common cost unit {common_cost_unit}. Error: {e}' + ) + table_grouped_errors[label].append(idx) + continue + # If compatible but not strictly equal, we still flag it as non-standard + label = ( + f'{ct.table_name}: Non-standard cost found ' + f'(expected common cost units of {common_cost_unit}) got ' + f'{cost_unit_obj}' + ) + table_grouped_errors[label].append(idx) + + # Test 2: Check the units of measure to ensure alignment with the + # tech's output units. Find the referenced commodity units from the tech + # or commodity depending on table structure... + expected_measure_units: Unit | None = None + if ct.commodity_reference: + expected_measure_units = commodity_lut.get(tech) + if not expected_measure_units: + label = f'{ct.table_name}: Unprocessed row (unknown commodity: {tech}) ' + table_grouped_errors[label].append(idx) + continue + else: + tech_io = tech_lut.get(tech) + if tech_io: + # If capacity-based, we need to multiply by C2A and nominal period + if ct.capacity_based: + c2a_unit = c2a_lut.get(tech) + if c2a_unit: + expected_measure_units = tech_io.output_units * c2a_unit * ureg.year + else: + label = ( + f'{ct.table_name}: Unprocessed row (missing C2A for tech: {tech}) ' + ) + table_grouped_errors[label].append(idx) + continue + else: + expected_measure_units = tech_io.output_units + else: + label = f'{ct.table_name}: Unprocessed row (unknown tech: {tech}) ' + table_grouped_errors[label].append(idx) + continue + + # Now adjust for period-based if needed + if ct.period_based and expected_measure_units is not None: + expected_measure_units = expected_measure_units / ureg.year + + # Handle case where both are None (could indicate data issue) + if measure_units is None and expected_measure_units is None: + label = f'{ct.table_name}: Unable to determine measure units for tech/comm: {tech}' + table_grouped_errors[label].append(idx) + continue + + # Check if measure_units matches expected + matched = ( + measure_units == expected_measure_units + if (measure_units and expected_measure_units) + else False + ) + + if not matched: + label = f'{ct.table_name}: Non-matching measure unit for tech/comm: {tech}' + # Simplified detail without c2a_units/oring_measure_units + detail = ( + f'\n Table entry: {raw_units_expression}' + f'\n Expected: {expected_measure_units}' + f'\n Found: {measure_units}' + ) + label += detail + + table_grouped_errors[label].append(idx) + + for label, listed_lines in table_grouped_errors.items(): + error_msgs.append(f'{label} at rows: {consolidate_lines(listed_lines)}\n') + return error_msgs diff --git a/temoa/model_checking/unit_checking/screener.py b/temoa/model_checking/unit_checking/screener.py new file mode 100644 index 000000000..9f8d6a49a --- /dev/null +++ b/temoa/model_checking/unit_checking/screener.py @@ -0,0 +1,320 @@ +""" +The main executable to screen for units +""" + +import logging +import sqlite3 +from pathlib import Path +from typing import Any + +from temoa.model_checking.unit_checking.common import ( + RelationType, + activity_based_tables, + capacity_based_tables, + commodity_based_tables, + cost_based_tables, + input_tables_with_units, +) +from temoa.model_checking.unit_checking.relations_checker import ( + check_cost_tables, + check_efficiency_table, + check_inter_table_relations, + make_c2a_lut, + make_commodity_lut, +) +from temoa.model_checking.unit_checking.table_checker import check_table + +logger = logging.getLogger(__name__) + + +def _check_db_version(conn: sqlite3.Connection, report_entries: list[str]) -> tuple[bool, int, int]: + """ + Check the database version and return success/failure indicator and version info + :param conn: sqlite3 database connection + :param report_entries: list to append report messages to + :return: tuple of (success_indicator, major_version, minor_version) + """ + msg = '======== Units Check 1 (DB Version): Started ========' + report_entries.extend((msg, '\n')) + logger.info(msg) + + data = conn.execute('SELECT element, value FROM metadata').fetchall() + meta_data = dict(data) + major = int(meta_data.get('DB_MAJOR', 0)) + minor = int(meta_data.get('DB_MINOR', 0)) + + if major >= 4: + msg = 'Units Check 1 (DB Version): Passed' + report_entries.extend((msg, '\n')) + logger.info(msg) + return True, major, minor + else: + msg = 'Units Check 1 (DB Version): Failed. DB must be v4.0 or greater for units checking' + report_entries.extend((msg, '\n')) + logger.warning(msg) + return False, major, minor + + +def _check_units_entries(conn: sqlite3.Connection, report_entries: list[str]) -> bool: + """ + Check units entries in all tables and return success/failure indicator + :param conn: sqlite3 database connection + :param report_entries: list to append report messages to + :return: True if all units entries are valid, False otherwise + """ + report_entries.append('\n') + msg = '======== Units Check 2 (Units Entries in Tables): Started ========' + logger.info(msg) + report_entries.extend((msg, '\n')) + + tables_to_check = input_tables_with_units.copy() + + errors_test2 = False + for table in tables_to_check: + _, table_errors = check_table(conn, table) + if table_errors: + errors_test2 = True + for error in table_errors: + logger.info('%s: %s', table, error) + report_entries.extend((f'{table}: {error}', '\n')) + + if not errors_test2: + msg = 'Units Check 2 (Units Entries in Tables): Passed' + logger.info(msg) + report_entries.extend((msg, '\n')) + + report_entries.append('\n') + return not errors_test2 + + +def _check_efficiency_table( + conn: sqlite3.Connection, report_entries: list[str], comm_units: dict[str, Any] +) -> tuple[dict[str, Any], bool]: + """ + Check efficiency table and return tech_io_lut and success/failure indicator + :param conn: sqlite3 database connection + :param report_entries: list to append report messages to + :param comm_units: commodity units lookup table + :return: tuple of (tech_io_lut, success_indicator) + """ + report_entries.append('\n') + msg = '======== Units Check 3 (Tech I/O via Efficiency Table): Started ========' + report_entries.extend((msg, '\n')) + logger.info(msg) + + tech_io_lut, efficiency_errors = check_efficiency_table(conn, comm_units=comm_units) + + if efficiency_errors: + report_entries.append('Efficiency: \n') + for error in efficiency_errors: + report_entries.append(error) + report_entries.append('\n') + logger.warning('Unit conflicts found in Efficiency table. See report.') + return tech_io_lut, False + else: + msg = 'Units Check 3 (Efficiency): Passed' + report_entries.extend((msg, '\n')) + logger.info(msg) + return tech_io_lut, True + + +def _check_related_tables( + conn: sqlite3.Connection, + report_entries: list[str], + tech_io_lut: dict[str, Any], + comm_units: dict[str, Any], +) -> bool: + """ + Check related tables and return success/failure indicator + :param conn: sqlite3 database connection + :param report_entries: list to append report messages to + :param tech_io_lut: technology I/O units lookup table + :param comm_units: commodity units lookup table + :return: True if all related tables are valid, False otherwise + """ + report_entries.append('\n') + msg = '======== Units Check 4 (Related Tables): Started ========' + report_entries.extend((msg, '\n')) + logger.info(msg) + + errors_test4 = False + + # Activity-based + for table in activity_based_tables: + activity_errors = check_inter_table_relations( + conn=conn, + table_name=table, + tech_lut=tech_io_lut, + comm_lut=comm_units, + relation_type=RelationType.ACTIVITY, + ) + if activity_errors: + errors_test4 = True + report_entries.append(f'{table}: \n') + for error in activity_errors: + report_entries.append(error) + report_entries.append('\n') + logger.info('%s: %s', table, error) + + # Capacity-based + for table in capacity_based_tables: + capacity_errors = check_inter_table_relations( + conn=conn, + table_name=table, + tech_lut=tech_io_lut, + comm_lut=comm_units, + relation_type=RelationType.CAPACITY, + ) + if capacity_errors: + errors_test4 = True + report_entries.append(f'{table}: \n') + for error in capacity_errors: + report_entries.append(error) + report_entries.append('\n') + logger.info('%s: %s', table, error) + + # Commodity-based + for table in commodity_based_tables: + commodity_errors = check_inter_table_relations( + conn=conn, + table_name=table, + tech_lut=tech_io_lut, + comm_lut=comm_units, + relation_type=RelationType.COMMODITY, + ) + if commodity_errors: + errors_test4 = True + report_entries.append(f'{table}: \n') + for error in commodity_errors: + report_entries.append(error) + report_entries.append('\n') + logger.info('%s: %s', table, error) + + if not errors_test4: + msg = 'Units Check 4: (Related Tables): Passed' + logger.info(msg) + report_entries.extend((msg, '\n')) + + report_entries.append('\n') + return not errors_test4 + + +def _check_cost_tables( + conn: sqlite3.Connection, + report_entries: list[str], + tech_io_lut: dict[str, Any], + c2a_units: dict[str, Any], + comm_units: dict[str, Any], +) -> bool: + """ + Check cost tables and return success/failure indicator + :param conn: sqlite3 database connection + :param report_entries: list to append report messages to + :param tech_io_lut: technology I/O units lookup table + :param c2a_units: capacity to activity units lookup table + :param comm_units: commodity units lookup table + :return: True if all cost tables are valid, False otherwise + """ + msg = '======== Units Check 5 (Cost Tables): Started ========' + logger.info(msg) + report_entries.extend((msg, '\n')) + + errors = check_cost_tables( + conn, + cost_tables=cost_based_tables, + tech_lut=tech_io_lut, + c2a_lut=c2a_units, + commodity_lut=comm_units, + ) + + if errors: + for error in errors: + logger.info('Cost Tables: %s', error) + report_entries.extend(('Cost Tables: ', error, '\n')) + + return False + else: + msg = 'Units Check 5 (Cost Tables): Passed' + logger.info(msg) + report_entries.extend((msg, '\n')) + + return True + + +def screen(*db_paths: Path, report_dir: Path | None = None) -> bool: + """ + Run series of units screens on the database + :param db_paths: the abs path(S) to the database(s) + :param report_dir: directory to write the report to. If None, no report is written + :return: indicator of whether all checks passed "cleanly" or not + """ + all_clear = True + report_entries = [] + + for db_path in db_paths: + if not db_path.is_file(): + raise FileNotFoundError(f'Database file not found: {db_path}') + initialization_msg = f'\n======== Units Check on DB: {db_path}: Started ========\n\n' + report_entries.append(initialization_msg) + logger.info('Starting Units Check on DB: %s', db_path) + + with sqlite3.connect(db_path) as conn: + # test 1: DB version + db_version_ok, _major_version, _minor_version = _check_db_version(conn, report_entries) + if not db_version_ok: + # we are non-viable, write the (very short) report and return + if report_dir: + _write_report(report_dir, report_entries) + return False + + # test 2: Units in tables + comm_units = make_commodity_lut(conn) + c2a_units = make_c2a_lut(conn) + + units_entries_ok = _check_units_entries(conn, report_entries) + if not units_entries_ok: + all_clear = False + + # test 3: efficiency table + tech_io_lut, efficiency_ok = _check_efficiency_table(conn, report_entries, comm_units) + if not efficiency_ok: + all_clear = False + + # test 4: related tables + related_tables_ok = _check_related_tables(conn, report_entries, tech_io_lut, comm_units) + if not related_tables_ok: + all_clear = False + + # test 5: Cost-Based Tables + cost_tables_ok = _check_cost_tables( + conn, report_entries, tech_io_lut, c2a_units, comm_units + ) + if not cost_tables_ok: + all_clear = False + + # wrap it up + if report_dir: + _write_report(report_dir, report_entries) + logger.info('Finished Units Check') + return all_clear + + +def _write_report(report_dir: Path, report_entries: list[str]) -> None: + """write out a report if the path is specified""" + import datetime + + timestamp = datetime.datetime.now(datetime.UTC).strftime('%Y-%m-%d_%H%M%S') + report_dir.mkdir(parents=True, exist_ok=True) + report_file_path = report_dir / f'units_check_{timestamp}.txt' + with open(report_file_path, 'w', encoding='utf-8') as report_file: + report_file.writelines(report_entries) + + +if __name__ == '__main__': + import sys + + if len(sys.argv) > 1: + db_path = Path(sys.argv[1]) + screen(db_path, report_dir=Path('temp')) + else: + print('Usage: python screener.py ') diff --git a/temoa/model_checking/unit_checking/table_checker.py b/temoa/model_checking/unit_checking/table_checker.py new file mode 100644 index 000000000..182a9ac21 --- /dev/null +++ b/temoa/model_checking/unit_checking/table_checker.py @@ -0,0 +1,116 @@ +""" +functions to check tables within a database for units compliance +""" + +import logging +import re +import sqlite3 +from typing import cast + +from pint.registry import Unit + +from temoa.model_checking.unit_checking import ureg +from temoa.model_checking.unit_checking.common import ( + ACCEPTABLE_CHARACTERS, + RATIO_ELEMENT, + SINGLE_ELEMENT, + capacity_based_tables, + consolidate_lines, + ratio_capture_tables, +) +from temoa.model_checking.unit_checking.entry_checker import ( + gather_from_table, + validate_units_expression, + validate_units_format, +) + +logger = logging.getLogger(__name__) + + +def check_table(conn: sqlite3.Connection, table_name: str) -> tuple[dict[str, Unit], list[str]]: + """ + Check all entries in a table for format and registry compliance + This "first pass" gathers common entries for efficiency""" + errors = [] + res = {} + format_type = RATIO_ELEMENT if table_name in ratio_capture_tables else SINGLE_ELEMENT + + # this function gathers all unique entries by row number for efficiency in larger tables + entries = gather_from_table(conn, table_name) + for expr, line_nums in entries.items(): + # mark the blanks + if not expr: + listed_lines = consolidate_lines(line_nums) + errors.append(f'Blank units entry found at rows: {listed_lines}') + continue + + # check characters + valid_chars = re.search(ACCEPTABLE_CHARACTERS, expr) + if not valid_chars: + listed_lines = consolidate_lines(line_nums) + errors.append( + f'Invalid character(s): {expr if expr else ""} ' + f'[only letters, digits, underscore and "*, /, ^, ()" operators allowed] ' + f'at rows: {listed_lines} ' + ) + continue + + # Check format + valid_format, elements = validate_units_format(expr, format_type) + if not valid_format: + listed_lines = consolidate_lines(line_nums) + if format_type == RATIO_ELEMENT: + msg = ( + f'Format violation at rows. {listed_lines}: {expr}. ' + f'Check illegal chars/operators and that denominator is isolated ' + f'in parentheses.' + ) + else: + msg = ( + f'Format violation at rows. {listed_lines}: {expr}. ' + f'Check for illegal characters or operators.' + ) + errors.append(msg) + continue + elif elements is None: + listed_lines = consolidate_lines(line_nums) + errors.append(f'No units found for expression: {expr} at rows: {listed_lines}') + continue + + # Check registry compliance + converted_units = [] + for element in elements: + if element: + success, unit_obj = validate_units_expression(element) + if not success or unit_obj is None: + listed_lines = consolidate_lines(line_nums) + errors.append( + f'Registry violation (UNK units): {element} at rows: {listed_lines}' + ) + else: + # Capacity table validation: check for inappropriate time dimensions + if table_name in capacity_based_tables and format_type == SINGLE_ELEMENT: + unit_dimensionality = unit_obj.dimensionality + time_exponent = unit_dimensionality.get('[time]', 0) + + if float(cast('float', time_exponent)) > -3: # cast needed to satisfy mypy + listed_lines = consolidate_lines(line_nums) + errors.append( + f'Energy units (not capacity) in capacity table: {element} ' + f'at rows: {listed_lines}. ' + f'Expected power units (e.g., GW, MW, kW), not energy units. ' + f'Remove time component: use {unit_obj / ureg.year} instead?' + ) + converted_units.append(unit_obj) + + # assemble a reference of item: units-relationship if we have a valid entry + if len(converted_units) == format_type.groups: + if format_type == SINGLE_ELEMENT: + ref = {expr: converted_units[0]} + res.update(ref) + elif format_type == RATIO_ELEMENT: + ref = {expr: converted_units[0] / converted_units[1]} + res.update(ref) + else: + raise ValueError(f'Unknown units format: {format_type}') + return res, errors diff --git a/temoa/model_checking/unit_checking/temoa_units.txt b/temoa/model_checking/unit_checking/temoa_units.txt new file mode 100644 index 000000000..a84099cee --- /dev/null +++ b/temoa/model_checking/unit_checking/temoa_units.txt @@ -0,0 +1,15 @@ +# a few additions needed to for completeness in Temoa model +# the units here AUGMENT the default units in pint. See pint's documentation for more info. + +passenger = [person] +seat = [object] +vehicle = [object] + +# see pint's notes about currency and conversion before getting ideas about currency conversions. ;) +# it would be OK to add other currencies here in addition to USD. + +dollar = [currency] = USD +euro = [currency] = EUR + +# Temoa uses ethos as an original source, so we add it here as an "empty" base class unit +ethos = [empty] diff --git a/temoa/model_checking/unit_checking/unit_propagator.py b/temoa/model_checking/unit_checking/unit_propagator.py new file mode 100644 index 000000000..69df296c1 --- /dev/null +++ b/temoa/model_checking/unit_checking/unit_propagator.py @@ -0,0 +1,334 @@ +""" +Unit Propagator - derives units for output tables from input table definitions. + +This module provides the UnitPropagator class which builds lookup tables from +input data and provides unit derivation methods for populating output tables. + +All methods should return None gracefully when units cannot be determined, ensuring +backward compatibility with databases that lack unit information. +""" + +import logging +import re +import sqlite3 +from typing import TYPE_CHECKING + +from temoa.model_checking.unit_checking.relations_checker import ( + IOUnits, + check_efficiency_table, + make_c2a_lut, + make_commodity_lut, +) + +if TYPE_CHECKING: + from pint import Unit + +logger = logging.getLogger(__name__) + + +class UnitPropagator: + """ + Provides unit derivation for output table writing. + + Builds lookup tables once at initialization from input tables and provides + simple getter methods for each output table type. All methods should return None + if units cannot be determined, ensuring graceful fallback for databases + without unit information. + + Usage: + propagator = UnitPropagator(conn) + flow_units = propagator.get_flow_out_units('electricity') # e.g., 'PJ' + cap_units = propagator.get_capacity_units('E_NUCLEAR') # e.g., 'GW' + """ + + def __init__(self, conn: sqlite3.Connection) -> None: + """ + Initialize the propagator by building lookup tables from input data. + + Args: + conn: SQLite connection to the database with input tables. + """ + self._conn = conn + self._commodity_units: dict[str, Unit] | None = None + self._tech_io_units: dict[str, IOUnits] | None = None + self._c2a_units: dict[str, Unit] | None = None + self._capacity_units: dict[str, str] | None = None + self._cost_unit: str | None = None + self._storage_tech_commodities: dict[str, str] | None = None + + # Build all lookups, handling failures gracefully + self._build_lookups() + + def _build_lookups(self) -> None: + """Build all lookup tables, logging warnings on failure.""" + try: + self._commodity_units = make_commodity_lut(self._conn) + except (sqlite3.Error, KeyError) as e: + logger.debug('Could not build commodity units lookup: %s', e) + self._commodity_units = {} + + try: + if self._commodity_units: + tech_result = check_efficiency_table(self._conn, self._commodity_units) + self._tech_io_units = tech_result[0] + else: + self._tech_io_units = {} + except (sqlite3.Error, KeyError) as e: + logger.debug('Could not build tech I/O units lookup: %s', e) + self._tech_io_units = {} + + try: + self._c2a_units = make_c2a_lut(self._conn) + except (sqlite3.Error, KeyError) as e: + logger.debug('Could not build C2A units lookup: %s', e) + self._c2a_units = {} + + try: + self._capacity_units = self._build_capacity_lut() + except (sqlite3.Error, KeyError) as e: + logger.debug('Could not build capacity units lookup: %s', e) + self._capacity_units = {} + + try: + self._cost_unit = self._derive_common_cost_unit() + except (sqlite3.Error, KeyError) as e: + logger.debug('Could not derive common cost unit: %s', e) + self._cost_unit = None + + try: + self._storage_tech_commodities = self._build_storage_commodity_lut() + except (sqlite3.Error, KeyError) as e: + logger.debug('Could not build storage commodity lookup: %s', e) + self._storage_tech_commodities = {} + + def _build_capacity_lut(self) -> dict[str, str]: + """ + Build lookup of tech -> capacity units. + + Sources (in order of precedence): + 1. existing_capacity table (direct unit definition) + 2. cost_invest table (derived from denominator, e.g. Mdollar/GW -> GW) + + Returns: + Dictionary mapping technology name to capacity unit string. + """ + result: dict[str, str] = {} + + # 1. Check existing_capacity + try: + query = 'SELECT tech, units FROM existing_capacity WHERE units IS NOT NULL' + rows = self._conn.execute(query).fetchall() + for tech, units in rows: + if units and tech not in result: + result[tech] = units + except sqlite3.OperationalError: + pass + + # 2. Check cost_invest for new technologies + try: + query = 'SELECT tech, units FROM cost_invest WHERE units IS NOT NULL' + rows = self._conn.execute(query).fetchall() + for tech, units in rows: + if tech not in result and units: + cap_unit = self._extract_capacity_unit(units) + if cap_unit: + result[tech] = cap_unit + except sqlite3.OperationalError: + pass + + return result + + @staticmethod + def _extract_capacity_unit(unit_str: str) -> str | None: + """ + Scavenge for a capacity unit within a complex unit string. + + Handles complex composite units like 'Mdollar / (PJ^2 / GW)' by extracting + the known power unit (GW, MW, kW, etc.). + """ + # Prioritize finding standard power units + # Use word boundaries to avoid partial matches (e.g. GWh matching GW) + patterns = [ + r'\bGW\b', + r'\bMW\b', + r'\bkW\b', + r'\bTW\b', + r'\bgigawatt\b', + r'\bmegawatt\b', + r'\bkilowatt\b', + ] + for pat in patterns: + match = re.search(pat, unit_str) + if match: + return match.group(0) + return None + + def _derive_common_cost_unit(self) -> str | None: + """ + Derive the common cost unit from cost input tables. + + Extracts the currency portion (numerator) from the first valid cost + table entry. + + Returns: + Common cost unit string (e.g., 'Mdollar') or None. + """ + cost_tables = ['cost_invest', 'cost_fixed', 'cost_variable', 'cost_emission'] + for table in cost_tables: + try: + query = f'SELECT units FROM {table} WHERE units IS NOT NULL LIMIT 1' + row = self._conn.execute(query).fetchone() + if row and row[0]: + units_str = row[0] + # Extract numerator from ratio format "MUSD / (GW)" + # Use regex to safely split at the main division " / (" + # This handles cases like "kWh/day / (GW)" where numerator has slashes + parts = re.split(r'\s*/\s*\(', units_str, maxsplit=1) + if len(parts) > 1: + return parts[0].strip() + + # Fallback for simple ratios without parentheses or if strict format not used + if '/' in units_str: + return units_str.split('/', 1)[0].strip() + return units_str + except sqlite3.OperationalError as e: + logger.debug('Cost table %s not found or query failed: %s', table, e) + continue + return None + + def _build_storage_commodity_lut(self) -> dict[str, str]: + """ + Build lookup of storage tech -> output commodity from efficiency table. + + Returns: + Dictionary mapping storage technology to its output commodity. + """ + result: dict[str, str] = {} + try: + # Get storage technologies from storage_duration table + query = """ + SELECT DISTINCT e.tech, e.output_comm + FROM efficiency e + LEFT JOIN technology t ON e.tech = t.tech + LEFT JOIN storage_duration sd ON e.tech = sd.tech + WHERE t.flag = 'ps' + """ + rows = self._conn.execute(query).fetchall() + for tech, output_comm in rows: + if tech not in result: + result[tech] = output_comm + except sqlite3.OperationalError: + pass + return result + + def get_flow_out_units(self, output_comm: str) -> str | None: + """ + Get units for output flow based on output commodity. + + Args: + output_comm: Output commodity name. + + Returns: + Unit string or None if not available. + """ + if not self._commodity_units: + return None + unit = self._commodity_units.get(output_comm) + return f'{unit:~}' if unit else None + + def get_flow_in_units(self, input_comm: str) -> str | None: + """ + Get units for input flow based on input commodity. + + Args: + input_comm: Input commodity name. + + Returns: + Unit string or None if not available. + """ + if not self._commodity_units: + return None + unit = self._commodity_units.get(input_comm) + return f'{unit:~}' if unit else None + + def get_curtailment_units(self, output_comm: str) -> str | None: + """ + Get units for curtailment based on output commodity. + + Args: + output_comm: Output commodity name. + + Returns: + Unit string or None if not available. + """ + return self.get_flow_out_units(output_comm) + + def get_capacity_units(self, tech: str) -> str | None: + """ + Get capacity units for a technology. + + Args: + tech: Technology name. + + Returns: + Unit string or None if not available. + """ + if not self._capacity_units: + return None + return self._capacity_units.get(tech) + + def get_emission_units(self, emis_comm: str) -> str | None: + """ + Get units for emissions based on emission commodity. + + Args: + emis_comm: Emission commodity name. + + Returns: + Unit string or None if not available. + """ + if not self._commodity_units: + return None + unit = self._commodity_units.get(emis_comm) + return f'{unit:~}' if unit else None + + def get_cost_units(self) -> str | None: + """ + Get common cost units for cost output. + + Returns: + Common cost unit string (e.g., 'Mdollar') or None. + """ + return self._cost_unit + + def get_storage_units(self, tech: str) -> str | None: + """ + Get storage level units for a storage technology. + + Storage levels are in the units of the stored commodity. + + Args: + tech: Storage technology name. + + Returns: + Unit string or None if not available. + """ + if not self._storage_tech_commodities or not self._commodity_units: + return None + commodity = self._storage_tech_commodities.get(tech) + if commodity: + unit = self._commodity_units.get(commodity) + return f'{unit:~}' if unit else None + return None + + @property + def has_unit_data(self) -> bool: + """ + Check if any unit information is available. + + Returns: + True if at least one lookup has data, False otherwise. + """ + return bool( + self._commodity_units or self._capacity_units or self._cost_unit or self._tech_io_units + ) diff --git a/temoa/model_checking/validators.py b/temoa/model_checking/validators.py new file mode 100644 index 000000000..4db893b28 --- /dev/null +++ b/temoa/model_checking/validators.py @@ -0,0 +1,403 @@ +""" +These "validators" are used as validation tools for several elements in the TemoaModel + +""" + +from __future__ import annotations + +import re +from logging import getLogger +from typing import TYPE_CHECKING + +import deprecated +from pyomo.environ import NonNegativeReals + +if TYPE_CHECKING: + from pyomo.core import Set + + from temoa.core.model import TemoaModel + from temoa.types.core_types import ( + Commodity, + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, + ) + +logger = getLogger(__name__) + + +# ============================================================================ +# Public API - Functions intended for external import +# ============================================================================ +__all__ = [ + 'no_slash_or_pipe', + 'region_check', + 'region_group_check', + 'validate_0to1', + 'validate_efficiency', + 'validate_linked_tech', + 'validate_reserve_margin', + 'validate_tech_sets', +] + + +def validate_linked_tech(model: TemoaModel) -> bool: + """ + A validation that for all the linked techs, they have the same lifetime in each possible vintage + + The Constraint that this check supports is indexed by a set that fundamentally expands the + (r, t, e) + index of the LinkedTech data table (where t==driver tech) to include valid vintages. + The implication is that there is a driven tech in the same region, of + the same vintage, with the same lifetime as the driver tech. We should check that. + + We can filter the index down to (r, t_driver, v, e) and then query the lifetime of the driver + and driven + to ensure they are the same + + :param M: + :return: True if "OK" else False + """ + logger.debug('Starting to validate linked techs.') + + base_idx = model.linked_emissions_tech_constraint_rpsdtve + + drivers = {(r, t, v, e) for r, p, s, d, t, v, e in base_idx} + for r, t_driver, v, e in drivers: + # get the linked tech of same region, emission + t_driven = model.linked_techs[r, t_driver, e] + + # check for equality in lifetimes for vintage v + driver_lifetime = model.lifetime_process[r, t_driver, v] + try: + driven_lifetime = model.lifetime_process[r, t_driven, v] + except KeyError: + logger.error( + 'Linked Tech Error: Driven tech %s does not have a vintage entry %d to match ' + 'driver %s', + t_driven, + v, + t_driver, + ) + print('Problem with Linked Tech validation: See log file') + return False + if driven_lifetime != driver_lifetime: + logger.error( + 'Linked Tech Error: Driven tech %s has lifetime %d in vintage %d while driver ' + 'tech %s has lifetime %d', + t_driven, + driven_lifetime, + v, + t_driver, + driver_lifetime, + ) + print('Problem with Linked Tech validation: See log file') + return False + + return True + + +def no_slash_or_pipe(model: TemoaModel, element: object) -> bool: + """ + No slash character in element + :param M: + :param element: + :return: + """ + if isinstance(element, int | float): + return True + good = '/' not in str(element) and '|' not in str(element) + if not good: + logger.error('no slash "/" or pipe "|" character is allowed in: %s', str(element)) + return False + return True + + +def region_check(model: TemoaModel, region: Region) -> bool: + """ + Validate the region name (letters + numbers only + underscore) + """ + # screen against illegal names + illegal_region_names = { + 'global', + } + if region in illegal_region_names: + return False + + # if this matches, return is true, fail -> false + if re.match(r'[a-zA-Z0-9_]+\Z', region): # string that has only letters and numbers + return True + return False + + +def linked_region_check(model: TemoaModel, region_pair: str) -> bool: + """ + Validate a pair of regions (r-r format where r ∈ M.R ) + """ + linked_regions = re.match(r'([a-zA-Z0-9_]+)\-([a-zA-Z0-9_]+)\Z', region_pair) + if linked_regions: + r1 = linked_regions.group(1) + r2 = linked_regions.group(2) + if ( + all(r in model.regions for r in (r1, r2)) and r1 != r2 + ): # both captured regions are in the set of M.R + return True + return False + + +def region_group_check(model: TemoaModel, rg: str) -> bool: + """ + Validate the region-group name (region or regions separated by '+') + """ + if '-' in rg: # it should just be evaluated as a linked_region + return linked_region_check(model, rg) + if re.search(r'\A[a-zA-Z0-9\+_]+\Z', rg): + # it has legal characters only + if '+' in rg: + # break up the group + contained_regions = rg.strip().split('+') + if all(t in model.regions for t in contained_regions) and len( + set(contained_regions) + ) == len(contained_regions): # no dupes + return True + else: # it is a singleton + return (rg in model.regions) or rg == 'global' + return False + + +@deprecated.deprecated('needs to be updated if re-instated to accommodate group restructuring') +def tech_groups_set_check(model: TemoaModel, rg: str, g: str, t: str) -> bool: + """ + Validate this entry to the tech_groups set + :param M: the model + :param rg: region-group index + :param g: tech group name + :param t: tech + :return: True if valid entry, else False + """ + return all((region_group_check(model, rg), g in model.tech_group_names, t in model.tech_all)) + + +# TODO: Several of these param checkers below are not in use because the params cannot +# accept new values for the indexing sets that aren't in an already-constructed set. Now +# that we are +# making the GlobalRegionalIndices, we can probably come back and employ them instead of +# using +# the buildAction approach + + +def activity_param_check(model: TemoaModel, val: float, rg: str, p: Period, t: Technology) -> bool: + """ + Validate the index and the value for an entry into an activity param indexed with region-groups + :param M: the model + :param val: the value of the parameter for this index + :param rg: region-group + :param p: time period + :param t: tech + :return: True if all OK + """ + return all( + ( + val in NonNegativeReals, # the value should be in this set + region_group_check(model, rg), + p in model.time_optimize, + t in model.tech_all, + ) + ) + + +def capacity_param_check( + model: TemoaModel, val: float, rg: str, p: Period, t: Technology, carrier: Commodity +) -> bool: + """ + validate entries to capacity params + :param M: the model + :param val: the param value at this index + :param rg: region-group + :param p: time period + :param t: tech + :param carrier: commodity carrier + :return: True if all OK + """ + return all( + ( + val in NonNegativeReals, + region_group_check(model, rg), + p in model.time_optimize, + t in model.tech_all, + carrier in model.commodity_carrier, + ) + ) + + +def activity_group_param_check(model: TemoaModel, val: float, rg: str, p: Period, g: str) -> bool: + """ + validate entries into capacity groups + :param M: the model + :param val: the value at this index + :param rg: region-group + :param p: time period + :param g: tech group name + :return: True if all OK + """ + return all( + ( + val in NonNegativeReals, + region_group_check(model, rg), + p in model.time_optimize, + g in model.tech_group_names, + ) + ) + + +def emission_limit_param_check( + model: TemoaModel, val: float, rg: str, p: Period, e: Commodity +) -> bool: + """ + validate entries into EmissionLimit param + :param M: the model + :param val: the value at this index + :param rg: region-group + :param p: time period + :param e: commodity emission + :return: True if all OK + """ + return all( + (region_group_check(model, rg), p in model.time_optimize, e in model.commodity_emissions) + ) + + +def validate_capacity_factor_process( + model: TemoaModel, + val: float, + r: Region, + s: Season, + d: TimeOfDay, + t: Technology, + v: Vintage, +) -> bool: + """ + validate the rsdtv index + :param val: the parameter value + :param M: the model + :param r: region + :param s: season + :param d: time of day + :param t: tech + :param v: vintage + :return: + """ + # devnote: capacity_factor_process can be a BIG table and most of these seem redundant + # when they're already enforced by the domain of the parameter + # Doesn't seem worth the compute time + return all( + ( + r in model.regions, + s in model.time_season, + d in model.time_of_day, + t in model.tech_with_capacity, + v in model.vintage_all, + 0 <= val <= 1.0, + ) + ) + + +def validate_efficiency( + model: TemoaModel, + val: float, + r: Region, + si: Commodity, + t: Technology, + v: Vintage, + so: Commodity, +) -> bool: + """Handy for troubleshooting problematic entries""" + + if all( + ( + isinstance(val, float), + val > 0, + r in model.regional_indices, + si in model.commodity_physical, + t in model.tech_all, + so in model.commodity_carrier, + v in model.vintage_all, + ) + ): + return True + print('Element Validations:') + print('region', r in model.regional_indices) + print('input_commodity', si in model.commodity_physical) + print('tech', t in model.tech_all) + print('vintage', v in model.vintage_all) + print('output_commodity', so in model.commodity_carrier) + return False + + +def validate_reserve_margin(model: TemoaModel) -> None: + for r in model.planning_reserve_margin.sparse_keys(): + if all((r, p) not in model.process_reserve_periods for p in model.time_optimize): + logger.warning( + 'Planning reserve margin provided but there are no reserve technologies serving ' + 'this ' + 'region: %s', + (r, model.planning_reserve_margin[r]), + ) + + +def validate_tech_sets(model: TemoaModel) -> None: + """ + Check tech sets for any forbidden intersections + """ + if not all( + ( + check_no_intersection(model.tech_annual, model.tech_baseload), + check_no_intersection(model.tech_annual, model.tech_storage), + check_no_intersection(model.tech_annual, model.tech_upramping), + check_no_intersection(model.tech_annual, model.tech_downramping), + check_no_intersection(model.tech_annual, model.tech_curtailment), + check_no_intersection(model.tech_curtailment, model.tech_flex), + check_no_intersection(model.tech_all, model.tech_group_names), + check_no_intersection(model.tech_uncap, model.tech_reserve), + ) + ): + raise ValueError('Technology sets failed to validate. Check log file for details.') + + +def check_no_intersection(set_one: Set, set_two: Set) -> bool: + violations = set_one & set_two + if violations: + msg = ( + f'The following are in both {set_one} and {set_two}, which is not permitted:\n' + f'{list(violations)}' + ) + logger.error(msg) + return False + return True + + +# Seems unused +def validate_tech_split( + model: TemoaModel, val: float, r: Region, p: Period, c: Commodity, t: Technology +) -> bool: + if all( + ( + r in model.regions, + p in model.time_optimize, + c in model.commodity_physical, + t in model.tech_all, + ) + ): + return True + print('r', r in model.regions) + print('p', p in model.time_optimize) + print('c', c in model.commodity_physical) + print('t', t in model.tech_all) + return False + + +def validate_0to1(model: TemoaModel, val: float, *args: object) -> bool: + return 0.0 <= val <= 1.0 diff --git a/temoa/py.typed b/temoa/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/temoa/temoa_model/hybrid_loader.py b/temoa/temoa_model/hybrid_loader.py deleted file mode 100644 index 5963433ab..000000000 --- a/temoa/temoa_model/hybrid_loader.py +++ /dev/null @@ -1,1217 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 1/21/24 - -A module to build/load a Data Portal for myopic run using both SQL to pull data -and python to filter results - -""" - - -import sys -import time -from collections import defaultdict -from logging import getLogger -from sqlite3 import Connection, OperationalError -from typing import Sequence - -from pyomo.core import Param, Set -from pyomo.dataportal import DataPortal - -from temoa.extensions.myopic.myopic_index import MyopicIndex -from temoa.temoa_model.model_checking import network_model_data, element_checker -from temoa.temoa_model.model_checking.commodity_network_manager import CommodityNetworkManager -from temoa.temoa_model.model_checking.element_checker import ViableSet -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_mode import TemoaMode -from temoa.temoa_model.temoa_model import TemoaModel - -logger = getLogger(__name__) - -# the tables below are ones in which we might find regional groups which should be captured -# to make the members of the RegionalGlobalIndices Set in the model. They need to aggregated -tables_with_regional_groups = { - 'MaxActivity': 'region', - 'MinActivity': 'region', - 'MinAnnualCapacityFactor': 'region', - 'MaxAnnualCapacityFactor': 'region', - 'EmissionLimit': 'region', - 'MinActivityGroup': 'region', - 'MaxActivityGroup': 'region', - 'MinCapacityGroup': 'region', - 'MaxCapacityGroup': 'region', -} - - -class HybridLoader: - """ - An instance of the HybridLoader - """ - - def __init__(self, db_connection: Connection, config: TemoaConfig): - """ - build a loader for an instance. - :param db_connection: a Connection to the database - :param config: the config, which controls some options during execution - """ - self.debugging = False # for T/S, will print to screen the data load values - self.con = db_connection - self.config = config - - self.manager: CommodityNetworkManager | None = None - - # filters for myopic ops - self.viable_techs: ViableSet | None = None - self.viable_comms: ViableSet | None = None - self.viable_input_comms: ViableSet | None = None - self.viable_output_comms: ViableSet | None = None - self.viable_vintages: ViableSet | None = None - self.viable_ritvo: ViableSet | None = None - self.viable_rpto: ViableSet | None = None - self.viable_rtv: ViableSet | None = None - self.viable_rt: ViableSet | None = None - self.viable_rpit: ViableSet | None = None - self.viable_rtt: ViableSet | None = None # to support scanning LinkedTech - self.efficiency_values: list[tuple] = [] - - # container for loaded data - self.data: dict | None = None - - def source_trace_only(self, make_plots: bool = False, myopic_index: MyopicIndex | None = None): - if myopic_index and not isinstance(myopic_index, MyopicIndex): - raise ValueError('myopic_index must be an instance of MyopicIndex') - self._source_trace(myopic_index) - self.manager = None # to prevent possible out-of-synch build from stale data - - def _source_trace(self, myopic_index: MyopicIndex = None): - network_data = network_model_data.build(self.con, myopic_index=myopic_index) - cur = self.con.cursor() - # need periods to execute the source check by [r, p]. At this point, we can only pull from DB - periods = { - period for (period, *_) in cur.execute("SELECT period FROM TimePeriod WHERE flag = 'f'") - } - # we need to exclude the last period, it is a non-demand year - periods = sorted(periods)[:-1] - - if myopic_index: - periods = { - p for p in periods if myopic_index.base_year <= p <= myopic_index.last_demand_year - } - self.manager = CommodityNetworkManager(periods=periods, network_data=network_data) - all_regions_clean = self.manager.analyze_network() - if not all_regions_clean and not self.config.silent: - print('\nWarning: Orphaned processes detected. See log file for details.') - self.manager.analyze_graphs(self.config) - - def _build_efficiency_dataset( - self, use_raw_data=False, myopic_index: MyopicIndex | None = None - ): - """ - Build the efficiency dataset. For myopic mode, this means pull from MyopicEfficiency table - and we cannot use raw data. For other modes, we can either use raw data or the filtered data - provided by the manager (normal) - :param use_raw_data: if True, use raw data (without source-trace filtering) for build - :param myopic_index: the myopic index to use (or None for other modes) - :return: - """ - if myopic_index and use_raw_data: - raise RuntimeError('Cannot build from raw data in myopic mode... Likely coding error.') - cur = self.con.cursor() - # pull the data based on whether myopic/not - if myopic_index: - # pull from MyopicEfficiency, and filter by myopic index years - contents = cur.execute( - 'SELECT region, input_comm, tech, vintage, output_comm, efficiency, lifetime ' - 'FROM MyopicEfficiency ' - 'WHERE vintage + lifetime > ?', - (myopic_index.base_year,), - ).fetchall() - else: - # pull from regular Efficiency table - contents = cur.execute( - 'SELECT region, input_comm, tech, vintage, output_comm, efficiency, NULL FROM main.Efficiency' - ).fetchall() - - # set up filters, if requested... - if use_raw_data: - efficiency_entries = [row[:-1] for row in contents] - - else: # (always must when myopic) - if self.manager: - filts = self.manager.build_filters() - else: - raise RuntimeError('trying to filter, but manager has not analyzed network yet.') - self.viable_ritvo = filts['ritvo'] - self.viable_rtv = filts['rtv'] - self.viable_rt = filts['rt'] - self.viable_rpit = filts['rpit'] - self.viable_rpto = filts['rpto'] - self.viable_techs = filts['t'] - self.viable_input_comms = filts['ic'] - self.viable_vintages = filts['v'] - self.viable_output_comms = filts['oc'] - self.viable_comms = ViableSet( - elements=self.viable_input_comms.members | self.viable_output_comms.members - ) - rtt = { - (r, t1, t2) for r, t1 in self.viable_rt.members for t2 in self.viable_techs.members - } - self.viable_rtt = ViableSet( - elements=rtt, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES - ) - efficiency_entries = { - (r, i, t, v, o, eff) - for r, i, t, v, o, eff, lifetime in contents - if (r, i, t, v, o) in self.viable_ritvo.members - } - logger.debug('polled %d elements from MyopicEfficiency table', len(efficiency_entries)) - - # book the EfficiencyTable - # we should sort here for deterministic results after pulling from set - self.efficiency_values = sorted(efficiency_entries) - - def table_exists(self, table_name: str) -> bool: - """ - Check if a table exists in the schema... for use with "optional" tables - :param table_name: the table name to check - :return: True if it exists in the schema - """ - table_name_check = ( - self.con.cursor() - .execute("SELECT name FROM sqlite_master WHERE type='table' AND name= ?", (table_name,)) - .fetchone() - ) - if table_name_check: - return True - logger.info('Did not find existing table for (optional) table: %s', table_name) - return False - - pass - - def load_data_portal(self, myopic_index: MyopicIndex | None = None) -> DataPortal: - # time the creation of the data portal - tic = time.time() - data_dict = self.create_data_dict(myopic_index=myopic_index) - - # pyomo namespace format has data[namespace][idx]=value - # the default namespace is None, thus... - namespace = {None: data_dict} - if self.debugging: - for item in namespace[None].items(): - print(item[0], item[1]) - dp = DataPortal(data_dict=namespace) - toc = time.time() - logger.debug('Data Portal Load time: %0.5f seconds', (toc - tic)) - return dp - - @staticmethod - def data_portal_from_data(data_source: dict) -> DataPortal: - """ - Create a DataPortal object from a data dictionary. Useful when the data has been modified - :param data_source: the dataset to use - :return: a new DataPortal object - """ - namespace = {None: data_source} - dp = DataPortal(data_dict=namespace) - return dp - - def create_data_dict(self, myopic_index: MyopicIndex | None = None) -> dict: - """ - Create and Load a Data Portal. If source tracing is enabled in the config, the source trace will - be executed and filtered data will be used. Without source-trace, raw (unfiltered) data will be loaded. - :param myopic_index: the MyopicIndex for myopic run. None for other modes - :return: - """ - # the general plan: - # 0. determine if source trace needs to be done, and do it - # 1. build the efficiency table - # 2. iterate through the model elements that are directly read from data - # 3. use SQL query to get the full table - # 4. (OPTIONALLY) filter it, as needed for myopic - # 5. load it into the data dictionary - logger.info('Loading data dictionary') - - # some logic checking... - if myopic_index is not None: - if not isinstance(myopic_index, MyopicIndex): - raise ValueError(f'received an illegal entry for the myopic index: {myopic_index}') - if self.config.scenario_mode != TemoaMode.MYOPIC: - raise RuntimeError( - 'Myopic Index passed to data dictionary build, but mode is not Myopic.... ' - 'Likely code error.' - ) - elif myopic_index is None and self.config.scenario_mode == TemoaMode.MYOPIC: - raise RuntimeError( - 'Mode is myopic, but no MyopicIndex specified in data portal build.... Likely code ' - 'error.' - ) - - if self.config.source_trace or self.config.scenario_mode == TemoaMode.MYOPIC: - use_raw_data = False - self._source_trace(myopic_index=myopic_index) - else: - use_raw_data = True - - # build the Efficiency Dataset - self._build_efficiency_dataset(use_raw_data=use_raw_data, myopic_index=myopic_index) - - mi = myopic_index # convenience - - # housekeeping - data: dict[str, list | dict] = dict() - - def load_element( - c: Set | Param, - values: Sequence[tuple], - validation: ViableSet | None = None, - val_loc: tuple = (0,), - ) -> Sequence[tuple]: - """ - Helper to alleviate some typing! - Expects that the values passed in are an iterable of tuples, like a standard - query result. Note that any filtering is disregarded if there is no myopic index in use - :param c: the model component to load - :param values: the keys for param or the item values for set as tuples (should be Sequence to help - get deterministic results) - :param validation: the set to validate the keys/set value against - :param val_loc: tuple of the positions of r, t, v in the key for validation - :return: a sequence of the values loaded - """ - if len(values) == 0: - logger.info('table, but no (usable) values for param or set: %s', c.name) - return [] - if not isinstance(values[0], tuple): - raise ValueError('values must be an iterable of tuples') - - if use_raw_data or validation is None: - screened = list(values) - else: - try: - screened = element_checker.filter_elements( - values=values, validation=validation, value_locations=val_loc - ) - except ValueError as e: - raise ValueError( - 'Failed to validate members of %s. Coding error likely.' - '\n%s' % (c.name, e) - ) - match c: - case Set(): - if not screened: # no available values - data[c.name] = [] - elif len(screened[0]) == 1: # set of individual values - data[c.name] = [t[0] for t in screened] - else: # set of tuples, pass directly... - data[c.name] = screened - case Param(): - data[c.name] = {t[:-1]: t[-1] for t in screened} - return screened - - def load_indexed_set(indexed_set: Set, index_value, element, element_validator): - """ - load an element into an indexed set in the data store - :param indexed_set: the name of the pyomo Set - :param index_value: the index value to load into - :param element: the value to add to the indexed set - :param element_validator: a set of legal elements for the element to be added, or None for all elements - :return: None - """ - if element_validator and element not in element_validator: - return - data_store = data.get(indexed_set.name, defaultdict(list)) - data_store[index_value].append(element) - data[indexed_set.name] = data_store - - M: TemoaModel = TemoaModel() # for typing purposes only - cur = self.con.cursor() - - # === TIME SETS === - - # time_exist - if mi: - raw = cur.execute( - 'SELECT period FROM main.TimePeriod WHERE period < ? ORDER BY sequence', - (mi.base_year,), - ).fetchall() - else: - raw = cur.execute( - "SELECT period FROM main.TimePeriod WHERE flag = 'e' ORDER BY sequence" - ).fetchall() - load_element(M.time_exist, raw) - - # time_future - if mi: - raw = cur.execute( - 'SELECT period FROM main.TimePeriod WHERE ' - 'period >= ? AND period <= ? ORDER BY sequence', - (mi.base_year, mi.last_year), - ).fetchall() - else: - raw = cur.execute( - "SELECT period FROM main.TimePeriod WHERE flag = 'f' ORDER BY sequence" - ).fetchall() - load_element(M.time_future, raw) - - # time_of_day - raw = cur.execute('SELECT tod FROM main.TimeOfDay ORDER BY sequence').fetchall() - load_element(M.time_of_day, raw) - - # time_season - raw = cur.execute('SELECT season FROM main.TimeSeason ORDER BY sequence').fetchall() - load_element(M.time_season, raw) - - # myopic_base_year - if mi: - raw = cur.execute( - "SELECT value from MetaData WHERE element == 'myopic_base_year'" - ).fetchall() - # load as a singleton... - if not raw: - raise ValueError('No myopic_base_year found in MetaData table.') - data[M.MyopicBaseyear.name] = {None: int(raw[0][0])} - - # === REGION SETS === - - # regions - raw = cur.execute('SELECT region FROM main.Region').fetchall() - load_element(M.regions, raw) - - # region-groups (these are the R1+R2, R1+R4+R6 type region labels) AND regular region names - # currently, we just load all the indices from the tables that could employ them. - # the validator is used to ensure they are legit. (see temoa_model) - regions_and_groups = set() - for table, field_name in tables_with_regional_groups.items(): - if self.table_exists(table): - raw = cur.execute(f'SELECT {field_name} from main.{table}').fetchall() - regions_and_groups.update({t[0] for t in raw}) - if None in regions_and_groups: - raise ValueError('Table %s appears to have an empty entry for region.' % table) - # sort (for deterministic pyomo behavior) - list_of_groups = sorted((t,) for t in regions_and_groups) - load_element(M.RegionalGlobalIndices, list_of_groups) - - # region-exchanges - # auto-generated - - # === TECH SETS === - - # tech_resource - raw = cur.execute("SELECT tech FROM main.Technology WHERE flag = 'r'").fetchall() - load_element(M.tech_resource, raw, self.viable_techs) - - # tech_production - raw = cur.execute("SELECT tech FROM main.Technology WHERE flag LIKE 'p%'").fetchall() - load_element(M.tech_production, raw, self.viable_techs) - - # tech_uncap - try: - raw = cur.execute('SELECT tech FROM main.Technology WHERE unlim_cap > 0').fetchall() - load_element(M.tech_uncap, raw, self.viable_techs) - except OperationalError: - logger.info( - 'The current database does not support non-capacity techs and should be upgraded.' - ) - - # tech_baseload - raw = cur.execute("SELECT tech FROM main.Technology WHERE flag = 'pb'").fetchall() - load_element(M.tech_baseload, raw, self.viable_techs) - - # tech_storage - raw = cur.execute("SELECT tech FROM main.Technology WHERE flag = 'ps'").fetchall() - load_element(M.tech_storage, raw, self.viable_techs) - - # tech_reserve - raw = cur.execute('SELECT tech FROM Technology WHERE reserve > 0').fetchall() - load_element(M.tech_reserve, raw, self.viable_techs) - - # tech_ramping - techs = set() - if self.table_exists('RampUp'): - ramp_up_techs = cur.execute('SELECT tech FROM main.RampUp').fetchall() - techs.update({t[0] for t in ramp_up_techs}) - if self.table_exists('RampDown'): - ramp_dn_techs = cur.execute('SELECT tech FROM main.RampDown').fetchall() - techs.update({t[0] for t in ramp_dn_techs}) - load_element(M.tech_ramping, sorted((t,) for t in techs), self.viable_techs) # sort for - # deterministic behavior - - # tech_curtailment - raw = cur.execute('SELECT tech FROM Technology WHERE curtail > 0').fetchall() - load_element(M.tech_curtailment, raw, self.viable_techs) - - # tech_flex - raw = cur.execute('SELECT tech FROM Technology WHERE flex > 0').fetchall() - load_element(M.tech_flex, raw, self.viable_techs) - - # tech_exchange - raw = cur.execute('SELECT tech FROM Technology WHERE exchange > 0').fetchall() - load_element(M.tech_exchange, raw, self.viable_techs) - - # groups & tech_groups (supports RPS and general tech grouping) - if self.table_exists('TechGroup'): - raw = cur.execute('SELECT group_name FROM main.TechGroup').fetchall() - load_element(M.tech_group_names, raw) - - if self.table_exists('TechGroupMember'): - raw = cur.execute('SELECT group_name, tech FROM main.TechGroupMember').fetchall() - validator = self.viable_techs.members if self.viable_techs else None - for row in raw: - load_indexed_set( - M.tech_group_members, - index_value=row[0], - element=row[1], - element_validator=validator, - ) - - # tech_annual - raw = cur.execute('SELECT tech FROM Technology WHERE annual > 0').fetchall() - load_element(M.tech_annual, raw, self.viable_techs) - - # tech_variable - raw = cur.execute('SELECT tech FROM Technology WHERE variable > 0').fetchall() - load_element(M.tech_variable, raw, self.viable_techs) - - # tech_retirement - raw = cur.execute('SELECT tech FROM Technology WHERE retire > 0').fetchall() - load_element(M.tech_retirement, raw, self.viable_techs) - - # === COMMODITIES === - - # commodity_demand - raw = cur.execute("SELECT name FROM main.Commodity WHERE flag = 'd'").fetchall() - load_element(M.commodity_demand, raw, self.viable_comms) - - # commodity_emissions - # currently NOT validated against anything... shouldn't be a problem ? - raw = cur.execute("SELECT name FROM main.Commodity WHERE flag = 'e'").fetchall() - load_element(M.commodity_emissions, raw) - - # commodity_physical - raw = cur.execute( - "SELECT name FROM main.Commodity WHERE flag = 'p' OR flag = 's'" - ).fetchall() - # The model enforces 0 symmetric difference between the physical commodities - # and the input commodities, so we need to include only the viable INPUTS - load_element(M.commodity_physical, raw, self.viable_input_comms) - - # commodity_source - raw = cur.execute("SELECT name FROM main.Commodity WHERE flag = 's'").fetchall() - load_element(M.commodity_source, raw, self.viable_input_comms) - - # === PARAMS === - - # Efficiency - - # we have already computed/filtered this... no need for another data pull - raw = self.efficiency_values - load_element(M.Efficiency, raw) - - # ExistingCapacity - if mi: - # In order to get accurate capacity at start of this interval, we want to - # 1. Only look at the previous period in the net capacity table (things that had some capacity) - # 2. Omit any techs that are "unlimited capacity" to keep them out of capacity variables - # 3. add in everything from the original ExistingCapacity table - - # get previous period - raw = cur.execute( - 'SELECT MAX(period) FROM main.TimePeriod WHERE period < ?', (mi.base_year,) - ).fetchone() - previous_period = raw[0] - # noinspection SqlUnused - raw = cur.execute( - 'SELECT region, tech, vintage, capacity FROM main.OutputNetCapacity ' - ' WHERE period = ? ' - ' AND scenario = ? ' - 'UNION ' - ' SELECT region, tech, vintage, capacity FROM main.ExistingCapacity ', - (previous_period, self.config.scenario), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, tech, vintage, capacity FROM main.ExistingCapacity' - ).fetchall() - load_element(M.ExistingCapacity, raw, self.viable_rtv, (0, 1, 2)) - - # GlobalDiscountRate - raw = cur.execute( - "SELECT value FROM main.MetaDataReal WHERE element = 'global_discount_rate'" - ).fetchall() - # do this separately as it is non-indexed, so we need to make a mapping with None - data[M.GlobalDiscountRate.name] = {None: raw[0][0]} - - # SegFrac - raw = cur.execute('SELECT season, tod, segfrac FROM main.TimeSegmentFraction').fetchall() - load_element(M.SegFrac, raw) - - # DemandSpecificDistribution - raw = cur.execute( - 'SELECT region, season, tod, demand_name, dds FROM main.DemandSpecificDistribution' - ).fetchall() - load_element(M.DemandSpecificDistribution, raw) - - # Demand - if mi: - raw = cur.execute( - 'SELECT region, period, commodity, demand FROM main.Demand ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, commodity, demand FROM main.Demand ' - ).fetchall() - load_element(M.Demand, raw) - - # RescourceBound - # Not currently implemented - - # CapacityToActivity - raw = cur.execute('SELECT region, tech, c2a FROM main.CapacityToActivity ').fetchall() - load_element(M.CapacityToActivity, raw, self.viable_rt, (0, 1)) - - # CapacityFactorTech - raw = cur.execute( - 'SELECT region, season, tod, tech, factor ' 'FROM main.CapacityFactorTech' - ).fetchall() - load_element(M.CapacityFactorTech, raw, self.viable_rt, (0, 3)) - - # CapacityFactorProcess - raw = cur.execute( - 'SELECT region, season, tod, tech, vintage, factor ' ' FROM main.CapacityFactorProcess' - ).fetchall() - load_element(M.CapacityFactorProcess, raw, self.viable_rtv, (0, 3, 4)) - - # LifetimeTech - raw = cur.execute('SELECT region, tech, lifetime FROM main.LifetimeTech').fetchall() - load_element(M.LifetimeTech, raw, self.viable_rt, val_loc=(0, 1)) - - # LifetimeProcess - raw = cur.execute( - 'SELECT region, tech, vintage, lifetime FROM main.LifetimeProcess' - ).fetchall() - load_element(M.LifetimeProcess, raw, self.viable_rtv, val_loc=(0, 1, 2)) - - # LoanLifetimeTech - raw = cur.execute('SELECT region, tech, lifetime FROM main.LoanLifetimeTech').fetchall() - load_element(M.LoanLifetimeTech, raw, self.viable_rt, (0, 1)) - - # TechInputSplit - if mi: - raw = cur.execute( - 'SELECT region, period, input_comm, tech, min_proportion FROM main.TechInputSplit ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, input_comm, tech, min_proportion FROM main.TechInputSplit ' - ).fetchall() - loaded = load_element(M.TechInputSplit, raw, self.viable_rpit, (0, 1, 2, 3)) - # we need to see if anything was filtered out here and raise warning if so as it may have invalidated - # a blending process and any missing items should be reviewed - if len(loaded) < len(raw): - missing = set(raw) - set(loaded) - for item in sorted(missing, key=lambda x: (x[0], x[1], x[3], x[2])): - region, period, ic, tech, _ = item - logger.warning( - 'Technology Input Split requirement in region %s, period %d for tech %s with input' - 'commodity %s has ' - 'been removed because the tech path with that input is ' - 'invalid/not available/orphan. See the other warnings for this TECH in ' - 'this region-period, and check for availability of all components in data.', - region, - period, - tech, - ic, - ) - - # TechInputSplitAverage - if self.table_exists('TechInputSplitAverage'): - if mi: - raw = cur.execute( - 'SELECT region, period, input_comm, tech, min_proportion ' - 'FROM main.TechInputSplitAverage ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, input_comm, tech, min_proportion ' - 'FROM main.TechInputSplitAverage ' - ).fetchall() - loaded = load_element(M.TechInputSplitAverage, raw, self.viable_rpit, (0, 1, 2, 3)) - # we need to see if anything was filtered out here and raise warning if so as it may have invalidated - # a blending process and any missing items should be reviewed - if len(loaded) < len(raw): - missing = set(raw) - set(loaded) - for item in sorted(missing, key=lambda x: (x[0], x[1], x[3], x[2])): - region, period, ic, tech, _ = item - logger.warning( - 'Technology Input Split requirement in region %s, period %d for tech %s with input' - 'commodity %s has ' - 'been removed because the tech path with that input is ' - 'invalid/not available/orphan. See the other warnings for this TECH in ' - 'this region-period, and check for availability of all components in data.', - region, - period, - tech, - ic, - ) - # TechOutputSplit - if self.table_exists('TechOutputSplit'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, output_comm, min_proportion FROM main.TechOutputSplit ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, output_comm, min_proportion FROM main.TechOutputSplit ' - ).fetchall() - loaded = load_element(M.TechOutputSplit, raw, self.viable_rpto, (0, 1, 2, 3)) - # raise warning regarding any deletions here... similar to input split above - if len(loaded) < len(raw): - missing = set(raw) - set(loaded) - for item in sorted(missing): - region, period, tech, oc, _ = item - logger.warning( - 'Technology Output Split requirement in region %s, period %d for tech %s with output' - 'commodity %s has ' - 'been removed because the tech path with that input is ' - 'invalid/not available/orphan. See the other warnings for this TECH in ' - 'this region-period, and check for availability of all components in data.', - region, - period, - tech, - oc, - ) - - # RenewablePortfolioStandard - if self.table_exists('RPSRequirement'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech_group, requirement FROM main.RPSRequirement ' - ' WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech_group, requirement FROM main.RPSRequirement ' - ).fetchall() - load_element(M.RenewablePortfolioStandard, raw) - - # CostFixed - if mi: - raw = cur.execute( - 'SELECT region, period, tech, vintage, cost FROM main.CostFixed ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, vintage, cost FROM main.CostFixed ' - ).fetchall() - load_element(M.CostFixed, raw, self.viable_rtv, val_loc=(0, 2, 3)) - - # CostInvest - # exclude "existing" vintages by screening for base year and beyond. - # the "viable_rtv" will filter anything beyond view - if mi: - raw = cur.execute( - 'SELECT region, tech, vintage, cost FROM main.CostInvest ' 'WHERE vintage >= ?', - (mi.base_year,), - ).fetchall() - else: - raw = cur.execute('SELECT region, tech, vintage, cost FROM main.CostInvest ').fetchall() - load_element(M.CostInvest, raw, self.viable_rtv, (0, 1, 2)) - - # CostVariable - if mi: - raw = cur.execute( - 'SELECT region, period, tech, vintage, cost FROM main.CostVariable ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, vintage, cost FROM main.CostVariable ' - ).fetchall() - load_element(M.CostVariable, raw, self.viable_rtv, (0, 2, 3)) - - # CostEmissions (and supporting index set) - if self.table_exists('CostEmission'): - if mi: - raw = cur.execute( - 'SELECT region, period, emis_comm from main.CostEmission ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - load_element(M.CostEmission_rpe, raw) - - raw = cur.execute( - 'SELECT region, period, emis_comm, cost from main.CostEmission ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - load_element(M.CostEmission, raw) - else: - raw = cur.execute( - 'SELECT region, period, emis_comm from main.CostEmission ' - ).fetchall() - load_element(M.CostEmission_rpe, raw) - - raw = cur.execute( - 'SELECT region, period, emis_comm, cost from main.CostEmission ' - ).fetchall() - load_element(M.CostEmission, raw) - - # DefaultLoanRate - raw = cur.execute( - "SELECT value FROM main.MetaDataReal WHERE element = 'default_loan_rate'" - ).fetchall() - # do this separately as it is non-indexed, so we need to make a mapping with None - data[M.DefaultLoanRate.name] = {None: raw[0][0]} - - # LoanRate - if mi: - raw = cur.execute( - 'SELECT region, tech, vintage, rate FROM main.LoanRate ' 'WHERE vintage >= ?', - (mi.base_year,), - ).fetchall() - else: - raw = cur.execute('SELECT region, tech, vintage, rate FROM main.LoanRate ').fetchall() - - load_element(M.LoanRate, raw, self.viable_rtv, (0, 1, 2)) - - # MinCapacity - if self.table_exists('MinCapacity'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, min_cap FROM main.MinCapacity ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, min_cap FROM main.MinCapacity ' - ).fetchall() - load_element(M.MinCapacity, raw, self.viable_rt, (0, 2)) - - # MaxCapacity - if self.table_exists('MaxCapacity'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, max_cap FROM main.MaxCapacity ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, max_cap FROM main.MaxCapacity ' - ).fetchall() - load_element(M.MaxCapacity, raw, self.viable_rt, (0, 2)) - - # MinNewCap - if self.table_exists('MinNewCapacity'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, min_cap FROM main.MinNewCapacity ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, min_cap FROM main.MinNewCapacity ' - ).fetchall() - load_element(M.MinNewCapacity, raw, self.viable_rt, (0, 2)) - - # MaxNewCap - if self.table_exists('MaxNewCapacity'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, max_cap FROM main.MaxNewCapacity ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, max_cap FROM main.MaxNewCapacity ' - ).fetchall() - load_element(M.MaxNewCapacity, raw, self.viable_rt, (0, 2)) - - # MaxCapacityGroup - if self.table_exists('MaxCapacityGroup'): - if mi: - raw = cur.execute( - 'SELECT region, period, group_name, max_cap FROM main.MaxCapacityGroup ' - ' WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, group_name, max_cap FROM main.MaxCapacityGroup ' - ).fetchall() - load_element(M.MaxCapacityGroup, raw) - - # MinCapacityGroup - if self.table_exists('MinCapacityGroup'): - if mi: - raw = cur.execute( - 'SELECT region, period, group_name, min_cap FROM main.MinCapacityGroup ' - ' WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, group_name, min_cap FROM main.MinCapacityGroup ' - ).fetchall() - load_element(M.MinCapacityGroup, raw) - - # MinNewCapacityGroup - if self.table_exists('MinNewCapacityGroup'): - if mi: - raw = cur.execute( - 'SELECT region, period, group_name, min_new_cap FROM main.MinNewCapacityGroup ' - ' WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, group_name, min_new_cap FROM main.MinNewCapacityGroup ' - ).fetchall() - load_element(M.MinNewCapacityGroup, raw) - - # MaxNewCapacityGroup - if self.table_exists('MaxNewCapacityGroup'): - if mi: - raw = cur.execute( - 'SELECT region, period, group_name, max_new_cap FROM main.MaxNewCapacityGroup ' - ' WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, group_name, max_new_cap FROM main.MaxNewCapacityGroup ' - ).fetchall() - load_element(M.MaxNewCapacityGroup, raw) - - # MinCapacityShare - if self.table_exists('MinCapacityShare'): - raw = cur.execute( - 'SELECT region, period, tech, group_name, min_proportion FROM main.MinCapacityShare' - ).fetchall() - load_element(M.MinCapacityShare, raw, self.viable_rt, (0, 2)) - - # MaxCapacityShare - if self.table_exists('MaxCapacityShare'): - raw = cur.execute( - 'SELECT region, period, tech, group_name, max_proportion FROM main.MaxCapacityShare' - ).fetchall() - load_element(M.MaxCapacityShare, raw, self.viable_rt, (0, 2)) - - # MinNewCapacityShare - if self.table_exists('MinNewCapacityShare'): - raw = cur.execute( - 'SELECT region, period, tech, group_name, max_proportion FROM main.MinNewCapacityShare' - ).fetchall() - load_element(M.MinCapacityShare, raw, self.viable_rt, (0, 2)) - - # MaxNewCapacityShare - if self.table_exists('MaxNewCapacityShare'): - raw = cur.execute( - 'SELECT region, period, tech, group_name, max_proportion FROM main.MaxNewCapacityShare' - ).fetchall() - load_element(M.MaxCapacityShare, raw, self.viable_rt, (0, 2)) - - # MinActivityGroup - if self.table_exists('MinActivityGroup'): - if mi: - raw = cur.execute( - 'SELECT region, period, group_name, min_act FROM main.MinActivityGroup ' - ' WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, group_name, min_act FROM main.MinActivityGroup ' - ).fetchall() - load_element(M.MinActivityGroup, raw) - - # MaxActivityGroup - if self.table_exists('MaxActivityGroup'): - if mi: - raw = cur.execute( - 'SELECT region, period, group_name, max_act FROM main.MaxActivityGroup ' - ' WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, group_name, max_act FROM main.MaxActivityGroup ' - ).fetchall() - load_element(M.MaxActivityGroup, raw) - - # MinActivityShare - if self.table_exists('MinActivityShare'): - raw = cur.execute( - 'SELECT region, period, tech, group_name, min_proportion FROM main.MinActivityShare' - ).fetchall() - load_element(M.MinActivityShare, raw, self.viable_rt, (0, 2)) - - # MaxActivityShare - if self.table_exists('MaxActivityShare'): - raw = cur.execute( - 'SELECT region, period, tech, group_name, max_proportion FROM main.MaxActivityShare' - ).fetchall() - load_element(M.MaxActivityShare, raw, self.viable_rt, (0, 2)) - - # MaxResource - if self.table_exists('MaxResource'): - raw = cur.execute('SELECT region, tech, max_res FROM main.MaxResource').fetchall() - load_element(M.MaxResource, raw, self.viable_rt, (0, 1)) - - # MaxActivity - if self.table_exists('MaxActivity'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, max_act FROM main.MaxActivity ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, max_act FROM main.MaxActivity ' - ).fetchall() - load_element(M.MaxActivity, raw, self.viable_rt, (0, 2)) - - # MinActivity - if self.table_exists('MinActivity'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, min_act FROM main.MinActivity ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, min_act FROM main.MinActivity ' - ).fetchall() - load_element(M.MinActivity, raw, self.viable_rt, (0, 2)) - - # MinAnnualCapacityFactor - if self.table_exists('MinAnnualCapacityFactor'): - raw = cur.execute( - 'SELECT region, period, tech, output_comm, factor FROM main.MinAnnualCapacityFactor' - ).fetchall() - load_element(M.MinAnnualCapacityFactor, raw, self.viable_rt, (0, 2)) - - # MaxAnnualCapacityFactor - if self.table_exists('MaxAnnualCapacityFactor'): - raw = cur.execute( - 'SELECT region, period, tech, output_comm, factor FROM main.MaxAnnualCapacityFactor' - ).fetchall() - load_element(M.MaxAnnualCapacityFactor, raw, self.viable_rt, (0, 2)) - - # GrowthRateMax - if self.table_exists('GrowthRateMax'): - raw = cur.execute('SELECT region, tech, rate FROM main.GrowthRateMax').fetchall() - load_element(M.GrowthRateMax, raw, self.viable_rt, (0, 1)) - - # GrowthRateSeed - if self.table_exists('GrowthRateSeed'): - raw = cur.execute('SELECT region, tech, seed FROM main.GrowthRateSeed').fetchall() - load_element(M.GrowthRateSeed, raw, self.viable_rt, (0, 1)) - - # EmissionLimit - if self.table_exists('EmissionLimit'): - if mi: - raw = cur.execute( - 'SELECT region, period, emis_comm, value FROM main.EmissionLimit ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, emis_comm, value FROM main.EmissionLimit ' - ).fetchall() - - load_element(M.EmissionLimit, raw) - - # EmissionActivity - # The current emission constraint screens by valid inputs, so if it is NOT - # built in a particular region, this should still be OK - if self.table_exists('EmissionActivity'): - if mi: - raw = cur.execute( - 'SELECT region, emis_comm, input_comm, tech, vintage, output_comm, activity ' - 'FROM main.EmissionActivity ' - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, emis_comm, input_comm, tech, vintage, output_comm, activity ' - 'FROM main.EmissionActivity ' - ).fetchall() - load_element(M.EmissionActivity, raw, self.viable_ritvo, (0, 2, 3, 4, 5)) - - # LinkedTechs - # Note: Both of the linked techs must be viable. As this is non period/vintage - # specific, it should be true that if one is built, the other is also - if self.table_exists('LinkedTech'): - raw = cur.execute( - 'SELECT primary_region, primary_tech, emis_comm, driven_tech FROM main.LinkedTech' - ).fetchall() - loaded = load_element(M.LinkedTechs, raw, self.viable_rtt, (0, 1, 3)) - # The below is a second check (belt and suspenders) and shouldn't really be needed, but it is - # preserved for now. - # we are checking that for each of the rejected LinkedTechs that each of the individual - # techs are also to be rejected (not members of valid_tech) ... if not ODD behavior - # could occur if the linkage is NOT established and the techs operate independently! - if len(loaded) < len(raw): - missing = set(raw) - set(loaded) - valid_techs = self.viable_techs.members - for item in missing: - t1 = item[1] - t2 = item[3] - if t1 in valid_techs or t2 in valid_techs: - # this is a PROBLEM. The commodity network should have removed both the - # driver and driven techs from the valid tech set, and they should not be in - # the valid tech set lest they be allowed in the model independently. - logger.error('Linked Tech item %s is not valid. Check data', item) - print('problem loading linked tech. See log file') - sys.exit(-1) - - # RampUp - if self.table_exists('RampUp'): - raw = cur.execute('SELECT region, tech, rate FROM main.RampUp').fetchall() - load_element(M.RampUp, raw, self.viable_rt, (0, 1)) - - # RampDown - if self.table_exists('RampDown'): - raw = cur.execute('SELECT region, tech, rate FROM main.RampDown').fetchall() - load_element(M.RampDown, raw, self.viable_rt, (0, 1)) - - # CapacityCredit - if self.table_exists('CapacityCredit'): - if mi: - raw = cur.execute( - 'SELECT region, period, tech, vintage, credit FROM main.CapacityCredit ' - 'WHERE period >= ? AND period <= ?', - (mi.base_year, mi.last_demand_year), - ).fetchall() - else: - raw = cur.execute( - 'SELECT region, period, tech, vintage, credit FROM main.CapacityCredit ' - ).fetchall() - load_element(M.CapacityCredit, raw, self.viable_rtv, (0, 2, 3)) - - # PlanningReserveMargin - if self.table_exists('PlanningReserveMargin'): - raw = cur.execute('SELECT region, margin FROM main.PlanningReserveMargin').fetchall() - load_element(M.PlanningReserveMargin, raw) - - # StorageDuration - if self.table_exists('StorageDuration'): - raw = cur.execute('SELECT region, tech, duration FROM main.StorageDuration').fetchall() - load_element(M.StorageDuration, raw, self.viable_rt, (0, 1)) - - # StorageInit - # Not currently supported -- odd behavior and not region-indexed - if self.table_exists('StorageInit'): - raw = cur.execute('SELECT * FROM main.StorageInit').fetchall() - if len(raw) > 0: - logger.warning( - 'Initialization of storage values currently NOT supported.' - ' Values in StorageInit table will be ignored, and storage init value' - ' will be optimized.' - ) - - # For T/S: dump the size of all data elements into the log - if self.debugging: - temp = '\n'.join((f'{k} : {len(v)}' for k, v in data.items())) - logger.info(temp) - - # capture the parameter indexing sets - set_data = self.load_param_idx_sets(data=data) - data.update(set_data) - self.data = data - - return data - - def load_param_idx_sets(self, data: dict) -> dict: - """ - Build a dictionary of sparse sets that can be used for indexing the parameters. - :param data: The parameters to peel out index values from - :return: a dictionary of the set name: values - - The purpose of this function is to use the data we have already captured for the parameters - to make indexing sets in the model. This replaces all of the "lambda" functions which were - previously used to reverse engineer the built parameters. - - Having these sets allows quicker constraint builds because they are the basis of many constraints - - It also enables the model to be serialized by python's pickle by removing functions from the model - definitions - """ - - M: TemoaModel = TemoaModel() # for typing - param_idx_sets = { - M.CostInvest.name: M.CostInvest_rtv.name, - M.EmissionLimit.name: M.EmissionLimitConstraint_rpe.name, - M.MaxActivity.name: M.MaxActivityConstraint_rpt.name, - M.MaxActivityGroup.name: M.MaxActivityGroup_rpg.name, - M.MaxActivityShare.name: M.MaxActivityShareConstraint_rptg.name, - M.MaxAnnualCapacityFactor.name: M.MaxAnnualCapacityFactorConstraint_rpto.name, - M.MaxCapacity.name: M.MaxCapacityConstraint_rpt.name, - M.MaxCapacityGroup.name: M.MaxCapacityGroupConstraint_rpg.name, - M.MaxCapacityShare.name: M.MaxCapacityShareConstraint_rptg.name, - M.MaxNewCapacity.name: M.MaxNewCapacityConstraint_rpt.name, - M.MaxNewCapacityGroup.name: M.MaxNewCapacityGroupConstraint_rpg.name, - M.MaxNewCapacityShare.name: M.MaxNewCapacityShareConstraint_rptg.name, - M.MaxResource.name: M.MaxResourceConstraint_rt.name, - M.MinActivity.name: M.MinActivityConstraint_rpt.name, - M.MinActivityGroup.name: M.MinActivityGroup_rpg.name, - M.MinActivityShare.name: M.MinActivityShareConstraint_rptg.name, - M.MinAnnualCapacityFactor.name: M.MinAnnualCapacityFactorConstraint_rpto.name, - M.MinCapacity.name: M.MinCapacityConstraint_rpt.name, - M.MinCapacityGroup.name: M.MinCapacityGroupConstraint_rpg.name, - M.MinCapacityShare.name: M.MinCapacityShareConstraint_rptg.name, - M.MinNewCapacity.name: M.MinNewCapacityConstraint_rpt.name, - M.MinNewCapacityGroup.name: M.MinNewCapacityGroupConstraint_rpg.name, - M.MinNewCapacityShare.name: M.MinNewCapacityShareConstraint_rptg.name, - M.RenewablePortfolioStandard.name: M.RenewablePortfolioStandardConstraint_rpg.name, - M.ResourceBound.name: M.ResourceConstraint_rpr.name, - } - - res = {} - for p, s in param_idx_sets.items(): - param_data = data.get(p) - if param_data is None: - # no data for this param... nothing to capture for idx set - continue - idxs = list(param_data.keys()) - res[s] = idxs - return res diff --git a/temoa/temoa_model/model_checking/__init__.py b/temoa/temoa_model/model_checking/__init__.py deleted file mode 100644 index 130d21721..000000000 --- a/temoa/temoa_model/model_checking/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/10/24 - -""" diff --git a/temoa/temoa_model/model_checking/commodity_graph.py b/temoa/temoa_model/model_checking/commodity_graph.py deleted file mode 100644 index 50d3a9474..000000000 --- a/temoa/temoa_model/model_checking/commodity_graph.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -A quick & dirty graph of the commodity network for troubleshooting purposes. Future -development may enhance this quite a bit.... lots of opportunity! -""" -import logging -from pathlib import Path -from typing import Iterable - -import gravis as gv -import networkx as nx - -from temoa.temoa_model.model_checking.network_model_data import NetworkModelData, Tech -from temoa.temoa_model.temoa_config import TemoaConfig - -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 2/14/24 - -""" - -logger = logging.getLogger(__name__) - - -def generate_graph( - region, - period, - network_data: NetworkModelData, - demand_orphans: Iterable[Tech], - other_orphans: Iterable[Tech], - driven_techs: Iterable[Tech], - config: TemoaConfig, -): - """ - generate graph for region/period from network data - :param region: region of interest - :param period: period of interest - :param network_data: the data showing all edges to be graphed. "orphans" will be added, if they aren't included - :param demand_orphans: container of orphans [orphanage ;)] - :param other_orphans: container of orphans - :param driven_techs: the "driven" techs in LinkedTech pairs - :param config: - :return: - """ - layers = {} - for c in network_data.all_commodities: - layers[c] = 2 # physical - for c in network_data.source_commodities: - layers[c] = 1 - for c in network_data.demand_commodities[region, period]: - layers[c] = 3 - - edge_colors = {} - edge_weights = {} - # dev note: the generators below do 2 things: put the data in the format expected by the - # graphing code and reduce redundant vintages to 1 representation - # Note that there is a heirarchy here and the latter loops may overwrite earlier color/weight - # decisions, so primary stuff goes last! - all_edges = { - (tech.ic, tech.name, tech.oc) for tech in network_data.available_techs[region, period] - } - # troll through the tech_data and label things of low importance - for edge in all_edges: - tech = edge[1] - characteristics = network_data.tech_data.get(tech, None) - if characteristics and characteristics.get('neg_cost', False): - edge_weights[edge] = 3 - edge_colors[edge] = 'green' - # other growth here... - # label other things of higher importance (these will override) - for edge in ((tech.ic, tech.name, tech.oc) for tech in driven_techs): - edge_colors[edge] = 'blue' - edge_weights[edge] = 2 - all_edges.add(edge) - for edge in ((tech.ic, tech.name, tech.oc) for tech in other_orphans): - edge_colors[edge] = 'yellow' - edge_weights[edge] = 3 - all_edges.add(edge) - for edge in ((tech.ic, tech.name, tech.oc) for tech in demand_orphans): - edge_colors[edge] = 'red' - edge_weights[edge] = 5 - all_edges.add(edge) - - dg = make_nx_graph(all_edges, edge_colors, edge_weights, layers) - - # loop finder... - try: - cycles = nx.simple_cycles(G=dg) - for cycle in cycles: - cycle = list(cycle) - if len(cycle) < 2: # a storage item--not reportable - continue - logger.warning( - 'Found cycle in region %s, period %d. No action needed if this is correct:', - region, - period, - ) - res = ' ' - first = cycle[0] - for node in cycle: - res += f'{node} --> ' - res += first - logger.info(res) - except nx.NetworkXError as e: - logger.warning('NetworkX exception encountered: %s. Loop evaluation NOT performed.', e) - if config.plot_commodity_network: - filename_label = f'{region}_{period}' - _graph_connections( - directed_graph=dg, - file_label=filename_label, - output_path=config.output_path, - ) - - -def _graph_connections( - directed_graph: nx.MultiDiGraph | nx.DiGraph, - file_label: str, - output_path: Path, -): - """ - Make an HTML file containing the network graph - :param file_label: the name of the output file - :param output_path: the output directory - :return: - """ - try: - fig = gv.d3( - directed_graph, - show_menu=True, - show_node_label=True, - node_label_data_source='label', - show_edge_label=True, - edge_label_data_source='label', - edge_curvature=0.4, - graph_height=1000, - zoom_factor=1.0, - node_drag_fix=True, - node_label_size_factor=0.5, - layout_algorithm_active=True, - ) - except Exception as e: - logger.error('Failed to create a figure for the network graph: %s', e) - return - filename = f'Commodity_Graph_{file_label}.html' - output_path = output_path / filename - try: - fig.export_html(output_path, overwrite=True) - except UnicodeEncodeError as e: - logger.warning( - 'Failed to export the network graph into HTML. Bad character in names of commodities or ' - 'tech?\n Error message: %s', - e, - ) - except Exception as e: - logger.error('Failed to export the network graph into HTML. Error message: %s', e) - - -def make_nx_graph(connections, edge_colors, edge_weights, layer_map) -> nx.MultiDiGraph: - """ - Make a nx graph of the commodity network. Additional info passed in to embed it within the nx data - :param connections: an iterable container of connections of format (input_comm, tech, output_comm) - :param edge_colors: An map of the layers. 1: source commodity, 2: physical commodity, 3: demand commodity - :param edge_weights: color map of edges (technologies). Non-entries default to black - :param layer_map: weight map of edges (technologies). Non-entries default to 1.0 - :return: a nx MultiDiGraph - """ - dg = nx.MultiDiGraph() # networkx multi(edge) directed graph - layer_colors = {1: 'limegreen', 2: 'violet', 3: 'darkorange'} - node_size = {1: 50, 2: 15, 3: 30} - for ic, tech, oc in connections: - dg.add_node( - ic, - name=ic, - layer=layer_map[ic], - label=ic, - color=layer_colors[layer_map[ic]], - size=node_size[layer_map[ic]], - ) - dg.add_node( - oc, - name=oc, - layer=layer_map[oc], - label=oc, - color=layer_colors[layer_map[oc]], - size=node_size[layer_map[oc]], - ) - dg.add_edge( - ic, - oc, - label=tech, - color=edge_colors.get((ic, tech, oc), 'black'), - size=edge_weights.get((ic, tech, oc), 1), - ) - return dg diff --git a/temoa/temoa_model/model_checking/commodity_network.py b/temoa/temoa_model/model_checking/commodity_network.py deleted file mode 100644 index 530b276a4..000000000 --- a/temoa/temoa_model/model_checking/commodity_network.py +++ /dev/null @@ -1,401 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/11/24 - -""" - -from collections import defaultdict -from itertools import chain -from logging import getLogger - -from temoa.temoa_model.model_checking.network_model_data import NetworkModelData, Tech - -logger = getLogger(__name__) - - -class CommodityNetwork: - """ - class to hold the network for a particular region/period. Note that the commodity network here is blind - to vintage. Determining whether a connection is valid/invalid is independent of vintage or how many - vintages are represented. It is the responsibility of the commodity network manager to use these - determinations correctly across available vintages - """ - - def __init__(self, region, period: int, model_data: NetworkModelData): - """ - make the network - :param region: region - :param period: period - :param model_data: a NetworkModelData object - """ - # check the marking of source commodities first, as the db may not be configured for source check... - self.model_data = model_data - if not self.model_data.source_commodities: - logger.error( - 'No source commodities discovered when initializing Commodity Network. ' - 'Have source commodities been identified in commodities ' - "table with 's'?" - ) - raise ValueError( - 'Attempted to do source trace with no source commodities marked. ' - 'Have source commodities been identified in commodities ' - "table with 's'?" - ) - self.demand_orphans: set[tuple] = set() - self.other_orphans: set[tuple] = set() - self.good_connections: set[tuple] = set() - self.region = region - self.period = period - # the cataloguing of inputs/outputs by tech is needed for implicit links like via emissions in LinkedTech - self.tech_inputs: dict[str, set[str]] = defaultdict(set) - self.tech_outputs: dict[str, set[str]] = defaultdict(set) - self.connections: dict[str, set[tuple[str, str]]] = defaultdict(set) - """All connections in the model {oc: {(ic, tech), ...}}""" - - self.viable_linked_tech: set[tuple[str, str]] = set() # name-name pairings - - self.orig_connex: set[tuple] = set() - - if not self.model_data.demand_commodities[self.region, self.period]: - raise ValueError( - f'No demand commodities discovered in region {self.region} period {self.period}. Check ' - f'Demand table data' - ) - # dev note: This code was originally designed/tested to run on tuples of (ic, tech_name, oc) - # since the implementation of Tech named tuple, we could switch over to that soon, - # but it will be work to re-work the tests. The networks are smaller this way - # because of reduced redundant links for multi-vintages, but we will have to - # filter the Tech tuples for output generation against the names... - - # scan techs for this r, p - for tech in self.model_data.available_techs[self.region, self.period]: - self.connections[tech.oc].add((tech.ic, tech.name)) - self.tech_inputs[tech.name].add(tech.ic) - self.tech_outputs[tech.name].add(tech.oc) - - # make synthetic connection between linked techs - self.prescreen_linked_tech() - - def remove_tech_by_name(self, tech_name: str) -> None: - """ - Remove a tech by name from the network - :param tech_name: The string name of the tech - :return: - """ - # remove from data sources - self.tech_inputs.pop(tech_name, None) - self.tech_outputs.pop(tech_name, None) - - removed = set() - for oc, s in self.connections.items(): - removals = set() - for ic, name in s: - if name == tech_name: # we found a reference - removals.add((ic, name)) - removed.add((ic, tech_name, oc)) - s -= removals # take out all the removals from the connection - # add every removed tech to the orphan list, so it can be processed by the manager - for r in removed: - self.other_orphans.add(r) - logger.debug('removed %s with a by-name removal', r) - - def reload(self, connections: set[Tech]): - """ - reload the network model with a new set of connections and clear existing ones - :param connections: a set of connections (Techs) to evaluate - :return: None - """ - self.tech_inputs.clear() - self.tech_outputs.clear() - self.connections.clear() - self.orig_connex.clear() - # reload 'em - for tech in connections: - self.connections[tech.oc].add((tech.ic, tech.name)) - self.tech_inputs[tech.name].add(tech.ic) - self.tech_outputs[tech.name].add(tech.oc) - - def get_valid_tech(self) -> set[Tech]: - return { - tech - for tech in self.model_data.available_techs[self.region, self.period] - if (tech.ic, tech.name, tech.oc) in self.good_connections - } - - def get_demand_side_orphans(self) -> set[Tech]: - return { - tech - for tech in self.model_data.available_techs[self.region, self.period] - if (tech.ic, tech.name, tech.oc) in self.demand_orphans - } - - def get_other_orphans(self) -> set[Tech]: - return { - tech - for tech in self.model_data.available_techs[self.region, self.period] - if (tech.ic, tech.name, tech.oc) in self.other_orphans - } - - def prescreen_linked_tech(self): - """Screen the linked tech to ensure both members of the available link are available. If not, remove strays""" - - for r, driver, emission, driven in self.model_data.available_linked_techs: - if r == self.region: - # check that the driver & driven techs both exist... we only check by NAME for LinkedTech - if driver in self.tech_outputs and driven in self.tech_outputs: # we're gtg. - logger.debug( - 'Both %s and %s are available in region %s, period %s to establish link', - driver, - driven, - self.region, - self.period, - ) - self.viable_linked_tech.add((driver, driven)) - - # else, document errors in linkage... - elif driver not in self.tech_outputs and driven not in self.tech_outputs: - # neither tech is present, not a problem - logger.debug( - 'Note (no action reqd.): Neither linked tech %s nor %s are active in region %s, period %s', - driver, - driven, - self.region, - self.period, - ) - elif driver in self.tech_outputs and driven not in self.tech_outputs: - logger.info( - 'No driven linked tech available for driver %s in region %s, period %d. ' - 'Driver REMOVED.', - driver, - self.region, - self.period, - ) - self.remove_tech_by_name(driver) - else: # ... only the driven tech is present - logger.warning( - 'Driven linked tech %s is not connected to an active or available ' - 'driver in region %s, period %d. Driven tech REMOVED.', - driven, - self.region, - self.period, - ) - self.remove_tech_by_name(driven) - - def analyze_network(self): - # Due to fact that each "scan" is static, we may discover that only 1 member of a linked tech - # is viable. IF that happens, we need to remove the partner and go again. In a bizarre situation, - # this may be an iterative process, so we might as well do it till done... - done = False - demand_side_connections = set() - while not done: - done = True # assume the best! - # dev note: send a copy of connections... - # it is consumed by the function. (easier than managing it in the recursion) - discovered_sources, demand_side_connections = _visited_dfs( - self.model_data.demand_commodities[self.region, self.period], - self.model_data.source_commodities, - self.connections.copy(), - ) - self.good_connections = _mark_good_connections( - good_ic=discovered_sources, connections=demand_side_connections.copy() - ) - observed_tech = {tech for (ic, tech, oc) in self.good_connections} - sour_links = set() - for link in self.viable_linked_tech: - if not all((link[0] in observed_tech, link[1] in observed_tech)): - sour_links.add(link) - self.remove_tech_by_name(link[0]) - self.remove_tech_by_name(link[1]) - done = False - logger.warning( - 'Both members of link %s are not valid in the network. Both members REMOVED in region %s, period %s', - link, - self.region, - self.period, - ) - self.viable_linked_tech -= sour_links - - logger.debug( - 'Got %d good technologies (possibly multi-vintage) from %d techs in region %s, period %d', - len(self.good_connections), - len(tuple(chain(*self.connections.values()))), - self.region, - self.period, - ) - - # Sort out the demand-side and supply-side orphans - # Now we should have: - # 1. The original connections - # 2. The demand-side connections from the first search (all things backward from Demands) - # 3. The "good" connections that have full linkage from source back to demand - - # So we can infer (these are set operations): - # 4. demand-side orphans = demand_side_connections - good_connections - # 5. other orphans = original_connections - demand_side_connections - good_connections - - # flat lists are easier for comparison, so... - self.orig_connex: set[tuple] = { - (ic, tech, oc) for oc in self.connections for (ic, tech) in self.connections[oc] - } - # dev note: recall, the demand connex are inventoried by IC for use in the 2nd search, so we need to poll by IC... - demand_connex: set[tuple] = { - (ic, tech, oc) - for ic in demand_side_connections - for (oc, tech) in demand_side_connections[ic] - } - - self.demand_orphans = demand_connex - self.good_connections - # we may already have some removed things in "other orphans" so add to it... - self.other_orphans |= self.orig_connex - demand_connex - self.good_connections - - if self.other_orphans: - logger.info( - 'Source tracing revealed %d "other" (non-demand) orphaned processes in region %s, period %d.', - len(self.other_orphans), - self.region, - self.period, - ) - for orphan in sorted(self.other_orphans, key=lambda x: x[1]): - logger.info( - 'Discovered orphaned process: %s in region %s, period %d', - orphan, - self.region, - self.period, - ) - if self.demand_orphans: - logger.info( - 'Source tracing revealed %d demand-side orphaned processes in region %s, period %d.', - len(self.demand_orphans), - self.region, - self.period, - ) - for orphan in sorted(self.demand_orphans, key=lambda x: x[1]): - logger.info( - 'Discovered orphaned process: %s in region %s, period %d', - orphan, - self.region, - self.period, - ) - - def unsupported_demands(self) -> set[str]: - """ - Look for demand commodities that are not connected via a "good connection" - :return: set of improperly supported demands - """ - supported_demands = {t[2] for t in self.good_connections} - bad_demands = { - d - for d in self.model_data.demand_commodities[self.region, self.period] - if d not in supported_demands - } - return bad_demands - - -def _mark_good_connections( - good_ic: set[str], connections: dict[str, set[tuple]], start: str | None = None -) -> set[tuple]: - """ - Now that we have ID'ed the good ic that have been discovered, we need to work back up - the chain of visited nodes to identify the good connections (this is the reverse of the - previous search where we looked backward from demand. Here we look up from the Input Commodities - :param start: The current node to start from - :param good_ic: The set of Input Commodities that were discovered by the first search - :param connections: The set of connections to analyze. It is consumed by the function via pop() - :return: - """ - - # end conditions... - if not good_ic and not start: # nothing to discover - return set() - else: - good_connections = set() - - if not start: - for start in good_ic: - good_connections |= _mark_good_connections(good_ic, connections, start=start) - - # recurse... - for oc, tech in connections.pop(start, []): # prevent re-expanding this later by popping - good_connections.add((start, tech, oc)) - # explore all upstream - good_connections |= _mark_good_connections( - good_ic=good_ic, connections=connections, start=oc - ) - return good_connections - - -def _visited_dfs( - start_nodes: set[str], - end_nodes: set[str], - connections: dict[str, set[tuple]], - current_start=None, -) -> tuple[set, dict[str, set[tuple]]]: - """ - recursive depth-first search to identify discovered source nodes and connections from - a start point and set of connections - :param start_nodes: the set of demand commodities (oc ∈ demand) - :param end_nodes: source nodes, or ones traceable to source nodes - :param connections: the connections to explore {output: {(ic, tech)}} - :param current_start: the current node (ic) index - :return: the set of viable tech tuples (ic, tech, oc) - """ - # setup... - discovered_sources = set() - visited = defaultdict(set) - - # end conditions... - if not current_start and not start_nodes: # no more starts, we're done - return set(), dict() - if not current_start: # start from each node in the starts - for node in start_nodes: - ds, v = _visited_dfs( - start_nodes=start_nodes, - end_nodes=end_nodes, - connections=connections, - current_start=node, - ) - discovered_sources.update(ds) - for k in v: - visited[k].update(v[k]) - return discovered_sources, visited - - # we have a start node, dig from here. - for ic, tech in connections.pop(current_start, []): # we can pop, no need to re-explore - visited[ic].add((current_start, tech)) - if ic in end_nodes: # we have struck gold - # add the current ic to discoveries - discovered_sources.add(ic) - else: - # explore from here - ds, v = _visited_dfs( - start_nodes, - end_nodes, - connections, - current_start=ic, - ) - discovered_sources.update(ds) - for k in v: - visited[k].update(v[k]) - return discovered_sources, visited diff --git a/temoa/temoa_model/model_checking/commodity_network_manager.py b/temoa/temoa_model/model_checking/commodity_network_manager.py deleted file mode 100644 index 2bfd2e392..000000000 --- a/temoa/temoa_model/model_checking/commodity_network_manager.py +++ /dev/null @@ -1,206 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/11/24 - -This module is responsible for running the network analysis and generating the filters -that can be used to filter down the data to valid data. It uses the NetworkModelData -and subdivides this into regions and periods and analyzes each one. - -""" - -from collections import defaultdict -from logging import getLogger -from typing import Iterable - -from temoa.temoa_model.model_checking.commodity_graph import generate_graph -from temoa.temoa_model.model_checking.commodity_network import CommodityNetwork -from temoa.temoa_model.model_checking.element_checker import ViableSet -from temoa.temoa_model.model_checking.network_model_data import NetworkModelData, Tech -from temoa.temoa_model.temoa_config import TemoaConfig - -logger = getLogger(__name__) - - -class CommodityNetworkManager: - """Manager to run the network analysis recursively for a region and set of periods""" - - def __init__(self, periods: Iterable[str | int], network_data: NetworkModelData): - self.regions = None - self.analyzed = False - self.periods = sorted(periods) - self.orig_data = network_data - self.filtered_data: NetworkModelData | None = None - - # outputs / saves for graphing networks - # orig_tech is saved copy of the links for graphing purposes - # this is a quick "deep copy" - self.orig_tech = {k: v.copy() for k, v in network_data.available_techs.items()} - self.demand_orphans: dict[tuple[str, str], set[Tech]] = defaultdict(set) - self.other_orphans: dict[tuple[str, str], set[Tech]] = defaultdict(set) - - def _analyze_region(self, region: str, data: NetworkModelData): - """ - Iteratively whittle away at the region, within the window until no new invalid techs appear - - Note: this is done in a "while" loop because actions taken in one particular period *might* - have repercussions in another period. For instance, if a tech is deemed an "orphan" in - period 5 and needs to be removed, but it was alive in periods 1-4, those periods now need to - be re-analyzed post-removal. In practice, this seems to work very quickly with few iterations, but some - datasets with complex lifetime relationships between dependent techs and few alternative - vintages or such may take a few iterations to clean. - """ - done = False - iter_count = 0 - - while not done: - iter_count += 1 - demand_orphans_this_pass: set[Tech] = set() - other_orphans_this_pass: set[Tech] = set() - for period in self.periods: - cn = CommodityNetwork(region=region, period=period, model_data=data) - cn.analyze_network() - - # check for unsupported demands.. - unsupported_demands = cn.unsupported_demands() - for commodity in unsupported_demands: - logger.error( - 'Demand %s is not supported back to source commodities in region %s period %d', - commodity, - cn.region, - cn.period, - ) - - # gather orphans... - new_demand_orphans = cn.get_demand_side_orphans() - new_other_orphans = cn.get_other_orphans() - - # add the orphans to the orphanages... - self.demand_orphans[region, period] |= new_demand_orphans - self.other_orphans[region, period] |= new_other_orphans - - # add them to the collections for the "pass" - demand_orphans_this_pass |= new_demand_orphans - other_orphans_this_pass |= new_other_orphans - - # clean up the good tech listing and decide whether to go again... - # dev note: we could clean up the good techs in the loop, before processing next period, but - # by doing it this way, we properly capture full set of orphans by period/region - # for later use - for period in self.periods: - # any orphans need to be removed from all periods where they exist - data.available_techs[region, period] -= demand_orphans_this_pass - data.available_techs[region, period] -= other_orphans_this_pass - - done = not demand_orphans_this_pass and not other_orphans_this_pass - logger.debug( - 'Finished %d pass(es) on region %s during removal of orphan techs', - iter_count, - region, - ) - logger.debug( - 'Removed %d orphans', len(demand_orphans_this_pass) + len(other_orphans_this_pass) - ) - for orphan in sorted(demand_orphans_this_pass): - logger.warning('Removed %s as demand-side orphan', orphan) - for orphan in sorted(other_orphans_this_pass): - logger.warning('Removed %s as other orphan', orphan) - - def analyze_network(self) -> bool: - """ - Analyze all regions in the model, excluding exchanges - :return: True if all regions come back "clean" (no orphans), False otherwise - """ - # NOTE: by excluding '-' regions, we are deciding NOT to screen any regional exchange techs, - # which would be a whole different level of difficulty to do. - - self.filtered_data = self.orig_data.clone() - self.regions = {r for (r, p) in self.orig_data.available_techs if '-' not in r} - for region in self.regions: - logger.info('starting network analysis for region %s', region) - self._analyze_region(region, data=self.filtered_data) - self.analyzed = True - orphans_found = any(self.demand_orphans.values()) or any(self.other_orphans.values()) - return not orphans_found - - def build_filters(self) -> dict[str, ViableSet]: - """populate the filters from the data, after network analysis""" - if not self.analyzed: - raise RuntimeError('Trying to build filters before network analysis. Code error') - valid_ritvo = set() - valid_rtv = set() - valid_rt = set() - valid_rpit = set() - valid_rpto = set() - valid_t = set() - valid_input_commodities = set() - valid_output_commodities = set() - valid_vintages = set() - for r, p in self.filtered_data.available_techs: - for tech in self.filtered_data.available_techs[r, p]: - valid_ritvo.add((tech.region, tech.ic, tech.name, tech.vintage, tech.oc)) - valid_rtv.add((tech.region, tech.name, tech.vintage)) - valid_rt.add((tech.region, tech.name)) - valid_rpit.add((tech.region, p, tech.ic, tech.name)) - valid_rpto.add((tech.region, p, tech.name, tech.oc)) - valid_t.add(tech.name) - valid_input_commodities.add(tech.ic) - valid_output_commodities.add(tech.oc) - valid_vintages.add(tech.vintage) - - filts = { - 'ritvo': ViableSet( - elements=valid_ritvo, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES - ), - 'rtv': ViableSet( - elements=valid_rtv, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES - ), - 'rt': ViableSet( - elements=valid_rt, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES - ), - 'rpit': ViableSet(valid_rpit, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES), - 'rpto': ViableSet(valid_rpto, exception_loc=0, exception_vals=ViableSet.REGION_REGEXES), - 't': ViableSet(elements=valid_t), - 'v': ViableSet(elements=valid_vintages), - 'ic': ViableSet(elements=valid_input_commodities), - 'oc': ViableSet(elements=valid_output_commodities), - } - return filts - - def analyze_graphs(self, config: TemoaConfig): - if not self.analyzed: - raise RuntimeError( - 'Trying to build/analyze graphs before network analysis. Code error' - ) - for region in self.regions: - for period in self.periods: - generate_graph( - region, - period, - network_data=self.orig_data, - demand_orphans=self.demand_orphans[region, period], - other_orphans=self.other_orphans[region, period], - driven_techs=self.orig_data.get_driven_techs(region, period), - config=config, - ) diff --git a/temoa/temoa_model/model_checking/element_checker.py b/temoa/temoa_model/model_checking/element_checker.py deleted file mode 100644 index 30f129b4b..000000000 --- a/temoa/temoa_model/model_checking/element_checker.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/24/24 - -class to hold members of "validation sets" used by loader to validate elements as they are -read in to the DataPortal or other structure. Motivation is to contain the values AND -any extra validation information in one instance. - -""" -import re -from collections.abc import Iterable, Sequence -from operator import itemgetter - - -class ViableSet: - # automatic approvals for regions. Stored here for reference - REGION_REGEXES = [ - r'\+', # any grouping with a plus sign - r'^global\Z', # the exact word 'global' with no leader/trailer - ] - - def __init__( - self, - elements: Iterable, - exception_loc: int | None = None, - exception_vals: Iterable[str] | None = None, - ): - """ - Construct a match object. The direct matches and the location/item for exceptions - :param elements: The core elements to match against - :param exception_loc: The location to consider for the exception - :param exception_vals: Iterable of exception regexes to match against - - Example: core elements {('a', 1), ('a', 2)} - exceptions {0: {r'dog', r'cat'}} - should "match" for ('a', 1), ('a', 2), ('dog', 1), ('cat', 1) - fail for: ('a', 4), ('cat', 3), etc. - """ - - self._elements: set[tuple] = {self.tupleize(element) for element in elements} - - if exception_vals and exception_loc is None: - raise ValueError('cannot have exception_vals without a location') - self._exception_loc = exception_loc - self._exceptions = exception_vals - self.non_excepted_items = set() - - self.calc_dim() - - if self._exceptions and self.dim > 0: - self._update() - - def calc_dim(self): - # calculate the dimension... - if self._elements: - an_element = self._elements.pop() - self._elements.add(an_element) - self.dim = len(an_element) - else: - self.dim = 0 - - def _update(self): - """construct the set of non-excepted items in tuple format""" - # we need to remove the item at the "excepted" location - locs = list(range(self.dim)) - locs.remove(self._exception_loc) - if len(locs) == 0: - self.non_excepted_items = None - return - self.non_excepted_items = {itemgetter(*locs)(t) for t in self._elements} - - @property - def exception_loc(self): - return self._exception_loc - - @property - def val_exceptions(self) -> Iterable[str]: - return self._exceptions - - def set_val_exceptions(self, exception_loc: int, exception_vals: Iterable): - if exception_loc is None or exception_vals is None: - raise ValueError('cannot have exception_vals without a location') - self._exception_loc = exception_loc - self._exceptions = exception_vals - self._update() - - @property - def member_tuples(self): - """the elements of the membership set AS TUPLES, including singleton tuples""" - return self._elements - - @staticmethod - def tupleize(element): - return element if isinstance(element, tuple) else (element,) - - @member_tuples.setter - def member_tuples(self, elements): - self._elements = {self.tupleize(element) for element in elements} - self.calc_dim() - self._update() - - @property - def members(self): - """the members of the validation set""" - if self.dim > 1: - return self.member_tuples - else: - return {t[0] for t in self.member_tuples} - - -# dev note: The reason for this filtering construct is to allow passage of items that either -# match the basic 'valid' elements exactly OR match the exception regex in one -# position and match all other elements. The use case is for regions, where we -# want to match explicit regions exactly, but also match 'global' and -# region groups where we have a '+' sign in the name without having to -# create all the possible permutations. An alternate (rejected) approach -# would be to re-create the region groups for something like 'global' to the -# actually legal combinations on-the-fly from data, which would be more complex -# and mask the intent of the original data. -def filter_elements( - values: Sequence[tuple], validation: ViableSet, value_locations: tuple = (0,) -) -> list: - """ - Filter elements according to a set of criteria. - :param values: the values to filter - :param validation: the validation item to use for filtering - :param value_locations: the locations in the value items corresponding to the values in the validation - :return: a list of filtered elements - - Ex: if filtering by (region, tech, vintage) and the data is (region, _, _, tech, vintage, value) we need to identify - the location of r, t, v in the element under review by the tuple (0, 3, 4) - """ - if not isinstance(validation, ViableSet): - raise ValueError("'validation' must be an instance of ViableSet") - if 0 < validation.dim != len(value_locations): - # if validation.dim == 0, it is empty, but might still be used for exempted items - raise ValueError('the value locations must have same dimensionality as the validation set') - - # determine the location of the non-exempted items for comparison by removing the exempted location - non_exempt_item_locs = None - if validation.val_exceptions: - non_exempt_item_locs = list(value_locations) - non_exempt_item_locs.remove(validation.exception_loc) - - res = [] - - for item in values: - # check the "base case" first: it's in the fundamental elements - element = itemgetter(*value_locations)(item) - if not isinstance(element, tuple): - element = (element,) - if element in validation.member_tuples: - res.append(item) - elif validation.val_exceptions: # check each of the exceptions - if ( - validation.non_excepted_items - and itemgetter(*non_exempt_item_locs)(item) not in validation.non_excepted_items - ): - continue - for val_exception in validation.val_exceptions: - if re.search(val_exception, str(item[validation.exception_loc])): - res.append(item) - break - - return res diff --git a/temoa/temoa_model/model_checking/network_model_data.py b/temoa/temoa_model/model_checking/network_model_data.py deleted file mode 100644 index d15c6e631..000000000 --- a/temoa/temoa_model/model_checking/network_model_data.py +++ /dev/null @@ -1,256 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/10/24 - -The purpose of this module is to build an Object to hold all of the network data for the entire -model in a usable format for the commodity_network_manager to use in building the individual -network. - -""" - -import logging -import sqlite3 -from collections import defaultdict, namedtuple -from itertools import chain -from typing import Self, Any - -import deprecated -from pyomo.core import ConcreteModel - -from temoa.extensions.myopic.myopic_index import MyopicIndex -from temoa.temoa_model.temoa_model import TemoaModel - -Tech = namedtuple('Tech', ['region', 'ic', 'name', 'vintage', 'oc']) -LinkedTech = namedtuple('LinkedTech', ['region', 'driver', 'emission', 'driven']) - -logger = logging.getLogger(__name__) - - -class NetworkModelData: - """A simple encapsulation of data needed for the Commodity Network""" - - def __init__(self, **kwargs): - self.demand_commodities: dict[tuple[str, int | str], set[str]] = kwargs.get( - 'demand_commodities' - ) - self.source_commodities: set[str] = kwargs.get('source_commodities') - self.all_commodities: set[str] = kwargs.get('all_commodities') - # dict of (region, period): {Tech} - self._available_techs: dict[tuple[str, int | str], set[Tech]] = kwargs.get( - 'available_techs' - ) - self.available_linked_techs: set[LinkedTech] = kwargs.get('available_linked_techs', set()) - # a catch-all for indicators for techs...growth potential - # dev note: this is indexed by tech name, and is blind to vintage. The intended use is in the - # network graph, which is also blind to vintage. So it is interpreted as "at least one" - # tech (and likely all) have/has this characteristic if multi-vintage - self.tech_data: dict[str, dict[str, Any]] = defaultdict(dict) - - def clone(self) -> Self: - """create a copy of the current""" - return NetworkModelData( - demand_commodities=self.demand_commodities.copy(), - source_commodities=self.source_commodities.copy(), - all_commodities=self.all_commodities.copy(), - available_techs=self.available_techs.copy(), - available_linked_techs=self.available_linked_techs.copy(), - ) - - @property - def available_techs(self) -> dict[tuple[str, int | str], set[Tech]]: - return self._available_techs - - @available_techs.setter - def available_techs(self, available_techs: dict[tuple[str, int], set[Tech]]) -> None: - # check for region violations - for r, p in available_techs.keys(): - for tech in available_techs[r, p]: - if tech.region != r: - raise ValueError( - f'Improperly constructed set of techs for region {r}, tech: {tech}' - ) - self._available_techs = available_techs - - def update_tech_data(self, tech: str, element: str, value: Any) -> None: - """ - Update a data element for a tech - :param tech: the tech - :param element: the string name of the data element - :param value: the new value - :return: - """ - self.tech_data[tech][element] = value - - def get_driven_techs(self, region, period) -> set[Tech]: - """identifies all linked techs by name from the linked tech names""" - driven_tech_names = {linked_tech.driven for linked_tech in self.available_linked_techs} - driven_techs = { - tech for tech in self.available_techs[region, period] if tech.name in driven_tech_names - } - return driven_techs - - def __str__(self): - return ( - f'all commodities: {len(self.all_commodities)}, demand commodities: {len(self.demand_commodities)}, ' - f'source commodities: {len(self.source_commodities)},' - f'available techs: {len(tuple(chain(*self.available_techs.values())))}, ' - f'linked techs: {len(self.available_linked_techs)}' - ) - - -def build(data, *args, **kwargs) -> NetworkModelData: - builder = _get_builder(data) - return builder(data, *args, **kwargs) - - -def _get_builder(data): - if isinstance(data, TemoaModel | ConcreteModel): - # dev note: The built instance will be a ConcreteModel - builder = _build_from_model - return builder - elif isinstance(data, sqlite3.Connection): - builder = _build_from_db - return builder - else: - raise NotImplementedError('cannot build from that type of data') - - -@deprecated.deprecated('no longer supported... build from db connection instead') -def _build_from_model(M: TemoaModel, myopic_index=None) -> NetworkModelData: - """Build a NetworkModelData from a TemoaModel""" - if myopic_index is not None: - raise NotImplementedError('cannot build network data from model using a MyopicIndex') - res = NetworkModelData() - res.all_commodities = set(M.commodity_all) - res.source_commodities = set(M.commodity_source) - dem_com = defaultdict(set) - for r, p, d in M.Demand: - dem_com[r, p].add(d) - res.demand_commodities = dem_com - techs = defaultdict(set) - # scan non-annual techs... - for r, p, s, d, ic, tech, v, oc in M.activeFlow_rpsditvo: - techs[r, p].add(Tech(r, ic, tech, v, oc)) - # scan annual techs... - for r, p, ic, tech, v, oc in M.activeFlow_rpitvo: - techs[r, p].add(Tech(r, ic, tech, v, oc)) - res.available_techs = techs - linked_techs = set() - for r, driver, emission, driven in M.LinkedTechs: - linked_techs.add(LinkedTech(r, driver, emission, driven)) - res.available_linked_techs = linked_techs - logger.debug('built network data: %s', res.__str__()) - return res - - -def _build_from_db( - con: sqlite3.Connection, myopic_index: MyopicIndex | None = None -) -> NetworkModelData: - """Build NetworkModelData object from a sqlite database.""" - # dev note: sadly, this will duplicate some code, I think. Perhaps a later refactoring can - # re-use some of the hybrid loader code in a clear way. Not too much overlap, though - res = NetworkModelData() - cur = con.cursor() - raw = cur.execute('SELECT Commodity.name FROM Commodity').fetchall() - res.all_commodities = {t[0] for t in raw} - raw = cur.execute("SELECT Commodity.name FROM Commodity WHERE flag = 's'").fetchall() - res.source_commodities = {t[0] for t in raw} - # use Demand to get the region, period specific demand comms - raw = cur.execute('SELECT region, period, commodity FROM main.Demand').fetchall() - demand_dict = defaultdict(set) - for r, p, d in raw: - demand_dict[r, p].add(d) - res.demand_commodities = demand_dict - # need lifetime to screen techs... :/ - default_lifetime = TemoaModel.default_lifetime_tech - if not myopic_index: - query = ( - ' SELECT main.Efficiency.region, input_comm, Efficiency.tech, Efficiency.vintage, output_comm, ' - f' coalesce(main.LifetimeProcess.lifetime, main.LifetimeTech.lifetime, {default_lifetime}) AS lifetime ' - ' FROM main.Efficiency ' - ' LEFT JOIN main.LifetimeProcess ' - ' ON main.Efficiency.tech = LifetimeProcess.tech ' - ' AND main.Efficiency.vintage = LifetimeProcess.vintage ' - ' AND main.Efficiency.region = LifetimeProcess.region ' - ' LEFT JOIN main.LifetimeTech ' - ' ON main.Efficiency.tech = main.LifetimeTech.tech ' - ' AND main.Efficiency.region = main.LifeTimeTech.region ' - ' JOIN TimePeriod ' - ' ON Efficiency.vintage = TimePeriod.period ' - ) - else: # we need to pull from the MyopicEfficiency Table - query = ( - ' SELECT main.MyopicEfficiency.region, input_comm, MyopicEfficiency.tech, MyopicEfficiency.vintage, output_comm, ' - f' coalesce(main.LifetimeProcess.lifetime, main.LifetimeTech.lifetime, {default_lifetime}) AS lifetime ' - ' FROM main.MyopicEfficiency ' - ' LEFT JOIN main.LifetimeProcess ' - ' ON main.MyopicEfficiency.tech = LifetimeProcess.tech ' - ' AND main.MyopicEfficiency.vintage = LifetimeProcess.vintage ' - ' AND main.MyopicEfficiency.region = LifetimeProcess.region ' - ' LEFT JOIN main.LifetimeTech ' - ' ON main.MyopicEfficiency.tech = main.LifetimeTech.tech ' - ' AND main.MyopicEfficiency.region = main.LifeTimeTech.region ' - ' JOIN TimePeriod ' - ' ON MyopicEfficiency.vintage = TimePeriod.period ' - # f' WHERE main.MyopicEfficiency.vintage <= {myopic_index.last_demand_year}' - ) - raw = cur.execute(query).fetchall() - periods = cur.execute('SELECT period FROM TimePeriod').fetchall() - # need to exclude the final year which is a non-demand year and should have no tech data - # This ensures that the periods in this will match the periods in the hybrid loader. - periods = [t[0] for t in sorted(periods)[:-1]] - # filter further if myopic - if myopic_index: - periods = { - p for p in periods if myopic_index.base_year <= p <= myopic_index.last_demand_year - } - techs = defaultdict(set) - living_techs = set() # for screening the linked techs below - # filter out the dead ones... - for element in raw: - for p in periods: - (r, ic, tech, v, oc, lifetime) = element - if v <= p < v + lifetime: - techs[r, p].add(Tech(r, ic, tech, v, oc)) - living_techs.add(tech) - res.available_techs = techs - - # pick up the linked techs... - raw = cur.execute( - 'SELECT primary_region, primary_tech, emis_comm, driven_tech FROM main.LinkedTech' - ).fetchall() - res.available_linked_techs = { - LinkedTech(region=r, driver=driver, emission=emiss, driven=driven) - for (r, driver, emiss, driven) in raw - if driver in living_techs and driven in living_techs - } - - # pick up negative costs... - raw = cur.execute('SELECT DISTINCT tech FROM CostVariable where cost < 0').fetchall() - for row in raw: - tech = row[0] - res.update_tech_data(tech=tech, element='neg_cost', value=True) - logger.debug('built network data: %s', res.__str__()) - return res diff --git a/temoa/temoa_model/model_checking/validators.py b/temoa/temoa_model/model_checking/validators.py deleted file mode 100644 index 16799edde..000000000 --- a/temoa/temoa_model/model_checking/validators.py +++ /dev/null @@ -1,329 +0,0 @@ -""" -These "validators" are used as validation tools for several elements in the TemoaModel - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 9/27/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import re -from logging import getLogger -from typing import TYPE_CHECKING - -import deprecated -from pyomo.environ import NonNegativeReals - -if TYPE_CHECKING: - from temoa.temoa_model.temoa_model import TemoaModel - -logger = getLogger(__name__) - - -def validate_linked_tech(M: 'TemoaModel') -> bool: - """ - A validation that for all the linked techs, they have the same lifetime in each possible vintage - - The Constraint that this check supports is indexed by a set that fundamentally expands the (r, t, e) - index of the LinkedTech data table (where t==driver tech) to include valid vintages. - The implication is that there is a driven tech in the same region, of - the same vintage, with the same lifetime as the driver tech. We should check that. - - We can filter the index down to (r, t_driver, v, e) and then query the lifetime of the driver and driven - to ensure they are the same - - :param M: - :return: True if "OK" else False - """ - logger.debug('Starting to validate linked techs.') - - base_idx = M.LinkedEmissionsTechConstraint_rpsdtve - - drivers = {(r, t, v, e) for r, p, s, d, t, v, e in base_idx} - for r, t_driver, v, e in drivers: - # get the linked tech of same region, emission - t_driven = M.LinkedTechs[r, t_driver, e] - - # check for equality in lifetimes for vintage v - driver_lifetime = M.LifetimeProcess[r, t_driver, v] - try: - driven_lifetime = M.LifetimeProcess[r, t_driven, v] - except KeyError: - logger.error( - 'Linked Tech Error: Driven tech %s does not have a vintage entry %d to match driver %s', - t_driven, - v, - t_driver, - ) - print('Problem with Linked Tech validation: See log file') - return False - if driven_lifetime != driver_lifetime: - logger.error( - 'Linked Tech Error: Driven tech %s has lifetime %d in vintage %d while driver tech %s has lifetime %d', - t_driven, - driven_lifetime, - v, - t_driver, - driver_lifetime, - ) - print('Problem with Linked Tech validation: See log file') - return False - - return True - - -def no_slash_or_pipe(M: 'TemoaModel', element) -> bool: - """ - No slash character in element - :param M: - :param element: - :return: - """ - if isinstance(element, int | float): - return True - good = '/' not in str(element) and '|' not in str(element) - if not good: - logger.error('no slash "/" or pipe "|" character is allowed in: %s', str(element)) - return False - return True - - -def region_check(M: 'TemoaModel', region) -> bool: - """ - Validate the region name (letters + numbers only + underscore) - """ - # screen against illegal names - illegal_region_names = { - 'global', - } - if region in illegal_region_names: - return False - - # if this matches, return is true, fail -> false - if re.match(r'[a-zA-Z0-9_]+\Z', region): # string that has only letters and numbers - return True - return False - - -def linked_region_check(M: 'TemoaModel', region_pair) -> bool: - """ - Validate a pair of regions (r-r format where r ∈ M.R ) - """ - linked_regions = re.match(r'([a-zA-Z0-9_]+)\-([a-zA-Z0-9_]+)\Z', region_pair) - if linked_regions: - r1 = linked_regions.group(1) - r2 = linked_regions.group(2) - if ( - all(r in M.regions for r in (r1, r2)) and r1 != r2 - ): # both captured regions are in the set of M.R - return True - return False - - -def region_group_check(M: 'TemoaModel', rg) -> bool: - """ - Validate the region-group name (region or regions separated by '+') - """ - if '-' in rg: # it should just be evaluated as a linked_region - return linked_region_check(M, rg) - if re.search(r'\A[a-zA-Z0-9\+_]+\Z', rg): - # it has legal characters only - if '+' in rg: - # break up the group - contained_regions = rg.strip().split('+') - if all(t in M.regions for t in contained_regions) and len( - set(contained_regions) - ) == len(contained_regions): # no dupes - return True - else: # it is a singleton - return (rg in M.regions) or rg == 'global' - return False - - -@deprecated.deprecated('needs to be updated if re-instated to accommodate group restructuring') -def tech_groups_set_check(M: 'TemoaModel', rg, g, t) -> bool: - """ - Validate this entry to the tech_groups set - :param M: the model - :param rg: region-group index - :param g: tech group name - :param t: tech - :return: True if valid entry, else False - """ - return all((region_group_check(M, rg), g in M.tech_group_names, t in M.tech_all)) - - -# TODO: Several of these param checkers below are not in use because the params cannot -# accept new values for the indexing sets that aren't in an already-constructed set. Now that we are -# making the GlobalRegionalIndices, we can probably come back and employ them instead of using -# the buildAction approach - - -def activity_param_check(M: 'TemoaModel', val, rg, p, t) -> bool: - """ - Validate the index and the value for an entry into an activity param indexed with region-groups - :param M: the model - :param val: the value of the parameter for this index - :param rg: region-group - :param p: time period - :param t: tech - :return: True if all OK - """ - return all( - ( - val in NonNegativeReals, # the value should be in this set - region_group_check(M, rg), - p in M.time_optimize, - t in M.tech_all, - ) - ) - - -def capacity_param_check(M: 'TemoaModel', val, rg, p, t, carrier) -> bool: - """ - validate entries to capacity params - :param M: the model - :param val: the param value at this index - :param rg: region-group - :param p: time period - :param t: tech - :param carrier: commodity carrier - :return: True if all OK - """ - return all( - ( - val in NonNegativeReals, - region_group_check(M, rg), - p in M.time_optimize, - t in M.tech_all, - carrier in M.commodity_carrier, - ) - ) - - -def activity_group_param_check(M: 'TemoaModel', val, rg, p, g) -> bool: - """ - validate entries into capacity groups - :param M: the model - :param val: the value at this index - :param rg: region-group - :param p: time period - :param g: tech group name - :return: True if all OK - """ - return all( - ( - val in NonNegativeReals, - region_group_check(M, rg), - p in M.time_optimize, - g in M.tech_group_names, - ) - ) - - -def emission_limit_param_check(M: 'TemoaModel', val, rg, p, e) -> bool: - """ - validate entries into EmissionLimit param - :param M: the model - :param val: the value at this index - :param rg: region-group - :param p: time period - :param e: commodity emission - :return: True if all OK - """ - return all((region_group_check(M, rg), p in M.time_optimize, e in M.commodity_emissions)) - - -def validate_CapacityFactorProcess(M: 'TemoaModel', val, r, s, d, t, v) -> bool: - """ - validate the rsdtv index - :param val: the parameter value - :param M: the model - :param r: region - :param s: season - :param d: time of day - :param t: tech - :param v: vintage - :return: - """ - return all( - ( - r in M.regions, - s in M.time_season, - d in M.time_of_day, - t in M.tech_all, - v in M.vintage_all, - 0 <= val <= 1.0, - ) - ) - - -def validate_Efficiency(M: 'TemoaModel', val, r, si, t, v, so) -> bool: - """Handy for troubleshooting problematic entries""" - - if all( - ( - isinstance(val, float), - val > 0, - r in M.RegionalIndices, - si in M.commodity_physical, - t in M.tech_all, - so in M.commodity_carrier, - v in M.vintage_all, - ) - ): - return True - print('Element Validations:') - print('region', r in M.RegionalIndices) - print('input_commodity', si in M.commodity_physical) - print('tech', t in M.tech_all) - print('vintage', v in M.vintage_all) - print('output_commodity', so in M.commodity_carrier) - return False - - -def check_flex_curtail(M: 'TemoaModel'): - violations = M.tech_flex & M.tech_curtailment - if violations: - logger.error( - 'The following technologies are in both flex and curtail, which is not permitted:', - violations, - ) - return False - return True - - -def validate_tech_input_split(M: 'TemoaModel', val, r, p, c, t): - if all( - ( - r in M.regions, - p in M.time_optimize, - c in M.commodity_physical, - t in M.tech_all, - ) - ): - return True - print('r', r in M.regions) - print('p', p in M.time_optimize) - print('c', c in M.commodity_physical) - print('t', t in M.tech_all) - return False diff --git a/temoa/temoa_model/run_actions.py b/temoa/temoa_model/run_actions.py deleted file mode 100644 index 17f083d5d..000000000 --- a/temoa/temoa_model/run_actions.py +++ /dev/null @@ -1,360 +0,0 @@ -""" -Basic-level atomic functions that can be used by a sequencer, as needed - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/15/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import sqlite3 -import sys -from logging import getLogger -from pathlib import Path -from sys import stderr as SE, version_info -from time import time -from typing import Tuple - -from pyomo.environ import ( - DataPortal, - Suffix, - Var, - Constraint, - value, - UnknownSolver, - SolverFactory, - check_optimal_termination, -) -from pyomo.opt import SolverResults - -from temoa.data_processing.DB_to_Excel import make_excel -from temoa.temoa_model.table_writer import TableWriter -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_model import TemoaModel - -logger = getLogger(__name__) - - -def check_python_version(min_major, min_minor) -> bool: - if (min_major, min_minor) >= version_info: - logger.error( - 'Model is being run with python %d.%d. Expecting version %d.%d or later. ', - version_info.major, - version_info.minor, - min_major, - min_minor, - ) - return False - return True - - -def check_database_version(config: TemoaConfig, db_major_reqd: int, min_db_minor) -> bool: - """ - check the db version - :param config: TemoaConfig instance - :param db_major_reqd: the required major version (equality test) - :param min_db_minor: the required minimum minor version (GTE test) - :return: T/F - """ - input_conn, input_path = sqlite3.connect(config.input_database), config.input_database - if config.input_database == config.output_database: - output_conn = None - else: - output_conn = sqlite3.connect(config.output_database) - cons = [ - (input_conn, input_path), - ] - if output_conn is not None: - cons.append((output_conn, config.output_database)) - # check for correct version - all_good = True - - for con, name in cons: - try: - db_major = con.execute( - "SELECT value from MetaData where element = 'DB_MAJOR'" - ).fetchone() - db_minor = con.execute( - "SELECT value from MetaData where element = 'DB_MINOR'" - ).fetchone() - db_major = db_major[0] if db_major else -1 - db_minor = db_minor[0] if db_minor else -1 - except sqlite3.OperationalError: - logger.error( - 'Database does not appear to have MetaData table with required versioning info. See schema for v3+.' - ) - SE.write( - 'Database does not appear to have MetaData table with required. Is this version 3+ compatible?\n' - 'If required, see dox on using the database migrator to move to v3.' - ) - db_major, db_minor = -1, -1 - finally: - con.close() - - good_version = db_major == db_major_reqd and db_minor >= min_db_minor - if not good_version: - logger.error( - 'Database %s version %d.%d does not match the major version %d and have at least minor version %d', - str(name), - db_major, - db_minor, - db_major_reqd, - min_db_minor, - ) - all_good &= good_version - - return all_good - - -def build_instance( - loaded_portal: DataPortal, - model_name=None, - silent=False, - keep_lp_file=False, - lp_path: Path = None, -) -> TemoaModel: - """ - Build a Temoa Instance from data - :param lp_path: the path to save the LP file to - :param keep_lp_file: True to keep the LP file - :param loaded_portal: a DataPortal instance - :param silent: Run silently - :param model_name: Optional name for this instance - :return: a built TemoaModel - """ - model = TemoaModel() - - model.dual = Suffix(direction=Suffix.IMPORT) - # self.model.rc = Suffix(direction=Suffix.IMPORT) - # self.model.slack = Suffix(direction=Suffix.IMPORT) - - hack = time() - if not silent: - SE.write('[ ] Creating model instance.') - SE.flush() - logger.info('Started creating model instance from data') - instance = model.create_instance(loaded_portal, name=model_name) - if not silent: - SE.write('\r[%8.2f] Instance created.\n' % (time() - hack)) - SE.flush() - logger.info('Finished creating model instance from data') - - # save LP if requested - if keep_lp_file: - save_lp(instance, lp_path) - - # gather some stats... - c_count = 0 - v_count = 0 - for constraint in instance.component_objects(ctype=Constraint): - c_count += len(constraint) - for var in instance.component_objects(ctype=Var): - v_count += len(var) - logger.info('model built... Variables: %d, Constraints: %d', v_count, c_count) - return instance - - -def save_lp(instance: TemoaModel, lp_path: Path) -> None: - """ - quick utility to save the LP file to disc. - Note: if saving multiple LP's they need to be differentiated by path - """ - if not lp_path: - logger.warning('Requested "keep LP file", but no path is provided...skipped') - else: - if not Path.is_dir(lp_path): - Path.mkdir(lp_path) - filename = lp_path / 'model.lp' - instance.write(filename, format='lp', io_options={'symbolic_solver_labels': True}) - - -def solve_instance( - instance: TemoaModel, solver_name, silent: bool = False, solver_suffixes=None -) -> Tuple[TemoaModel, SolverResults]: - """ - Solve the instance and return a loaded instance - :param solver_suffixes: iterable of string names for suffixes. See pyomo dox. right now, only - 'duals' is supported in the Temoa Framework. Some solvers may not support duals. - :param silent: Run silently - :param solver_name: The name of the solver to request from the SolverFactory - :param instance: the instance to solve - :return: loaded instance - """ - - # QA the solver name and get a handle on solver - if not solver_name: - logger.error('No solver specified in solve sequence') - raise TypeError('Error occurred during solve, see log') - optimizer = SolverFactory(solver_name) - if isinstance(optimizer, UnknownSolver): - logger.error( - 'Failed to create a solver instance for name: %s. Check name and availability on ' - 'this system', - solver_name, - ) - raise TypeError('Failed to make Solver instance. See log.') - - hack = time() - if not silent: - SE.write('[ ] Solving.') - SE.flush() - - logger.info( - 'Starting the solve process using %s solver on model %s', solver_name, instance.name - ) - if solver_name == 'neos': - raise NotImplementedError('Neos based solve is not currently supported') - - else: - if solver_name == 'cbc': - pass - # dev note: I think these options are outdated. Getting decent results without them... - # preserved for now. - # Solver options. Reference: - # https://genxproject.github.io/GenX/dev/solver_configuration/ - # optimizer.options["dualTolerance"] = 1e-6 - # optimizer.options["primalTolerance"] = 1e-6 - # optimizer.options["zeroTolerance"] = 1e-12 - # optimizer.options["crossover"] = 'off' - - elif solver_name == 'cplex': - # Note: these parameter values are taken to be the same as those in PyPSA - # (see: https://pypsa-eur.readthedocs.io/en/latest/configuration.html) - optimizer.options['lpmethod'] = 4 # barrier - optimizer.options['solutiontype'] = 2 # non basic solution, ie no crossover - optimizer.options['barrier convergetol'] = 1.0e-5 - optimizer.options['feasopt tolerance'] = 1.0e-6 - - elif solver_name == 'gurobi': - pass - - elif solver_name == 'appsi_highs': - pass - - # dev note: The handling of suffixes is pretty weak. As of today 4/4/2024, highspy crashes if - # the keyword suffixes is passed in (regardless if there are any requested). CBC only - # supports some. Perhaps in the future, this will be easier. For now, we need a different - # solve command for highspy and no suffixes because it works so well. - if solver_suffixes: - solver_suffixes = set(solver_suffixes) - legit_suffixes = {'dual', 'slack', 'rc'} - bad_apples = solver_suffixes - legit_suffixes - solver_suffixes &= legit_suffixes - if bad_apples: - logger.warning( - 'Solver suffix %s is not in pyomo standards (see pyomo dox). Removed', - bad_apples, - ) - # convert back to list... - solver_suffixes = list(solver_suffixes) - else: - solver_suffixes = [] - result: SolverResults | None = None - try: - # currently, the highs solver call will puke if the suffixes are passed, so we need to - # differentiate... - if solver_name == 'appsi_highs': - result = optimizer.solve(instance) - else: - result = optimizer.solve(instance, suffixes=solver_suffixes) - except RuntimeError as error: - logger.error('Solver failed to solve and returned an error: %s', error) - logger.error( - 'This may be due to asking for suffixes (duals) for an incompatible solver. ' - "Try de-selecting 'save_duals' in the config. (see note in run_actions.py code)" - ) - if result: - logger.error( - 'Solver reported termination condition (if any): %s', result['Solution'].Status - ) - SE.write('solver failure. See log file.') - sys.exit(-1) - - if check_optimal_termination(result): - if solver_suffixes: - instance.solutions.store_to( - result - ) # this is needed to capture the duals/suffixes from the Solutions obj - - logger.info('Solve process complete') - logger.debug('Solver results: \n %s', result.solver) - - if not silent: - SE.write('\r[%8.2f] Model solved.\n' % (time() - hack)) - SE.flush() - - return instance, result - - -def check_solve_status(result: SolverResults) -> tuple[bool, str]: - """ - Check the status of the solve. - :param result: the results object returned by the solver - :return: tuple of status boolean (True='optimal', others False), and string message if not optimal - """ - soln = result['Solution'] - - lesser_responses = ('feasible', 'globallyOptimal', 'locallyOptimal') - logger.info('The solver reported status as: %s', soln.Status) - if check_optimal_termination(results=result): - return True, '' - else: - return False, f'{soln.Status} was returned from solve' - - -def handle_results( - instance: TemoaModel, results, config: TemoaConfig, append=False, iteration=None -): - hack = time() - if not config.silent: - msg = '[ ] Calculating reporting variables and formatting results.' - # yield 'Calculating reporting variables and formatting results.' - SE.write(msg) - SE.flush() - - table_writer = TableWriter(config=config) - if config.save_duals: - table_writer.write_results( - M=instance, results_with_duals=results, append=append, iteration=iteration - ) - else: - table_writer.write_results(M=instance, append=append, iteration=iteration) - - if not config.silent: - SE.write( - '\r[%8.2f] Results processed. \n' % (time() - hack) - ) - SE.flush() - - if config.save_excel: - scenario_name = ( - config.scenario + f'-{iteration}' if iteration is not None else config.scenario - ) - temp_scenario = set() - temp_scenario.add(scenario_name) - excel_filename = config.output_path / scenario_name - make_excel(str(config.output_database), excel_filename, temp_scenario) - - # normal (non-MGA) run will have a TotalCost as the OBJ: - if hasattr(instance, 'TotalCost'): - logger.info('TotalCost value: %0.2f', value(instance.TotalCost)) - return diff --git a/temoa/temoa_model/table_data_puller.py b/temoa/temoa_model/table_data_puller.py deleted file mode 100644 index 4301662cb..000000000 --- a/temoa/temoa_model/table_data_puller.py +++ /dev/null @@ -1,488 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 12/5/24 - -A companion module to the table writer to hold some data-pulling functions and small utilities and separate them -from the writing process for organization and to isolate the DB access in the writer such that -these functions can be called on a model instance without any DB interactions. (Intended to support use -by Workers who shouldn't interact with DB). Dev Note: In future, if transition away from sqlite, this -could all be refactored to perform tasks within workers, but concurrent access to sqlite is a no-go -""" -import functools -import logging -from collections import namedtuple, defaultdict -from enum import unique, Enum - -from pyomo.common.numeric_types import value -from pyomo.core import Objective - -from temoa.temoa_model import temoa_rules -from temoa.temoa_model.exchange_tech_cost_ledger import ExchangeTechCostLedger, CostType -from temoa.temoa_model.temoa_model import TemoaModel - -logger = logging.getLogger(__name__) - - -def _marks(num: int) -> str: - """convenience to make a sequence of question marks for query""" - qs = ','.join('?' for _ in range(num)) - marks = '(' + qs + ')' - return marks - - -EI = namedtuple('EI', ['r', 'p', 't', 'v', 'e']) -"""Emission Index""" - - -@unique -class FlowType(Enum): - """Types of flow tracked""" - - IN = 1 - OUT = 2 - CURTAIL = 3 - FLEX = 4 - LOST = 5 - - -FI = namedtuple('FI', ['r', 'p', 's', 'd', 'i', 't', 'v', 'o']) -"""Flow Index""" - -CapData = namedtuple('CapData', ['built', 'net', 'retired']) -"""Small container to hold named dictionaries of capacity data for processing""" - - -def ritvo(fi: FI) -> tuple: - """convert FI to ritvo index""" - return fi.r, fi.i, fi.t, fi.v, fi.o - - -def rpetv(fi: FI, e: str) -> tuple: - """convert FI and emission to rpetv index""" - return fi.r, fi.p, e, fi.t, fi.v - - -def poll_capacity_results(M: TemoaModel, epsilon=1e-5) -> CapData: - """ - Poll a solved model for capacity results. - :param M: Solved Model - :param epsilon: epsilon (default 1e-5) - :return: a CapData object - """ - # Built Capacity - built = [] - for r, t, v in M.V_NewCapacity: - if v in M.time_optimize: - val = value(M.V_NewCapacity[r, t, v]) - if abs(val) < epsilon: - continue - new_cap = (r, t, v, val) - built.append(new_cap) - - # NetCapacity - net = [] - for r, p, t, v in M.V_Capacity: - val = value(M.V_Capacity[r, p, t, v]) - if abs(val) < epsilon: - continue - new_net_cap = (r, p, t, v, val) - net.append(new_net_cap) - - # Retired Capacity - ret = [] - for r, p, t, v in M.V_RetiredCapacity: - val = value(M.V_RetiredCapacity[r, p, t, v]) - if abs(val) < epsilon: - continue - new_retired_cap = (r, p, t, v, val) - ret.append(new_retired_cap) - - return CapData(built=built, net=net, retired=ret) - - -def poll_flow_results(M: TemoaModel, epsilon=1e-5) -> dict[FI, dict[FlowType, float]]: - """ - Poll a solved model for flow results. - :param M: A solved Model - :param epsilon: epsilon (default 1e-5) - :return: nested dictionary of FlowIndex, FlowType : value - """ - dd = functools.partial(defaultdict, float) - res: dict[FI, dict[FlowType, float]] = defaultdict(dd) - - # ---- NON-annual ---- - - # Storage, which has a unique v_flow_in (non-storage techs do not have this variable) - for key in M.V_FlowIn: - fi = FI(*key) - flow = value(M.V_FlowIn[fi]) - if abs(flow) < epsilon: - continue - res[fi][FlowType.IN] = flow - res[fi][FlowType.LOST] = (1 - value(M.Efficiency[ritvo(fi)])) * flow - - # regular flows - for key in M.V_FlowOut: - fi = FI(*key) - flow = value(M.V_FlowOut[fi]) - if abs(flow) < epsilon: - continue - res[fi][FlowType.OUT] = flow - - if fi.t not in M.tech_storage: # we can get the flow in by out/eff... - flow = value(M.V_FlowOut[fi]) / value(M.Efficiency[ritvo(fi)]) - res[fi][FlowType.IN] = flow - res[fi][FlowType.LOST] = (1 - value(M.Efficiency[ritvo(fi)])) * flow - - # curtailment flows - for key in M.V_Curtailment: - fi = FI(*key) - val = value(M.V_Curtailment[fi]) - if abs(val) < epsilon: - continue - res[fi][FlowType.CURTAIL] = val - - # flex techs. This will subtract the flex from their output flow IOT make OUT the "net" - for key in M.V_Flex: - fi = FI(*key) - flow = value(M.V_Flex[fi]) - if abs(flow) < epsilon: - continue - res[fi][FlowType.FLEX] = flow - res[fi][FlowType.OUT] -= flow - - # ---- annual ---- - - # basic annual flows - for r, p, i, t, v, o in M.V_FlowOutAnnual: - for s in M.time_season: - for d in M.time_of_day: - fi = FI(r, p, s, d, i, t, v, o) - flow = value(M.V_FlowOutAnnual[r, p, i, t, v, o]) * value(M.SegFrac[s, d]) - if abs(flow) < epsilon: - continue - res[fi][FlowType.OUT] = flow - res[fi][FlowType.IN] = flow / value(M.Efficiency[ritvo(fi)]) - res[fi][FlowType.LOST] = (1 - value(M.Efficiency[ritvo(fi)])) * res[fi][FlowType.IN] - - # flex annual - for r, p, i, t, v, o in M.V_FlexAnnual: - for s in M.time_season: - for d in M.time_of_day: - fi = FI(r, p, s, d, i, t, v, o) - flow = value(M.V_FlexAnnual[r, p, i, t, v, o]) * value(M.SegFrac[s, d]) - if abs(flow) < epsilon: - continue - res[fi][FlowType.FLEX] = flow - res[fi][FlowType.OUT] -= flow - - return res - - -def poll_objective(M: TemoaModel) -> list[tuple[str, float]]: - """gather objective name, value tuples for all active objectives""" - objs: list[Objective] = list(M.component_data_objects(Objective)) - active_objs = [obj for obj in objs if obj.active] - if len(active_objs) > 1: - logger.warning('Multiple active objectives found. All will be logged in db') - res = [] - for obj in active_objs: - obj_name, obj_value = obj.getname(fully_qualified=True), value(obj) - res.append((obj_name, obj_value)) - return res - - -def poll_cost_results( - M: TemoaModel, p_0: int | None, epsilon=1e-5 -) -> tuple[dict[tuple, dict], ...]: - """ - Poll a solved model for all cost results - :param M: Solved Model - :param p_0: a base year for discounting of loans, typically only used in MYOPIC. If none, first optimization year used - :param epsilon: epsilon (default 1e-5) - :return: tuple of cost_dict, exchange_cost_dict (for exchange techs) - """ - if not p_0: - p_0 = min(M.time_optimize) - - p_e = M.time_future.last() - - # conveniences... - GDR = value(M.GlobalDiscountRate) - MPL = M.ModelProcessLife - LLN = M.LoanLifetimeProcess - - exchange_costs = ExchangeTechCostLedger(M) - entries = defaultdict(dict) - for r, t, v in M.CostInvest.sparse_iterkeys(): # Returns only non-zero values - # gather details... - cap = value(M.V_NewCapacity[r, t, v]) - if abs(cap) < epsilon: - continue - loan_life = value(LLN[r, t, v]) - loan_rate = value(M.LoanRate[r, t, v]) - - model_loan_cost, undiscounted_cost = loan_costs( - loan_rate=loan_rate, - loan_life=loan_life, - capacity=cap, - invest_cost=value(M.CostInvest[r, t, v]), - process_life=value(M.LifetimeProcess[r, t, v]), - p_0=p_0, - p_e=p_e, - global_discount_rate=GDR, - vintage=v, - ) - # screen for linked region... - if '-' in r: - exchange_costs.add_cost_record( - r, - period=v, - tech=t, - vintage=v, - cost=model_loan_cost, - cost_type=CostType.D_INVEST, - ) - exchange_costs.add_cost_record( - r, - period=v, - tech=t, - vintage=v, - cost=undiscounted_cost, - cost_type=CostType.INVEST, - ) - else: - # enter it into the entries table with period of cost = vintage (p=v) - entries[r, v, t, v].update( - {CostType.D_INVEST: model_loan_cost, CostType.INVEST: undiscounted_cost} - ) - - for r, p, t, v in M.CostFixed.sparse_iterkeys(): - cap = value(M.V_Capacity[r, p, t, v]) - if abs(cap) < epsilon: - continue - - fixed_cost = value(M.CostFixed[r, p, t, v]) - undiscounted_fixed_cost = cap * fixed_cost * value(MPL[r, p, t, v]) - - model_fixed_cost = temoa_rules.fixed_or_variable_cost( - cap, fixed_cost, value(MPL[r, p, t, v]), GDR=GDR, P_0=p_0, p=p - ) - if '-' in r: - exchange_costs.add_cost_record( - r, - period=p, - tech=t, - vintage=v, - cost=model_fixed_cost, - cost_type=CostType.D_FIXED, - ) - exchange_costs.add_cost_record( - r, - period=p, - tech=t, - vintage=v, - cost=undiscounted_fixed_cost, - cost_type=CostType.FIXED, - ) - else: - entries[r, p, t, v].update( - {CostType.D_FIXED: model_fixed_cost, CostType.FIXED: undiscounted_fixed_cost} - ) - - for r, p, t, v in M.CostVariable.sparse_iterkeys(): - if t not in M.tech_annual: - activity = sum( - value(M.V_FlowOut[r, p, S_s, S_d, S_i, t, v, S_o]) - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - for S_s in M.time_season - for S_d in M.time_of_day - ) - else: - activity = sum( - value(M.V_FlowOutAnnual[r, p, S_i, t, v, S_o]) - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - if abs(activity) < epsilon: - continue - - var_cost = value(M.CostVariable[r, p, t, v]) - undiscounted_var_cost = activity * var_cost * value(MPL[r, p, t, v]) - - model_var_cost = temoa_rules.fixed_or_variable_cost( - activity, var_cost, value(MPL[r, p, t, v]), GDR=GDR, P_0=p_0, p=p - ) - if '-' in r: - exchange_costs.add_cost_record( - r, - period=p, - tech=t, - vintage=v, - cost=model_var_cost, - cost_type=CostType.D_VARIABLE, - ) - exchange_costs.add_cost_record( - r, - period=p, - tech=t, - vintage=v, - cost=undiscounted_var_cost, - cost_type=CostType.VARIABLE, - ) - else: - entries[r, p, t, v].update( - {CostType.D_VARIABLE: model_var_cost, CostType.VARIABLE: undiscounted_var_cost} - ) - exchange_entries = exchange_costs.get_entries() - return entries, exchange_entries - - -def loan_costs( - loan_rate, # this is referred to as LoanRate in parameters - loan_life, - capacity, - invest_cost, - process_life, - p_0, - p_e, - global_discount_rate, - vintage, - **kwargs, -) -> tuple[float, float]: - """ - Calculate Loan costs by calling the loan annualize and loan cost functions in temoa_rules - :return: tuple of [model-view discounted cost, un-discounted annuity] - """ - # dev note: this is a passthrough function. Sole intent is to use the EXACT formula the - # model uses for these costs - loan_ar = temoa_rules.loan_annualization_rate(loan_rate=loan_rate, loan_life=loan_life) - model_ic = temoa_rules.loan_cost( - capacity, - invest_cost, - loan_annualize=loan_ar, - lifetime_loan_process=loan_life, - P_0=p_0, - P_e=p_e, - GDR=global_discount_rate, - vintage=vintage, - ) - # Override the GDR to get the undiscounted value - global_discount_rate = 0 - undiscounted_cost = temoa_rules.loan_cost( - capacity, - invest_cost, - loan_annualize=loan_ar, - lifetime_loan_process=loan_life, - P_0=p_0, - P_e=p_e, - GDR=global_discount_rate, - vintage=vintage, - ) - return model_ic, undiscounted_cost - - -def poll_emissions( - M: 'TemoaModel', p_0=None, epsilon=1e-5 -) -> tuple[dict[tuple, dict], dict[EI, float]]: - """ - Gather all emission flows, cost them and provide a tuple of costs and flows - :param M: the model - :param p_0: the first period, if other than min(time_optimize), as in MYOPIC - :param epsilon: a minimal epsilon for ignored values - :return: cost_dict, flow_dict - """ - - # UPDATE: older versions brought forward had some accounting errors here for flex/curtailed emissions - # see the note on emissions in the Cost function in temoa_rules - if not p_0: - p_0 = min(M.time_optimize) - - GDR = value(M.GlobalDiscountRate) - MPL = M.ModelProcessLife - - base = [ - (r, p, e, i, t, v, o) - for (r, e, i, t, v, o) in M.EmissionActivity - for p in M.time_optimize - if (r, p, t, v) in M.processInputs - ] - - # The "base set" can be expanded now to cover normal/annual indexing sets - normal = [ - (r, p, e, s, d, i, t, v, o) - for (r, p, e, i, t, v, o) in base - for s in M.time_season - for d in M.time_of_day - if t not in M.tech_annual - ] - annual = [(r, p, e, i, t, v, o) for (r, p, e, i, t, v, o) in base if t in M.tech_annual] - - flows: dict[EI, float] = defaultdict(float) - # iterate through the normal and annual and accumulate flow values - for r, p, e, s, d, i, t, v, o in normal: - flows[EI(r, p, t, v, e)] += ( - value(M.V_FlowOut[r, p, s, d, i, t, v, o]) * M.EmissionActivity[r, e, i, t, v, o] - ) - - for r, p, e, i, t, v, o in annual: - flows[EI(r, p, t, v, e)] += ( - value(M.V_FlowOutAnnual[r, p, i, t, v, o]) * M.EmissionActivity[r, e, i, t, v, o] - ) - - # gather costs - ud_costs = defaultdict(float) - d_costs = defaultdict(float) - for ei in flows: - # screen to see if there is an associated cost - cost_index = (ei.r, ei.p, ei.e) - if cost_index not in M.CostEmission: - continue - # check for epsilon - if abs(flows[ei]) < epsilon: - flows[ei] = 0.0 - continue - undiscounted_emiss_cost = ( - flows[ei] * M.CostEmission[ei.r, ei.p, ei.e] * MPL[ei.r, ei.p, ei.t, ei.v] - ) - discounted_emiss_cost = temoa_rules.fixed_or_variable_cost( - cap_or_flow=flows[ei], - cost_factor=M.CostEmission[ei.r, ei.p, ei.e], - process_lifetime=MPL[ei.r, ei.p, ei.t, ei.v], - GDR=GDR, - P_0=p_0, - p=ei.p, - ) - ud_costs[ei.r, ei.p, ei.t, ei.v] += undiscounted_emiss_cost - d_costs[ei.r, ei.p, ei.t, ei.v] += discounted_emiss_cost - costs = defaultdict(dict) - for k in ud_costs: - costs[k][CostType.EMISS] = ud_costs[k] - for k in d_costs: - costs[k][CostType.D_EMISS] = d_costs[k] - - # wow, that was like pulling teeth - return costs, flows diff --git a/temoa/temoa_model/table_writer.py b/temoa/temoa_model/table_writer.py deleted file mode 100644 index 7e97b6bf0..000000000 --- a/temoa/temoa_model/table_writer.py +++ /dev/null @@ -1,563 +0,0 @@ -""" -tool for writing outputs to database tables -""" -import sqlite3 -import sys -from collections import defaultdict -from collections.abc import Iterable -from logging import getLogger -from pathlib import Path -from typing import TYPE_CHECKING - -from pyomo.opt import SolverResults - -from definitions import PROJECT_ROOT -from temoa.extensions.monte_carlo.mc_run import ChangeRecord -from temoa.temoa_model.data_brick import DataBrick -from temoa.temoa_model.exchange_tech_cost_ledger import CostType -from temoa.temoa_model.table_data_puller import ( - poll_capacity_results, - poll_flow_results, - FI, - FlowType, - EI, - _marks, - CapData, - poll_objective, - poll_cost_results, - poll_emissions, -) -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_mode import TemoaMode -from temoa.temoa_model.temoa_model import TemoaModel - -if TYPE_CHECKING: - pass - -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 2/9/24 - -Note: This file borrows heavily from the legacy pformat_results.py, and is somewhat of a restructure of that code - to accommodate the run modes more cleanly - -""" - -logger = getLogger(__name__) - -basic_output_tables = [ - 'OutputBuiltCapacity', - 'OutputCost', - 'OutputCurtailment', - 'OutputDualVariable', - 'OutputEmission', - 'OutputFlowIn', - 'OutputFlowOut', - 'OutputNetCapacity', - 'OutputObjective', - 'OutputRetiredCapacity', -] -optional_output_tables = ['OutputFlowOutSummary', 'OutputMCDelta'] - -flow_summary_file_loc = Path( - PROJECT_ROOT, 'temoa/extensions/modeling_to_generate_alternatives/make_flow_summary_table.sql' -) -mc_tweaks_file_loc = Path(PROJECT_ROOT, 'temoa/extensions/monte_carlo/make_deltas_table.sql') - - -class TableWriter: - def __init__(self, config: TemoaConfig, epsilon=1e-5): - self.config = config - self.epsilon = epsilon - self.tech_sectors: dict[str, str] | None = None - self.flow_register: dict[FI, dict[FlowType, float]] = {} - self.emission_register: dict[EI, float] | None = None - try: - self.con = sqlite3.connect(config.output_database) - except sqlite3.OperationalError as e: - logger.error('Failed to connect to output database: %s', config.output_database) - logger.error(e) - sys.exit(-1) - - def write_results( - self, - M: TemoaModel, - results_with_duals: SolverResults | None = None, - append=False, - iteration: int | None = None, - ) -> None: - """ - Write results to output database - :param iteration: An interation count for repeated runs, to be passed to tables that support it - :param results_with_duals: if provided, this will trigger the writing of dual variables, pulled from the SolverResults - :param M: the model - :param append: append whatever is already in the tables. If False (default), clear existing tables by scenario name - :return: - """ - if not append: - self.clear_scenario() - if not self.tech_sectors: - self._set_tech_sectors() - self.write_objective(M, iteration=iteration) - self.write_capacity_tables(M, iteration=iteration) - # analyze the emissions to get the costs and flows - if self.config.scenario_mode == TemoaMode.MYOPIC: - p_0 = M.MyopicBaseyear - else: - p_0 = None # min year will be used in poll - e_costs, e_flows = poll_emissions(M=M, p_0=p_0) - self.emission_register = e_flows - self.write_emissions(iteration=iteration) - self.write_costs(M, emission_entries=e_costs, iteration=iteration) - self.flow_register = self.calculate_flows(M) - self.check_flow_balance(M) - self.write_flow_tables(iteration=iteration) - if results_with_duals: # write the duals - self.write_dual_variables(results_with_duals, iteration=iteration) - # catch-all - self.con.commit() - self.con.execute('VACUUM') - - def write_mm_results(self, M: TemoaModel, iteration: int): - """ - tailored writer function for Method of Morris which: - (a) appends data (so scenario needs to be cleared elsewhere - (b) requires an iteration number to separate results - (c) only writes to MM required tables (obj, emissions right now) - :param M: solved model - :param iteration: an iteration index for scenario labeling - :return: - """ - if not self.tech_sectors: - self._set_tech_sectors() - self.write_objective(M, iteration=iteration) - # analyze the emissions to get the costs and flows - e_costs, e_flows = poll_emissions(M=M) - self.emission_register = e_flows - self.write_emissions(iteration=iteration) - self.con.commit() - self.con.execute('VACUUM') - - def write_mc_results(self, brick: DataBrick, iteration: int): - """ - tailored write function to capture appropriate monte carlo results - :param M: solve model - :param iteration: iteration number - :return: - """ - if not self.tech_sectors: - self._set_tech_sectors() - # analyze the emissions to get the costs and flows - e_costs, e_flows = brick.emission_cost_data, brick.emission_flows - self.emission_register = e_flows - self.write_emissions(iteration=iteration) - - # the rest can be directly inserted from the data_brick - self._insert_capacity_results(brick.capacity_data, iteration=iteration) - self._insert_summary_flow_results(flow_data=brick.flow_data, iteration=iteration) - self._insert_cost_results( - regular_entries=brick.cost_data, - exchange_entries=brick.exchange_cost_data, - emission_entries=e_costs, - iteration=iteration, - ) - self._insert_objective_results(brick.obj_data, iteration=iteration) - self.con.commit() - self.con.execute('VACUUM') - - def _set_tech_sectors(self): - """pull the sector info and fill the mapping""" - qry = 'SELECT tech, sector FROM Technology' - data = self.con.execute(qry).fetchall() - self.tech_sectors = dict(data) - - def clear_scenario(self): - cur = self.con.cursor() - for table in basic_output_tables: - cur.execute(f'DELETE FROM {table} WHERE scenario = ?', (self.config.scenario,)) - self.con.commit() - self.clear_iterative_runs() - - def clear_iterative_runs(self): - """ - clear runs that are iterative extensions to the scenario name - Ex: scenario = 'Red Monkey" ... will clear "Red Monkey-1, Red Monkey-2, Red Monkey-3, Red Monkey-4' - :return: None - """ - target = self.config.scenario + '-%' # the dash followed by wildcard for anything after - cur = self.con.cursor() - for table in basic_output_tables: - cur.execute(f'DELETE FROM {table} WHERE scenario like ?', (target,)) - self.con.commit() - for table in optional_output_tables: - try: - cur.execute(f'DELETE FROM {table} WHERE scenario like ?', (target,)) - except sqlite3.OperationalError: - pass - self.con.commit() - - def write_objective(self, M: TemoaModel, iteration=None) -> None: - """Write the value of all ACTIVE objectives to the DB""" - obj_vals = poll_objective(M=M) - self._insert_objective_results(obj_vals, iteration=iteration) - - def _insert_objective_results(self, obj_vals: list, iteration: int) -> None: - scenario_name = ( - self.config.scenario + f'-{iteration}' - if iteration is not None - else self.config.scenario - ) - for obj_name, obj_value in obj_vals: - qry = 'INSERT INTO OutputObjective VALUES (?, ?, ?)' - data = (scenario_name, obj_name, obj_value) - self.con.execute(qry, data) - self.con.commit() - - def write_emissions(self, iteration=None) -> None: - """Write the emission table to the DB""" - if not self.tech_sectors: - raise RuntimeError('tech sectors not available... code error') - - data = [] - scenario = ( - self.config.scenario + f'-{iteration}' - if iteration is not None - else self.config.scenario - ) - for ei in self.emission_register: - sector = self.tech_sectors[ei.t] - val = self.emission_register[ei] - if abs(val) < self.epsilon: - continue - entry = (scenario, ei.r, sector, ei.p, ei.e, ei.t, ei.v, val) - data.append(entry) - qry = f'INSERT INTO OutputEmission VALUES {_marks(8)}' - self.con.executemany(qry, data) - self.con.commit() - - def _insert_capacity_results(self, cap_data: CapData, iteration: int | None) -> None: - if not self.tech_sectors: - raise RuntimeError('tech sectors not available... code error') - scenario = self.config.scenario - if iteration is not None: - scenario = scenario + f'-{iteration}' - # Built Capacity - data = [] - for r, t, v, val in cap_data.built: - s = self.tech_sectors.get(t) - new_cap = (scenario, r, s, t, v, val) - data.append(new_cap) - qry = 'INSERT INTO OutputBuiltCapacity VALUES (?, ?, ?, ?, ?, ?)' - self.con.executemany(qry, data) - - # NetCapacity - data = [] - for r, p, t, v, val in cap_data.net: - s = self.tech_sectors.get(t) - new_net_cap = (scenario, r, s, p, t, v, val) - data.append(new_net_cap) - qry = 'INSERT INTO OutputNetCapacity VALUES (?, ?, ?, ?, ?, ?, ?)' - self.con.executemany(qry, data) - - # Retired Capacity - data = [] - for r, p, t, v, val in cap_data.retired: - s = self.tech_sectors.get(t) - new_retired_cap = (scenario, r, s, p, t, v, val) - data.append(new_retired_cap) - qry = 'INSERT INTO OutputRetiredCapacity VALUES (?, ?, ?, ?, ?, ?, ?)' - self.con.executemany(qry, data) - self.con.commit() - - def write_capacity_tables(self, M: TemoaModel, iteration: int | None = None) -> None: - """Write the capacity tables to the DB""" - cap_data = poll_capacity_results(M=M) - self._insert_capacity_results(cap_data=cap_data, iteration=iteration) - - def write_flow_tables(self, iteration=None) -> None: - """Write the flow tables""" - if not self.tech_sectors: - raise RuntimeError('tech sectors not available... code error') - if not self.flow_register: - raise RuntimeError('flow_register not available... code error') - # sort the flows - flows_by_type: dict[FlowType, list[tuple]] = defaultdict(list) - scenario = ( - self.config.scenario + f'-{iteration}' - if iteration is not None - else self.config.scenario - ) - for fi in self.flow_register: - sector = self.tech_sectors.get(fi.t) - for flow_type in self.flow_register[fi]: - val = self.flow_register[fi][flow_type] - if abs(val) < self.epsilon: - continue - entry = (scenario, fi.r, sector, fi.p, fi.s, fi.d, fi.i, fi.t, fi.v, fi.o, val) - flows_by_type[flow_type].append(entry) - - table_associations = { - FlowType.OUT: 'OutputFlowOut', - FlowType.IN: 'OutputFlowIn', - FlowType.CURTAIL: 'OutputCurtailment', - FlowType.FLEX: 'OutputCurtailment', - } - - for flow_type, table_name in table_associations.items(): - qry = f'INSERT INTO {table_name} VALUES {_marks(11)}' - self.con.executemany(qry, flows_by_type[flow_type]) - - self.con.commit() - - def write_summary_flow(self, M: TemoaModel, iteration: int | None = None): - """ - This is normally called from MGA (other?) - iterative solves where capturing the annual summary of flow out is desired vs. flows by season, tod for - single instances - :param iteration: the number of the sequential iteration - :param M: The solved model - :return: None - """ - flow_data = self.calculate_flows(M=M) - self._insert_summary_flow_results(flow_data=flow_data, iteration=iteration) - - def _insert_summary_flow_results(self, flow_data: dict, iteration: int | None) -> None: - if not self.tech_sectors: - raise RuntimeError('tech sectors not available... code error') - - self.flow_register = flow_data - if isinstance(iteration, int): - scenario = self.config.scenario + f'-{iteration}' - elif iteration is None: - scenario = self.config.scenario - else: - raise ValueError(f'Illegal (non integer) value received for iteration: {iteration}') - - # iterate through all elements of the flow register, look for output flows only, - # and gather the total by index (region, period, input_comm, tech, vintage, output_comm) - # this is summing across season, tod - output_flows = defaultdict(float) - for fi in self.flow_register: - sector = self.tech_sectors.get(fi.t) - # get the output flow for this index, if it exists... - flow_out_value = self.flow_register[fi].get(FlowType.OUT, None) - if flow_out_value: - idx = (scenario, fi.r, sector, fi.p, fi.i, fi.t, fi.v, fi.o) - output_flows[idx] += flow_out_value - - # convert to entries, if the sum is non-negligible - entries = [] - for idx, flow in output_flows.items(): - if abs(flow) < self.epsilon: - continue - entry = (*idx, flow) - entries.append(entry) - - qry = f'INSERT INTO OutputFlowOutSummary VALUES {_marks(9)}' - self.con.executemany(qry, entries) - - self.con.commit() - - # @staticmethod - # def poll_summary_flow_results( M:TemoaModel) -> dict: - # flow_data = self.calculate_flows(M) - - def check_flow_balance(self, M: TemoaModel) -> bool: - """An easy sanity check to ensure that the flow tables are balanced, except for storage""" - flows = self.flow_register - all_good = True - deltas = defaultdict(float) - for fi in flows: - if fi.t in M.tech_storage: - continue - - # some conveniences for the players... - fin = flows[fi][FlowType.IN] - fout = flows[fi][FlowType.OUT] - fcurt = flows[fi][FlowType.CURTAIL] - fflex = flows[fi][FlowType.FLEX] - flost = flows[fi][FlowType.LOST] - # some identifiers - tech = fi.t - var_tech = fi.t in M.tech_variable - flex_tech = fi.t in M.tech_flex - annual_tech = fi.t in M.tech_annual - - # ----- flow balance equation ----- - deltas[fi] = fin - fout - flost - fflex - # dev note: in constraint, flex is taken out of flow_out, but in output processing, - # we are treating flow out as "net of flex" so this is not double-counting - - if ( - flows[fi][FlowType.IN] != 0 and abs(deltas[fi] / flows[fi][FlowType.IN]) > 0.02 - ): # 2% of input is missing / surplus - all_good = False - logger.warning( - 'Flow balance check failed for index: %s, delta: %0.2f', fi, deltas[fi] - ) - logger.info( - 'Tech: %s, Var: %s, Flex: %s, Annual: %s', - tech, - var_tech, - flex_tech, - annual_tech, - ) - logger.info( - 'IN: %0.6f, OUT: %0.6f, LOST: %0.6f, CURT: %0.6f, FLEX: %0.6f', - fin, - fout, - flost, - fcurt, - fflex, - ) - elif flows[fi][FlowType.IN] == 0 and abs(deltas[fi]) > 0.02: - all_good = False - logger.warning( - 'Flow balance check failed for index: %s, delta: %0.2f. Flows happening with 0 input', - fi, - deltas[fi], - ) - return all_good - - def calculate_flows(self, M: TemoaModel) -> dict[FI, dict[FlowType, float]]: - """Gather all flows by Flow Index and Type""" - return poll_flow_results(M, self.epsilon) - - def write_costs(self, M: TemoaModel, emission_entries=None, iteration=None): - """ - Gather the cost data vars - :param iteration: tag for iteration in scenario name - :param emission_entries: cost dictionary for emissions - :param M: the Temoa Model - :return: dictionary of results of format variable name -> {idx: value} - """ - - # P_0 is usually the first optimization year, but if running myopic, we could assign it via - # table entry. Perhaps in future it is just always the first optimization year of the 1st iter. - if self.config.scenario_mode == TemoaMode.MYOPIC: - p_0 = M.MyopicBaseyear - else: - p_0 = min(M.time_optimize) - - entries, exchange_entries = poll_cost_results(M, p_0, self.epsilon) - - # write to table - self._insert_cost_results(entries, exchange_entries, emission_entries, iteration) - - def _insert_cost_results(self, regular_entries, exchange_entries, emission_entries, iteration): - # add the emission costs to the same row data, if provided - if emission_entries: - for k in emission_entries.keys(): - regular_entries[k].update(emission_entries[k]) - self._write_cost_rows(regular_entries, iteration=iteration) - self._write_cost_rows(exchange_entries, iteration=iteration) - - def _write_cost_rows(self, entries, iteration=None): - """Write the entries to the OutputCost table""" - scenario_name = ( - self.config.scenario + f'-{iteration}' - if iteration is not None - else self.config.scenario - ) - rows = [ - ( - scenario_name, - r, - p, - t, - v, - entries[r, p, t, v].get(CostType.D_INVEST, 0), - entries[r, p, t, v].get(CostType.D_FIXED, 0), - entries[r, p, t, v].get(CostType.D_VARIABLE, 0), - entries[r, p, t, v].get(CostType.D_EMISS, 0), - entries[r, p, t, v].get(CostType.INVEST, 0), - entries[r, p, t, v].get(CostType.FIXED, 0), - entries[r, p, t, v].get(CostType.VARIABLE, 0), - entries[r, p, t, v].get(CostType.EMISS, 0), - ) - for (r, p, t, v) in entries - ] - # let's be kind and sort by something reasonable (r, v, t, p) - rows.sort(key=lambda r: (r[1], r[4], r[3], r[2])) - cur = self.con.cursor() - qry = 'INSERT INTO OutputCost VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' - cur.executemany(qry, rows) - self.con.commit() - - def write_dual_variables(self, results: SolverResults, iteration=None): - """Write the dual variables to the OutputDualVariable table""" - scenario_name = ( - self.config.scenario + f'-{iteration}' - if iteration is not None - else self.config.scenario - ) # collect the values - constraint_data = results['Solution'].Constraint.items() - dual_data = [(scenario_name, t[0], t[1]['Dual']) for t in constraint_data] - qry = 'INSERT INTO OutputDualVariable VALUES (?, ?, ?)' - self.con.executemany(qry, dual_data) - self.con.commit() - - # MONTE CARLO stuff - - def write_tweaks(self, iteration: int, change_records: Iterable[ChangeRecord]): - scenario = f'{self.config.scenario}-{iteration}' - records = [] - for change_record in change_records: - element = ( - scenario, - iteration, - change_record.param_name, - str(change_record.param_index).replace("'", ''), - change_record.old_value, - change_record.new_value, - ) - records.append(element) - qry = 'INSERT INTO OutputMCDelta VALUES (?, ?, ?, ?, ?, ?)' - self.con.executemany(qry, records) - self.con.commit() - - def __del__(self): - if self.con: - self.con.close() - - def make_summary_flow_table(self): - # make the additional output table, if needed... - self.execute_script(flow_summary_file_loc) - - def make_mc_tweaks_table(self): - # make the table for monte carlo tweaks, if needed... - self.execute_script(mc_tweaks_file_loc) - - def execute_script(self, script_file: str | Path): - """ - A utility to execute a sql script on the current db connection - :return: - """ - with open(script_file, 'r') as table_script: - sql_commands = table_script.read() - logger.debug('Executing sql from file: %s ', script_file) - - self.con.executescript(sql_commands) - self.con.commit() diff --git a/temoa/temoa_model/temoa_config.py b/temoa/temoa_model/temoa_config.py deleted file mode 100644 index 9a3583c08..000000000 --- a/temoa/temoa_model/temoa_config.py +++ /dev/null @@ -1,250 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users expanding this from an archive may not have -received this license file. If not, see . -""" - -import tomllib -from logging import getLogger -from pathlib import Path -from sys import stderr as SE - -from temoa.temoa_model.temoa_mode import TemoaMode - -logger = getLogger(__name__) - - -class TemoaConfig: - """ - The overall configuration for a Temoa Scenario - """ - - def __init__( - self, - scenario: str, - scenario_mode: TemoaMode | str, - input_database: Path, - output_database: Path, - output_path: Path, - solver_name: str, - neos: bool = False, - save_excel: bool = False, - save_duals: bool = False, - save_lp_file: bool = False, - MGA: dict | None = None, - SVMGA: dict | None = None, - myopic: dict | None = None, - morris: dict | None = None, - monte_carlo: dict | None = None, - config_file: Path | None = None, - silent: bool = False, - stream_output: bool = False, - price_check: bool = True, - source_trace: bool = False, - plot_commodity_network: bool = False, - ): - if '-' in scenario: - raise ValueError( - 'Scenario name must not contain "-". Dashes are used internally to indicate iterative ' - 'runs. Please rename scenario' - ) - self.scenario = scenario - # capture the operating mode - self.scenario_mode: TemoaMode - match scenario_mode: - case TemoaMode(): - self.scenario_mode = scenario_mode - case str(): - try: - self.scenario_mode = TemoaMode[scenario_mode.upper()] - except KeyError: - raise AttributeError( - f'The mode selection received by TemoaConfig: ' - f'{scenario_mode} is invalid.\nPossible choices are ' - f'{list(TemoaMode.__members__.keys())} (case ' - f'insensitive).' - ) - case _: - raise AttributeError( - f'The mode selection received by TemoaConfig: ' - f'{scenario_mode} is invalid.\nPossible choices are ' - f'{list(TemoaMode.__members__.keys())} (case ' - f'insensitive).' - ) - - self.config_file = config_file - - # accept and screen the input file - self.input_database = Path(input_database) - if not self.input_database.is_file(): - raise FileNotFoundError(f'could not locate the input database: {self.input_database}') - if self.input_database.suffix not in {'.db', '.sqlite'}: - logger.error('Input file is not of type .ddb or .sqlite') - raise AttributeError('Input file is not of type .db or .sqlite') - - # accept and validate the output db - self.output_database = Path(output_database) - if not self.output_database.is_file(): - raise FileNotFoundError(f'Could not locate the output db: {self.output_database}') - if self.output_database.suffix != '.sqlite': - logger.error('Output DB does not appear to be a sqlite db') - raise AttributeError('Output DB should be .sqlite type') - - # create a placeholder for .dat file. If conversion is needed, this - # is the destination... - self.dat_file: Path | None = None - - self.output_path = output_path - self.neos = neos - if self.neos: - raise NotImplementedError('Neos is currently not supported.') - - self.solver_name = solver_name - self.save_excel = save_excel - self.save_duals = save_duals - self.save_lp_file = save_lp_file - - self.mga_inputs = MGA - self.svmga_inputs = SVMGA - self.myopic_inputs = myopic - self.morris_inputs = morris - self.monte_carlo_inputs = monte_carlo - self.silent = silent - self.stream_output = stream_output - self.price_check = price_check - self.source_trace = source_trace - if plot_commodity_network and not self.source_trace: - logger.warning( - 'Commodity Network plotting was selected, but Source Trace was not selected. ' - 'Both are required to produce plots.' - ) - self.plot_commodity_network = plot_commodity_network and self.source_trace - - # warn if output db != input db - if self.input_database.suffix == self.output_database.suffix: # they are both .db/.sqlite - if self.input_database != self.output_database: # they are not the same db - msg = ( - 'Input file, which is a database, does not match the output file\n User ' - 'is responsible to ensure the data ~ results congruency in the output db' - ) - logger.warning(msg) - if not self.silent: - SE.write('Warning: ' + msg) - - @staticmethod - def build_config(config_file: Path, output_path: Path, silent=False) -> 'TemoaConfig': - """ - build a Temoa Config from a config file - :param silent: suppress warnings and confirmations - :param output_path: - :param config_file: the path to the config file to use - :return: a TemoaConfig instance - """ - with open(config_file, 'rb') as f: - data = tomllib.load(f) - - tc = TemoaConfig(output_path=output_path, config_file=config_file, silent=silent, **data) - logger.info('Scenario Name: %s', tc.scenario) - logger.info('Data source: %s', tc.input_database) - logger.info('Data target: %s', tc.output_database) - logger.info('Mode: %s', tc.scenario_mode.name) - return tc - - def __repr__(self): - width = 25 - spacer = '\n' + '-' * width + '\n' - msg = spacer - - msg += '{:>{}s}: {}\n'.format('Scenario', width, self.scenario) - msg += '{:>{}s}: {}\n'.format('Scenario mode', width, self.scenario_mode.name) - msg += '{:>{}s}: {}\n'.format('Config file', width, self.config_file) - msg += '{:>{}s}: {}\n'.format('Data source', width, self.input_database) - msg += '{:>{}s}: {}\n'.format('Output database target', width, self.output_database) - msg += '{:>{}s}: {}\n'.format('Path for outputs and log', width, self.output_path) - - msg += spacer - msg += '{:>{}s}: {}\n'.format('Price check', width, self.price_check) - msg += '{:>{}s}: {}\n'.format('Source trace', width, self.source_trace) - msg += '{:>{}s}: {}\n'.format('Commodity network plots', width, self.plot_commodity_network) - - msg += spacer - msg += '{:>{}s}: {}\n'.format('Selected solver', width, self.solver_name) - msg += '{:>{}s}: {}\n'.format('NEOS status', width, self.neos) - - msg += spacer - msg += '{:>{}s}: {}\n'.format('Spreadsheet output', width, self.save_excel) - msg += '{:>{}s}: {}\n'.format('Pyomo LP write status', width, self.save_lp_file) - msg += '{:>{}s}: {}\n'.format('Save duals to output db', width, self.save_duals) - - if self.scenario_mode == TemoaMode.MYOPIC: - msg += spacer - msg += '{:>{}s}: {}\n'.format( - 'Myopic view depth', width, self.myopic_inputs.get('view_depth') - ) - msg += '{:>{}s}: {}\n'.format( - 'Myopic step size', width, self.myopic_inputs.get('step_size') - ) - - if self.scenario_mode == TemoaMode.MGA: - msg += spacer - msg += '{:>{}s}: {}\n'.format( - 'MGA Cost Epsilon', width, self.mga_inputs.get('cost_epsilon') - ) - msg += '{:>{}s}: {}\n'.format( - 'MGA Iteration Limit', width, self.mga_inputs.get('iteration_limit') - ) - msg += '{:>{}s}: {}\n'.format( - 'MGA Time Limit (hrs)', width, self.mga_inputs.get('time_limit_hrs') - ) - msg += '{:>{}s}: {}\n'.format('MGA Axis:', width, self.mga_inputs.get('axis')) - msg += '{:>{}s}: {}\n'.format('MGA Weighting', width, self.mga_inputs.get('weighting')) - - if self.scenario_mode == TemoaMode.METHOD_OF_MORRIS: - msg += spacer - msg += '{:>{}s}: {}\n'.format( - 'Morris Perturbation', width, self.morris_inputs.get('perturbation') - ) - msg += '{:>{}s}: {}\n'.format( - 'Morris Param Levels', width, self.morris_inputs.get('levels') - ) - msg += '{:>{}s}: {}\n'.format( - 'Morris Trajectories', width, self.morris_inputs.get('trajectories') - ) - msg += '{:>{}s}: {}\n'.format( - 'Morris Random Seed', width, self.morris_inputs.get('seed', 'Auto') - ) - msg += '{:>{}s}: {}\n'.format( - 'Morris CPU Cores Requested', width, self.morris_inputs.get('cores') - ) - - if self.scenario_mode == TemoaMode.SVMGA: - msg += spacer - msg += '{:>{}s}: {}\n'.format( - 'SVMGA Cost Epsilon', width, self.svmga_inputs.get('cost_epsilon') - ) - msg += '{:>{}s}: {}\n'.format( - 'Emission Labels', width, self.svmga_inputs.get('emission_labels') - ) - msg += '{:>{}s}: {}\n'.format( - 'Capacity Labels', width, self.svmga_inputs.get('capacity_labels') - ) - msg += '{:>{}s}: {}\n'.format( - 'Activity Labels', width, self.svmga_inputs.get('activity_labels') - ) - - return msg diff --git a/temoa/temoa_model/temoa_initialize.py b/temoa/temoa_model/temoa_initialize.py deleted file mode 100644 index 252bc7aea..000000000 --- a/temoa/temoa_model/temoa_initialize.py +++ /dev/null @@ -1,1363 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" -from collections import defaultdict -from itertools import product as cross_product, product -from operator import itemgetter as iget -from sys import stderr as SE -from typing import TYPE_CHECKING - -from deprecated import deprecated -from pyomo.core import Set - -if TYPE_CHECKING: - from temoa.temoa_model.temoa_model import TemoaModel - -from io import StringIO - -from pyomo.environ import value - -from logging import getLogger - -logger = getLogger(__name__) - - -# --------------------------------------------------------------- -# Validation and initialization routines. -# There are a variety of functions in this section that do the following: -# Check valid indices, validate parameter specifications, and set default -# parameter values. -# --------------------------------------------------------------- - - -def isValidProcess(M: 'TemoaModel', r, p, i, t, v, o): - """\ -Returns a boolean (True or False) indicating whether, in any given period, a -technology can take a specified input carrier and convert it to and specified -output carrier. Not currently used. -""" - index = (r, p, t, v) - if index in M.processInputs and index in M.processOutputs: - if i in M.processInputs[index]: - if o in M.processOutputs[index]: - return True - - return False - - -def get_str_padding(obj): - return len(str(obj)) - - -def CommodityBalanceConstraintErrorCheck(vflow_out, vflow_in, r, p, s, d, c): - # an "int" here indicates that the summation ended up without any variables (empty) - if isinstance(vflow_out, int): - flow_in_expr = StringIO() - vflow_in.pprint(ostream=flow_in_expr) - msg = ( - "Unable to meet an interprocess '{}' transfer in ({}, {}, {}).\n" - 'No flow out. Constraint flow in:\n {}\n' - 'Possible reasons:\n' - " - Is there a missing period in set 'time_future'?\n" - " - Is there a missing tech in set 'tech_resource'?\n" - " - Is there a missing tech in set 'tech_production'?\n" - " - Is there a missing commodity in set 'commodity_physical'?\n" - ' - Are there missing entries in the Efficiency parameter?\n' - ' - Does a process need a longer LifetimeProcess parameter setting?' - ) - logger.error(msg.format(r, c, s, d, p, flow_in_expr.getvalue())) - raise Exception(msg.format(r, c, s, d, p, flow_in_expr.getvalue())) - - -def CommodityBalanceConstraintErrorCheckAnnual(vflow_out, vflow_in, r, p, c): - # note: if a pyomo equation simplifies to an int, there are no variables in it, which - # is an indicator of a problem - if isinstance(vflow_out, int): - flow_in_expr = StringIO() - vflow_in.pprint(ostream=flow_in_expr) - msg = ( - "Unable to meet an interprocess '{}' transfer in ({}, {}, {}).\n" - 'No flow out. Constraint flow in:\n {}\n' - 'Possible reasons:\n' - " - Is there a missing period in set 'time_future'?\n" - " - Is there a missing tech in set 'tech_resource'?\n" - " - Is there a missing tech in set 'tech_production'?\n" - " - Is there a missing commodity in set 'commodity_physical'?\n" - ' - Are there missing entries in the Efficiency parameter?\n' - ' - Does a process need a longer LifetimeProcess parameter setting?' - ) - logger.error(msg) - raise Exception(msg.format(r, c, p, flow_in_expr.getvalue())) - - -def DemandConstraintErrorCheck(supply, r, p, s, d, dem): - # note: if a pyomo equation simplifies to an int, there are no variables in it, which - # is an indicator of a problem - if isinstance(supply, int): - msg = ( - "Error: Demand '{}' for ({}, {}, {}) unable to be met by any " - 'technology.\n\tPossible reasons:\n' - ' - Is the Efficiency parameter missing an entry for this demand?\n' - ' - Does a tech that satisfies this demand need a longer ' - 'LifetimeProcess?\n' - ) - logger.error(msg) - raise Exception(msg.format(r, dem, p, s, d)) - - -def validate_time(M: 'TemoaModel'): - """ - We check for integer status here, rather than asking Pyomo to do this via - a 'within=Integers' clause in the definition so that we can have a very - specific error message. If we instead use Pyomo's mechanism, the - python invocation of Temoa throws an error (including a traceback) - that has proven to be scary and/or impenetrable for the typical modeler. - """ - logger.debug('Started validating time index') - for year in M.time_exist: - if isinstance(year, int): - continue - - msg = f'Set "time_exist" requires integer-only elements.\n\n ' f'Invalid element: "{year}"' - logger.error(msg) - raise Exception(msg) - - for year in M.time_future: - if isinstance(year, int): - continue - - msg = f'Set "time_future" requires integer-only elements.\n\n ' f'invalid element: "{year}"' - logger.error(msg) - raise Exception(msg) - - if len(M.time_future) < 2: - msg = ( - 'Set "time_future" needs at least 2 specified years. \nTemoa ' - 'treats the integer numbers specified in this set as boundary years \n' - 'between periods, and uses them to automatically ascertain the length \n' - '(in years) of each period. Note that this means that there will be \n' - 'one less optimization period than the number of elements in this set.' - ) - - logger.error(msg) - raise RuntimeError(msg) - - # Ensure that the time_exist < time_future - max_exist = max(M.time_exist) - min_horizon = min(M.time_future) - - if not (max_exist < min_horizon): - msg = ( - 'All items in time_future must be larger than in time_exist.' - '\ntime_exist max: {}' - '\ntime_future min: {}' - ) - logger.error(msg.format(max_exist, min_horizon)) - raise Exception(msg.format(max_exist, min_horizon)) - logger.debug('Finished validating time') - - -def validate_SegFrac(M: 'TemoaModel'): - """Ensure that the segment fractions adds up to 1""" - total = sum(i for i in M.SegFrac.values()) - - if abs(float(total) - 1.0) > 0.001: - # We can't explicitly test for "!= 1.0" because of incremental rounding - # errors associated with the specification of SegFrac by time slice, - # but we check to make sure it is within the specified tolerance. - - key_padding = max(map(get_str_padding, M.SegFrac.sparse_iterkeys())) - - fmt = '%%-%ds = %%s' % key_padding - # Works out to something like "%-25s = %s" - - items = sorted(M.SegFrac.items()) - items = '\n '.join(fmt % (str(k), v) for k, v in items) - - msg = ( - 'The values of the SegFrac parameter do not sum to 1. Each item ' - 'in SegFrac represents a fraction of a year, so they must total to ' - '1. Current values:\n {}\n\tsum = {}' - ) - logger.error(msg.format(items, total)) - raise Exception(msg.format(items, total)) - - -def CheckEfficiencyIndices(M: 'TemoaModel'): - """ - Ensure that there are no unused items in any of the Efficiency index sets. - """ - # TODO: This could be upgraded to scan for finer resolution - # by checking by REGION and PERIOD... Each region/period is unique. - c_physical = set(i for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) - techs = set(t for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) - c_outputs = set(o for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) - - symdiff = c_physical.symmetric_difference(M.commodity_physical) - if symdiff: - msg = ( - 'Unused or unspecified physical carriers. Either add or remove ' - 'the following elements to the Set commodity_physical.' - '\n\n Element(s): {}' - ) - symdiff = (str(i) for i in symdiff) - f_msg = msg.format(', '.join(symdiff)) - logger.error(f_msg) - raise ValueError(f_msg) - - symdiff = techs.symmetric_difference(M.tech_all) - if symdiff: - msg = ( - 'Unused or unspecified technologies. Either add or remove ' - 'the following technology(ies) to the tech_resource or ' - 'tech_production Sets.\n\n Technology(ies): {}' - ) - symdiff = (str(i) for i in symdiff) - f_msg = msg.format(', '.join(symdiff)) - logger.error(f_msg) - raise ValueError(f_msg) - - diff = M.commodity_demand - c_outputs - if diff: - msg = ( - 'Unused or unspecified outputs. Either add or remove the ' - 'following elements to the commodity_demand Set.' - '\n\n Element(s): {}' - ) - diff = (str(i) for i in diff) - f_msg = msg.format(', '.join(diff)) - logger.error(f_msg) - raise ValueError(f_msg) - - -@deprecated('should not be needed. We are pulling the default on-the-fly where used') -def CreateCapacityFactors(M: 'TemoaModel'): - """ - Steps to creating capacity factors: - 1. Collect all possible processes - 2. Find the ones _not_ specified in CapacityFactorProcess - 3. Set them, based on CapacityFactorTech. - """ - # Shorter names, for us lazy programmer types - CFP = M.CapacityFactorProcess - - # Step 1 - processes = set((r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) - - all_cfs = set( - (r, s, d, t, v) - for (r, t, v), s, d in cross_product(processes, M.time_season, M.time_of_day) - ) - - # Step 2 - unspecified_cfs = all_cfs.difference(CFP.sparse_iterkeys()) - - # Step 3 - - # Some hackery: We futz with _constructed because Pyomo thinks that this - # Param is already constructed. However, in our view, it is not yet, - # because we're specifically targeting values that have not yet been - # constructed, that we know are valid, and that we will need. - - if unspecified_cfs: - # CFP._constructed = False - for r, s, d, t, v in unspecified_cfs: - CFP[r, s, d, t, v] = M.CapacityFactorTech[r, s, d, t] - logger.debug( - 'Created Capacity Factors for %d processes without an explicit specification', - len(unspecified_cfs), - ) - # CFP._constructed = True - - -def get_default_process_lifetime(M: 'TemoaModel', r, t, v): - """ - This initializer used to initialize the LifetimeProcess parameter from LifetimeTech where needed - - Priority: - 1. Specified in LifetimeProcess data (provided as a fill and would not call this function) - 2. Specified in LifetimeTech data - 3. The default value from the LifetimeTech param (automatic) - :param M: generic model reference (not used) - :param r: region - :param t: tech - :param v: vintage - :return: the final lifetime value - """ - return M.LifetimeTech[r, t] - - -def get_default_capacity_factor(M: 'TemoaModel', r, s, d, t, v): - """ - This initializer is used to fill the CapacityFactorProcess from the CapacityFactorTech where needed. - - Priority: - 1. As specified in data input (this function not called) - 2. Here - 3. The default from CapacityFactorTech param - :param M: generic model reference - :param r: region - :param s: season - :param d: time-of-day slice - :param t: tech - :param v: vintage - :return: the capacity factor - """ - return M.CapacityFactorTech[r, s, d, t] - - -def get_default_loan_rate(M, *_): - """get the default loan rate from the DefaultLoanRate param""" - return M.DefaultLoanRate() - - -def CreateDemands(M: 'TemoaModel'): - """ - Steps to create the demand distributions - 1. Use Demand keys to ensure that all demands in commodity_demand are used - 2. Find any slices not set in DemandDefaultDistribution, and set them based - on the associated SegFrac slice. - 3. Validate that the DemandDefaultDistribution sums to 1. - 4. Find any per-demand DemandSpecificDistribution values not set, and set - them from DemandDefaultDistribution. Note that this only sets a - distribution for an end-use demand if the user has *not* specified _any_ - anything for that end-use demand. Thus, it is up to the user to fully - specify the distribution, or not. No in-between. - 5. Validate that the per-demand distributions sum to 1. - """ - logger.debug('Started creating demand distributions in CreateDemands()') - - # Step 0: some setup for a couple of reusable items - - # iget(3): 3 = magic number to specify the fourth column. Currently the - # demand in the tuple (r, s, d, dem) - DSD_dem_getter = iget(3) - - # iget(0): 0 = magic number to specify the first column. Currently the - # demand in the tuple (r, s, d, dem) - DSD_region_getter = iget(0) - - # Step 1 - used_dems = set(dem for r, p, dem in M.Demand.sparse_iterkeys()) - unused_dems = sorted(M.commodity_demand.difference(used_dems)) - if unused_dems: - for dem in unused_dems: - msg = "Warning: Demand '{}' is unused\n" - logger.warning(msg.format(dem)) - SE.write(msg.format(dem)) - - # Step 2 - DDD = M.DemandDefaultDistribution # Shorter, for us lazy programmer types - unset_defaults = set(M.SegFrac.sparse_iterkeys()) - unset_defaults.difference_update(DDD.sparse_iterkeys()) - if unset_defaults: - # Some hackery because Pyomo thinks that this Param is constructed. - # However, in our view, it is not yet, because we're specifically - # targeting values that have not yet been constructed, that we know are - # valid, and that we will need. - # DDD._constructed = False - for tslice in unset_defaults: - DDD[tslice] = M.SegFrac[tslice] # DDD._constructed = True - - # Step 3 - total = sum(i for i in DDD.values()) - if abs(value(total) - 1.0) > 0.001: - # We can't explicitly test for "!= 1.0" because of incremental rounding - # errors associated with the specification of demand shares by time slice, - # but we check to make sure it is within the specified tolerance. - - key_padding = max(map(get_str_padding, DDD.sparse_iterkeys())) - - fmt = '%%-%ds = %%s' % key_padding - # Works out to something like "%-25s = %s" - - items = sorted(DDD.items()) - items = '\n '.join(fmt % (str(k), v) for k, v in items) - - msg = ( - 'The values of the DemandDefaultDistribution parameter do not ' - 'sum to 1. The DemandDefaultDistribution specifies how end-use ' - 'demands are distributed among the time slices (i.e., time_season, ' - 'time_of_day), so together, the data must total to 1. Current ' - 'values:\n {}\n\tsum = {}' - ) - logger.error(msg.format(items, total)) - raise ValueError(msg.format(items, total)) - - # Step 4 - DSD = M.DemandSpecificDistribution - - demands_specified = set(map(DSD_dem_getter, (i for i in DSD.sparse_iterkeys()))) - unset_demand_distributions = used_dems.difference( - demands_specified - ) # the demands not mentioned in DSD *at all* - unset_distributions = set( - cross_product(M.regions, M.time_season, M.time_of_day, unset_demand_distributions) - ) - - if unset_distributions: - # Some hackery because Pyomo thinks that this Param is constructed. - # However, in our view, it is not yet, because we're specifically - # targeting values that have not yet been constructed, that we know are - # valid, and that we will need. - # DSD._constructed = False - for r, s, d, dem in unset_distributions: - DSD[r, s, d, dem] = DDD[s, d] # DSD._constructed = True - - # Step 5: A final "sum to 1" check for all DSD members (which now should be everything) - # Also check that all keys are made... The demand distro should be supported - # by the full set of (r, p, dem) keys because it is an equality constraint - # and we need to ensure even the zeros are passed in - expected_key_length = len(M.time_season) * len(M.time_of_day) - used_reg_dems = set((r, dem) for r, p, dem in M.Demand.sparse_iterkeys()) - for r, dem in used_reg_dems: - keys = [ - k - for k in DSD.sparse_iterkeys() - if DSD_dem_getter(k) == dem and DSD_region_getter(k) == r - ] - if len(keys) != expected_key_length: - logger.debug( - 'Missing some keys in this set for creation of Demand Distribution %s: %s', - dem, - keys, - ) - total = sum(DSD[i] for i in keys) - if abs(value(total) - 1.0) > 0.001: - # We can't explicitly test for "!= 1.0" because of incremental rounding - # errors associated with the specification of demand shares by time slice, - # but we check to make sure it is within the specified tolerance. - - keys = [ - k - for k in DSD.sparse_iterkeys() - if DSD_dem_getter(k) == dem and DSD_region_getter(k) == r - ] - key_padding = max(map(get_str_padding, keys)) - - fmt = '%%-%ds = %%s' % key_padding - # Works out to something like "%-25s = %s" - - items = sorted((k, DSD[k]) for k in keys) - items = '\n '.join(fmt % (str(k), v) for k, v in items) - - msg = ( - 'The values of the DemandSpecificDistribution parameter do not ' - 'sum to 1. The DemandSpecificDistribution specifies how end-use ' - 'demands are distributed per time-slice (i.e., time_season, ' - 'time_of_day). Within each end-use Demand, then, the distribution ' - 'must total to 1.\n\n Demand-specific distribution in error: ' - ' {}\n\n {}\n\tsum = {}' - ) - logger.error(msg.format(dem, items, total)) - raise ValueError(msg.format(dem, items, total)) - logger.debug('Finished creating demand distributions') - - -@deprecated(reason='vintage defaults are no longer available, so this should not be needed') -def CreateCosts(M: 'TemoaModel'): - """ - Steps to creating fixed and variable costs: - 1. Collect all possible cost indices (CostFixed, CostVariable) - 2. Find the ones _not_ specified in CostFixed and CostVariable - 3. Set them, based on Cost*VintageDefault - """ - logger.debug('Started Creating Fixed and Variable costs in CreateCosts()') - # Shorter names, for us lazy programmer types - CF = M.CostFixed - CV = M.CostVariable - - # Step 1 - fixed_indices = set(M.CostFixed_rptv) - var_indices = set(M.CostVariable_rptv) - - # Step 2 - unspecified_fixed_prices = fixed_indices.difference(CF.sparse_iterkeys()) - unspecified_var_prices = var_indices.difference(CV.sparse_iterkeys()) - - # Step 3 - - # Some hackery: We futz with _constructed because Pyomo thinks that this - # Param is already constructed. However, in our view, it is not yet, - # because we're specifically targeting values that have not yet been - # constructed, that we know are valid, and that we will need. - - if unspecified_fixed_prices: - # CF._constructed = False - for r, p, t, v in unspecified_fixed_prices: - if (r, t, v) in M.CostFixedVintageDefault: - CF[r, p, t, v] = M.CostFixedVintageDefault[r, t, v] # CF._constructed = True - - if unspecified_var_prices: - # CV._constructed = False - for r, p, t, v in unspecified_var_prices: - if (r, t, v) in M.CostVariableVintageDefault: - CV[r, p, t, v] = M.CostVariableVintageDefault[r, t, v] - # CV._constructed = True - logger.debug('Created M.CostFixed with size: %d', len(M.CostFixed)) - logger.debug('Created M.CostVariable with size: %d', len(M.CostVariable)) - logger.debug('Finished creating Fixed and Variable costs') - - -def init_set_time_optimize(M: 'TemoaModel'): - return sorted(M.time_future)[:-1] - - -def init_set_vintage_exist(M: 'TemoaModel'): - return sorted(M.time_exist) - - -def init_set_vintage_optimize(M: 'TemoaModel'): - return sorted(M.time_optimize) - - -def CreateRegionalIndices(M: 'TemoaModel'): - """Create the set of all regions and all region-region pairs""" - regional_indices = set() - for r_i in M.regions: - if '-' in r_i: - logger.error("Individual region names can not have '-' in their names: %s", str(r_i)) - raise ValueError("Individual region names can not have '-' in their names: " + str(r_i)) - for r_j in M.regions: - if r_i == r_j: - regional_indices.add(r_i) - else: - regional_indices.add(r_i + '-' + r_j) - # dev note: Sorting these passed them to pyomo in an ordered container and prevents warnings - return sorted(regional_indices) - - -# --------------------------------------------------------------- -# The functions below perform the sparse matrix indexing, allowing Pyomo to only -# create the necessary parameter, variable, and constraint indices. This -# cuts down *tremendously* on memory usage, which decreases time and increases -# the maximum specifiable problem size. -# -# It begins below in CreateSparseDicts, which creates a set of -# dictionaries that serve as the basis of the sparse indices. -# --------------------------------------------------------------- - - -def CreateSparseDicts(M: 'TemoaModel'): - """ - This function creates customized dictionaries with only the key / value pairs - defined in the associated datafile. The dictionaries defined here are used to - do the sparse matrix indexing for all parameters, variables, and constraints - in the model. The function works by looping over the sparse indices in the - Efficiency table. For each iteration of the loop, the appropriate key / value - pairs are defined as appropriate for each dictionary. - """ - l_first_period = min(M.time_future) - l_exist_indices = M.ExistingCapacity.sparse_keys() - l_used_techs = set() - - # The basis for the dictionaries are the sparse keys defined in the - # Efficiency table. - logger.debug( - 'Starting creation of SparseDicts with Efficiency table size: %d', len(M.Efficiency) - ) - for r, i, t, v, o in M.Efficiency.sparse_iterkeys(): - if '-' in r and t not in M.tech_exchange: - msg = ( - f'Technology {t} seems to be an exchange technology ' - f'but it is not specified in tech_exchange set' - ) - logger.error(msg) - raise ValueError(msg) - l_process = (r, t, v) - l_lifetime = value(M.LifetimeProcess[l_process]) - # Do some error checking for the user. - if v in M.vintage_exist: - if l_process not in l_exist_indices and t not in M.tech_uncap: - msg = ( - 'Warning: %s has a specified Efficiency, but does not ' - 'have any existing install base (ExistingCapacity).\n' - ) - SE.write(msg % str(l_process)) - continue - if t not in M.tech_uncap and M.ExistingCapacity[l_process] == 0: - msg = ( - 'Notice: Unnecessary specification of ExistingCapacity ' - '%s. If specifying a capacity of zero, you may simply ' - 'omit the declaration.\n' - ) - logger.info(msg, str(l_process)) - SE.write(msg % str(l_process)) - continue - if v + l_lifetime <= l_first_period: - msg = ( - '\nWarning: %s specified as ExistingCapacity, but its ' - 'LifetimeProcess parameter does not extend past the beginning ' - 'of time_future. (i.e. useless parameter)' - '\n\tLifetime: %s' - '\n\tFirst period: %s\n' - ) - logger.warning(msg, l_process, l_lifetime, l_first_period) - SE.write(msg % (l_process, l_lifetime, l_first_period)) - continue - - eindex = (r, i, t, v, o) - if M.Efficiency[eindex] == 0: - msg = ( - '\nNotice: Unnecessary specification of Efficiency %s. If ' - 'specifying an efficiency of zero, you may simply omit the ' - 'declaration.\n' - ) - logger.info(msg, str(eindex)) - SE.write(msg % str(eindex)) - continue - - l_used_techs.add(t) - - if t in M.tech_flex: - M.flex_commodities.add(o) - - # Add in the period (p) index, since it's not included in the efficiency - # table. - for p in M.time_optimize: - # Can't build a vintage before it's been invented - if p < v: - continue - - pindex = (r, p, t, v) - - # dev note: this gathering of processLoans appears to be unused in any meaningful way - # it is just plucked later for (r, t, v) combos which aren't needed anyhow. - # if v in M.time_optimize: - # l_loan_life = value(M.LoanLifetimeProcess[l_process]) - # if v + l_loan_life >= p: - # M.processLoans[pindex] = True - - # if tech is no longer active, don't include it - if v + l_lifetime <= p: - continue - - # Here we utilize the indices in a given iteration of the loop to - # create the dictionary keys, and initialize the associated values - # to an empty set. - if pindex not in M.processInputs: - M.processInputs[pindex] = set() - M.processOutputs[pindex] = set() - if (r, p, i) not in M.commodityDStreamProcess: - M.commodityDStreamProcess[r, p, i] = set() - if (r, p, o) not in M.commodityUStreamProcess: - M.commodityUStreamProcess[r, p, o] = set() - if (r, p, t, v, i) not in M.ProcessOutputsByInput: - M.ProcessOutputsByInput[r, p, t, v, i] = set() - if (r, p, t, v, o) not in M.ProcessInputsByOutput: - M.ProcessInputsByOutput[r, p, t, v, o] = set() - if (r, t) not in M.processTechs: - M.processTechs[r, t] = set() - # While the dictionary just above identifies the vintage (v) - # associated with each (r,p,t) we need to do the same below for various - # technology subsets. - if (r, p, t) not in M.processVintages: - M.processVintages[r, p, t] = set() - if t in M.tech_curtailment and (r, p, t) not in M.curtailmentVintages: - M.curtailmentVintages[r, p, t] = set() - if t in M.tech_baseload and (r, p, t) not in M.baseloadVintages: - M.baseloadVintages[r, p, t] = set() - if t in M.tech_storage and (r, p, t) not in M.storageVintages: - M.storageVintages[r, p, t] = set() - if t in M.tech_ramping and (r, p, t) not in M.rampVintages: - M.rampVintages[r, p, t] = set() - if (r, p, i, t) in M.TechInputSplit.sparse_iterkeys() and ( - r, - p, - i, - t, - ) not in M.inputsplitVintages: - M.inputsplitVintages[r, p, i, t] = set() - if (r, p, i, t) in M.TechInputSplitAverage.sparse_iterkeys() and ( - r, - p, - i, - t, - ) not in M.inputsplitaverageVintages: - M.inputsplitaverageVintages[r, p, i, t] = set() - if (r, p, t, o) in M.TechOutputSplit.sparse_iterkeys() and ( - r, - p, - t, - o, - ) not in M.outputsplitVintages: - M.outputsplitVintages[r, p, t, o] = set() - if t in M.tech_resource and (r, p, o) not in M.ProcessByPeriodAndOutput: - M.ProcessByPeriodAndOutput[r, p, o] = set() - if t in M.tech_reserve and (r, p) not in M.processReservePeriods: - M.processReservePeriods[r, p] = set() - - # since t is in M.tech_exchange, r here has *-* format (e.g. 'US-Mexico'). # r[ - # :r.find("-")] extracts the region index before the "-". - if t in M.tech_exchange and (r[: r.find('-')], p, i) not in M.exportRegions: - M.exportRegions[r[: r.find('-')], p, i] = set() - if t in M.tech_exchange and (r[r.find('-') + 1 :], p, o) not in M.importRegions: - M.importRegions[r[r.find('-') + 1 :], p, o] = set() - - # Now that all of the keys have been defined, and values initialized - # to empty sets, we fill in the appropriate values for each - # dictionary. - M.processInputs[pindex].add(i) - M.processOutputs[pindex].add(o) - M.commodityDStreamProcess[r, p, i].add((t, v)) - M.commodityUStreamProcess[r, p, o].add((t, v)) - M.ProcessOutputsByInput[r, p, t, v, i].add(o) - M.ProcessInputsByOutput[r, p, t, v, o].add(i) - M.processTechs[r, t].add((p, v)) - M.processVintages[r, p, t].add(v) - if t in M.tech_curtailment: - M.curtailmentVintages[r, p, t].add(v) - if t in M.tech_baseload: - M.baseloadVintages[r, p, t].add(v) - if t in M.tech_storage: - M.storageVintages[r, p, t].add(v) - if t in M.tech_ramping: - M.rampVintages[r, p, t].add(v) - if (r, p, i, t) in M.TechInputSplit.sparse_iterkeys(): - M.inputsplitVintages[r, p, i, t].add(v) - if (r, p, i, t) in M.TechInputSplitAverage.sparse_iterkeys(): - M.inputsplitaverageVintages[r, p, i, t].add(v) - if (r, p, t, o) in M.TechOutputSplit.sparse_iterkeys(): - M.outputsplitVintages[r, p, t, o].add(v) - if t in M.tech_resource: - M.ProcessByPeriodAndOutput[r, p, o].add((i, t, v)) - if t in M.tech_reserve: - M.processReservePeriods[r, p].add((t, v)) - if t in M.tech_exchange: - M.exportRegions[r[: r.find('-')], p, i].add((r[r.find('-') + 1 :], t, v, o)) - if t in M.tech_exchange: - M.importRegions[r[r.find('-') + 1 :], p, o].add((r[: r.find('-')], t, v, i)) - - for r, i, t, v, o in M.Efficiency.sparse_iterkeys(): - if t in M.tech_exchange: - reg = r.split('-')[0] - for r1, i1, t1, v1, o1 in M.Efficiency.sparse_iterkeys(): - if (r1 == reg) & (o1 == i): - for p in M.time_optimize: - if p >= v and (r1, p, o1) not in M.commodityDStreamProcess: - msg = ( - 'The {} process in region {} has no downstream process other ' - 'than a transport ({}) process. This will cause the commodity ' - 'balance constraint to fail. Add a dummy technology downstream ' - 'of the {} process to the Efficiency table to avoid this ' - 'issue. The dummy technology should have the same region and ' - 'vintage as the {} process, an efficiency of 100%, with the {} ' - 'commodity as the input and output.' - 'The dummy technology may also need a corresponding row in the ' - 'ExistingCapacity table with capacity values that equal the {} ' - 'technology.' - ) - f_msg = msg.format(t1, r1, t, t1, t1, o1, t1) - logger.error(f_msg) - raise ValueError(f_msg) - - l_unused_techs = M.tech_all - l_used_techs - if l_unused_techs: - msg = ( - "Notice: '{}' specified as technology, but it is not utilized in " - 'the Efficiency parameter.\n' - ) - for i in sorted(l_unused_techs): - SE.write(msg.format(i)) - - M.activeFlow_rpsditvo = set( - (r, p, s, d, i, t, v, o) - for r, p, t in M.processVintages.keys() - if t not in M.tech_annual - for v in M.processVintages[r, p, t] - for i in M.processInputs[r, p, t, v] - for o in M.ProcessOutputsByInput[r, p, t, v, i] - for s in M.time_season - for d in M.time_of_day - ) - - M.activeFlow_rpitvo = set( - (r, p, i, t, v, o) - for r, p, t in M.processVintages.keys() - if t in M.tech_annual - for v in M.processVintages[r, p, t] - for i in M.processInputs[r, p, t, v] - for o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - M.activeFlex_rpsditvo = set( - (r, p, s, d, i, t, v, o) - for r, p, t in M.processVintages.keys() - if (t not in M.tech_annual) and (t in M.tech_flex) - for v in M.processVintages[r, p, t] - for i in M.processInputs[r, p, t, v] - for o in M.ProcessOutputsByInput[r, p, t, v, i] - for s in M.time_season - for d in M.time_of_day - ) - - M.activeFlex_rpitvo = set( - (r, p, i, t, v, o) - for r, p, t in M.processVintages.keys() - if (t in M.tech_annual) and (t in M.tech_flex) - for v in M.processVintages[r, p, t] - for i in M.processInputs[r, p, t, v] - for o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - M.activeFlowInStorage_rpsditvo = set( - (r, p, s, d, i, t, v, o) - for r, p, t in M.processVintages.keys() - if t in M.tech_storage - for v in M.processVintages[r, p, t] - for i in M.processInputs[r, p, t, v] - for o in M.ProcessOutputsByInput[r, p, t, v, i] - for s in M.time_season - for d in M.time_of_day - ) - - M.activeCurtailment_rpsditvo = set( - (r, p, s, d, i, t, v, o) - for r, p, t in M.curtailmentVintages.keys() - for v in M.curtailmentVintages[r, p, t] - for i in M.processInputs[r, p, t, v] - for o in M.ProcessOutputsByInput[r, p, t, v, i] - for s in M.time_season - for d in M.time_of_day - ) - - M.activeActivity_rptv = set( - (r, p, t, v) for r, p, t in M.processVintages.keys() for v in M.processVintages[r, p, t] - ) - - M.activeRegionsForTech = defaultdict(set) - for r, p, t, v in M.activeActivity_rptv: - M.activeRegionsForTech[p, t].add(r) - - M.activeCapacity_rtv = set( - (r, t, v) - for r, p, t in M.processVintages.keys() - for v in M.processVintages[r, p, t] - if t not in M.tech_uncap - ) - - M.activeCapacityAvailable_rpt = set( - (r, p, t) - for r, p, t in M.processVintages.keys() - if M.processVintages[r, p, t] - if t not in M.tech_uncap - ) - - M.activeCapacityAvailable_rptv = set( - (r, p, t, v) - for r, p, t in M.processVintages.keys() - for v in M.processVintages[r, p, t] - if t not in M.tech_uncap - ) - logger.debug('Completed creation of SparseDicts') - - -# --------------------------------------------------------------- -# Create sparse parameter indices. -# These functions are called from temoa_model.py and use the sparse keys -# associated with specific parameters. -# --------------------------------------------------------------- - - -@deprecated('switched over to validator... this set is typically VERY empty') -def CapacityFactorProcessIndices(M: 'TemoaModel'): - indices = set( - (r, s, d, t, v) - for r, i, t, v, o in M.Efficiency.sparse_iterkeys() - for s in M.time_season - for d in M.time_of_day - ) - - return indices - - -def CapacityFactorTechIndices(M: 'TemoaModel'): - processes = set((r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) - all_cfs = set( - (r, s, d, t) for (r, t, v), s, d in cross_product(processes, M.time_season, M.time_of_day) - ) - return all_cfs - - -def CostFixedIndices(M: 'TemoaModel'): - # we pull the unlimited capacity techs from this index. They cannot have fixed costs - return {(r, p, t, v) for r, p, t, v in M.activeActivity_rptv if t not in M.tech_uncap} - - -def CostVariableIndices(M: 'TemoaModel'): - return M.activeActivity_rptv - - -# dev note: appears superfluous... -# def CostInvestIndices(M: 'TemoaModel'): -# indices = set((r, t, v) for r, p, t, v in M.processLoans) -# -# return indices - - -@deprecated('No longer used. See the region_group_check in validators.py') -def RegionalGlobalInitializedIndices(M: 'TemoaModel'): - from itertools import permutations - - indices = set() - for n in range(1, len(M.regions) + 1): - regional_perms = permutations(M.regions, n) - for i in regional_perms: - indices.add('+'.join(i)) - indices.add('global') - indices = indices.union(M.RegionalIndices) - - return indices - - -def GroupShareIndices(M: 'TemoaModel'): - indices = set( - (r, p, t, g) - for g in M.tech_group_names - for r, p, t in M.processVintages.keys() - if t in M.tech_group_members[g] - ) - - return indices - - -def EmissionActivityIndices(M: 'TemoaModel'): - indices = set( - (r, e, i, t, v, o) - for r, i, t, v, o in M.Efficiency.sparse_iterkeys() - for e in M.commodity_emissions - if r in M.regions # omit any exchange/groups - ) - - return indices - - -def EmissionActivityByPeriodAndTechVariableIndices(M: 'TemoaModel'): - indices = set( - (e, p, t) for e, i, t, v, o in M.EmissionActivity.sparse_iterkeys() for p in M.time_optimize - ) - - return indices - - -def ModelProcessLifeIndices(M: 'TemoaModel'): - """\ -Returns the set of sensical (region, period, tech, vintage) tuples. The tuple indicates -the periods in which a process is active, distinct from TechLifeFracIndices that -returns indices only for processes that EOL mid-period. -""" - return M.activeActivity_rptv - - -def LifetimeProcessIndices(M: 'TemoaModel'): - """\ -Based on the Efficiency parameter's indices, this function returns the set of -process indices that may be specified in the LifetimeProcess parameter. -""" - indices = set((r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys()) - - return indices - - -def LifetimeLoanProcessIndices(M: 'TemoaModel'): - """\ -Based on the Efficiency parameter's indices and time_future parameter, this -function returns the set of process indices that may be specified in the -CostInvest parameter. -""" - min_period = min(M.vintage_optimize) - - indices = set((r, t, v) for r, i, t, v, o in M.Efficiency.sparse_iterkeys() if v >= min_period) - - return indices - - -# --------------------------------------------------------------- -# Create sparse indices for decision variables. -# These functions are called from temoa_model.py and use the dictionaries -# created above in CreateSparseDicts() -# --------------------------------------------------------------- - - -def CapacityVariableIndices(M: 'TemoaModel'): - return M.activeCapacity_rtv - - -def RetiredCapacityVariableIndices(M: 'TemoaModel'): - return set( - (r, p, t, v) - for r, p, t in M.processVintages.keys() - if t in M.tech_retirement - for v in M.processVintages[r, p, t] - if p > v and t not in M.tech_uncap - ) - - -def CapacityAvailableVariableIndices(M: 'TemoaModel'): - return M.activeCapacityAvailable_rpt - - -def CapacityAvailableVariableIndicesVintage(M: 'TemoaModel'): - return M.activeCapacityAvailable_rptv - - -def FlowVariableIndices(M: 'TemoaModel'): - return M.activeFlow_rpsditvo - - -def FlowVariableAnnualIndices(M: 'TemoaModel'): - return M.activeFlow_rpitvo - - -def FlexVariablelIndices(M: 'TemoaModel'): - return M.activeFlex_rpsditvo - - -def FlexVariableAnnualIndices(M: 'TemoaModel'): - return M.activeFlex_rpitvo - - -def FlowInStorageVariableIndices(M: 'TemoaModel'): - return M.activeFlowInStorage_rpsditvo - - -def CurtailmentVariableIndices(M: 'TemoaModel'): - return M.activeCurtailment_rpsditvo - - -def CapacityConstraintIndices(M: 'TemoaModel'): - capacity_indices = set( - (r, p, s, d, t, v) - for r, p, t, v in M.activeActivity_rptv - if t not in M.tech_annual - if t not in M.tech_uncap - for s in M.time_season - for d in M.time_of_day - ) - - return capacity_indices - - -def LinkedTechConstraintIndices(M: 'TemoaModel'): - linkedtech_indices = set( - (r, p, s, d, t, v, e) - for r, t, e in M.LinkedTechs.sparse_iterkeys() - for p in M.time_optimize - if (r, p, t) in M.processVintages.keys() - for v in M.processVintages[r, p, t] - if (r, p, t, v) in M.activeActivity_rptv - for s in M.time_season - for d in M.time_of_day - ) - - return linkedtech_indices - - -def CapacityAnnualConstraintIndices(M: 'TemoaModel'): - capacity_indices = set( - (r, p, t, v) - for r, p, t, v in M.activeActivity_rptv - if t in M.tech_annual - if t not in M.tech_uncap - ) - - return capacity_indices - - -# --------------------------------------------------------------- -# Create sparse indices for constraints. -# These functions are called from temoa_model.py and use the dictionaries -# created above in CreateSparseDicts() -# --------------------------------------------------------------- - - -def DemandActivityConstraintIndices(M: 'TemoaModel'): - """\ -This function returns a set of sparse indices that are used in the -DemandActivity constraint. It returns a tuple of the form: -(p,s,d,t,v,dem,first_s,first_d) where "dem" is a demand commodity, and "first_s" -and "first_d" are the reference season and time-of-day, respectively used to -ensure demand activity remains consistent across time slices. -""" - - # needed data structures... - # the count of techs that supply a commodity - suppliers = defaultdict(set) - # (region, demand): (season, tod) # the goal of the exercise! - anchor_season_tod = {} - # (region, demand): (period, tech, vintage) # the viable tech and vintage per region, demand - viable_tech_vintage = defaultdict(list) - - # start the loop over possible combos - for r, p, t, v, dem in M.ProcessInputsByOutput: - # we aren't concerned with non-demand commodities or annual techs - if dem not in M.commodity_demand or t in M.tech_annual: - continue - # capture the (p, t, v) in case we need to act on it - viable_tech_vintage[r, dem].append((p, t, v)) - suppliers[dem].add(t) # one more recognized supplier - if len(suppliers[dem]) > 1: - # We need to act on (build) for this region-demand, put in a placeholder - anchor_season_tod[r, dem] = None - - # Find the first timestep of the year where the demand is appreciably sized: - # appreciable = not so small that we get into numerical instability when applying small multipliers - appreciable_size = 0.0001 - - for r, dem in anchor_season_tod: - found_flag = False - s0, d0 = None, None - for s0, d0 in ((ss, dd) for ss in M.time_season for dd in M.time_of_day): - if (r, s0, d0, dem) in M.DemandSpecificDistribution.sparse_iterkeys(): - if value(M.DemandSpecificDistribution[(r, s0, d0, dem)]) >= appreciable_size: - found_flag = True - break # we have one with some value associated - found = 'found' if found_flag else 'not found' - # set it. If nothing was found the first indices should work just fine... - anchor_season_tod[r, dem] = (s0, d0) - logger.debug( - 'Using season/tod: %s, %s for commodity %s in region %s which was %s in DSD ' - 'to set DemandActivity baseline', - s0, - d0, - dem, - r, - found, - ) - - # Start yielding the constraint indices - for r, dem in anchor_season_tod: - s0, d0 = anchor_season_tod[r, dem] - for p, t, v in viable_tech_vintage[r, dem]: - for s in M.time_season: - for d in M.time_of_day: - if s != s0 or d != d0: - yield r, p, s, d, t, v, dem, s0, d0 - - -def DemandConstraintIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, d, dem) - for r, p, dem in M.Demand.sparse_iterkeys() - for s in M.time_season - for d in M.time_of_day - ) - - return indices - - -def BaseloadDiurnalConstraintIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, d, t, v) - for r, p, t in M.baseloadVintages.keys() - for v in M.baseloadVintages[r, p, t] - for s in M.time_season - for d in M.time_of_day - ) - - return indices - - -def RegionalExchangeCapacityConstraintIndices(M: 'TemoaModel'): - indices = set( - (r_e, r_i, p, t, v) - for r_e, p, i in M.exportRegions.keys() - for r_i, t, v, o in M.exportRegions[r_e, p, i] - ) - - return indices - - -def CommodityBalanceConstraintIndices(M: 'TemoaModel'): - # Generate indices only for those commodities that are produced by - # technologies with varying output at the time slice level. - period_commodity_with_up = set(M.commodityUStreamProcess.keys()) - period_commodity_with_dn = set(M.commodityDStreamProcess.keys()) - period_commodity = period_commodity_with_up.intersection(period_commodity_with_dn) - indices = set( - (r, p, s, d, o) - for r, p, o in period_commodity - # r in this line includes interregional transfer combinations (not needed). - if r in M.regions # this line ensures only the regions are included. - for t, v in M.commodityUStreamProcess[r, p, o] - if (r, t) not in M.tech_storage and t not in M.tech_annual - for s in M.time_season - for d in M.time_of_day - ) - - return indices - - -def CommodityBalanceAnnualConstraintIndices(M: 'TemoaModel'): - # Generate indices only for those commodities that are produced by - # technologies with constant annual output. - period_commodity_with_up = set(M.commodityUStreamProcess.keys()) - period_commodity_with_dn = set(M.commodityDStreamProcess.keys()) - period_commodity = period_commodity_with_up.intersection(period_commodity_with_dn) - indices = set( - (r, p, o) - for r, p, o in period_commodity - # r in this line includes interregional transfer combinations (not needed). - if r in M.regions # this line ensures only the regions are included. - for t, v in M.commodityUStreamProcess[r, p, o] - if (r, t) not in M.tech_storage and t in M.tech_annual - ) - - return indices - - -def StorageVariableIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, d, t, v) - for r, p, t in M.storageVintages.keys() - for s in M.time_season - for d in M.time_of_day - for v in M.storageVintages[r, p, t] - ) - - return indices - - -def StorageInitIndices(M: 'TemoaModel'): - indices = set( - (r, t, v) for r, p, t in M.storageVintages.keys() for v in M.storageVintages[r, p, t] - ) - - return indices - - -def StorageInitConstraintIndices(M: 'TemoaModel'): - indices = set((r, t, v) for r, t, v in M.StorageInitFrac.sparse_iterkeys()) - - return indices - - -def RampConstraintDayIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, d, t, v) - for r, p, t in M.rampVintages.keys() - for s in M.time_season - for d in M.time_of_day - for v in M.rampVintages[r, p, t] - ) - - return indices - - -def RampConstraintSeasonIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, t, v) - for r, p, t in M.rampVintages.keys() - for s in M.time_season - for v in M.rampVintages[r, p, t] - ) - - return indices - - -def RampConstraintPeriodIndices(M: 'TemoaModel'): - indices = set( - (r, p, t, v) for r, p, t in M.rampVintages.keys() for v in M.rampVintages[r, p, t] - ) - - return indices - - -def ReserveMarginIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, d) - for r in M.regions - for p in M.time_optimize - for s in M.time_season - for d in M.time_of_day - ) - return indices - - -def TechInputSplitConstraintIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, d, i, t, v) - for r, p, i, t in M.inputsplitVintages.keys() - if t not in M.tech_annual and t not in M.tech_variable - for v in M.inputsplitVintages[r, p, i, t] - for s in M.time_season - for d in M.time_of_day - ) - - return indices - - -def TechInputSplitAnnualConstraintIndices(M: 'TemoaModel'): - indices = set( - (r, p, i, t, v) - for r, p, i, t in M.inputsplitVintages.keys() - if t in M.tech_annual - for v in M.inputsplitVintages[r, p, i, t] - ) - - return indices - - -def TechInputSplitAverageConstraintIndices(M: 'TemoaModel'): - indices = set( - (r, p, i, t, v) - for r, p, i, t in M.inputsplitaverageVintages.keys() - if t in M.tech_variable - for v in M.inputsplitaverageVintages[r, p, i, t] - ) - return indices - - -def TechOutputSplitConstraintIndices(M: 'TemoaModel'): - indices = set( - (r, p, s, d, t, v, o) - for r, p, t, o in M.outputsplitVintages.keys() - if t not in M.tech_annual - for v in M.outputsplitVintages[r, p, t, o] - for s in M.time_season - for d in M.time_of_day - ) - - return indices - - -def TechOutputSplitAnnualConstraintIndices(M: 'TemoaModel'): - indices = set( - (r, p, t, v, o) - for r, p, t, o in M.outputsplitVintages.keys() - if t in M.tech_annual and t not in M.tech_variable - for v in M.outputsplitVintages[r, p, t, o] - ) - - return indices - - -def get_loan_life(M, r, t, _): - return M.LoanLifetimeTech[r, t] - - -def GrowthRateMax_rtv_initializer(M: 'TemoaModel'): - # need to do this outside of the model because the elements are not initialized yet for 'product' - return set(product(M.time_optimize, M.GrowthRateMax.sparse_iterkeys())) - - -def copy_from(other_set): - """a cheap reference function to replace the lambdas in orig temoa_model""" - return Set(other_set.sparse_iterkeys()) diff --git a/temoa/temoa_model/temoa_mode.py b/temoa/temoa_model/temoa_mode.py deleted file mode 100644 index dcd510cf6..000000000 --- a/temoa/temoa_model/temoa_mode.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/28/23 - -The possible operating modes for a scenario - -""" - -from enum import Enum, unique - - -@unique -class TemoaMode(Enum): - """The processing mode for the scenario""" - - PERFECT_FORESIGHT = 1 # Normal run, single execution for full time horizon - MGA = 2 # Modeling for Generation of Alternatives, multiple runs w/ changing constrained obj - MYOPIC = 3 # Step-wise execution through the future - METHOD_OF_MORRIS = 4 # Method-of-Morris run - BUILD_ONLY = 5 # Just build the model, no solve - CHECK = 6 # build and run price check, source trace it - SVMGA = 7 # single-vector MGA - MONTE_CARLO = 8 # MC optimization diff --git a/temoa/temoa_model/temoa_model.py b/temoa/temoa_model/temoa_model.py deleted file mode 100755 index a602e3ab8..000000000 --- a/temoa/temoa_model/temoa_model.py +++ /dev/null @@ -1,819 +0,0 @@ -#!/usr/bin/env python - -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" -import logging - -from pyomo.core import BuildCheck -from pyomo.environ import ( - Any, - NonNegativeReals, - AbstractModel, - BuildAction, - Param, - Objective, - minimize, -) - -from temoa.temoa_model.model_checking.validators import ( - validate_linked_tech, - region_check, - validate_CapacityFactorProcess, - region_group_check, - validate_Efficiency, - check_flex_curtail, - no_slash_or_pipe, -) -from temoa.temoa_model.temoa_initialize import * -from temoa.temoa_model.temoa_initialize import get_loan_life -from temoa.temoa_model.temoa_rules import * - -logger = logging.getLogger(__name__) - -# disable linter rule that complains about star imports for this file -# ruff: noqa: F405 - - -class TemoaModel(AbstractModel): - """ - An instance of the abstract Temoa model - """ - - # this is used in several places outside this class, and this provides no-build access to it - default_lifetime_tech = 40 - - def __init__(M, *args, **kwargs): - AbstractModel.__init__(M, *args, **kwargs) - - ################################################ - # Internally used Data Containers # - # (not formal model elements) # - ################################################ - - # Dev Note: The triple-quotes UNDER the items below pop up as dox in most IDEs - M.processInputs = dict() - M.processOutputs = dict() - M.processLoans = dict() - M.activeFlow_rpsditvo = None - """a flow index for techs NOT in tech_annual""" - - M.activeFlow_rpitvo = None - """a flow index for techs in tech_annual only""" - - M.activeFlex_rpsditvo = None - M.activeFlex_rpitvo = None - M.activeFlowInStorage_rpsditvo = None - M.activeCurtailment_rpsditvo = None - M.activeActivity_rptv = None - """currently available (within lifespan) (r, p, t, v) tuples (from M.processVintages)""" - - M.activeRegionsForTech = None - """currently available regions by period and tech {(p, t) : r}""" - - M.activeCapacity_rtv = None - M.activeCapacityAvailable_rpt = None - M.activeCapacityAvailable_rptv = None - M.commodityDStreamProcess = dict() # The downstream process of a commodity during a period - M.commodityUStreamProcess = dict() # The upstream process of a commodity during a period - M.ProcessInputsByOutput = dict() - M.ProcessOutputsByInput = dict() - M.processTechs = dict() - M.processReservePeriods = dict() - M.processVintages = dict() - """current available (within lifespan) vintages {(r, p, t) : set(v)}""" - - M.baseloadVintages = dict() - M.curtailmentVintages = dict() - M.storageVintages = dict() - M.rampVintages = dict() - M.inputsplitVintages = dict() - M.inputsplitaverageVintages = dict() - M.outputsplitVintages = dict() - M.ProcessByPeriodAndOutput = dict() - M.exportRegions = dict() - M.importRegions = dict() - M.flex_commodities = set() - - ################################################ - # Model Sets # - # (used for indexing model elements) # - ################################################ - - M.progress_marker_1 = BuildAction(['Starting to build Sets'], rule=progress_check) - - # Define time periods - M.time_exist = Set(ordered=True) - M.time_future = Set(ordered=True) - M.time_optimize = Set(ordered=True, initialize=init_set_time_optimize, within=M.time_future) - # Define time period vintages to track capacity installation - M.vintage_exist = Set(ordered=True, initialize=init_set_vintage_exist) - M.vintage_optimize = Set(ordered=True, initialize=init_set_vintage_optimize) - M.vintage_all = Set(initialize=M.time_exist | M.time_optimize) - # Perform some basic validation on the specified time periods. - M.validate_time = BuildAction(rule=validate_time) - - # Define the model time slices - M.time_season = Set(ordered=True, validate=no_slash_or_pipe) - M.time_of_day = Set(ordered=True, validate=no_slash_or_pipe) - - # Define regions - M.regions = Set(validate=region_check) - # RegionalIndices is the set of all the possible combinations of interregional exchanges - # plus original region indices. If tech_exchange is empty, RegionalIndices =regions. - M.RegionalIndices = Set(initialize=CreateRegionalIndices) - M.RegionalGlobalIndices = Set(validate=region_group_check) - - # Define technology-related sets - M.tech_resource = Set() - M.tech_production = Set() - M.tech_all = Set(initialize=M.tech_resource | M.tech_production, validate=no_slash_or_pipe) - M.tech_baseload = Set(within=M.tech_all) - M.tech_annual = Set(within=M.tech_all) - # annual storage not supported in Storage constraint or TableWriter, so exclude from domain - M.tech_storage = Set(within=M.tech_all - M.tech_annual) - M.tech_reserve = Set(within=M.tech_all) - M.tech_ramping = Set(within=M.tech_all) - M.tech_curtailment = Set(within=M.tech_all) - M.tech_flex = Set(within=M.tech_all) - # ensure there is no overlap flex <=> curtailable technologies - M.check_flex_and_curtailment = BuildAction(rule=check_flex_curtail) - M.tech_exchange = Set(within=M.tech_all) - - # Define groups for technologies - M.tech_group_names = Set() - M.tech_group_members = Set(M.tech_group_names, within=M.tech_all) - - M.tech_uncap = Set(within=M.tech_all - M.tech_reserve) - """techs with unlimited capacity, ALWAYS available within lifespan""" - - # the below is a convenience for domain checking in params below that should not accept uncap techs... - M.tech_with_capacity = Set(initialize=M.tech_all - M.tech_uncap) - """techs eligible for capacitization""" - - # Define techs for use with TechInputSplitAverage constraint, - # where techs have variable annual output but the user wishes to constrain them annually - M.tech_variable = Set(within=M.tech_all) - # Define techs for which economic retirement is an option - # Note: Storage techs cannot (currently) be retired due to linkage to initialization - # process, which is currently incapable of reducing initializations on retirements. - M.tech_retirement = Set(within=M.tech_all - M.tech_storage) - - # Define commodity-related sets - M.commodity_demand = Set() - M.commodity_emissions = Set() - M.commodity_physical = Set() - M.commodity_source = Set(within=M.commodity_physical) - M.commodity_carrier = Set(initialize=M.commodity_physical | M.commodity_demand) - M.commodity_all = Set( - initialize=M.commodity_carrier | M.commodity_emissions, validate=no_slash_or_pipe - ) - - # Define sets for MGA weighting - M.tech_mga = Set(within=M.tech_all) - M.tech_electric = Set(within=M.tech_all) - M.tech_transport = Set(within=M.tech_all) - M.tech_industrial = Set(within=M.tech_all) - M.tech_commercial = Set(within=M.tech_all) - M.tech_residential = Set(within=M.tech_all) - M.tech_PowerPlants = Set(within=M.tech_all) - - ################################################ - # Model Parameters # - # (data gathered/derived from source) # - ################################################ - - # --------------------------------------------------------------- - # Dev Note: - # In order to increase model efficiency, we use sparse - # indexing of parameters, variables, and equations to prevent the - # creation of indices for which no data exists. While basic model sets - # are defined above, sparse index sets are defined below adjacent to the - # appropriate parameter, variable, or constraint and all are initialized - # in temoa_initialize.py. - # Because the function calls that define the sparse index sets obscure the - # sets utilized, we use a suffix that includes a one character name for each - # set. Example: "_tv" indicates a set defined over "technology" and "vintage". - # The complete index set is: psditvo, where p=period, s=season, d=day, - # i=input commodity, t=technology, v=vintage, o=output commodity. - # --------------------------------------------------------------- - - # these "progress markers" report build progress in the log, if the level == debug - M.progress_marker_2 = BuildAction(['Starting to build Params'], rule=progress_check) - - M.GlobalDiscountRate = Param() - - # Define time-related parameters - M.PeriodLength = Param(M.time_optimize, initialize=ParamPeriodLength) - M.SegFrac = Param(M.time_season, M.time_of_day) - M.validate_SegFrac = BuildAction(rule=validate_SegFrac) - - # Define demand- and resource-related parameters - # Dev Note: There does not appear to be a DB table supporting DemandDefaultDistro. This does not - # cause any problems, so let it be for now. - M.DemandDefaultDistribution = Param(M.time_season, M.time_of_day, mutable=True) - M.DemandSpecificDistribution = Param( - M.regions, M.time_season, M.time_of_day, M.commodity_demand, mutable=True, default=0 - ) - - M.Demand = Param(M.regions, M.time_optimize, M.commodity_demand) - M.initialize_Demands = BuildAction(rule=CreateDemands) - - M.ResourceConstraint_rpr = Set(within=M.regions * M.time_optimize * M.commodity_physical) - - # Dev Note: This parameter is currently NOT implemented. Preserved for later refactoring - M.ResourceBound = Param(M.ResourceConstraint_rpr) - - # Define technology performance parameters - M.CapacityToActivity = Param(M.RegionalIndices, M.tech_all, default=1) - - M.ExistingCapacity = Param(M.RegionalIndices, M.tech_with_capacity, M.vintage_exist) - - # Dev Note: The below is temporarily useful for passing down to validator to find set violations - # Uncomment this assignment, and comment out the orig below it... - # M.Efficiency = Param( - # Any, Any, Any, Any, Any, - # within=NonNegativeReals, validate=validate_Efficiency - # ) - M.Efficiency = Param( - M.RegionalIndices, - M.commodity_physical, - M.tech_all, - M.vintage_all, - M.commodity_carrier, - within=NonNegativeReals, - validate=validate_Efficiency, - ) - - M.validate_UsedEfficiencyIndices = BuildAction(rule=CheckEfficiencyIndices) - - M.CapacityFactor_rsdt = Set(dimen=4, initialize=CapacityFactorTechIndices) - M.CapacityFactorTech = Param(M.CapacityFactor_rsdt, default=1) - - # Dev note: using a default function below alleviates need to make this set. - # M.CapacityFactor_rsdtv = Set(dimen=5, initialize=CapacityFactorProcessIndices) - M.CapacityFactorProcess = Param( - M.regions, - M.time_season, - M.time_of_day, - M.tech_with_capacity, - M.vintage_all, - validate=validate_CapacityFactorProcess, - default=get_default_capacity_factor, - ) - - # M.initialize_CapacityFactors = BuildAction(rule=CreateCapacityFactors) - - M.LifetimeTech = Param( - M.RegionalIndices, M.tech_all, default=TemoaModel.default_lifetime_tech - ) - - M.LifetimeProcess_rtv = Set(dimen=3, initialize=LifetimeProcessIndices) - M.LifetimeProcess = Param(M.LifetimeProcess_rtv, default=get_default_process_lifetime) - - M.LoanLifetimeTech = Param(M.RegionalIndices, M.tech_all, default=10) - M.LoanLifetimeProcess_rtv = Set(dimen=3, initialize=LifetimeLoanProcessIndices) - - # Dev Note: The LoanLifetimeProcess table *could* be removed. There is no longer a supporting - # table in the database. It is just a "passthrough" now to the default LoanLifetimeTech. - # It is already stitched in to the model, so will leave it for now. Table may be revived. - - M.LoanLifetimeProcess = Param(M.LoanLifetimeProcess_rtv, default=get_loan_life) - - M.TechInputSplit = Param(M.regions, M.time_optimize, M.commodity_physical, M.tech_all) - M.TechInputSplitAverage = Param( - M.regions, M.time_optimize, M.commodity_physical, M.tech_variable - ) - M.TechOutputSplit = Param(M.regions, M.time_optimize, M.tech_all, M.commodity_carrier) - - M.RenewablePortfolioStandardConstraint_rpg = Set( - within=M.regions * M.time_optimize * M.tech_group_names - ) - M.RenewablePortfolioStandard = Param(M.RenewablePortfolioStandardConstraint_rpg) - - # The method below creates a series of helper functions that are used to - # perform the sparse matrix of indexing for the parameters, variables, and - # equations below. - M.Create_SparseDicts = BuildAction(rule=CreateSparseDicts) - - # Define technology cost parameters - # dev note: the CostFixed_rptv isn't truly needed, but it is included in a constraint, so - # let it go for now - M.CostFixed_rptv = Set(dimen=4, initialize=CostFixedIndices) - M.CostFixed = Param(M.CostFixed_rptv) - - M.CostInvest_rtv = Set(within=M.RegionalIndices * M.tech_all * M.time_optimize) - M.CostInvest = Param(M.CostInvest_rtv) - - M.DefaultLoanRate = Param(domain=NonNegativeReals) - M.LoanRate = Param(M.CostInvest_rtv, domain=NonNegativeReals, default=get_default_loan_rate) - M.LoanAnnualize = Param(M.CostInvest_rtv, initialize=ParamLoanAnnualize_rule) - - M.CostVariable_rptv = Set(dimen=4, initialize=CostVariableIndices) - M.CostVariable = Param(M.CostVariable_rptv) - - M.CostEmission_rpe = Set( - dimen=3, domain=M.regions * M.time_optimize * M.commodity_emissions - ) # read from data - M.CostEmission = Param(M.CostEmission_rpe, domain=NonNegativeReals) - - M.ModelProcessLife_rptv = Set(dimen=4, initialize=ModelProcessLifeIndices) - M.ModelProcessLife = Param(M.ModelProcessLife_rptv, initialize=ParamModelProcessLife_rule) - - M.ProcessLifeFrac_rptv = Set(dimen=4, initialize=ModelProcessLifeIndices) - M.ProcessLifeFrac = Param(M.ProcessLifeFrac_rptv, initialize=ParamProcessLifeFraction_rule) - - M.MinCapacityConstraint_rpt = Set( - within=M.RegionalIndices * M.time_optimize * M.tech_with_capacity - ) - - M.MinCapacity = Param(M.MinCapacityConstraint_rpt) - - M.MaxCapacityConstraint_rpt = Set( - within=M.RegionalIndices * M.time_optimize * M.tech_with_capacity - ) - M.MaxCapacity = Param(M.MaxCapacityConstraint_rpt) - - M.MinNewCapacityConstraint_rpt = Set( - within=M.RegionalIndices * M.time_optimize * M.tech_with_capacity - ) - M.MinNewCapacity = Param(M.MinNewCapacityConstraint_rpt) - - M.MaxNewCapacityConstraint_rpt = Set( - within=M.RegionalIndices * M.time_optimize * M.tech_with_capacity - ) - M.MaxNewCapacity = Param(M.MaxNewCapacityConstraint_rpt) - - M.MaxResourceConstraint_rt = Set(within=M.RegionalIndices * M.tech_all) - M.MaxResource = Param(M.MaxResourceConstraint_rt) - - M.MaxActivityConstraint_rpt = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_all - ) - M.MaxActivity = Param(M.MaxActivityConstraint_rpt) - - M.MinActivityConstraint_rpt = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_all - ) - M.MinActivity = Param(M.MinActivityConstraint_rpt) - - M.MinAnnualCapacityFactorConstraint_rpto = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_all * M.commodity_carrier - ) - M.MinAnnualCapacityFactor = Param(M.MinAnnualCapacityFactorConstraint_rpto) - - M.MaxAnnualCapacityFactorConstraint_rpto = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_all * M.commodity_carrier - ) - M.MaxAnnualCapacityFactor = Param(M.MaxAnnualCapacityFactorConstraint_rpto) - M.GrowthRateMax = Param(M.RegionalIndices, M.tech_all) - M.GrowthRateSeed = Param(M.RegionalIndices, M.tech_all) - - M.EmissionLimitConstraint_rpe = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.commodity_emissions - ) - M.EmissionLimit = Param(M.EmissionLimitConstraint_rpe) - M.EmissionActivity_reitvo = Set(dimen=6, initialize=EmissionActivityIndices) - M.EmissionActivity = Param(M.EmissionActivity_reitvo) - - M.MinActivityGroup_rpg = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_group_names - ) - M.MinActivityGroup = Param(M.MinActivityGroup_rpg) - - M.MaxActivityGroup_rpg = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_group_names - ) - - M.MaxActivityGroup = Param(M.MaxActivityGroup_rpg) - - M.MinCapacityGroupConstraint_rpg = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_group_names - ) - M.MinCapacityGroup = Param(M.MinCapacityGroupConstraint_rpg) - - M.MaxCapacityGroupConstraint_rpg = Set( - within=M.RegionalGlobalIndices * M.time_optimize * M.tech_group_names - ) - M.MaxCapacityGroup = Param(M.MaxCapacityGroupConstraint_rpg) - - M.MinNewCapacityGroupConstraint_rpg = Set( - within=M.RegionalIndices * M.time_optimize * M.tech_group_names - ) - M.MinNewCapacityGroup = Param(M.MinNewCapacityGroupConstraint_rpg) - - M.MaxNewCapacityGroupConstraint_rpg = Set( - within=M.RegionalIndices * M.time_optimize * M.tech_group_names - ) - M.MaxNewCapacityGroup = Param(M.MaxNewCapacityGroupConstraint_rpg) - M.GroupShareIndices = Set(dimen=4, initialize=GroupShareIndices) - - M.MinCapacityShareConstraint_rptg = Set(within=M.GroupShareIndices) - M.MinCapacityShare = Param(M.GroupShareIndices) - - M.MaxCapacityShareConstraint_rptg = Set(within=M.GroupShareIndices) - M.MaxCapacityShare = Param(M.GroupShareIndices) - - M.MinActivityShareConstraint_rptg = Set(within=M.GroupShareIndices) - M.MinActivityShare = Param(M.GroupShareIndices) - - M.MaxActivityShareConstraint_rptg = Set(within=M.GroupShareIndices) - M.MaxActivityShare = Param(M.GroupShareIndices) - - M.MinNewCapacityShareConstraint_rptg = Set(within=M.GroupShareIndices) - M.MinNewCapacityShare = Param(M.GroupShareIndices) - - M.MaxNewCapacityShareConstraint_rptg = Set(within=M.GroupShareIndices) - M.MaxNewCapacityShare = Param(M.GroupShareIndices) - M.LinkedTechs = Param(M.RegionalIndices, M.tech_all, M.commodity_emissions, within=Any) - - # Define parameters associated with electric sector operation - M.RampUp = Param(M.regions, M.tech_ramping) - M.RampDown = Param(M.regions, M.tech_ramping) - M.CapacityCredit = Param( - M.RegionalIndices, M.time_optimize, M.tech_all, M.vintage_all, default=0 - ) - M.PlanningReserveMargin = Param(M.regions, default=0.2) - # Storage duration is expressed in hours - M.StorageDuration = Param(M.regions, M.tech_storage, default=4) - # Initial storage charge level, expressed as fraction of full energy capacity. - # If the parameter is not defined, the model optimizes the initial storage charge level. - M.StorageInit_rtv = Set(dimen=3, initialize=StorageInitIndices) - M.StorageInitFrac = Param(M.StorageInit_rtv) - - M.MyopicBaseyear = Param(default=0) - - ################################################ - # Model Variables # - # (assigned by solver) # - ################################################ - - # --------------------------------------------------------------- - # Dev Note: - # Decision variables are optimized in order to minimize cost. - # Base decision variables represent the lowest-level variables - # in the model. Derived decision variables are calculated for - # convenience, where 1 or more indices in the base variables are - # summed over. - # --------------------------------------------------------------- - - M.progress_marker_3 = BuildAction(['Starting to build Variables'], rule=progress_check) - - # Define base decision variables - M.FlowVar_rpsditvo = Set(dimen=8, initialize=FlowVariableIndices) - M.V_FlowOut = Var(M.FlowVar_rpsditvo, domain=NonNegativeReals) - - M.FlowVarAnnual_rpitvo = Set(dimen=6, initialize=FlowVariableAnnualIndices) - M.V_FlowOutAnnual = Var(M.FlowVarAnnual_rpitvo, domain=NonNegativeReals) - - M.FlexVar_rpsditvo = Set(dimen=8, initialize=FlexVariablelIndices) - M.V_Flex = Var(M.FlexVar_rpsditvo, domain=NonNegativeReals) - - M.FlexVarAnnual_rpitvo = Set(dimen=6, initialize=FlexVariableAnnualIndices) - M.V_FlexAnnual = Var(M.FlexVarAnnual_rpitvo, domain=NonNegativeReals) - - M.CurtailmentVar_rpsditvo = Set(dimen=8, initialize=CurtailmentVariableIndices) - M.V_Curtailment = Var(M.CurtailmentVar_rpsditvo, domain=NonNegativeReals, initialize=0) - - M.FlowInStorage_rpsditvo = Set(dimen=8, initialize=FlowInStorageVariableIndices) - M.V_FlowIn = Var(M.FlowInStorage_rpsditvo, domain=NonNegativeReals) - - M.StorageLevel_rpsdtv = Set(dimen=6, initialize=StorageVariableIndices) - M.V_StorageLevel = Var(M.StorageLevel_rpsdtv, domain=NonNegativeReals) - M.V_StorageInit = Var(M.StorageInit_rtv, domain=NonNegativeReals) - - # Derived decision variables - - M.CapacityVar_rptv = Set(dimen=4, initialize=CostFixedIndices) - M.V_Capacity = Var(M.CapacityVar_rptv, domain=NonNegativeReals) - - M.NewCapacityVar_rtv = Set(dimen=3, initialize=CapacityVariableIndices) - M.V_NewCapacity = Var(M.NewCapacityVar_rtv, domain=NonNegativeReals, initialize=0) - - M.RetiredCapacityVar_rptv = Set(dimen=4, initialize=RetiredCapacityVariableIndices) - M.V_RetiredCapacity = Var(M.RetiredCapacityVar_rptv, domain=NonNegativeReals, initialize=0) - - M.CapacityAvailableVar_rpt = Set(dimen=3, initialize=CapacityAvailableVariableIndices) - M.V_CapacityAvailableByPeriodAndTech = Var( - M.CapacityAvailableVar_rpt, domain=NonNegativeReals, initialize=0 - ) - - ################################################ - # Objective Function # - # (minimize total cost) # - ################################################ - - M.TotalCost = Objective(rule=TotalCost_rule, sense=minimize) - - ################################################ - # Constraints # - # # - ################################################ - - # --------------------------------------------------------------- - # Dev Note: - # Constraints are specified to ensure proper system behavior, - # and also to calculate some derived quantities. Note that descriptions - # of these constraints are provided in the associated comment blocks - # in temoa_rules.py, where the constraints are defined. - # --------------------------------------------------------------- - M.progress_marker_4 = BuildAction(['Starting to build Constraints'], rule=progress_check) - - # Declare constraints to calculate derived decision variables - M.CapacityConstraint_rpsdtv = Set(dimen=6, initialize=CapacityConstraintIndices) - M.CapacityConstraint = Constraint(M.CapacityConstraint_rpsdtv, rule=Capacity_Constraint) - - M.CapacityAnnualConstraint_rptv = Set(dimen=4, initialize=CapacityAnnualConstraintIndices) - M.CapacityAnnualConstraint = Constraint( - M.CapacityAnnualConstraint_rptv, rule=CapacityAnnual_Constraint - ) - - M.CapacityAvailableByPeriodAndTechConstraint = Constraint( - M.CapacityAvailableVar_rpt, rule=CapacityAvailableByPeriodAndTech_Constraint - ) - - M.RetiredCapacityConstraint = Constraint( - M.RetiredCapacityVar_rptv, rule=RetiredCapacity_Constraint - ) - M.AdjustedCapacityConstraint = Constraint( - M.CostFixed_rptv, rule=AdjustedCapacity_Constraint - ) - M.progress_marker_5 = BuildAction(['Finished Capacity Constraints'], rule=progress_check) - - # Declare core model constraints that ensure proper system functioning - # In driving order, starting with the need to meet end-use demands - - M.DemandConstraint_rpsdc = Set(dimen=5, initialize=DemandConstraintIndices) - M.DemandConstraint = Constraint(M.DemandConstraint_rpsdc, rule=Demand_Constraint) - - M.DemandActivityConstraint_rpsdtv_dem_s0d0 = Set( - dimen=9, initialize=DemandActivityConstraintIndices - ) - M.DemandActivityConstraint = Constraint( - M.DemandActivityConstraint_rpsdtv_dem_s0d0, rule=DemandActivity_Constraint - ) - - M.CommodityBalanceConstraint_rpsdc = Set( - dimen=5, initialize=CommodityBalanceConstraintIndices - ) - M.CommodityBalanceConstraint = Constraint( - M.CommodityBalanceConstraint_rpsdc, rule=CommodityBalance_Constraint - ) - - M.CommodityBalanceAnnualConstraint_rpc = Set( - dimen=3, initialize=CommodityBalanceAnnualConstraintIndices - ) - M.CommodityBalanceAnnualConstraint = Constraint( - M.CommodityBalanceAnnualConstraint_rpc, rule=CommodityBalanceAnnual_Constraint - ) - - M.ResourceExtractionConstraint = Constraint( - M.ResourceConstraint_rpr, rule=ResourceExtraction_Constraint - ) - - M.BaseloadDiurnalConstraint_rpsdtv = Set( - dimen=6, initialize=BaseloadDiurnalConstraintIndices - ) - M.BaseloadDiurnalConstraint = Constraint( - M.BaseloadDiurnalConstraint_rpsdtv, rule=BaseloadDiurnal_Constraint - ) - - M.RegionalExchangeCapacityConstraint_rrptv = Set( - dimen=5, initialize=RegionalExchangeCapacityConstraintIndices - ) - M.RegionalExchangeCapacityConstraint = Constraint( - M.RegionalExchangeCapacityConstraint_rrptv, rule=RegionalExchangeCapacity_Constraint - ) - - M.progress_marker_6 = BuildAction(['Starting Storage Constraints'], rule=progress_check) - # This set works for all the storage-related constraints - M.StorageConstraints_rpsdtv = Set(dimen=6, initialize=StorageVariableIndices) - M.StorageEnergyConstraint = Constraint( - M.StorageConstraints_rpsdtv, rule=StorageEnergy_Constraint - ) - - # We make use of this following set in some of the storage constraints. - # Pre-computing it is considerably faster. - M.SegFracPerSeason = Param(M.time_season, initialize=SegFracPerSeason_rule) - - M.StorageEnergyUpperBoundConstraint = Constraint( - M.StorageConstraints_rpsdtv, rule=StorageEnergyUpperBound_Constraint - ) - - M.StorageChargeRateConstraint = Constraint( - M.StorageConstraints_rpsdtv, rule=StorageChargeRate_Constraint - ) - - M.StorageDischargeRateConstraint = Constraint( - M.StorageConstraints_rpsdtv, rule=StorageDischargeRate_Constraint - ) - - M.StorageThroughputConstraint = Constraint( - M.StorageConstraints_rpsdtv, rule=StorageThroughput_Constraint - ) - - M.StorageInitConstraint_rtv = Set(dimen=2, initialize=StorageInitConstraintIndices) - M.StorageInitConstraint = Constraint( - M.StorageInitConstraint_rtv, rule=StorageInit_Constraint - ) - - M.RampConstraintDay_rpsdtv = Set(dimen=6, initialize=RampConstraintDayIndices) - M.RampUpConstraintDay = Constraint(M.RampConstraintDay_rpsdtv, rule=RampUpDay_Constraint) - M.RampDownConstraintDay = Constraint( - M.RampConstraintDay_rpsdtv, rule=RampDownDay_Constraint - ) - - # M.RampConstraintSeason_rpstv = Set(dimen=5, initialize=RampConstraintSeasonIndices) - # M.RampUpConstraintSeason = Constraint( - # M.RampConstraintSeason_rpstv, rule=RampUpSeason_Constraint - # ) - # M.RampDownConstraintSeason = Constraint( - # M.RampConstraintSeason_rpstv, rule=RampDownSeason_Constraint - # ) - - M.RampConstraintPeriod_rptv = Set(dimen=4, initialize=RampConstraintPeriodIndices) - M.RampUpConstraintPeriod = Constraint( - M.RampConstraintPeriod_rptv, rule=RampUpPeriod_Constraint - ) - M.RampDownConstraintPeriod = Constraint( - M.RampConstraintPeriod_rptv, rule=RampDownPeriod_Constraint - ) - - M.ReserveMargin_rpsd = Set(dimen=4, initialize=ReserveMarginIndices) - M.ReserveMarginConstraint = Constraint(M.ReserveMargin_rpsd, rule=ReserveMargin_Constraint) - - M.EmissionLimitConstraint = Constraint( - M.EmissionLimitConstraint_rpe, rule=EmissionLimit_Constraint - ) - M.progress_marker_7 = BuildAction( - ['Starting Growth and Activity Constraints'], rule=progress_check - ) - - M.GrowthRateMaxConstraint_rtv = Set( - dimen=3, - initialize=GrowthRateMax_rtv_initializer, - ) - M.GrowthRateConstraint = Constraint( - M.GrowthRateMaxConstraint_rtv, rule=GrowthRateConstraint_rule - ) - - M.MaxActivityConstraint = Constraint( - M.MaxActivityConstraint_rpt, rule=MaxActivity_Constraint - ) - - M.MinActivityConstraint = Constraint( - M.MinActivityConstraint_rpt, rule=MinActivity_Constraint - ) - - M.MinActivityGroupConstraint = Constraint( - M.MinActivityGroup_rpg, rule=MinActivityGroup_Constraint - ) - - M.MaxActivityGroupConstraint = Constraint( - M.MaxActivityGroup_rpg, rule=MaxActivityGroup_Constraint - ) - - M.MaxCapacityConstraint = Constraint( - M.MaxCapacityConstraint_rpt, rule=MaxCapacity_Constraint - ) - - M.MaxNewCapacityConstraint = Constraint( - M.MaxNewCapacityConstraint_rpt, rule=MaxNewCapacity_Constraint - ) - - M.MaxCapacityGroupConstraint = Constraint( - M.MaxCapacityGroupConstraint_rpg, rule=MaxCapacityGroup_Constraint - ) - - M.MinCapacityGroupConstraint = Constraint( - M.MinCapacityGroupConstraint_rpg, rule=MinCapacityGroup_Constraint - ) - - M.MinNewCapacityGroupConstraint = Constraint( - M.MinNewCapacityGroupConstraint_rpg, rule=MinNewCapacityGroup_Constraint - ) - - M.MaxNewCapacityGroupConstraint = Constraint( - M.MinNewCapacityGroupConstraint_rpg, rule=MaxNewCapacityGroup_Constraint - ) - - M.MinCapacityShareConstraint = Constraint( - M.MinCapacityShareConstraint_rptg, rule=MinCapacityShare_Constraint - ) - - M.MaxCapacityShareConstraint = Constraint( - M.MaxCapacityShareConstraint_rptg, rule=MaxCapacityShare_Constraint - ) - - M.MinActivityShareConstraint = Constraint( - M.MinActivityShareConstraint_rptg, rule=MinActivityShare_Constraint - ) - - M.MaxActivityShareConstraint = Constraint( - M.MaxActivityShareConstraint_rptg, rule=MaxActivityShare_Constraint - ) - - M.MinNewCapacityShareConstraint = Constraint( - M.MinNewCapacityShareConstraint_rptg, rule=MinNewCapacityShare_Constraint - ) - - M.MaxNewCapacityShareConstraint = Constraint( - M.MaxNewCapacityShareConstraint_rptg, rule=MaxNewCapacityShare_Constraint - ) - - M.progress_marker_8 = BuildAction( - ['Starting Max/Min Capacity and Tech Split ' 'Constraints'], rule=progress_check - ) - - M.MaxResourceConstraint = Constraint( - M.MaxResourceConstraint_rt, rule=MaxResource_Constraint - ) - - M.MinCapacityConstraint = Constraint( - M.MinCapacityConstraint_rpt, rule=MinCapacity_Constraint - ) - - M.MinNewCapacityConstraint = Constraint( - M.MinNewCapacityConstraint_rpt, rule=MinNewCapacity_Constraint - ) - - M.MinAnnualCapacityFactorConstraint = Constraint( - M.MinAnnualCapacityFactorConstraint_rpto, rule=MinAnnualCapacityFactor_Constraint - ) - - M.MaxAnnualCapacityFactorConstraint = Constraint( - M.MaxAnnualCapacityFactorConstraint_rpto, rule=MaxAnnualCapacityFactor_Constraint - ) - - M.TechInputSplitConstraint_rpsditv = Set( - dimen=7, initialize=TechInputSplitConstraintIndices - ) - M.TechInputSplitConstraint = Constraint( - M.TechInputSplitConstraint_rpsditv, rule=TechInputSplit_Constraint - ) - - M.TechInputSplitAnnualConstraint_rpitv = Set( - dimen=5, initialize=TechInputSplitAnnualConstraintIndices - ) - M.TechInputSplitAnnualConstraint = Constraint( - M.TechInputSplitAnnualConstraint_rpitv, rule=TechInputSplitAnnual_Constraint - ) - - M.TechInputSplitAverageConstraint_rpitv = Set( - dimen=5, initialize=TechInputSplitAverageConstraintIndices - ) - M.TechInputSplitAverageConstraint = Constraint( - M.TechInputSplitAverageConstraint_rpitv, rule=TechInputSplitAverage_Constraint - ) - - M.TechOutputSplitConstraint_rpsdtvo = Set( - dimen=7, initialize=TechOutputSplitConstraintIndices - ) - M.TechOutputSplitConstraint = Constraint( - M.TechOutputSplitConstraint_rpsdtvo, rule=TechOutputSplit_Constraint - ) - - M.TechOutputSplitAnnualConstraint_rptvo = Set( - dimen=5, initialize=TechOutputSplitAnnualConstraintIndices - ) - M.TechOutputSplitAnnualConstraint = Constraint( - M.TechOutputSplitAnnualConstraint_rptvo, rule=TechOutputSplitAnnual_Constraint - ) - - M.RenewablePortfolioStandardConstraint = Constraint( - M.RenewablePortfolioStandardConstraint_rpg, rule=RenewablePortfolioStandard_Constraint - ) - - M.LinkedEmissionsTechConstraint_rpsdtve = Set( - dimen=7, initialize=LinkedTechConstraintIndices - ) - # the validation requires that the set above be built first: - M.validate_LinkedTech_lifetimes = BuildCheck(rule=validate_linked_tech) - - M.LinkedEmissionsTechConstraint = Constraint( - M.LinkedEmissionsTechConstraint_rpsdtve, rule=LinkedEmissionsTech_Constraint - ) - - M.progress_marker_9 = BuildAction(['Finished Constraints'], rule=progress_check) - - -def progress_check(M, checkpoint: str): - """A quick widget which is called by BuildAction in order to log creation progress""" - logger.debug('Model build progress: %s', checkpoint) diff --git a/temoa/temoa_model/temoa_rules.py b/temoa/temoa_model/temoa_rules.py deleted file mode 100644 index ae8561292..000000000 --- a/temoa/temoa_model/temoa_rules.py +++ /dev/null @@ -1,2999 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -from logging import getLogger -from sys import stderr as SE -from typing import TYPE_CHECKING, Iterable - -from pyomo.core import Var, Expression -from pyomo.environ import Constraint, value - -from temoa.temoa_model.temoa_initialize import ( - DemandConstraintErrorCheck, - CommodityBalanceConstraintErrorCheck, - CommodityBalanceConstraintErrorCheckAnnual, -) - -if TYPE_CHECKING: - from temoa.temoa_model.temoa_model import TemoaModel - -logger = getLogger(__name__) -# --------------------------------------------------------------- -# Define the derived variables used in the objective function -# and constraints below. -# --------------------------------------------------------------- - - -def AdjustedCapacity_Constraint(M: 'TemoaModel', r, p, t, v): - r""" - This constraint updates the capacity of a process by taking into account retirements. - For a given :code:`(r,p,t,v)` index, this constraint sets the capacity equal to - the amount installed in period :code:`v` and subtracts from it any and all retirements - that occurred up until the period in question, :code:`p`.""" - if t not in M.tech_retirement: - if v in M.time_exist: - return M.V_Capacity[r, p, t, v] == M.ExistingCapacity[r, t, v] - else: - return M.V_Capacity[r, p, t, v] == M.V_NewCapacity[r, t, v] - - else: - retired_cap = sum( - M.V_RetiredCapacity[r, S_p, t, v] for S_p in M.time_optimize if p >= S_p > v - ) - if v in M.time_exist: - return M.V_Capacity[r, p, t, v] == M.ExistingCapacity[r, t, v] - retired_cap - else: - return M.V_Capacity[r, p, t, v] == M.V_NewCapacity[r, t, v] - retired_cap - - -def Capacity_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - This constraint ensures that the capacity of a given process is sufficient - to support its activity across all time periods and time slices. The calculation - on the left hand side of the equality is the maximum amount of energy a process - can produce in the timeslice :code:`(s,d)`. Note that the curtailment variable - shown below only applies to technologies that are members of the curtailment set. - Curtailment is necessary to track explicitly in scenarios that include a high - renewable target. Without it, the model can generate more activity than is used - to meet demand, and have all activity (including the portion curtailed) count - towards the target. Tracking activity and curtailment separately prevents this - possibility. - - .. math:: - :label: Capacity - - \left ( - \text{CFP}_{r, t, v} - \cdot \text{C2A}_{r, t} - \cdot \text{SEG}_{s, d} - \cdot \text{PLF}_{r, p, t, v} - \right ) - \cdot \textbf{CAP}_{r, t, v} - = - \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} - + - \sum_{I, O} \textbf{CUR}_{r, p, s, d, i, t, v, o} - - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{FO}} - """ - if t in M.tech_storage: - return Constraint.Skip - # The expressions below are defined in-line to minimize the amount of - # expression cloning taking place with Pyomo. - - useful_activity = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - if (r, s, d, t, v) in M.CapacityFactorProcess: - # use the data provided - capacity = value(M.CapacityFactorProcess[r, s, d, t, v]) - else: # use the capacity factor for the tech - capacity = value(M.CapacityFactorTech[r, s, d, t]) - - if t in M.tech_curtailment: - # If technologies are present in the curtailment set, then enough - # capacity must be available to cover both activity and curtailment. - return capacity * value(M.CapacityToActivity[r, t]) * value(M.SegFrac[s, d]) * value( - M.ProcessLifeFrac[r, p, t, v] - ) * M.V_Capacity[r, p, t, v] == useful_activity + sum( - M.V_Curtailment[r, p, s, d, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - else: - return ( - capacity - * value(M.CapacityToActivity[r, t]) - * value(M.SegFrac[s, d]) - * value(M.ProcessLifeFrac[r, p, t, v]) - * M.V_Capacity[r, p, t, v] - >= useful_activity - ) - - -def CapacityAnnual_Constraint(M: 'TemoaModel', r, p, t, v): - r""" -Similar to Capacity_Constraint, but for technologies belonging to the -:code:`tech_annual` set. Technologies in the tech_annual set have constant output -across different timeslices within a year, so we do not need to ensure -that installed capacity is sufficient across all timeslices, thus saving -some computational effort. Instead, annual output is sufficient to calculate -capacity. - -.. math:: - :label: CapacityAnnual - - \left ( - \text{CFP}_{r, t, v} - \cdot \text{C2A}_{r, t} - \cdot \text{PLF}_{r, p, t, v} - \right ) - \cdot \textbf{CAP}_{r, t, v} - = - \sum_{I, O} \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} - - \\ - \forall \{r, p, t \in T^{a}, v\} \in \Theta_{\text{Activity}} - - -""" - CF = 1 # placeholder CF - - activity_rptv = sum( - M.V_FlowOutAnnual[r, p, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - return ( - CF - * value(M.CapacityToActivity[r, t]) - * value(M.ProcessLifeFrac[r, p, t, v]) - * M.V_Capacity[r, p, t, v] - >= activity_rptv - ) - - -def ActivityByTech_Constraint(M: 'TemoaModel', t): - r""" - This constraint is utilized by the MGA objective function and defines - the total activity of a technology over the planning horizon. The first version - below applies to technologies with variable output at the timeslice level, - and the second version applies to technologies with constant annual output - in the :code:`tech_annual` set. - - .. math:: - :label: ActivityByTech - - \textbf{ACT}_{t} = \sum_{R, P, S, D, I, V, O} \textbf{FO}_{r, p, s, d,i, t, v, o} - \; - \forall t \not\in T^{a} - - \textbf{ACT}_{t} = \sum_{R, P, I, V, O} \textbf{FOA}_{r, p, i, t, v, o} - \; - \forall t \in T^{a} - """ - if t not in M.tech_annual: - indices = [] - for s_index in M.FlowVar_rpsditvo: - if t in s_index: - indices.append(s_index) - activity = sum(M.V_FlowOut[s_index] for s_index in indices) - else: - indices = [] - for s_index in M.FlowVarAnnual_rpitvo: - if t in s_index: - indices.append(s_index) - activity = sum(M.V_FlowOutAnnual[s_index] for s_index in indices) - - if int is type(activity): - return Constraint.Skip - - expr = M.V_ActivityByTech[t] == activity - return expr - - -def CapacityAvailableByPeriodAndTech_Constraint(M: 'TemoaModel', r, p, t): - r""" - -The :math:`\textbf{CAPAVL}` variable is nominally for reporting solution values, -but is also used in the Max and Min constraint calculations. For any process -with an end-of-life (EOL) on a period boundary, all of its capacity is available -for use in all periods in which it is active (the process' PLF is 1). However, -for any process with an EOL that falls between periods, Temoa makes the -simplifying assumption that the available capacity from the expiring technology -is available through the whole period in proportion to its remaining lifetime. -For example, if a process expires 3 years into an 8-year model time period, -then only :math:`\frac{3}{8}` of the installed capacity is available for use -throughout the period. - -.. math:: - :label: CapacityAvailable - - \textbf{CAPAVL}_{r, p, t} = \sum_{v, p_i \leq p} {PLF}_{r, p, t, v} \cdot \left - ( \textbf{CAP}_{r, t, v} - \textbf{CAPRET}_{r, p_i, t, v} \right ) - - \\ - \forall p \in \text{P}^o, r \in R, t \in T -""" - cap_avail = sum( - value(M.ProcessLifeFrac[r, p, t, S_v]) * M.V_Capacity[r, p, t, S_v] - for S_v in M.processVintages[r, p, t] - ) - - expr = M.V_CapacityAvailableByPeriodAndTech[r, p, t] == cap_avail - return expr - - -def RetiredCapacity_Constraint(M: 'TemoaModel', r, p, t, v): - r""" - -Temoa allows for the economic retirement of technologies presented in the -:code:`tech_retirement` set. This constraint sets the upper limit of retired -capacity to the total installed capacity. -In the equation below, we set the upper bound of the retired capacity to the -previous period's total installed capacity (CAPAVL) - -.. math:: - :label: RetiredCapacity - - \textbf{CAPRET}_{r, p, t, v} \leq \sum_{V} {PLF}_{r, p, t, v} \cdot \textbf{CAP}_{r, t, v} - \\ - \forall \{r, p, t, v\} \in \Theta_{\text{RetiredCapacity}} -""" - if p == M.time_optimize.first(): - cap_avail = M.ExistingCapacity[r, t, v] - else: - cap_avail = M.V_Capacity[r, M.time_optimize.prev(p), t, v] - expr = M.V_RetiredCapacity[r, p, t, v] <= cap_avail - return expr - - -# --------------------------------------------------------------- -# Define the Objective Function -# --------------------------------------------------------------- -def TotalCost_rule(M): - r""" - - Using the :code:`FlowOut` and :code:`Capacity` variables, the Temoa objective - function calculates the cost of energy supply, under the assumption that capital - costs are paid through loans. This implementation sums up all the costs incurred, - and is defined as :math:`C_{tot} = C_{loans} + C_{fixed} + C_{variable}`. Each - term on the right-hand side represents the cost incurred over the model - time horizon and discounted to the initial year in the horizon (:math:`{P}_0`). - The calculation of each term is given below. - - .. math:: - :label: obj_loan - - C_{loans} = \sum_{r, t, v \in \Theta_{IC}} \left ( - \left [ - CI_{r, t, v} \cdot LA_{r, t, v} - \cdot \frac{(1 + GDR)^{P_0 - v +1} \cdot (1 - (1 + GDR)^{-LLP_{r, t, v}})}{GDR} \right. \right. - \\ \left. \left. \cdot \frac{ 1-(1+GDR)^{-LPA_{r,t,v}} }{ 1-(1+GDR)^{-LTP_{r,t,v}} } - \right ] - \cdot \textbf{CAP}_{r, t, v} - \right ) - - Note that capital costs (:math:`{IC}_{r,t,v}`) are handled in several steps. First, each capital cost - is amortized using the loan rate (i.e., technology-specific discount rate) and loan - period. Second, the annual stream of payments is converted into a lump sum using - the global discount rate and loan period. Third, the new lump sum is amortized - at the global discount rate and technology lifetime. Fourth, loan payments beyond - the model time horizon are removed and the lump sum recalculated. The terms used - in Steps 3-4 are :math:`\frac{ GDR }{ 1-(1+GDR)^{-LTP_{r,t,v} } }\cdot - \frac{ 1-(1+GDR)^{-LPA_{t,v}} }{ GDR }`. The product simplifies to - :math:`\frac{ 1-(1+GDR)^{-LPA_{r,t,v}} }{ 1-(1+GDR)^{-LTP_{r,t,v}} }`, where - :math:`LPA_{r,t,v}` represents the active lifetime of process t in region r :math:`(r,t,v)` - before the end of the model horizon, and :math:`LTP_{r,t,v}` represents the full - lifetime of a regional process :math:`(r,t,v)`. Fifth, the lump sum is discounted back to the - beginning of the horizon (:math:`P_0`) using the global discount rate. While an - explicit salvage term is not included, this approach properly captures the capital - costs incurred within the model time horizon, accounting for technology-specific - loan rates and periods. - - .. math:: - :label: obj_fixed - - C_{fixed} = \sum_{r, p, t, v \in \Theta_{CF}} \left ( - \left [ - CF_{r, p, t, v} - \cdot \frac{(1 + GDR)^{P_0 - p +1} \cdot (1 - (1 + GDR)^{-{MPL}_{r, t, v}})}{GDR} - \right ] - \cdot \textbf{CAP}_{r, t, v} - \right ) - - .. math:: - :label: obj_variable - - &C_{variable} = \\ &\quad \sum_{r, p, t, v \in \Theta_{CV}} \left ( - CV_{r, p, t, v} - \cdot - \frac{ - (1 + GDR)^{P_0 - p + 1} \cdot (1 - (1 + GDR)^{-{MPL}_{r,p,t,v}}) - }{ - GDR - }\cdot \sum_{S,D,I, O} \textbf{FO}_{r, p, s, d,i, t, v, o} - \right ) \\ &\quad + \sum_{r, p, t \not \in T^{a}, v \in \Theta_{VC}} \left ( - CV_{r, p, t, v} - \cdot - \frac{ - (1 + GDR)^{P_0 - p + 1} \cdot (1 - (1 + GDR)^{-{MPL}_{r,p,t,v}}) - }{ - GDR - } - \cdot \sum_{I, O} \textbf{FOA}_{r, p,i, t \in T^{a}, v, o} - \right ) - - .. math:: - :label: obj_emissions - - &C_{emissions} = \\ &\quad \sum_{r, p, t, v \in \Theta_{CV}} \left ( - CE_{r, p, c} \cdot EAC_{r,e,i,t,v,o} - \cdot - \frac{ - (1 + GDR)^{P_0 - p + 1} \cdot (1 - (1 + GDR)^{-{MPL}_{r,p,t,v}}) - }{ - GDR - }\cdot \sum_{S,D,I, O} \textbf{FO}_{r, p, s, d,i, t, v, o} - \right ) \\ &\quad + \sum_{r, p, t \not \in T^{a}, v \in \Theta_{CE}} \left ( - CE_{r, p, c} \cdot EAC_{r,e,i,t,v,o} - \cdot - \frac{ - (1 + GDR)^{P_0 - p + 1} \cdot (1 - (1 + GDR)^{-{MPL}_{r,p,t,v}}) - }{ - GDR - } - \cdot \sum_{I, O} \textbf{FOA}_{r, p,i, t \in T^{a}, v, o} - \right ) - - """ - - return sum(PeriodCost_rule(M, p) for p in M.time_optimize) - - -def loan_cost( - capacity: float | Var, - invest_cost: float, - loan_annualize: float, - lifetime_loan_process: float | int, - P_0: int, - P_e: int, - GDR: float, - vintage: int, -) -> float | Expression: - """ - function to calculate the loan cost. It can be used with fixed values to produce a hard number or - pyomo variables/params to make a pyomo Expression - :param capacity: The capacity to use to calculate cost - :param invest_cost: the cost/capacity - :param loan_annualize: parameter - :param lifetime_loan_process: lifetime of the loan - :param P_0: the year to discount the costs back to - :param P_e: the 'end year' or cutoff year for loan payments - :param GDR: Global Discount Rate - :param vintage: the base year of the loan - :return: fixed number or pyomo expression based on input types - """ - if GDR == 0: # return the non-discounted result - regular_payment = capacity * invest_cost * loan_annualize - payments_made = min(lifetime_loan_process, P_e - vintage) - return regular_payment * payments_made - x = 1 + GDR # a convenience - res = ( - capacity - * ( - invest_cost - * loan_annualize - * ( - lifetime_loan_process - if not GDR - else (x ** (P_0 - vintage + 1) * (1 - x ** (-lifetime_loan_process)) / GDR) - ) - ) - * ( - (1 - x ** (-min(lifetime_loan_process, P_e - vintage))) - / (1 - x ** (-lifetime_loan_process)) - ) - ) - return res - - -def fixed_or_variable_cost( - cap_or_flow: float | Var, - cost_factor: float, - process_lifetime: float, - GDR: float | None, - P_0: float, - p: int, -) -> float | Expression: - """ - Extraction of the fixed and var cost formulation. (It is same for both with either capacity or - flow as the driving variable.) - :param cap_or_flow: Capacity if fixed cost / flow out if variable - :param cost_factor: the cost (either fixed or variable) of the cap/flow variable - :param process_lifetime: see the computation of this variable separately - :param GDR: discount rate or None - :param P_0: the period to discount this back to - :param p: the period under evaluation - :return: - """ - x = 1 + GDR - res = cap_or_flow * ( - cost_factor - * ( - process_lifetime - if not GDR - else (x ** (P_0 - p + 1) * (1 - x ** (-process_lifetime)) / GDR) - ) - ) - return res - - -def PeriodCost_rule(M: 'TemoaModel', p): - P_0 = min(M.time_optimize) - P_e = M.time_future.last() # End point of modeled horizon - GDR = value(M.GlobalDiscountRate) - MPL = M.ModelProcessLife - - if value(M.MyopicBaseyear) != 0: - P_0 = value(M.MyopicBaseyear) - - loan_costs = sum( - loan_cost( - M.V_NewCapacity[r, S_t, S_v], - M.CostInvest[r, S_t, S_v], - M.LoanAnnualize[r, S_t, S_v], - value(M.LoanLifetimeProcess[r, S_t, S_v]), - P_0, - P_e, - GDR, - vintage=S_v, - ) - for r, S_t, S_v in M.CostInvest.sparse_iterkeys() - if S_v == p - ) - - fixed_costs = sum( - fixed_or_variable_cost( - M.V_Capacity[r, p, S_t, S_v], - M.CostFixed[r, p, S_t, S_v], - MPL[r, p, S_t, S_v], - GDR, - P_0, - p=p, - ) - for r, S_p, S_t, S_v in M.CostFixed.sparse_iterkeys() - if S_p == p - ) - - variable_costs = sum( - fixed_or_variable_cost( - M.V_FlowOut[r, p, s, d, S_i, S_t, S_v, S_o], - M.CostVariable[r, p, S_t, S_v], - MPL[r, p, S_t, S_v], - GDR, - P_0, - p, - ) - for r, S_p, S_t, S_v in M.CostVariable.sparse_iterkeys() - if S_p == p and S_t not in M.tech_annual - for S_i in M.processInputs[r, S_p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r, S_p, S_t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - ) - - variable_costs_annual = sum( - fixed_or_variable_cost( - M.V_FlowOutAnnual[r, p, S_i, S_t, S_v, S_o], - M.CostVariable[r, p, S_t, S_v], - MPL[r, p, S_t, S_v], - GDR, - P_0, - p, - ) - for r, S_p, S_t, S_v in M.CostVariable.sparse_iterkeys() - if S_p == p and S_t in M.tech_annual - for S_i in M.processInputs[r, S_p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r, S_p, S_t, S_v, S_i] - ) - - # The emissions costs occur over the five possible emission sources. - # to do any/all of them we need 2 baseline sets: The regular and annual sets - # of indices that are valid which is basically the filter of: - # EmissionActivty by CostEmission - # and to ensure that the techology is active we need to filter that - # result with processInput.keys() - - # ================= Emissions and Flex and Curtailment ================= - # Flex flows are deducted from V_FlowOut, so it is NOT NEEDED to tax them again. (See commodity balance constr) - # Curtailment does not draw any inputs, so it seems logical that curtailed flows not be taxed either - # Earlier versions of this code had accounting for flex & curtailment that have been removed. - - base = [ - (r, p, e, i, t, v, o) - for (r, e, i, t, v, o) in M.EmissionActivity - if (r, p, e) in M.CostEmission # tightest filter first - and (r, p, t, v) in M.processInputs - ] - - # then expand the base for the normal (season/tod) set and annual separately: - normal = [ - (r, p, e, s, d, i, t, v, o) - for (r, p, e, i, t, v, o) in base - for s in M.time_season - for d in M.time_of_day - if t not in M.tech_annual - ] - - annual = [(r, p, e, i, t, v, o) for (r, p, e, i, t, v, o) in base if t in M.tech_annual] - - # 1. variable emissions - var_emissions = sum( - fixed_or_variable_cost( - cap_or_flow=M.V_FlowOut[r, p, s, d, i, t, v, o] * M.EmissionActivity[r, e, i, t, v, o], - cost_factor=M.CostEmission[r, p, e], - process_lifetime=MPL[r, p, t, v], - GDR=GDR, - P_0=P_0, - p=p, - ) - for (r, p, e, s, d, i, t, v, o) in normal - ) - - # 2. flex emissions -- removed (double counting) - - # 3. curtailment emissions -- removed (curtailment consumes no input, so no emittances) - - # 4. annual emissions - var_annual_emissions = sum( - fixed_or_variable_cost( - cap_or_flow=M.V_FlowOutAnnual[r, p, i, t, v, o] * M.EmissionActivity[r, e, i, t, v, o], - cost_factor=M.CostEmission[r, p, e], - process_lifetime=MPL[r, p, t, v], - GDR=GDR, - P_0=P_0, - p=p, - ) - for (r, p, e, i, t, v, o) in annual - if t not in M.tech_flex - ) - # 5. flex annual emissions -- removed (double counting) - - period_emission_cost = var_emissions + var_annual_emissions - - period_costs = ( - loan_costs + fixed_costs + variable_costs + variable_costs_annual + period_emission_cost - ) - return period_costs - - -# --------------------------------------------------------------- -# Define the Model Constraints. -# The order of constraint definitions follows the same order as the -# declarations in temoa_model.py. -# --------------------------------------------------------------- - - -def Demand_Constraint(M: 'TemoaModel', r, p, s, d, dem): - r""" - - The Demand constraint drives the model. This constraint ensures that supply at - least meets the demand specified by the Demand parameter in all periods and - slices, by ensuring that the sum of all the demand output commodity (:math:`c`) - generated by both commodity flow at the time slice level (:math:`\textbf{FO}`) and - the annual level (:math:`\textbf{FOA}`) must meet the modeler-specified demand - in each time slice. - - .. math:: - :label: Demand - - \sum_{I, T-T^{a}, V} \textbf{FO}_{r, p, s, d, i, t \not \in T^{a}, v, dem} + - SEG_{s,d} \cdot \sum_{I, T^{a}, V} \textbf{FOA}_{r, p, i, t \in T^{a}, v, dem} - = - {DEM}_{r, p, dem} \cdot {DSD}_{r, s, d, dem} - - Note that the validity of this constraint relies on the fact that the - :math:`C^d` set is distinct from both :math:`C^e` and :math:`C^p`. In other - words, an end-use demand must only be an end-use demand. Note that if an output - could satisfy both an end-use and internal system demand, then the output from - :math:`\textbf{FO}` and :math:`\textbf{FOA}` would be double counted.""" - - supply = sum( - M.V_FlowOut[r, p, s, d, S_i, S_t, S_v, dem] - for S_t, S_v in M.commodityUStreamProcess[r, p, dem] - if S_t not in M.tech_annual - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, dem] - ) - - supply_annual = sum( - M.V_FlowOutAnnual[r, p, S_i, S_t, S_v, dem] - for S_t, S_v in M.commodityUStreamProcess[r, p, dem] - if S_t in M.tech_annual - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, dem] - ) * value(M.SegFrac[s, d]) - - DemandConstraintErrorCheck(supply + supply_annual, r, p, s, d, dem) - - expr = ( - supply + supply_annual == M.Demand[r, p, dem] * M.DemandSpecificDistribution[r, s, d, dem] - ) - - return expr - - -def DemandActivity_Constraint(M: 'TemoaModel', r, p, s, d, t, v, dem, s_0, d_0): - r""" - - For end-use demands, it is unreasonable to let the model arbitrarily shift the - use of demand technologies across time slices. For instance, if household A buys - a natural gas furnace while household B buys an electric furnace, then both units - should be used throughout the year. Without this constraint, the model might choose - to only use the electric furnace during the day, and the natural gas furnace during the - night. - - This constraint ensures that the ratio of a process activity to demand is - constant for all time slices. Note that if a demand is not specified in a given - time slice, or is zero, then this constraint will not be considered for that - slice and demand. This is transparently handled by the :math:`\Theta` superset. - - .. math:: - :label: DemandActivity - - DEM_{r, p, s, d, dem} \cdot \sum_{I} \textbf{FO}_{r, p, s_0, d_0, i, t \not \in T^{a}, v, dem} - = - DEM_{r, p, s_0, d_0, dem} \cdot \sum_{I} \textbf{FO}_{r, p, s, d, i, t \not \in T^{a}, v, dem} - - \\ - \forall \{r, p, s, d, t, v, dem, s_0, d_0\} \in \Theta_{\text{DemandActivity}} - - Note that this constraint is only applied to the demand commodities with diurnal - variations, and therefore the equation above only includes :math:`\textbf{FO}` - and not :math:`\textbf{FOA}` - """ - - act_a = sum( - M.V_FlowOut[r, p, s_0, d_0, S_i, t, v, dem] - for S_i in M.ProcessInputsByOutput[r, p, t, v, dem] - ) - act_b = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, dem] for S_i in M.ProcessInputsByOutput[r, p, t, v, dem] - ) - - expr = ( - act_a * M.DemandSpecificDistribution[r, s, d, dem] - == act_b * M.DemandSpecificDistribution[r, s_0, d_0, dem] - ) - return expr - - -def CommodityBalance_Constraint(M: 'TemoaModel', r, p, s, d, c): - r""" - Where the Demand constraint :eq:`Demand` ensures that end-use demands are met, - the CommodityBalance constraint ensures that the endogenous system demands are - met. This constraint requires the total production of a given commodity - to equal the amount consumed, thus ensuring an energy balance at the system - level. In this most general form of the constraint, the energy commodity being - balanced has variable production at the time slice level. The energy commodity - can then be consumed by three types of processes: storage technologies, non-storage - technologies with output that varies at the time slice level, and non-storage - technologies with constant annual output. - - Separate expressions are required in order to account for the consumption of - commodity :math:`c` by downstream processes. For the commodity flow into storage - technologies, we use :math:`\textbf{FI}_{r, p, s, d, i, t, v, c}`. Note that the FlowIn - variable is defined only for storage technologies, and is required because storage - technologies balance production and consumption across time slices rather than - within a single time slice. For commodity flows into non-storage processes with time - varying output, we use :math:`\textbf{FO}_{r, p, s, d, i, t, v, c}/EFF_{r, i,t,v,o}`. - The division by :math:`EFF_{r, c,t,v,o}` is applied to the output flows that consume - commodity :math:`c` to determine input flows. Finally, we need to account - for the consumption of commodity :math:`c` by the processes in - :code:`tech_annual`. Since the commodity flow of these processes is on an - annual basis, we use :math:`SEG_{s,d}` to calculate the consumption of - commodity :math:`c` in time-slice :math:`(s,d)` from the annual flows. - Formulating an expression for the production of commodity :math:`c` is - more straightforward, and is simply calculated by - :math:`\textbf{FO}_{r, p, s, d, i, t, v, c}`. - - In some cases, the overproduction of a commodity may be required, such - that the supply exceeds the endogenous demand. Refineries represent a - common example, where the share of different refined products are governed - by TechOutputSplit, but total production is driven by a particular commodity - like gasoline. Such a situtation can result in the overproduction of other - refined products, such as diesel or kerosene. In such cases, we need to - track the excess production of these commodities. To do so, the technology - producing the excess commodity should be added to the :code:`tech_flex` set. - This flexible technology designation will activate a slack variable - (:math:`\textbf{FLX}_{r, p, s, d, i, t, v, c}`) representing - the excess production in the :code:`CommodityBalanceAnnual_Constraint`. Note - that the :code:`tech_flex` set is different from :code:`tech_curtailment` set; - the latter is technology- rather than commodity-focused and is used in the - :code:`Capacity_Constraint` to track output that is used to produce useful - output and the amount curtailed, and to ensure that the installed capacity - covers both. - - This constraint also accounts for imports and exports between regions - when solving multi-regional systems. The import (:math:`\textbf{FIM}`) and export - (:math:`\textbf{FEX}`) variables are created on-the-fly by summing the - :math:`\textbf{FO}` variables over the appropriate import and export regions, - respectively, which are defined in :code:`temoa_initialize.py` by parsing the - :code:`tech_exchange` processes. - - Finally, for commodities that are exclusively produced at a constant annual rate, the - :code:`CommodityBalanceAnnual_Constraint` is used, which is simplified and - reduces computational burden. - - *production + imports = consumption + exports + excess* - - .. math:: - :label: CommodityBalance - - \sum_{I, T, V} \textbf{FO}_{r, p, s, d, i, t, v, c} - + - &\sum_{reg} \textbf{FIM}_{r-reg, p, s, d, i, t, v, c} \; \forall reg \neq r - \\ - = &\sum_{T^{s}, V, I} \textbf{FIS}_{r, p, s, d, c, t, v, o} - \\ &\quad + - \sum_{T-T^{s}, V, O} \textbf{FO}_{r, p, s, d, c, t, v, o} /EFF_{r, c,t,v,o} - \\ - &\quad + \; SEG_{s,d} \cdot - \sum_{I, T^{a}, V} \textbf{FOA}_{r, p, c, t \in T^{a}, v, o} /EFF_{r, c,t,v,o} \\ - &\quad + \sum_{reg} \textbf{FEX}_{r-reg, p, s, d, c, t, v, o} \; \forall reg \neq r - \\ &\quad + \; - \textbf{FLX}_{r, p, s, d, i, t, v, c} - - \\ - &\forall \{r, p, s, d, c\} \in \Theta_{\text{CommodityBalance}} - - """ - if c in M.commodity_demand: - return Constraint.Skip - - vflow_in_ToStorage = sum( - M.V_FlowIn[r, p, s, d, c, S_t, S_v, S_o] - for S_t, S_v in M.commodityDStreamProcess[r, p, c] - if S_t in M.tech_storage - for S_o in M.ProcessOutputsByInput[r, p, S_t, S_v, c] - ) - - vflow_in_ToNonStorage = sum( - M.V_FlowOut[r, p, s, d, c, S_t, S_v, S_o] / value(M.Efficiency[r, c, S_t, S_v, S_o]) - for S_t, S_v in M.commodityDStreamProcess[r, p, c] - if S_t not in M.tech_storage and S_t not in M.tech_annual - for S_o in M.ProcessOutputsByInput[r, p, S_t, S_v, c] - ) - - vflow_in_ToNonStorageAnnual = value(M.SegFrac[s, d]) * sum( - M.V_FlowOutAnnual[r, p, c, S_t, S_v, S_o] / value(M.Efficiency[r, c, S_t, S_v, S_o]) - for S_t, S_v in M.commodityDStreamProcess[r, p, c] - if S_t not in M.tech_storage and S_t in M.tech_annual - for S_o in M.ProcessOutputsByInput[r, p, S_t, S_v, c] - ) - - try: - vflow_out = sum( - M.V_FlowOut[r, p, s, d, S_i, S_t, S_v, c] - for S_t, S_v in M.commodityUStreamProcess[r, p, c] - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, c] - ) - - # export of commodity c from region r to other regions - interregional_exports = 0 - if (r, p, c) in M.exportRegions: - interregional_exports = sum( - M.V_FlowOut[r + '-' + reg, p, s, d, c, S_t, S_v, S_o] - / value(M.Efficiency[r + '-' + reg, c, S_t, S_v, S_o]) - for reg, S_t, S_v, S_o in M.exportRegions[r, p, c] - ) - - # import of commodity c from other regions into region r - interregional_imports = 0 - if (r, p, c) in M.importRegions: - interregional_imports = sum( - M.V_FlowOut[reg + '-' + r, p, s, d, S_i, S_t, S_v, c] - for reg, S_t, S_v, S_i in M.importRegions[r, p, c] - ) - - v_out_excess = 0 - if c in M.flex_commodities: - v_out_excess = sum( - M.V_Flex[r, p, s, d, S_i, S_t, S_v, c] - for S_t, S_v in M.commodityUStreamProcess[r, p, c] - if S_t not in M.tech_storage and S_t not in M.tech_annual and S_t in M.tech_flex - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, c] - ) - - except KeyError: - raise KeyError( - 'The commodity "' - + str(c) - + '" can be produced \ - by at least one technology in the tech_annual set and one technology \ - not in the tech_annual set. All the producers of the commodity must \ - either be in tech_annual or not in tech_annual' - ) - - CommodityBalanceConstraintErrorCheck( - vflow_out + interregional_imports, - vflow_in_ToStorage - + vflow_in_ToNonStorage - + vflow_in_ToNonStorageAnnual - + interregional_exports - + v_out_excess, - r, - p, - s, - d, - c, - ) - - expr = ( - vflow_out + interregional_imports - == vflow_in_ToStorage - + vflow_in_ToNonStorage - + vflow_in_ToNonStorageAnnual - + interregional_exports - + v_out_excess - ) - - return expr - - -def CommodityBalanceAnnual_Constraint(M: 'TemoaModel', r, p, c): - r""" - Similar to the CommodityBalance_Constraint, but this version applies only - to commodities produced at a constant annual rate. This version of the - constraint improves computational performance for commodities that do not - need to be balanced at the timeslice level. - - While the commodity :math:`c` can only be produced by technologies in the - :code:`tech_annual` set, it can be consumed by any technology in the - :math:`T-T^{s}` set. - - *production + imports = consumption + exports + excess* - - .. math:: - :label: CommodityBalanceAnnual - - \sum_{I,T, V} \textbf{FOA}_{r, p, i, t \in T^{a}, v, c} - + - &\sum_{reg} \textbf{FIM}_{reg-r, p, i, t, v, c} \; \forall reg \neq r - \\ = - &\sum_{S, D, T-T^{s}, V, O} \textbf{FO}_{r, p, s, d, c, t, v, o} /EFF_{r, c,t,v,o} - \\ + &\quad - \sum_{I, T^{a}, V, O} \textbf{FOA}_{r, p, c, t \in T^{a}, v, o} /EFF_{r, c,t,v,o} - \\ &+ - \sum_{reg} \textbf{FEX}_{r-reg, p, c, t, v, o} \; \forall reg \neq r - \\ &+ - \textbf{FX}_{r, p, i, t, v, c} - - \\ - &\forall \{r, p, c\} \in \Theta_{\text{CommodityBalanceAnnual}} - - """ - if c in M.commodity_demand: - return Constraint.Skip - - vflow_in = sum( - M.V_FlowOut[r, p, s, d, c, S_t, S_v, S_o] / value(M.Efficiency[r, c, S_t, S_v, S_o]) - for S_t, S_v in M.commodityDStreamProcess[r, p, c] - if S_t not in M.tech_annual - for S_o in M.ProcessOutputsByInput[r, p, S_t, S_v, c] - for d in M.time_of_day - for s in M.time_season - ) - - vflow_in_annual = sum( - M.V_FlowOutAnnual[r, p, c, S_t, S_v, S_o] / value(M.Efficiency[r, c, S_t, S_v, S_o]) - for S_t, S_v in M.commodityDStreamProcess[r, p, c] - if S_t in M.tech_annual - for S_o in M.ProcessOutputsByInput[r, p, S_t, S_v, c] - ) - - vflow_out = sum( - M.V_FlowOutAnnual[r, p, S_i, S_t, S_v, c] - for S_t, S_v in M.commodityUStreamProcess[r, p, c] - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, c] - ) - - # export of commodity c from region r to other regions - interregional_exports = 0 - if (r, p, c) in M.exportRegions: - interregional_exports = sum( - M.V_FlowOutAnnual[str(r) + '-' + str(reg), p, c, S_t, S_v, S_o] - / value(M.Efficiency[str(r) + '-' + str(reg), c, S_t, S_v, S_o]) - for reg, S_t, S_v, S_o in M.exportRegions[r, p, c] - ) - - # import of commodity c from other regions into region r - interregional_imports = 0 - if (r, p, c) in M.importRegions: - interregional_imports = sum( - M.V_FlowOutAnnual[str(reg) + '-' + str(r), p, S_i, S_t, S_v, c] - for reg, S_t, S_v, S_i in M.importRegions[r, p, c] - ) - - v_out_excess = 0 - if c in M.flex_commodities: - v_out_excess = sum( - M.V_FlexAnnual[r, p, S_i, S_t, S_v, c] - for S_t, S_v in M.commodityUStreamProcess[r, p, c] - if S_t in M.tech_flex and S_t in M.tech_annual - for S_i in M.ProcessInputsByOutput[r, p, S_t, S_v, c] - ) - - CommodityBalanceConstraintErrorCheckAnnual( - vflow_out + interregional_imports, - vflow_in_annual + vflow_in + interregional_exports + v_out_excess, - r, - p, - c, - ) - - expr = ( - vflow_out + interregional_imports - == vflow_in_annual + vflow_in + interregional_exports + v_out_excess - ) - - return expr - - -def ResourceExtraction_Constraint(M: 'TemoaModel', reg, p, r): - r""" - The ResourceExtraction constraint allows a modeler to specify an annual limit on - the amount of a particular resource Temoa may use in a period. The first version - of the constraint pertains to technologies with variable output at the time slice - level, and the second version pertains to technologies with constant annual output - belonging to the :code:`tech_annual` set. - - .. math:: - :label: ResourceExtraction - - \sum_{S, D, I, t \in T^r \& t \not \in T^{a}, V} \textbf{FO}_{r, p, s, d, i, t, v, c} \le RSC_{r, p, c} - - \forall \{r, p, c\} \in \Theta_{\text{ResourceExtraction}} - - \sum_{I, t \in T^r \& t \in T^{a}, V} \textbf{FOA}_{r, p, i, t, v, c} \le RSC_{r, p, c} - - \forall \{r, p, c\} \in \Theta_{\text{ResourceExtraction}} - """ - logger.warning( - 'The ResourceBound parameter / ResourceExtraction constraint is not currently supported. ' - 'Recommend removing data from supporting table' - ) - # dev note: This constraint does not have a table in the current schema - # Additionally, the below (incorrect) construct assumes that a resource cannot be used - # by BOTH a non-annual and annual tech. It should be re-written to add these - try: - collected = sum( - M.V_FlowOut[reg, p, S_s, S_d, S_i, S_t, S_v, r] - for S_i, S_t, S_v in M.ProcessByPeriodAndOutput.keys() - for S_s in M.time_season - for S_d in M.time_of_day - ) - except KeyError: - collected = sum( - M.V_FlowOutAnnual[reg, p, S_i, S_t, S_v, r] - for S_i, S_t, S_v in M.ProcessByPeriodAndOutput.keys() - ) - - expr = collected <= M.ResourceBound[reg, p, r] - return expr - - -def BaseloadDiurnal_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - - Some electric generators cannot ramp output over a short period of time (e.g., - hourly or daily). Temoa models this behavior by forcing technologies in the - :code:`tech_baseload` set to maintain a constant output across all times-of-day - within the same season. Note that the output of a baseload process can vary - between seasons. - - Ideally, this constraint would not be necessary, and baseload processes would - simply not have a :math:`d` index. However, implementing the more efficient - functionality is currently on the Temoa TODO list. - - .. math:: - :label: BaseloadDaily - - SEG_{s, D_0} - \cdot \sum_{I, O} \textbf{FO}_{r, p, s, d,i, t, v, o} - = - SEG_{s, d} - \cdot \sum_{I, O} \textbf{FO}_{r, p, s, D_0,i, t, v, o} - - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{BaseloadDiurnal}} - """ - # Question: How to set the different times of day equal to each other? - - # Step 1: Acquire a "canonical" representation of the times of day - l_times = sorted(M.time_of_day) # i.e. a sorted Python list. - # This is the commonality between invocations of this method. - - index = l_times.index(d) - if 0 == index: - # When index is 0, it means that we've reached the beginning of the array - # For the algorithm, this is a terminating condition: do not create - # an effectively useless constraint - return Constraint.Skip - - # Step 2: Set the rest of the times of day equal in output to the first. - # i.e. create a set of constraints that look something like: - # tod[ 2 ] == tod[ 1 ] - # tod[ 3 ] == tod[ 1 ] - # tod[ 4 ] == tod[ 1 ] - # and so on ... - d_0 = l_times[0] - - # Step 3: the actual expression. For baseload, must compute the /average/ - # activity over the segment. By definition, average is - # (segment activity) / (segment length) - # So: (ActA / SegA) == (ActB / SegB) - # computationally, however, multiplication is cheaper than division, so: - # (ActA * SegB) == (ActB * SegA) - activity_sd = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - activity_sd_0 = sum( - M.V_FlowOut[r, p, s, d_0, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr = activity_sd * M.SegFrac[s, d_0] == activity_sd_0 * M.SegFrac[s, d] - - return expr - - -def RegionalExchangeCapacity_Constraint(M: 'TemoaModel', r_e, r_i, p, t, v): - r""" - - This constraint ensures that the process (t,v) connecting regions - r_e and r_i is handled by one capacity variables. - - .. math:: - :label: RegionalExchangeCapacity - - \textbf{CAP}_{r_e,t,v} - = - \textbf{CAP}_{r_i,t,v} - - \\ - \forall \{r_e, r_i, t, v\} \in \Theta_{\text{RegionalExchangeCapacity}} - """ - - expr = M.V_Capacity[r_e + '-' + r_i, p, t, v] == M.V_Capacity[r_i + '-' + r_e, p, t, v] - - return expr - - -def StorageEnergy_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - - This constraint tracks the storage charge level (:math:`\textbf{SL}_{r, p, s, d, t, v}`) - assuming ordered time slices. The initial storage charge level is optimized - for the first time slice in each period, and then the charge level is updated each time - slice based on the amount of energy stored or discharged. At the end of the last time - slice associated with each period, the charge level must equal the starting charge level. - In the formulation below, note that :math:`\textbf{stored\_energy}` is an internal model - decision variable. - - First, the amount of stored energy in a given time slice is calculated as the - difference between the amount of energy stored (first term) and the amount of energy - dispatched (second term). Note that the storage device's roundtrip efficiency is applied - on the input side: - - .. math:: - :label: StorageEnergy - - \textbf{stored\_energy} = - \sum_{I, O} \textbf{FIS}_{r, p, s, d, i, t, v, o} \cdot - EFF_{r,i,t,v,o} - - - \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} - - With :math:`\textbf{stored\_energy}` calculated, the storage - charge level (:math:`\textbf{SL}_{r,p,s,d,t,v}`) is updated, but the update procedure varies - based on the time slice within each time period. For the first season and time-of-day within - a given period: - - .. math:: - \textbf{SL}_{r, p, s, d, t, v} = \textbf{SI}_{r,t,v} + \textbf{stored\_energy} - - For the first time-of-day slice in any other season except the first: - - .. math:: - \textbf{SL}_{r, p, s, d, t, v} = - \textbf{SL}_{r, p, s_{prev}, d_{last}, t, v} + \textbf{stored\_energy} - - For the last season and time-of-day in the year, the ending storage charge level - should be equal to the starting charge level: - - .. math:: - \textbf{SL}_{r, p, s, d, t, v} + \textbf{stored\_energy} = \textbf{SI}_{r,t,v} - - For all other time slices not explicitly outlined above: - - .. math:: - \textbf{SL}_{r, p, s, d, t, v} = \textbf{SL}_{r, p, s, d_{prev}, t, v} + \textbf{stored\_energy} - - All equations below are sparsely indexed such that: - - .. math:: - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageEnergy}} - """ - # This is the sum of all input=i sent TO storage tech t of vintage v with - # output=o in p,s,d - charge = sum( - M.V_FlowIn[r, p, s, d, S_i, t, v, S_o] * M.Efficiency[r, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - # This is the sum of all output=o withdrawn FROM storage tech t of vintage v - # with input=i in p,s,d - discharge = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_o in M.processOutputs[r, p, t, v] - for S_i in M.ProcessInputsByOutput[r, p, t, v, S_o] - ) - - stored_energy = charge - discharge - - # This storage formulation allows stored energy to carry over through - # time of day and seasons, but must be zeroed out at the end of each period, i.e., - # the last time slice of the last season must zero out - if d == M.time_of_day.last() and s == M.time_season.last(): - d_prev = M.time_of_day.prev(d) - expr = M.V_StorageLevel[r, p, s, d_prev, t, v] + stored_energy == M.V_StorageInit[r, t, v] - - # First time slice of the first season (i.e., start of period), starts at StorageInit level - elif d == M.time_of_day.first() and s == M.time_season.first(): - expr = M.V_StorageLevel[r, p, s, d, t, v] == M.V_StorageInit[r, t, v] + stored_energy - - # First time slice of any season that is NOT the first season - elif d == M.time_of_day.first(): - d_last = M.time_of_day.last() - s_prev = M.time_season.prev(s) - expr = ( - M.V_StorageLevel[r, p, s, d, t, v] - == M.V_StorageLevel[r, p, s_prev, d_last, t, v] + stored_energy - ) - - # Any time slice that is NOT covered above (i.e., not the time slice ending - # the period, or the first time slice of any season) - else: - d_prev = M.time_of_day.prev(d) - expr = ( - M.V_StorageLevel[r, p, s, d, t, v] - == M.V_StorageLevel[r, p, s, d_prev, t, v] + stored_energy - ) - - return expr - - -def StorageEnergyUpperBound_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - - This constraint ensures that the amount of energy stored does not exceed - the upper bound set by the energy capacity of the storage device, as calculated - on the right-hand side. - - Because the number and duration of time slices are user-defined, we need to adjust - the storage duration, which is specified in hours. First, the hourly duration is divided - by the number of hours in a year to obtain the duration as a fraction of the year. - Since the :math:`C2A` parameter assumes the conversion of capacity to annual activity, - we need to express the storage duration as fraction of a year. Then, :math:`SEG_{s,d}` - summed over the time-of-day slices (:math:`d`) multiplied by 365 days / yr yields the - number of days per season. This step is necessary because conventional time sliced models - use a single day to represent many days within a given season. Thus, it is necessary to - scale the storage duration to account for the number of days in each season. - - .. math:: - :label: StorageEnergyUpperBound - - \textbf{SL}_{r, p, s, d, t, v} \le - \textbf{CAP}_{r,t,v} \cdot C2A_{r,t} \cdot \frac {SD_{r,t}}{8760 hrs/yr} - \cdot \sum_{d} SEG_{s,d} \cdot 365 days/yr - - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageEnergyUpperBound}} - - """ - - energy_capacity = ( - M.V_Capacity[r, p, t, v] - * M.CapacityToActivity[r, t] - * (M.StorageDuration[r, t] / 8760) - * M.SegFracPerSeason[s] - * 365 - * value(M.ProcessLifeFrac[r, p, t, v]) - ) - expr = M.V_StorageLevel[r, p, s, d, t, v] <= energy_capacity - - return expr - - -def StorageChargeRate_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - - This constraint ensures that the charge rate of the storage unit is - limited by the power capacity (typically GW) of the storage unit. - - .. math:: - :label: StorageChargeRate - - \sum_{I, O} \textbf{FIS}_{r, p, s, d, i, t, v, o} \cdot EFF_{r,i,t,v,o} - \le - \textbf{CAP}_{r,t,v} \cdot C2A_{r,t} \cdot SEG_{s,d} - - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageChargeRate}} - - """ - # Calculate energy charge in each time slice - slice_charge = sum( - M.V_FlowIn[r, p, s, d, S_i, t, v, S_o] * M.Efficiency[r, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - # Maximum energy charge in each time slice - max_charge = ( - M.V_Capacity[r, p, t, v] - * M.CapacityToActivity[r, t] - * M.SegFrac[s, d] - * value(M.ProcessLifeFrac[r, p, t, v]) - ) - - # Energy charge cannot exceed the power capacity of the storage unit - expr = slice_charge <= max_charge - - return expr - - -def StorageDischargeRate_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - - This constraint ensures that the discharge rate of the storage unit - is limited by the power capacity (typically GW) of the storage unit. - - .. math:: - :label: StorageDischargeRate - - \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} - \le - \textbf{CAP}_{r,t,v} \cdot C2A_{r,t} \cdot SEG_{s,d} - - \\ - \forall \{r,p, s, d, t, v\} \in \Theta_{\text{StorageDischargeRate}} - """ - # Calculate energy discharge in each time slice - slice_discharge = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_o in M.processOutputs[r, p, t, v] - for S_i in M.ProcessInputsByOutput[r, p, t, v, S_o] - ) - - # Maximum energy discharge in each time slice - max_discharge = ( - M.V_Capacity[r, p, t, v] - * M.CapacityToActivity[r, t] - * M.SegFrac[s, d] - * value(M.ProcessLifeFrac[r, p, t, v]) - ) - - # Energy discharge cannot exceed the capacity of the storage unit - expr = slice_discharge <= max_discharge - - return expr - - -def StorageThroughput_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - - It is not enough to only limit the charge and discharge rate separately. We also - need to ensure that the maximum throughput (charge + discharge) does not exceed - the capacity (typically GW) of the storage unit. - - .. math:: - :label: StorageThroughput - - \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} - + - \sum_{I, O} \textbf{FIS}_{r, p, s, d, i, t, v, o} \cdot EFF_{r,i,t,v,o} - \le - \textbf{CAP}_{r,t,v} \cdot C2A_{r,t} \cdot SEG_{s,d} - - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{StorageThroughput}} - """ - discharge = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_o in M.processOutputs[r, p, t, v] - for S_i in M.ProcessInputsByOutput[r, p, t, v, S_o] - ) - - charge = sum( - M.V_FlowIn[r, p, s, d, S_i, t, v, S_o] * M.Efficiency[r, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - throughput = charge + discharge - max_throughput = ( - M.V_Capacity[r, p, t, v] - * M.CapacityToActivity[r, t] - * M.SegFrac[s, d] - * value(M.ProcessLifeFrac[r, p, t, v]) - ) - expr = throughput <= max_throughput - return expr - - -def StorageInit_Constraint(M: 'TemoaModel', r, t, v): - r""" - - This constraint is used if the users wishes to force a specific initial storage charge level - for certain storage technologies and vintages. In this case, the value of the decision variable - :math:`\textbf{SI}_{r,t,v}` is set by this constraint rather than being optimized. - User-specified initial storage charge levels that are sufficiently different from the optimal - :math:`\textbf{SI}_{r,t,v}` could impact the cost-effectiveness of storage. For example, if the - optimal initial charge level happens to be 50% of the full energy capacity, forced initial - charge levels (specified by parameter :math:`SIF_{r,t,v}`) equal to 10% or 90% of the full energy - capacity could lead to more expensive solutions. - - - .. math:: - :label: StorageInit - - \textbf{SI}_{r,t, v} \le - \ SIF_{r,t,v} - \cdot - \textbf{CAP}_{r,t,v} \cdot C2A_{r,t} \cdot \frac {SD_{r,t}}{8760 hrs/yr} - \cdot \sum_{d} SEG_{s_{first},d} \cdot 365 days/yr - - \\ - \forall \{r, t, v\} \in \Theta_{\text{StorageInit}} - """ - # dev note: This constraint is not currently accessible and needs close review. - # the hybrid loader currently screens out inputs for this to keep - # it idle. - raise NotImplementedError('This constraint needs overhaul...') - s = M.time_season.first() - # the only capacity of concern here is for the vintage year for initialization - vintage_period = s - - # devnote: storage techs are currently excluded from the tech_retirements, so no change in - # capacity should ever occur - energy_capacity = ( - M.V_Capacity[r, vintage_period, t, v] - * M.CapacityToActivity[r, t] - * (M.StorageDuration[r, t] / 8760) - * sum(M.SegFrac[s, S_d] for S_d in M.time_of_day) - * 365 - * value(M.ProcessLifeFrac[r, v, t, v]) - ) - expr = M.V_StorageInit[r, t, v] == energy_capacity * M.StorageInitFrac[r, t, v] - - return expr - - -def RampUpDay_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - # M.time_of_day is a sorted set, and M.time_of_day.first() returns the first - # element in the set, similarly, M.time_of_day.last() returns the last element. - # M.time_of_day.prev(d) function will return the previous element before s, and - # M.time_of_day.next(d) function will return the next element after s. - - r""" - - The ramp rate constraint is utilized to limit the rate of electricity generation - increase and decrease between two adjacent time slices in order to account for - physical limits associated with thermal power plants. Note that this constraint - only applies to technologies with ramp capability, which is defined in the set - :math:`T^{m}`. We assume for simplicity the rate limits for both - ramp up and down are equal and they do not vary with technology vintage. The - ramp rate limits (:math:`r_t`) for technology :math:`t` should be expressed in - percentage of its rated capacity. - - Note that when :math:`d_{nd}` is the last time-of-day, :math:`d_{nd + 1} \not \in - \textbf{D}`, i.e., if one time slice is the last time-of-day in a season and the - other time slice is the first time-of-day in the next season, the ramp rate - limits between these two time slices can not be expressed by :code:`RampUpDay`. - Therefore, the ramp rate constraints between two adjacent seasons are - represented in :code:`RampUpSeason`. - - In the :code:`RampUpDay` and :code:`RampUpSeason` constraints, we assume - :math:`\textbf{S} = \{s_i, i = 1, 2, \cdots, ns\}` and - :math:`\textbf{D} = \{d_i, i = 1, 2, \cdots, nd\}`. - - .. math:: - :label: RampUpDay - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s, d_{i + 1}, i, t, v, o} - }{ - SEG_{s, d_{i + 1}} \cdot C2A_{r,t} - } - - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s, d_i, i, t, v, o} - }{ - SEG_{s, d_i} \cdot C2A_{r,t} - } - \leq - r_t \cdot \textbf{CAPAVL}_{r,p,t} - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{RampUpDay}} - """ - if d != M.time_of_day.first(): - d_prev = M.time_of_day.prev(d) - activity_sd_prev = sum( - M.V_FlowOut[r, p, s, d_prev, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - activity_sd = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr_left = ( - activity_sd / value(M.SegFrac[s, d]) - activity_sd_prev / value(M.SegFrac[s, d_prev]) - ) / value(M.CapacityToActivity[r, t]) - expr_right = M.V_Capacity[r, p, t, v] * value(M.RampUp[r, t]) - expr = expr_left <= expr_right - else: - return Constraint.Skip - - return expr - - -def RampDownDay_Constraint(M: 'TemoaModel', r, p, s, d, t, v): - r""" - - Similar to the :code`RampUpDay` constraint, we use the :code:`RampDownDay` - constraint to limit ramp down rates between any two adjacent time slices. - - .. math:: - :label: RampDownDay - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s, d_{i + 1}, i, t, v, o} - }{ - SEG_{s, d_{i + 1}} \cdot C2A_{r,t} - } - - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s, d_i, i, t, v, o} - }{ - SEG_{s, d_i} \cdot C2A_{r,t} - } - \geq - -r_t \cdot \textbf{CAPAVL}_{r,p,t} - \\ - \forall \{r, p, s, d, t, v\} \in \Theta_{\text{RampDownDay}} - """ - if d != M.time_of_day.first(): - d_prev = M.time_of_day.prev(d) - activity_sd_prev = sum( - M.V_FlowOut[r, p, s, d_prev, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - activity_sd = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr_left = ( - activity_sd / value(M.SegFrac[s, d]) - activity_sd_prev / value(M.SegFrac[s, d_prev]) - ) / value(M.CapacityToActivity[r, t]) - expr_right = -(M.V_Capacity[r, p, t, v] * value(M.RampDown[r, t])) - expr = expr_left >= expr_right - else: - return Constraint.Skip - - return expr - - -def RampUpSeason_Constraint(M: 'TemoaModel', r, p, s, t, v): - r""" - - Note that :math:`d_1` and :math:`d_{nd}` represent the first and last time-of-day, - respectively. - - .. math:: - :label: - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s_{i + 1}, d_1, i, t, v, o} - }{ - SEG_{s_{i + 1}, d_1} \cdot C2A_{r,t} - } - - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s_i, d_{nd}, i, t, v, o} - }{ - SEG_{s_i, d_{nd}} \cdot C2A_{r,t} - } - \leq - r_t \cdot \textbf{CAPAVL}_{r,p,t} - \\ - \forall \{r, p, s, t, v\} \in \Theta_{\text{RampUpSeason}} - """ - if s != M.time_season.first(): - s_prev = M.time_season.prev(s) - d_first = M.time_of_day.first() - d_last = M.time_of_day.last() - - activity_sd_first = sum( - M.V_FlowOut[r, p, s, d_first, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - activity_s_prev_d_last = sum( - M.V_FlowOut[r, p, s_prev, d_last, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr_left = ( - activity_sd_first / M.SegFrac[s, d_first] - - activity_s_prev_d_last / M.SegFrac[s_prev, d_last] - ) / value(M.CapacityToActivity[r, t]) - expr_right = M.V_Capacity[r, p, t, v] * value(M.RampUp[r, t]) - expr = expr_left <= expr_right - else: - return Constraint.Skip - - return expr - - -def RampDownSeason_Constraint(M: 'TemoaModel', r, p, s, t, v): - r""" - - Similar to the :code:`RampUpSeason` constraint, we use the - :code:`RampDownSeason` constraint to limit ramp down rates - between any two adjacent seasons. - - .. math:: - :label: RampDownSeason - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s_{i + 1}, d_1, i, t, v, o} - }{ - SEG_{s_{i + 1}, d_1} \cdot C2A_{r,t} - } - - - \frac{ - \sum_{I, O} \textbf{FO}_{r, p, s_i, d_{nd}, i, t, v, o} - }{ - SEG_{s_i, d_{nd}} \cdot C2A_{r,t} - } - \geq - -r_t \cdot \textbf{CAPAVL}_{r,p,t} - \\ - \forall \{r, p, s, t, v\} \in \Theta_{\text{RampDownSeason}} - """ - if s != M.time_season.first(): - s_prev = M.time_season.prev(s) - d_first = M.time_of_day.first() - d_last = M.time_of_day.last() - - activity_sd_first = sum( - M.V_FlowOut[r, p, s, d_first, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - activity_s_prev_d_last = sum( - M.V_FlowOut[r, p, s_prev, d_last, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr_left = ( - activity_sd_first / value(M.SegFrac[s, d_first]) - - activity_s_prev_d_last / value(M.SegFrac[s_prev, d_last]) - ) / value(M.CapacityToActivity[r, t]) - expr_right = -(M.V_Capacity[r, p, t, v] * value(M.RampDown[r, t])) - expr = expr_left >= expr_right - else: - return Constraint.Skip - - return expr - - -def RampUpPeriod_Constraint(M: 'TemoaModel', r, p, t, v): - # if p != M.time_future.first(): - # p_prev = M.time_future.prev(p) - # s_first = M.time_season.first() - # s_last = M.time_season.last() - # d_first = M.time_of_day.first() - # d_last = M.time_of_day.last() - # expr_left = ( - # M.V_Activity[ p, s_first, d_first, t, v ] - - # M.V_Activity[ p_prev, s_last, d_last, t, v ] - # ) - # expr_right = ( - # M.V_Capacity[t, v]* - # value( M.RampUp[t] )* - # value( M.CapacityToActivity[ t ] )* - # value( M.SegFrac[s, d]) - # ) - # expr = (expr_left <= expr_right) - # else: - # return Constraint.Skip - - # return expr - - return Constraint.Skip # We don't need inter-period ramp up/down constraint. - - -def RampDownPeriod_Constraint(M: 'TemoaModel', r, p, t, v): - # if p != M.time_future.first(): - # p_prev = M.time_future.prev(p) - # s_first = M.time_season.first() - # s_last = M.time_season.last() - # d_first = M.time_of_day.first() - # d_last = M.time_of_day.last() - # expr_left = ( - # M.V_Activity[ p, s_first, d_first, t, v ] - - # M.V_Activity[ p_prev, s_last, d_last, t, v ] - # ) - # expr_right = ( - # -1* - # M.V_Capacity[t, v]* - # value( M.RampDown[t] )* - # value( M.CapacityToActivity[ t ] )* - # value( M.SegFrac[s, d]) - # ) - # expr = (expr_left >= expr_right) - # else: - # return Constraint.Skip - - # return expr - - return Constraint.Skip # We don't need inter-period ramp up/down constraint. - - -def ReserveMargin_Constraint(M: 'TemoaModel', r, p, s, d): - r""" - - During each period :math:`p`, the sum of the available capacity of all reserve - technologies :math:`\sum_{t \in T^{e}} \textbf{CAPAVL}_{r,p,t}`, which are - defined in the set :math:`\textbf{T}^{r,e}`, should exceed the peak load by - :math:`PRM`, the regional reserve margin. Note that the reserve - margin is expressed in percentage of the peak load. Generally speaking, in - a database we may not know the peak demand before running the model, therefore, - we write this equation for all the time-slices defined in the database in each region. - - .. math:: - :label: reserve_margin - - &\sum_{t \in T^{res} \setminus T^{e}} {CC_{t,r} \cdot \textbf{CAPAVL}_{p,t} \cdot SEG_{s^*,d^*} \cdot C2A_{r,t} }\\ - &+ \sum_{t \in T^{res} \cap T^{e}} {CC_{t,r_i-r} \cdot \textbf{CAPAVL}_{p,t} \cdot SEG_{s^*,d^*} \cdot C2A_{r_i-r,t} }\\ - &- \sum_{t \in T^{res} \cap T^{e}} {CC_{t,r-r_i} \cdot \textbf{CAPAVL}_{p,t} \cdot SEG_{s^*,d^*} \cdot C2A_{r_i-r,t} }\\ - &\geq \left [ \sum_{ t \in T^{res} \setminus T^{e},V,I,O } \textbf{FO}_{r, p, s, d, i, t, v, o}\right.\\ - &+ \sum_{ t \in T^{res} \cap T^{e},V,I,O } \textbf{FO}_{r_i-r, p, s, d, i, t, v, o}\\ - &- \sum_{ t \in T^{res} \cap T^{e},V,I,O } \textbf{FI}_{r-r_i, p, s, d, i, t, v, o}\\ - &- \left.\sum_{ t \in T^{res} \cap T^{s},V,I,O } \textbf{FI}_{r, p, s, d, i, t, v, o} \right] \cdot (1 + PRM_r)\\ - &\qquad \qquad \forall \{r, p, s, d\} \in \Theta_{\text{ReserveMargin}} \text{and} \forall r_i \in R - """ - if (not M.tech_reserve) or ( - (r, p) not in M.processReservePeriods.keys() - ): # If reserve set empty or if r,p not in M.processReservePeriod.keys(), skip the constraint - return Constraint.Skip - - cap_avail = sum( - value(M.CapacityCredit[r, p, t, v]) - * M.ProcessLifeFrac[r, p, t, v] - * M.V_Capacity[r, p, t, v] - * value(M.CapacityToActivity[r, t]) - * value(M.SegFrac[s, d]) - for t in M.tech_reserve - if (r, p, t) in M.processVintages.keys() - for v in M.processVintages[r, p, t] - # Make sure (r,p,t,v) combinations are defined - if (r, p, t, v) in M.activeCapacityAvailable_rptv - ) - - # The above code does not consider exchange techs, e.g. electricity - # transmission between two distinct regions. - # We take exchange takes into account below. - # Note that a single exchange tech linking regions Ri and Rj is twice - # defined: once for region "Ri-Rj" and once for region "Rj-Ri". - - # First, determine the amount of firm capacity each exchange tech - # contributes. - for r1r2 in M.RegionalIndices: - if '-' not in r1r2: - continue - r1, r2 = r1r2.split('-') - - # Only consider the capacity of technologies that import to - # the region in question -- i.e. for cases where r2 == r. - if r2 != r: - continue - - # add the available capacity of the exchange tech. - cap_avail += sum( - value(M.CapacityCredit[r1r2, p, t, v]) - * M.ProcessLifeFrac[r1r2, p, t, v] - * M.V_Capacity[r1r2, p, t, v] - * value(M.CapacityToActivity[r1r2, t]) - * value(M.SegFrac[s, d]) - for t in M.tech_reserve - if (r1r2, p, t) in M.processVintages.keys() - for v in M.processVintages[r1r2, p, t] - # Make sure (r,p,t,v) combinations are defined - if (r1r2, p, t, v) in M.activeCapacityAvailable_rptv - ) - - # In most Temoa input databases, demand is endogenous, so we use electricity - # generation instead as a proxy for electricity demand. - total_generation = sum( - M.V_FlowOut[r, p, s, d, S_i, t, S_v, S_o] - for (t, S_v) in M.processReservePeriods[r, p] - for S_i in M.processInputs[r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - ) - - # We must take into account flows into storage technologies. - # Flows into storage technologies need to be subtracted from the - # load calculation. - total_generation -= sum( - M.V_FlowIn[r, p, s, d, S_i, t, S_v, S_o] - for (t, S_v) in M.processReservePeriods[r, p] - if t in M.tech_storage - for S_i in M.processInputs[r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - ) - - # Electricity imports and exports via exchange techs are accounted - # for below: - for r1r2 in M.RegionalIndices: # ensure the region is of the form r1-r2 - if '-' not in r1r2: - continue - if (r1r2, p) not in M.processReservePeriods: # ensure the technology in question exists - continue - r1, r2 = r1r2.split('-') - # First, determine the exports, and subtract this value from the - # total generation. - if r1 == r: - total_generation -= sum( - M.V_FlowOut[r1r2, p, s, d, S_i, t, S_v, S_o] - / value(M.Efficiency[r1r2, S_i, t, S_v, S_o]) - for (t, S_v) in M.processReservePeriods[r1r2, p] - for S_i in M.processInputs[r1r2, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r1r2, p, t, S_v, S_i] - ) - # Second, determine the imports, and add this value from the - # total generation. - elif r2 == r: - total_generation += sum( - M.V_FlowOut[r1r2, p, s, d, S_i, t, S_v, S_o] - for (t, S_v) in M.processReservePeriods[r1r2, p] - for S_i in M.processInputs[r1r2, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r1r2, p, t, S_v, S_i] - if (t, S_v) in M.processReservePeriods[r1r2, p] - ) - - cap_target = total_generation * (1 + value(M.PlanningReserveMargin[r])) - return cap_avail >= cap_target - - -def EmissionLimit_Constraint(M: 'TemoaModel', r, p, e): - r""" - - A modeler can track emissions through use of the :code:`commodity_emissions` - set and :code:`EmissionActivity` parameter. The :math:`EAC` parameter is - analogous to the efficiency table, tying emissions to a unit of activity. The - EmissionLimit constraint allows the modeler to assign an upper bound per period - to each emission commodity. Note that this constraint sums emissions from - technologies with output varying at the time slice and those with constant annual - output in separate terms. - - .. math:: - :label: EmissionLimit - - \sum_{S,D,I,T,V,O|{r,e,i,t,v,o} \in EAC} \left ( - EAC_{r, e, i, t, v, o} \cdot \textbf{FO}_{r, p, s, d, i, t, v, o} - \right ) & \\ - + - \sum_{I,T,V,O|{r,e,i,t \in T^{a},v,o} \in EAC} ( - EAC_{r, e, i, t, v, o} \cdot & \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} - ) - \le - ELM_{r, p, e} - - \\ - & \forall \{r, p, e\} \in \Theta_{\text{EmissionLimit}} - - """ - emission_limit = M.EmissionLimit[r, p, e] - - # r can be an individual region (r='US'), or a combination of regions separated by a + (r='Mexico+US+Canada'), - # or 'global'. Note that regions!=M.regions. We iterate over regions to find actual_emissions - # and actual_emissions_annual. - - # if r == 'global', the constraint is system-wide - - regions = gather_group_regions(M=M, region=r) - - # ================= Emissions and Flex and Curtailment ================= - # Flex flows are deducted from V_FlowOut, so it is NOT NEEDED to tax them again. (See commodity balance constr) - # Curtailment does not draw any inputs, so it seems logical that curtailed flows not be taxed either - - actual_emissions = sum( - M.V_FlowOut[reg, p, S_s, S_d, S_i, S_t, S_v, S_o] - * M.EmissionActivity[reg, e, S_i, S_t, S_v, S_o] - for reg in regions - for tmp_r, tmp_e, S_i, S_t, S_v, S_o in M.EmissionActivity.sparse_iterkeys() - if tmp_e == e and tmp_r == reg and S_t not in M.tech_annual - # EmissionsActivity not indexed by p, so make sure (r,p,t,v) combos valid - if (reg, p, S_t, S_v) in M.processInputs.keys() - for S_s in M.time_season - for S_d in M.time_of_day - ) - - actual_emissions_annual = sum( - M.V_FlowOutAnnual[reg, p, S_i, S_t, S_v, S_o] - * M.EmissionActivity[reg, e, S_i, S_t, S_v, S_o] - for reg in regions - for tmp_r, tmp_e, S_i, S_t, S_v, S_o in M.EmissionActivity.sparse_iterkeys() - if tmp_e == e and tmp_r == reg and S_t in M.tech_annual - # EmissionsActivity not indexed by p, so make sure (r,p,t,v) combos valid - if (reg, p, S_t, S_v) in M.processInputs.keys() - ) - - expr = actual_emissions + actual_emissions_annual <= emission_limit - - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - msg = ( - "Warning: No technology produces emission '%s', though limit was " 'specified as %s.\n' - ) - logger.warning(msg, (e, emission_limit)) - SE.write(msg % (e, emission_limit)) - return Constraint.Skip - return expr - - -def GrowthRateConstraint_rule(M: 'TemoaModel', p, r, t): - r""" - - This constraint sets an upper bound growth rate on technology-specific capacity. - - .. math:: - :label: GrowthRate - - CAPAVL_{r, p_{i},t} \le GRM \cdot CAPAVL_{r,p_{i-1},t} + GRS - - \\ - \forall \{r, p, t\} \in \Theta_{\text{GrowthRate}} - - where :math:`GRM` is the maximum growth rate, and should be specified as - :math:`(1+r)` and :math:`GRS` is the growth rate seed, which has units of - capacity. Without the seed, any technology with zero capacity in the first time - period would be restricted to zero capacity for the remainder of the time - horizon. - """ - GRS = value(M.GrowthRateSeed[r, t]) - GRM = value(M.GrowthRateMax[r, t]) - CapPT = M.V_CapacityAvailableByPeriodAndTech - - periods = sorted(set(p_ for r_, p_, t_ in CapPT if t_ == t)) - - if p not in periods: - return Constraint.Skip - - if p == periods[0]: - expr = CapPT[r, p, t] <= GRS * GRM - - else: - p_prev = periods.index(p) - p_prev = periods[p_prev - 1] - if (r, p_prev, t) in CapPT.keys(): - expr = CapPT[r, p, t] <= GRM * CapPT[r, p_prev, t] - else: - expr = CapPT[r, p, t] <= GRS * GRM - - return expr - - -def MaxActivity_Constraint(M: 'TemoaModel', r, p, t): - r""" - - The MaxActivity sets an upper bound on the activity from a specific technology. - Note that the indices for these constraints are region, period and tech, not tech - and vintage. The first version of the constraint pertains to technologies with - variable output at the time slice level, and the second version pertains to - technologies with constant annual output belonging to the :code:`tech_annual` - set. - - .. math:: - :label: MaxActivity - - \sum_{S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} \le MAA_{r, p, t} - - \forall \{r, p, t\} \in \Theta_{\text{MaxActivity}} - - \sum_{I,V,O} \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} \le MAA_{r, p, t} - - \forall \{r, p, t \in T^{a}\} \in \Theta_{\text{MaxActivity}} - """ - # r can be an individual region (r='US'), or a combination of regions separated by - # a + (r='Mexico+US+Canada'), or 'global'. - # if r == 'global', the constraint is system-wide - reg = gather_group_regions(M=M, region=r) - - if t not in M.tech_annual: - activity_rpt = sum( - M.V_FlowOut[r, p, s, d, S_i, t, S_v, S_o] - for r in reg - for S_v in M.processVintages.get((r, p, t), []) - for S_i in M.processInputs[r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - if (r, p, s, d, S_i, t, S_v, S_o) in M.V_FlowOut - ) - else: - activity_rpt = sum( - M.V_FlowOutAnnual[r, p, S_i, t, S_v, S_o] - for r in reg - for S_v in M.processVintages.get((r, p, t), []) - for S_i in M.processInputs[r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - if (r, p, S_i, t, S_v, S_o) in M.V_FlowOutAnnual - ) - - max_act = value(M.MaxActivity[r, p, t]) - expr = activity_rpt <= max_act - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - return Constraint.Skip - return expr - - -def MinActivity_Constraint(M: 'TemoaModel', r, p, t): - r""" - - The MinActivity sets a lower bound on the activity from a specific technology. - Note that the indices for these constraints are region, period and tech, not tech and - vintage. The first version of the constraint pertains to technologies with - variable output at the time slice level, and the second version pertains to - technologies with constant annual output belonging to the :code:`tech_annual` - set. - - .. math:: - :label: MinActivity - - \sum_{S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} \ge MIA_{r, p, t} - - \forall \{r, p, t\} \in \Theta_{\text{MinActivity}} - - \sum_{I,V,O} \textbf{FOA}_{r, p, i, t, v, o} \ge MIA_{r, p, t} - - \forall \{r, p, t \in T^{a}\} \in \Theta_{\text{MinActivity}} - """ - # r can be an individual region (r='US'), or a combination of regions separated by - # a + (r='Mexico+US+Canada'), or 'global'. - # if r == 'global', the constraint is system-wide - regions = gather_group_regions(M, r) - - if t not in M.tech_annual: - activity_rpt = sum( - M.V_FlowOut[_r, p, s, d, S_i, t, S_v, S_o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[_r, p, t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - if (_r, p, s, d, S_i, t, S_v, S_o) in M.V_FlowOut - ) - else: - activity_rpt = sum( - M.V_FlowOutAnnual[_r, p, S_i, t, S_v, S_o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[_r, p, t, S_v, S_i] - if (_r, p, S_i, t, S_v, S_o) in M.V_FlowOutAnnual - ) - - min_act = value(M.MinActivity[r, p, t]) - expr = activity_rpt >= min_act - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - logger.error( - 'No elements available to support min-activity: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - t, - ) - return Constraint.Skip - return expr - - -def MinActivityGroup_Constraint(M: 'TemoaModel', r, p, g): - r""" - - The MinActivityGroup constraint sets a minimum activity limit for a user-defined - technology group. - - .. math:: - :label: MinActivityGroup - - \sum_{R,S,D,I,T,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} + \sum_{I,T,V,O} - \textbf{FOA}_{r, p, i, t, v, o} - \ge MnAG_{r, p, g} - \forall \{r, p, g\} \in \Theta_{\text{MinActivityGroup}} - - where :math:`g` represents the assigned technology group and :math:`MnAG` - refers to the :code:`MinActivityGroup` parameter.""" - - regions = gather_group_regions(M, r) - - activity_p = 0 - activity_p_annual = 0 - for r_i in regions: - activity_p += sum( - M.V_FlowOut[r_i, p, s, d, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - if (r_i, p, S_t) in M.processVintages and S_t not in M.tech_annual - for S_v in M.processVintages[r_i, p, S_t] - for S_i in M.processInputs[r_i, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r_i, p, S_t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - if (r_i, p, s, d, S_i, S_t, S_v, S_o) in M.V_FlowOut - ) - - activity_p_annual += sum( - M.V_FlowOutAnnual[r_i, p, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - if (r_i, p, S_t) in M.processVintages and S_t in M.tech_annual - for S_v in M.processVintages[r_i, p, S_t] - for S_i in M.processInputs[r_i, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r_i, p, S_t, S_v, S_i] - if (r_i, p, S_i, S_t, S_v, S_o) in M.V_FlowOutAnnual - ) - min_act = value(M.MinActivityGroup[r, p, g]) - expr = activity_p + activity_p_annual >= min_act - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - logger.error( - 'No elements available to support min-activity group: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - g, - ) - return Constraint.Skip - return expr - - -def MaxActivityGroup_Constraint(M: 'TemoaModel', r, p, g): - r""" - The MaxActivityGroup constraint sets a maximum activity limit for a user-defined - technology group. - .. math:: - :label: MaxActivityGroup - \sum_{R,S,D,I,T,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} + \sum_{I,T,V,O} - \textbf{FOA}_{r, p, i, t, v, o} - \le MxAG_{r, p, g} - \forall \{r, p, g\} \in \Theta_{\text{MaxActivityGroup}} - where :math:`g` represents the assigned technology group and :math:`MxAG` - refers to the :code:`MaxActivityGroup` parameter.""" - - regions = gather_group_regions(M, r) - - activity_p = 0 - activity_p_annual = 0 - for r_i in regions: - activity_p += sum( - M.V_FlowOut[r_i, p, s, d, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - if (r_i, p, S_t) in M.processVintages and S_t not in M.tech_annual - for S_v in M.processVintages[r_i, p, S_t] - for S_i in M.processInputs[r_i, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r_i, p, S_t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - if (r_i, p, s, d, S_i, S_t, S_v, S_o) in M.V_FlowOut - ) - activity_p_annual += sum( - M.V_FlowOutAnnual[r_i, p, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - if (r_i, p, S_t) in M.processVintages and S_t in M.tech_annual - for S_v in M.processVintages[r_i, p, S_t] - for S_i in M.processInputs[r_i, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r_i, p, S_t, S_v, S_i] - if (r_i, p, S_i, S_t, S_v, S_o) in M.V_FlowOutAnnual - ) - - max_act = value(M.MaxActivityGroup[r, p, g]) - expr = activity_p + activity_p_annual <= max_act - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - return Constraint.Skip - return expr - - -def MaxNewCapacity_Constraint(M: 'TemoaModel', r, p, t): - r""" - The MaxNewCapacity constraint sets a limit on the maximum newly installed capacity of a - given technology in a given year. Note that the indices for these constraints are region, - period and tech. - .. math:: - :label: MaxNewCapacity - \textbf{CAP}_{r, t, p} \le MAX_{r, p, t}""" - max_cap = value(M.MaxNewCapacity[r, p, t]) - expr = M.V_NewCapacity[r, t, p] <= max_cap - return expr - - -def MaxCapacity_Constraint(M: 'TemoaModel', r, p, t): - r""" - - The MaxCapacity constraint sets a limit on the maximum available capacity of a - given technology. Note that the indices for these constraints are region, period and - tech, not tech and vintage. - - .. math:: - :label: MaxCapacity - - \textbf{CAPAVL}_{r, p, t} \le MAC_{r, p, t} - - \forall \{r, p, t\} \in \Theta_{\text{MaxCapacity}}""" - max_cap = value(M.MaxCapacity[r, p, t]) - expr = M.V_CapacityAvailableByPeriodAndTech[r, p, t] <= max_cap - return expr - - -def MaxResource_Constraint(M: 'TemoaModel', r, t): - r""" - - The MaxResource constraint sets a limit on the maximum available resource of a - given technology across all model time periods. Note that the indices for these - constraints are region and tech. - - .. math:: - :label: MaxResource - - \sum_{P} \textbf{CAPAVL}_{r, p, t} \le MAR_{r, t} - - \forall \{r, t\} \in \Theta_{\text{MaxCapacity}}""" - logger.warning( - 'The MaxResource constraint is not currently supported in the model, pending review. Recommend ' - 'removing data from the MaxResource Table' - ) - # dev note: this constraint is a misnomer. It is actually a "global activity constraint on a tech" - # regardless of whatever "resources" are consumed. - max_resource = value(M.MaxResource[r, t]) - return Constraint.Skip - # try: - # activity_rt = sum( - # M.V_FlowOut[r, p, s, d, S_i, t, S_v, S_o] - # for p in M.time_optimize - # if (r, p, t) in M.processVintages.keys() - # for S_v in M.processVintages[r, p, t] - # for S_i in M.processInputs[r, p, t, S_v] - # for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - # for s in M.time_season - # for d in M.time_of_day - # ) - # except KeyError: - # activity_rt = sum( - # M.V_FlowOutAnnual[r, p, S_i, t, S_v, S_o] - # for p in M.time_optimize - # if (r, p, t) in M.processVintages.keys() - # for S_v in M.processVintages[r, p, t] - # for S_i in M.processInputs[r, p, t, S_v] - # for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - # ) - # - # expr = activity_rt <= max_resource - # return expr - - -def MaxCapacityGroup_Constraint(M: 'TemoaModel', r, p, g): - r""" - Similar to the :code:`MaxCapacity` constraint, but works on a group of technologies. - """ - regions = gather_group_regions(M, r) - - max_capgroup = value(M.MaxCapacityGroup[r, p, g]) - - cap = sum( - M.V_CapacityAvailableByPeriodAndTech[r_i, p, t] - for t in M.tech_group_members[g] - for r_i in regions - if (r_i, p, t) in M.V_CapacityAvailableByPeriodAndTech - ) - - expr = cap <= max_capgroup - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - return Constraint.Skip - return expr - - -def MinNewCapacity_Constraint(M: 'TemoaModel', r, p, t): - r""" - The MinNewCapacity constraint sets a limit on the minimum newly installed capacity of a - given technology in a given year. Note that the indices for these constraints are region, - period, and tech. - .. math:: - :label: MaxMinCapacity - \textbf{CAP}_{r, t, p} \ge MIN_{r, p, t}""" - min_cap = value(M.MinNewCapacity[r, p, t]) - expr = M.V_NewCapacity[r, t, p] >= min_cap - return expr - - -def MinCapacity_Constraint(M: 'TemoaModel', r, p, t): - r""" - - The MinCapacity constraint sets a limit on the minimum available capacity of a - given technology. Note that the indices for these constraints are region, period and - tech, not tech and vintage. - - .. math:: - :label: MinCapacityCapacityAvailableByPeriodAndTech - - \textbf{CAPAVL}_{r, p, t} \ge MIC_{r, p, t} - - \forall \{r, p, t\} \in \Theta_{\text{MinCapacity}}""" - min_cap = value(M.MinCapacity[r, p, t]) - expr = M.V_CapacityAvailableByPeriodAndTech[r, p, t] >= min_cap - return expr - - -def gather_group_regions(M: 'TemoaModel', region: str) -> Iterable[str]: - if region == 'global': - regions = M.regions - elif '+' in region: - regions = region.split('+') - else: - regions = (region,) - return regions - - -def MinCapacityGroup_Constraint(M: 'TemoaModel', r, p, g): - r""" - Similar to the :code:`MinCapacity` constraint, but works on a group of technologies. - """ - regions = gather_group_regions(M, r) - - min_capgroup = value(M.MinCapacityGroup[r, p, g]) - - cap = sum( - M.V_CapacityAvailableByPeriodAndTech[r_i, p, t] - for t in M.tech_group_members[g] - for r_i in regions - if (r_i, p, t) in M.V_CapacityAvailableByPeriodAndTech - ) - - expr = cap >= min_capgroup - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - logger.error( - 'No elements available to support min-capacity group: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - g, - ) - return Constraint.Skip - return expr - - -def MinNewCapacityGroup_Constraint(M: 'TemoaModel', r, p, g): - r""" - Similar to the :code:`MinNewCapacity` constraint, but works on a group of technologies.""" - min_new_cap = value(M.MinNewCapacityGroup[r, p, g]) - agg_new_cap = sum( - M.V_NewCapacity[r, t, p] - for t in M.tech_group_members[g] - if (r, p, t) in M.V_CapacityAvailableByPeriodAndTech - ) - expr = agg_new_cap >= min_new_cap - if isinstance(expr, bool): - logger.error( - 'No elements available to support min-activity group: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - g, - ) - return Constraint.Skip - return expr - - -def MaxNewCapacityGroup_Constraint(M: 'TemoaModel', r, p, g): - r""" - Similar to the :code:`MinNewCapacity` constraint, but works on a group of technologies.""" - max_new_cap = value(M.MaxNewCapacityGroup[r, p, g]) - agg_new_cap = sum( - M.V_NewCapacity[r, t, p] - for t in M.tech_group_members[g] - if (r, p, t) in M.V_CapacityAvailableByPeriodAndTech - ) - expr = max_new_cap >= agg_new_cap - if isinstance(expr, bool): - return Constraint.Skip - return expr - - -def MinActivityShare_Constraint(M: 'TemoaModel', r, p, t, g): - r""" - The MinActivityShare constraint sets a minimum capacity share for a given - technology within a technology groups to which it belongs. - For instance, you might define a tech_group of light-duty vehicles, whose - members are different types for LDVs. This constraint could be used to enforce - that no less than 10% of LDVs must be of a certain type.""" - - if t not in M.tech_annual: - activity_rpt = sum( - M.V_FlowOut[r, p, s, d, S_i, t, S_v, S_o] - for S_v in M.processVintages.get((r, p, t), []) - for S_i in M.processInputs[r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - ) - else: - activity_rpt = sum( - M.V_FlowOutAnnual[r, p, S_i, t, S_v, S_o] - for S_v in M.processVintages.get((r, p, t), []) - for S_i in M.processInputs[r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, t, S_v, S_i] - ) - - activity_t = activity_rpt - activity_p = sum( - M.V_FlowOut[r, p, s, d, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - if (r, p, S_t) in M.processVintages and S_t not in M.tech_annual - for S_v in M.processVintages[r, p, S_t] - for S_i in M.processInputs[r, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, S_t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - ) - - activity_p_annual = sum( - M.V_FlowOutAnnual[r, p, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - if (r, p, S_t) in M.processVintages and S_t in M.tech_annual - for S_v in M.processVintages[r, p, S_t] - for S_i in M.processInputs[r, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[r, p, S_t, S_v, S_i] - ) - activity_group = activity_p + activity_p_annual - min_activity_share = value(M.MinActivityShare[r, p, t, g]) - - expr = activity_t >= min_activity_share * activity_group - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - logger.error( - 'No elements available to support min-activity share group: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - g, - ) - return Constraint.Skip - return expr - - -def MaxActivityShare_Constraint(M: 'TemoaModel', r, p, t, g): - r""" - The MaxActivityShare constraint sets a maximum Activity share for a given - technology within a technology groups to which it belongs. - For instance, you might define a tech_group of light-duty vehicles, whose - members are different types for LDVs. This constraint could be used to enforce - that no more than 10% of LDVs must be of a certain type.""" - - regions = gather_group_regions(M, r) - - if t not in M.tech_annual: - activity_rpt = sum( - M.V_FlowOut[_r, p, s, d, S_i, t, S_v, S_o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[_r, p, t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - if (_r, p, s, d, S_i, t, S_v, S_o) in M.V_FlowOut - ) - else: - activity_rpt = sum( - M.V_FlowOutAnnual[_r, p, S_i, t, S_v, S_o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - for S_o in M.ProcessOutputsByInput[_r, p, t, S_v, S_i] - if (_r, p, S_i, t, S_v, S_o) in M.V_FlowOutAnnual - ) - - activity_t = activity_rpt - activity_p = sum( - M.V_FlowOut[_r, p, s, d, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - for _r in regions - if (_r, p, S_t) in M.processVintages and S_t not in M.tech_annual - for S_v in M.processVintages[_r, p, S_t] - for S_i in M.processInputs[_r, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[_r, p, S_t, S_v, S_i] - for s in M.time_season - for d in M.time_of_day - if (_r, p, s, d, S_i, S_t, S_v, S_o) in M.V_FlowOut - ) - - activity_p_annual = sum( - M.V_FlowOutAnnual[_r, p, S_i, S_t, S_v, S_o] - for S_t in M.tech_group_members[g] - for _r in regions - if (_r, p, S_t) in M.processVintages and S_t in M.tech_annual - for S_v in M.processVintages[_r, p, S_t] - for S_i in M.processInputs[_r, p, S_t, S_v] - for S_o in M.ProcessOutputsByInput[_r, p, S_t, S_v, S_i] - if (_r, p, S_i, S_t, S_v, S_o) in M.V_FlowOutAnnual - ) - activity_group = activity_p + activity_p_annual - max_activity_share = value(M.MaxActivityShare[r, p, t, g]) - - expr = activity_t <= max_activity_share * activity_group - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - return Constraint.Skip - logger.debug( - 'created max activity constraint for (%s, %d, %s, %s) of %0.2f', - (r, p, t, g, max_activity_share), - ) - return expr - - -def MinCapacityShare_Constraint(M: 'TemoaModel', r, p, t, g): - r""" - The MinCapacityShare constraint sets a minimum capacity share for a given - technology within a technology groups to which it belongs. - For instance, you might define a tech_group of light-duty vehicles, whose - members are different types for LDVs. This constraint could be used to enforce - that no less than 10% of LDVs must be of a certain type.""" - - capacity_t = M.V_CapacityAvailableByPeriodAndTech[r, p, t] - capacity_group = sum( - M.V_CapacityAvailableByPeriodAndTech[r, p, S_t] - for S_t in M.tech_group_members[g] - if (r, p, S_t) in M.processVintages.keys() - ) - min_cap_share = value(M.MinCapacityShare[r, p, t, g]) - - expr = capacity_t >= min_cap_share * capacity_group - if isinstance(expr, bool): - logger.error( - 'No elements available to support min-capacity share: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - g, - ) - return Constraint.Skip - return expr - - -def MaxCapacityShare_Constraint(M: 'TemoaModel', r, p, t, g): - r""" - The MaxCapacityShare constraint sets a maximum capacity share for a given - technology within a technology groups to which it belongs. - For instance, you might define a tech_group of light-duty vehicles, whose - members are different types for LDVs. This constraint could be used to enforce - that no more than 10% of LDVs must be of a certain type.""" - - capacity_t = M.V_CapacityAvailableByPeriodAndTech[r, p, t] - capacity_group = sum( - M.V_CapacityAvailableByPeriodAndTech[r, p, S_t] - for S_t in M.tech_group_members[g] - if (r, p, S_t) in M.processVintages.keys() - ) - max_cap_share = value(M.MaxCapacityShare[r, p, t, g]) - - expr = capacity_t <= max_cap_share * capacity_group - if isinstance(expr, bool): - return Constraint.Skip - return expr - - -def MinNewCapacityShare_Constraint(M: 'TemoaModel', r, p, t, g): - r""" - The MinNewCapacityShare constraint sets a minimum new capacity share for a given - technology within a technology groups to which it belongs. - For instance, you might define a tech_group of light-duty vehicles, whose - members are different types for LDVs. This constraint could be used to enforce - that no less than 10% of new LDV purchases in a given year must be of a certain type.""" - - capacity_t = M.V_NewCapacity[r, t, p] - capacity_group = sum( - M.V_NewCapacity[r, S_t, p] - for S_t in M.tech_group_members[g] - if (r, S_t, p) in M.V_NewCapacity.keys() - ) - min_cap_share = value(M.MinNewCapacityShare[r, p, t, g]) - - expr = capacity_t >= min_cap_share * capacity_group - if isinstance(expr, bool): - logger.error( - 'No elements available to support min-new capacity share: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - g, - ) - return Constraint.Skip - return expr - - -def MaxNewCapacityShare_Constraint(M: 'TemoaModel', r, p, t, g): - r""" - The MaxCapacityShare constraint sets a maximum new capacity share for a given - technology within a technology groups to which it belongs. - For instance, you might define a tech_group of light-duty vehicles, whose - members are different types for LDVs. This constraint could be used to enforce - that no more than 10% of LDV purchases in a given year must be of a certain type.""" - - capacity_t = M.V_NewCapacity[r, t, p] - capacity_group = sum( - M.V_NewCapacity[r, S_t, p] - for S_t in M.tech_group_members[g] - if (r, S_t, p) in M.V_NewCapacity.keys() - ) - max_cap_share = value(M.MaxNewCapacityShare[r, p, t, g]) - - expr = capacity_t <= max_cap_share * capacity_group - if isinstance(expr, bool): - return Constraint.Skip - return expr - - -def MinAnnualCapacityFactor_Constraint(M: 'TemoaModel', r, p, t, o): - r""" - The MinAnnualCapacityFactor sets a lower bound on the annual capacity factor - from a specific technology. The first portion of the constraint pertains to - technologies with variable output at the time slice level, and the second portion - pertains to technologies with constant annual output belonging to the - :code:`tech_annual` set. - .. math:: - :label: MinAnnualCapacityFactor - \sum_{S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} \ge MINCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} - \forall \{r, p, t, o\} \in \Theta_{\text{MinAnnualCapacityFactor}} - \sum_{I,V,O} \textbf{FOA}_{r, p, i, t, v, o} \ge MINCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} - \forall \{r, p, t, o \in T^{a}\} \in \Theta_{\text{MinAnnualCapacityFactor}}""" - # r can be an individual region (r='US'), or a combination of regions separated by plus (r='Mexico+US+Canada'), or 'global'. - # if r == 'global', the constraint is system-wide - regions = gather_group_regions(M, r) - # we need to screen here because it is possible that the restriction extends beyond the - # lifetime of any vintage of the tech... - if (r, p, t) not in M.V_CapacityAvailableByPeriodAndTech: - return Constraint.Skip - - if t not in M.tech_annual: - activity_rpt = sum( - M.V_FlowOut[_r, p, s, d, S_i, t, S_v, o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - for s in M.time_season - for d in M.time_of_day - if (_r, p, s, d, S_i, t, S_v, o) in M.V_FlowOut - ) - else: - activity_rpt = sum( - M.V_FlowOutAnnual[_r, p, S_i, t, S_v, o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - if (_r, p, S_i, t, S_v, o) in M.V_FlowOutAnnual - ) - - max_possible_activity_rpt = ( - M.V_CapacityAvailableByPeriodAndTech[r, p, t] * M.CapacityToActivity[r, t] - ) - min_annual_cf = value(M.MinAnnualCapacityFactor[r, p, t, o]) - expr = activity_rpt >= min_annual_cf * max_possible_activity_rpt - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - logger.error( - 'No elements available to support min-annual capacity factor: (%s, %d, %s).' - ' Check data/log for available/suppressed techs. Requirement IGNORED.', - r, - p, - t, - ) - return Constraint.Skip - return expr - - -def MaxAnnualCapacityFactor_Constraint(M: 'TemoaModel', r, p, t, o): - r""" - The MaxAnnualCapacityFactor sets an upper bound on the annual capacity factor - from a specific technology. The first portion of the constraint pertains to - technologies with variable output at the time slice level, and the second portion - pertains to technologies with constant annual output belonging to the - :code:`tech_annual` set. - .. math:: - :label: MaxAnnualCapacityFactor - \sum_{S,D,I,V,O} \textbf{FO}_{r, p, s, d, i, t, v, o} \le MAXCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} - \forall \{r, p, t, o\} \in \Theta_{\text{MaxAnnualCapacityFactor}} - \sum_{I,V,O} \textbf{FOA}_{r, p, i, t, v, o} \ge MAXCF_{r, p, t} * \textbf{CAPAVL}_{r, p, t} * \text{C2A}_{r, t} - \forall \{r, p, t, o \in T^{a}\} \in \Theta_{\text{MaxAnnualCapacityFactor}}""" - # r can be an individual region (r='US'), or a combination of regions separated by plus (r='Mexico+US+Canada'), or 'global'. - # if r == 'global', the constraint is system-wide - regions = gather_group_regions(M, r) - # we need to screen here because it is possible that the restriction extends beyond the - # lifetime of any vintage of the tech... - if (r, p, t) not in M.V_CapacityAvailableByPeriodAndTech: - return Constraint.Skip - - if t not in M.tech_annual: - activity_rpt = sum( - M.V_FlowOut[_r, p, s, d, S_i, t, S_v, o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - for s in M.time_season - for d in M.time_of_day - if (_r, p, s, d, S_i, t, S_v, o) in M.V_FlowOut - ) - else: - activity_rpt = sum( - M.V_FlowOutAnnual[_r, p, S_i, t, S_v, o] - for _r in regions - for S_v in M.processVintages.get((_r, p, t), []) - for S_i in M.processInputs[_r, p, t, S_v] - if (_r, p, S_i, t, S_v, o) in M.V_FlowOutAnnual - ) - - max_possible_activity_rpt = ( - M.V_CapacityAvailableByPeriodAndTech[r, p, t] * M.CapacityToActivity[r, t] - ) - max_annual_cf = value(M.MaxAnnualCapacityFactor[r, p, t, o]) - expr = activity_rpt <= max_annual_cf * max_possible_activity_rpt - # in the case that there is nothing to sum, skip - if isinstance(expr, bool): # an empty list was generated - return Constraint.Skip - return expr - - -def TechInputSplit_Constraint(M: 'TemoaModel', r, p, s, d, i, t, v): - r""" - Allows users to specify fixed or minimum shares of commodity inputs to a process - producing a single output. These shares can vary by model time period. See - TechOutputSplit_Constraint for an analogous explanation. Under this constraint, - only the technologies with variable output at the timeslice level (i.e., - NOT in the :code:`tech_annual` set) are considered.""" - inp = sum( - M.V_FlowOut[r, p, s, d, i, t, v, S_o] / value(M.Efficiency[r, i, t, v, S_o]) - for S_o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - total_inp = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] / value(M.Efficiency[r, S_i, t, v, S_o]) - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - expr = inp >= M.TechInputSplit[r, p, i, t] * total_inp - return expr - - -def TechInputSplitAnnual_Constraint(M: 'TemoaModel', r, p, i, t, v): - r""" - Allows users to specify fixed or minimum shares of commodity inputs to a process - producing a single output. These shares can vary by model time period. See - TechOutputSplitAnnual_Constraint for an analogous explanation. Under this - function, only the technologies with constant annual output (i.e., members - of the :math:`tech_annual` set) are considered.""" - inp = sum( - M.V_FlowOutAnnual[r, p, i, t, v, S_o] / value(M.Efficiency[r, i, t, v, S_o]) - for S_o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - total_inp = sum( - M.V_FlowOutAnnual[r, p, S_i, t, v, S_o] / value(M.Efficiency[r, S_i, t, v, S_o]) - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - expr = inp >= M.TechInputSplit[r, p, i, t] * total_inp - return expr - - -def TechInputSplitAverage_Constraint(M: 'TemoaModel', r, p, i, t, v): - r""" - Allows users to specify fixed or minimum shares of commodity inputs to a process - producing a single output. Under this constraint, only the technologies with variable - output at the timeslice level (i.e., NOT in the :code:`tech_annual` set) are considered. - This constraint differs from TechInputSplit as it specifies shares on an annual basis, - so even though it applies to technologies with variable output at the timeslice level, - the constraint only fixes the input shares over the course of a year.""" - - inp = sum( - M.V_FlowOut[r, p, s, d, i, t, v, S_o] / value(M.Efficiency[r, i, t, v, S_o]) - for s in M.time_season - for d in M.time_of_day - for S_o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - total_inp = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] / value(M.Efficiency[r, S_i, t, v, S_o]) - for s in M.time_season - for d in M.time_of_day - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, i] - ) - - expr = inp >= M.TechInputSplitAverage[r, p, i, t] * total_inp - return expr - - -def TechOutputSplit_Constraint(M: 'TemoaModel', r, p, s, d, t, v, o): - r""" - - Some processes take a single input and make multiple outputs, and the user would like to - specify either a constant or time-varying ratio of outputs per unit input. The most - canonical example is an oil refinery. Crude oil is used to produce many different refined - products. In many cases, the modeler would like to specify a minimum share of each refined - product produced by the refinery. - - For example, a hypothetical (and highly simplified) refinery might have a crude oil input - that produces 4 parts diesel, 3 parts gasoline, and 2 parts kerosene. The relative - ratios to the output then are: - - .. math:: - - d = \tfrac{4}{9} \cdot \text{total output}, \qquad - g = \tfrac{3}{9} \cdot \text{total output}, \qquad - k = \tfrac{2}{9} \cdot \text{total output} - - Note that it is possible to specify output shares that sum to less than unity. In such - cases, the model optimizes the remaining share. In addition, it is possible to change the - specified shares by model time period. Under this constraint, only the - technologies with variable output at the timeslice level (i.e., NOT in the - :code:`tech_annual` set) are considered. - - The constraint is formulated as follows: - - .. math:: - :label: TechOutputSplit - - \sum_{I, t \not \in T^{a}} \textbf{FO}_{r, p, s, d, i, t, v, o} - \geq - TOS_{r, p, t, o} \cdot \sum_{I, O, t \not \in T^{a}} \textbf{FO}_{r, p, s, d, i, t, v, o} - - \forall \{r, p, s, d, t, v, o\} \in \Theta_{\text{TechOutputSplit}}""" - out = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, o] for S_i in M.ProcessInputsByOutput[r, p, t, v, o] - ) - - total_out = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr = out >= M.TechOutputSplit[r, p, t, o] * total_out - return expr - - -def TechOutputSplitAnnual_Constraint(M: 'TemoaModel', r, p, t, v, o): - r""" - This constraint operates similarly to TechOutputSplit_Constraint. - However, under this function, only the technologies with constant annual - output (i.e., members of the :math:`tech_annual` set) are considered. - - .. math:: - :label: TechOutputSplitAnnual - - \sum_{I, T^{a}} \textbf{FOA}_{r, p, i, t \in T^{a}, v, o} - - \geq - - TOS_{r, p, t, o} \cdot \sum_{I, O, T^{a}} \textbf{FOA}_{r, p, s, d, i, t \in T^{a}, v, o} - - \forall \{r, p, t \in T^{a}, v, o\} \in \Theta_{\text{TechOutputSplitAnnual}}""" - out = sum( - M.V_FlowOutAnnual[r, p, S_i, t, v, o] for S_i in M.ProcessInputsByOutput[r, p, t, v, o] - ) - - total_out = sum( - M.V_FlowOutAnnual[r, p, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr = out >= M.TechOutputSplit[r, p, t, o] * total_out - return expr - - -def RenewablePortfolioStandard_Constraint(M: 'TemoaModel', r, p, g): - r""" - Allows users to specify the share of electricity generation in a region - coming from RPS-eligible technologies.""" - - inp = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for t in M.tech_group_members[g] - for (_t, v) in M.processReservePeriods[r, p] - if _t == t - for s in M.time_season - for d in M.time_of_day - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - total_inp = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] - for (t, v) in M.processReservePeriods[r, p] - for s in M.time_season - for d in M.time_of_day - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - expr = inp >= (value(M.RenewablePortfolioStandard[r, p, g]) * total_inp) - return expr - - -# --------------------------------------------------------------- -# Define rule-based parameters -# --------------------------------------------------------------- -def ParamModelProcessLife_rule(M: 'TemoaModel', r, p, t, v): - life_length = value(M.LifetimeProcess[r, t, v]) - tpl = min(v + life_length - p, value(M.PeriodLength[p])) - - return tpl - - -def ParamPeriodLength(M: 'TemoaModel', p): - # This specifically does not use time_optimize because this function is - # called /over/ time_optimize. - periods = sorted(M.time_future) - - i = periods.index(p) - - # The +1 won't fail, because this rule is called over time_optimize, which - # lacks the last period in time_future. - length = periods[i + 1] - periods[i] - - return length - - -def ParamProcessLifeFraction_rule(M: 'TemoaModel', r, p, t, v): - r""" - - Calculate the fraction of period p that process :math:`` operates. - - For most processes and periods, this will likely be one, but for any process - that will cease operation (rust out, be decommissioned, etc.) between periods, - calculate the fraction of the period that the technology is able to - create useful output. - """ - eol_year = v + value(M.LifetimeProcess[r, t, v]) - frac = eol_year - p - period_length = value(M.PeriodLength[p]) - if frac >= period_length: - # try to avoid floating point round-off errors for the common case. - return 1 - - # number of years into final period loan is complete - - frac /= float(period_length) - return frac - - -def loan_annualization_rate(loan_rate: float | None, loan_life: int | float) -> float: - """ - This calculation is broken out specifically so that it can be used for param creation - and separately to calculate loan costs rather than rely on fully-built model parameters - :param loan_rate: - :param loan_life: - - """ - if not loan_rate: - # dev note: this should not be needed as the LoanRate param has a default (see the definition) - return 1.0 / loan_life - annualized_rate = loan_rate / (1.0 - (1.0 + loan_rate) ** (-loan_life)) - return annualized_rate - - -def ParamLoanAnnualize_rule(M: 'TemoaModel', r, t, v): - dr = value(M.LoanRate[r, t, v]) - lln = value(M.LoanLifetimeProcess[r, t, v]) - annualized_rate = loan_annualization_rate(dr, lln) - return annualized_rate - - -def SegFracPerSeason_rule(M: 'TemoaModel', s): - return sum(M.SegFrac[s, S_d] for S_d in M.time_of_day) - - -def LinkedEmissionsTech_Constraint(M: 'TemoaModel', r, p, s, d, t, v, e): - r""" - This constraint is necessary for carbon capture technologies that produce - CO2 as an emissions commodity, but the CO2 also serves as a physical - input commodity to a downstream process, such as synthetic fuel production. - To accomplish this, a dummy technology is linked to the CO2-producing - technology, converting the emissions activity into a physical commodity - amount as follows: - - .. math:: - :label: LinkedEmissionsTech - - - \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} \cdot EAC_{r, e, i, t, v, o} - = \sum_{I, O} \textbf{FO}_{r, p, s, d, i, t, v, o} - - \forall \{r, p, s, d, t, v, e\} \in \Theta_{\text{LinkedTechs}} - - The relationship between the primary and linked technologies is given - in the :code:`LinkedTechs` table. Note that the primary and linked - technologies cannot be part of the :code:`tech_annual` set. It is implicit that - the primary region corresponds to the linked technology as well. The lifetimes - of the primary and linked technologies should be specified and identical. - """ - linked_t = M.LinkedTechs[r, t, e] - - primary_flow = sum( - M.V_FlowOut[r, p, s, d, S_i, t, v, S_o] * M.EmissionActivity[r, e, S_i, t, v, S_o] - for S_i in M.processInputs[r, p, t, v] - for S_o in M.ProcessOutputsByInput[r, p, t, v, S_i] - ) - - linked_flow = sum( - M.V_FlowOut[r, p, s, d, S_i, linked_t, v, S_o] - for S_i in M.processInputs[r, p, linked_t, v] - for S_o in M.ProcessOutputsByInput[r, p, linked_t, v, S_i] - ) - - return -primary_flow == linked_flow diff --git a/temoa/temoa_model/temoa_sequencer.py b/temoa/temoa_model/temoa_sequencer.py deleted file mode 100644 index 0a85197b2..000000000 --- a/temoa/temoa_model/temoa_sequencer.py +++ /dev/null @@ -1,287 +0,0 @@ -""" -The Temoa Sequencer's job is to sequence the actions needed to execute a scenario. Each -scenario has a declared processing mode (regular, myopic, mga, etc.) and the Temoa Sequencer sets -up the necessary run(s) to accomplish that. Several processing modes have requirements -for multiple runs, and the Temoa Sequencer may hand off to a mode-specific sequencer - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/14/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import sqlite3 -import sys -from logging import getLogger -from pathlib import Path - -import pyomo.opt - -from temoa.extensions.method_of_morris.morris_sequencer import MorrisSequencer -from temoa.extensions.modeling_to_generate_alternatives.mga_sequencer import MgaSequencer -from temoa.extensions.monte_carlo.mc_sequencer import MCSequencer -from temoa.extensions.myopic.myopic_sequencer import MyopicSequencer -from temoa.extensions.single_vector_mga.sv_mga_sequencer import SvMgaSequencer -from temoa.temoa_model.hybrid_loader import HybridLoader -from temoa.temoa_model.model_checking.pricing_check import price_checker -from temoa.temoa_model.run_actions import ( - build_instance, - solve_instance, - handle_results, - check_solve_status, - check_python_version, - check_database_version, -) -from temoa.temoa_model.temoa_config import TemoaConfig -from temoa.temoa_model.temoa_mode import TemoaMode -from temoa.temoa_model.temoa_model import TemoaModel -from temoa.version_information import ( - DB_MAJOR_VERSION, - MIN_DB_MINOR_VERSION, - MIN_PYTHON_MAJOR, - MIN_PYTHON_MINOR, -) - -logger = getLogger(__name__) - - -class TemoaSequencer: - """A Sequencer instance to control all runs for a scenario based on the TemoaMode""" - - def __init__( - self, - config_file: str | Path, - output_path: str | Path, - mode_override: TemoaMode | None = None, - silent: bool = False, - **kwargs, - ): - """ - Create a new Sequencer - :param config_file: Optional path to config file. If not provided, it will be read - from Command Line Args - :param mode_override: Optional override to execution mode. If not provided, - it will be read from config file - :param silent: boolean to indicate whether to silence run-time feedback - """ - self.config: TemoaConfig | None = None - self.temoa_mode: TemoaMode - - self.config_file: Path = Path(config_file) - # check it... - if not self.config_file.is_file(): - logger.error( - 'Config file location passed %s does not point to a file', self.config_file - ) - raise FileNotFoundError(f'Invalid config file: {self.config_file}') - - self.output_path: Path = Path(output_path) - # check it... - if not self.output_path.is_dir(): - logger.error('Output directory does not exist: %s', self.output_path) - raise FileNotFoundError(f'Invalid output directory: {self.output_path}') - - self.temoa_mode: TemoaMode = TemoaMode.BUILD_ONLY # placeholder, over-written in start() - self.mode_override: TemoaMode = mode_override - - # for feedback to user - self.silent = silent - - # for results catching for perfect_foresight, other modes / testing - self.pf_results: pyomo.opt.SolverResults | None = None - self.pf_solved_instance: TemoaModel | None = None - - def start(self) -> TemoaModel | None: - """Start the processing of the scenario""" - - # ----- Run the "preliminaries" - # Build a TemoaConfig - self.config = TemoaConfig.build_config( - config_file=self.config_file, output_path=self.output_path, silent=self.silent - ) - - # Run some checks... - good = True - good &= check_python_version(MIN_PYTHON_MAJOR, MIN_PYTHON_MINOR) - good &= check_database_version( - self.config, db_major_reqd=DB_MAJOR_VERSION, min_db_minor=MIN_DB_MINOR_VERSION - ) - if not good: - logger.error('Failed pre-run checks... See log file for details') - sys.exit(-1) - - # Distill the TemoaMode - if self.mode_override and self.mode_override != self.config.scenario_mode: - # capture and log the override... - self.temoa_mode = self.mode_override - if self.config: # register the override in the config - self.config.scenario_mode = self.mode_override - logger.info('Temoa Mode overridden to be: %s', self.temoa_mode) - else: - self.temoa_mode = self.config.scenario_mode - # check it... - if not isinstance(self.temoa_mode, TemoaMode): - logger.error( - 'Temoa Mode not set properly. Override: %s, Config File: %s', - self.mode_override, - self.config.scenario_mode, - ) - raise RuntimeError('Problem with mode selection, see log file.') - - # Get user confirmation if not silent - if not self.silent: - try: - print(self.config.__repr__()) - print('\nPlease press enter to continue or Ctrl+C to quit.\n') - input() # Give the user a chance to confirm input - except KeyboardInterrupt: - logger.warning('User aborted from confirmation page. Exiting') - print('\n\nUser requested quit. Exiting Temoa ...\n') - sys.exit() - - # ---- Select execution path based on mode ---- - match self.temoa_mode: - case TemoaMode.BUILD_ONLY: - # override the "extras" - if self.config.source_trace: - self.config.source_trace = False - logger.warning('Source trace disabled for BUILD_ONLY') - if self.config.plot_commodity_network: - self.config.plot_commodity_network = False - logger.warning('Plot commodity network disabled for BUILD_ONLY') - if self.config.price_check: - logger.warning('Price check disabled for BUILD_ONLY') - con = sqlite3.connect(self.config.input_database) - hybrid_loader = HybridLoader(db_connection=con, config=self.config) - data_portal = hybrid_loader.load_data_portal(myopic_index=None) - instance = build_instance(data_portal, silent=self.config.silent) - con.close() - return instance - - case TemoaMode.CHECK: - con = sqlite3.connect(self.config.input_database) - if self.config.source_trace is False: - logger.warning('Source trace automatic for CHECK') - self.config.source_trace = True - hybrid_loader = HybridLoader(db_connection=con, config=self.config) - data_portal = hybrid_loader.load_data_portal(myopic_index=None) - instance = build_instance( - data_portal, - silent=self.config.silent, - keep_lp_file=self.config.save_lp_file, - lp_path=self.config.output_path, - ) - # disregard what the config says about price_check and source_trace and just do it... - if self.config.price_check is False: - logger.warning('Price check of model is automatic with CHECK') - good_prices = price_checker(instance) - if not good_prices and not self.config.silent: - print('\nWarning: Cost anomalies discovered. Check log file for details.') - con.close() - - case TemoaMode.PERFECT_FORESIGHT: - con = sqlite3.connect(self.config.input_database) - hybrid_loader = HybridLoader(db_connection=con, config=self.config) - data_portal = hybrid_loader.load_data_portal(myopic_index=None) - instance = build_instance( - data_portal, - silent=self.config.silent, - keep_lp_file=self.config.save_lp_file, - lp_path=self.config.output_path, - ) - if self.config.price_check: - good_prices = price_checker(instance) - if not good_prices and not self.config.silent: - print('\nWarning: Cost anomalies discovered. Check log file for details.') - suffixes = ( - [ - 'dual', - ] - if self.config.save_duals - else None - ) - self.pf_solved_instance, self.pf_results = solve_instance( - instance, - self.config.solver_name, - silent=self.config.silent, - solver_suffixes=suffixes, - ) - good_solve, msg = check_solve_status(self.pf_results) - if not good_solve: - logger.error('The solve result is reported as %s. Aborting', msg) - logger.error( - 'This may be the result of the output messaging of the chosen solver' - 'If this is deemed an acceptable status, adjustment may be needed to the ' - 'function check_solve_status in run_actions.py' - ) - sys.exit(-1) - handle_results(self.pf_solved_instance, self.pf_results, self.config) - con.close() - - case TemoaMode.MYOPIC: - # create a myopic sequencer and shift control to it - myopic_sequencer = MyopicSequencer(config=self.config) - myopic_sequencer.start() - - case TemoaMode.MGA: - if self.config.solver_name == 'appsi_highs': - raise ValueError( - 'Multiprocessing currently not working with HiGHS solver. ' - 'Unknown fix...appears to be pyomo issue. Gurobi, CBC, Ipopt all work.' - ) - mga_sequencer = MgaSequencer(config=self.config) - mga_sequencer.start() - - case TemoaMode.SVMGA: - sv_mga_sequencer = SvMgaSequencer(config=self.config) - sv_mga_sequencer.start() - - case TemoaMode.METHOD_OF_MORRIS: - mm_sequencer = MorrisSequencer(config=self.config) - mm_sequencer.start() - - case TemoaMode.MONTE_CARLO: - if self.config.solver_name == 'appsi_highs': - raise ValueError( - 'Multiprocessing currently not working with HiGHS solver. ' - 'Unknown fix...appears to be pyomo issue. Gurobi, CBC, Ipopt all work.' - ) - if self.config.plot_commodity_network: - self.config.plot_commodity_network = False - logger.warning('Plot commodity network disabled for MONTE_CARLO') - if self.config.price_check: - self.config.price_check = False - logger.warning('Price check disabled for MONTE_CARLO') - if self.config.save_excel: - self.config.save_excel = False - logger.warning('Save excel disabled for MONTE_CARLO') - if self.config.save_lp_file: - self.config.save_lp_file = False - logger.warning('Save lp file disabled for MONTE_CARLO') - if self.config.save_duals: - self.config.save_duals = False - logger.warning('Save duals disabled for MONTE_CARLO') - mc_sequencer = MCSequencer(config=self.config) - mc_sequencer.start() - - case _: - raise NotImplementedError('not yet built') diff --git a/temoa/tutorial_assets/config_sample.toml b/temoa/tutorial_assets/config_sample.toml new file mode 100644 index 000000000..6b522212d --- /dev/null +++ b/temoa/tutorial_assets/config_sample.toml @@ -0,0 +1,214 @@ +# ---------------------------------------------------------- +# Configuration file for a Temoa Run +# Allows specification of run type and associated parameters +# ---------------------------------------------------------- +# +# For toml format info see: https://toml.io/en/ +# - comments may be added with hash +# - do NOT comment out table names in brackets like: [] + +# Scenario Name (Mandatory) +# This scenario name is used to label results within the output .sqlite file +# (cannot contain "-" dash) +scenario = "zulu" + +# Scenario Mode (Mandatory) +# See documentation for explanations. A standard single run is "perfect_foresight" +# mode must be one of (case-insensitive): +# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check, monte_carlo] +scenario_mode = "perfect_foresight" + +# Input database (Mandatory) +input_database = "utopia.sqlite" + +# Output file (Mandatory) +# The output file must be an existing .sqlite file +# For Perfect Foresight, the user may target the same input file or a separate / +# copied sqlite file in a different location. Myopic, MGA require that input_database = output_database +output_database = "utopia.sqlite" + +# ------------------------------------ +# DATABASE CONFIGURATION +# ------------------------------------ + +# See the documentation section on Data Quality for notes on the features below + +# Check the pricing structure for common errors, which are reported in the log file +# Strongly recommended +price_check = true + +# Check the network connectivity for processes in the model. Strongly +# recommended to ensure proper performance. Results are reported in log file +# This requires that source commodities be marked with 's' in Commodity table +# This is required for Myopic runs +source_trace = true + +# Produce HTML files for Commodity Networks. Requires source_trace above +plot_commodity_network = true + +# Check units consistency in the database +# Validates that units are properly formatted and consistent across related tables +# Recommended for production runs after units are populated in the database +check_units = true + +# Limit the number of cycles detected in the commodity graph +# -1 = unbounded (INFO), 0 = strictly disallow (ERROR), positive integer = limit +cycle_count_limit = 100 + +# Minimum cycle length to report (default: 1) +# Use this to filter out very small cycles if needed +cycle_length_limit = 1 + +# ------------------------------------ +# SOLVER +# Solver Selection +# ------------------------------------ + +# use the NEOS server to solve. (Currently NOT supported) +neos = false + +# solver (Mandatory) +# Depending on what client machine has installed. +# [appsi_highs, cbc, gurobi, cplex, ...] +solver_name = "appsi_highs" + +# ------------------------------------ +# OUTPUTS +# select desired output products/files +# ------------------------------------ + +# generate an Excel file in the output_files folder +save_excel = true + +# save the duals in the output Database (may slow execution slightly?) +save_duals = true + +# save storage levels by time slice (may be a large amount of data) +save_storage_levels = true + +# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!) +save_lp_file = false + +# graphviz dot file and svg for network visualization (requires graphviz to be installed separately) +graphviz_output = false + +# Optional output filtering thresholds (set to 0 to disable per category) +# Precedence is: TOML value > internal defaults. +output_threshold_capacity = 0.001 +output_threshold_activity = 0.001 +output_threshold_emission = 0.001 +output_threshold_cost = 0.01 + +# ------------------------------------ +# MODEL PARAMETERS +# these are specific to each model +# ------------------------------------ + +# What seasons represent in the model +# Options: +# 'consecutive_days' +# Seasons are a set of days in order, with each season representing only one day. Examples +# might be a model of a representative week with 7 days or a whole-year model with 365 days. +# Seasonal storage need not be tagged and the TimeSeasonSequential table can be left empty. +# 'representative_periods' +# Each season represents a number of days, though not necessarily in any particular order. +# If using inter-season constraints like seasonal storage or ramp rates, the true sequence +# must be defined using the TimeSeasonSequential table. Seasonal storage must also be tagged in +# the Technology table. +# 'seasonal_timeslices' +# Each season represents a sequential slice of the year, with one or many days represented per +# season. We assume that the true sequence is the same as the TimeSeason sequence, so the +# TimeSeasonSequential table can be left empty. Seasonal storage must still be tagged. +# 'manual' +# The sequence of time slices is defined manually in the TimeNext table (which is commented out +# in the schema). This is an advanced feature and not recommended for most users. Seasonal +# storage must be tagged and the TimeSeasonSequential table filled. +time_sequencing = 'seasonal_timeslices' + +# Number of days represented by each planning period. +# Used to adjust flow variables for number of days represented by each season. +# E.g. 365 if all seasons collectively represent a year, 7 if modelling a single representative week. +days_per_period = 365 + +# How contributions to the planning reserve margin are calculated +# Options: +# 'static' +# Traditional planning reserve formulation. Contributions are independent of hourly availability: +# capacity value = net capacity * capacity credit +# 'dynamic' +# Contributions are available output including a capacity derate factor (e.g., forced outage rate). +# For most generators, contributions are available (derated) output in each time slice: +# capacity value = net capacity * reserve capacity derate * capacity factor +# For storage, contributions are (derated) actual output in each time slice: +# capacity value = flow out * reserve capacity derate +reserve_margin = 'dynamic' + +# ------------------------------------ +# SQLITE PERFORMANCE TUNING +# ------------------------------------ + +[sqlite] +# These settings improve database performance, especially for large-scale +# runs and myopic/MGA modes which perform many small writes. + +# journal_mode: WAL (Write-Ahead Logging) provides better concurrency and speed. +# Note: This creates sidecar files (-wal and -shm) during execution. +journal_mode = 'WAL' + +# synchronous: NORMAL reduces disk flushes while remaining safe against +# application-level crashes. +synchronous = 'NORMAL' + +# mmap_size: Memory-map the database file for faster reads (bytes). +# 8589934592 = 8GB +mmap_size = 8589934592 + +# cache_size: SQLite page cache size. Negative values specify size in KiB. +# -512000 = 500MiB +cache_size = -512000 + +# --------------------------------------------------- +# MODE OPTIONS +# options below are mode-specific and will be ignored +# if the run is not executed in that mode. +# --------------------------------------------------- +[MGA] +# see notes on these in the extensions/modeling_to_generate_alternatives folder readme.txt +cost_epsilon = 0.03 # proportional relaxation on optimal cost (ex: 0.05 = bound at 105% of original optimal cost) +iteration_limit = 55 # max iterations to perform +time_limit_hrs = 1 # max time +axis = "tech_category_activity" # use the tech activity Manager to control exploration based on categories in Tech +weighting = "hull_expansion" # use a convex hull expansion algorithm to weight exploration + +[myopic] +view_depth = 2 # number of periods seen/analyzed per iteration +step_size = 2 # number of periods to step by (must be <= view depth) +# Evolving mode: if true, the evolution_script is called between myopic iterations +# to update the database. The view depth may shorten near the end of the planning +# horizon to ensure that all iterations are performed. +# If false, the evolution_script is not used and iterations end once view depth +# reaches the end of the planning horizon, to avoid redundant decisions with no +# new information. +evolving = false +evolution_script = "temoa/extensions/myopic/evolution_updater.py" + +[morris] +perturbation = 0.10 # amount to perturb marked parameters (ex: 0.10 -> +/- 10%) +levels = 8 # number of levels in param grid (must be even number) +trajectories = 10 # number of Morris trajectories to generate/explore +seed = false # random seed for use in generation/analysis for repeatable results. false=system derived +cores = 0 # number of CPU cores to use. 0 (default) = cpu count +# Note: Problem size (in general) is (Groups + 1) * trajectories see the SALib Dox +# Groups = number of unique labels used in MM analysis columns in DB + +[SVMGA] +cost_epsilon = 0.05 +# labels from appropriate tables in database. It is recommended to only use one of the lists below and leave +# the others blank +emission_labels = ['co2', 'nox'] +capacity_labels = ['TXD', 'TXG'] +activity_labels = [] + +[monte_carlo] +# a path from the PROJECT ROOT to the settings file that contains the run data. +run_settings = 'mc_settings.csv' diff --git a/temoa/tutorial_assets/mc_settings.csv b/temoa/tutorial_assets/mc_settings.csv new file mode 100644 index 000000000..f4b6b6ebb --- /dev/null +++ b/temoa/tutorial_assets/mc_settings.csv @@ -0,0 +1,3 @@ +run,param,index,mod,value,notes +1,cost_invest,utopia|TXD|2010,a,-44.0,reduce invest cost to 1000 +2,demand,utopia|2010|RH,r,0.1,increase demand by 10% diff --git a/temoa/tutorial_assets/utopia.sql b/temoa/tutorial_assets/utopia.sql new file mode 100644 index 000000000..a58fc9910 --- /dev/null +++ b/temoa/tutorial_assets/utopia.sql @@ -0,0 +1,1478 @@ +BEGIN TRANSACTION; +CREATE TABLE capacity_credit +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + credit REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage), + CHECK (credit >= 0 AND credit <= 1) +); +CREATE TABLE capacity_factor_process +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +INSERT INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2000,0.2753,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2000,0.2753,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2000,0.2753,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2000,0.2753,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2000,0.2753,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2000,0.2753,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2010,0.2756,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2010,0.2756,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2010,0.2756,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2010,0.2756,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2010,0.2756,''); +INSERT INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2010,0.2756,''); +CREATE TABLE capacity_factor_tech +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, tech), + CHECK (factor >= 0 AND factor <= 1) +); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E01',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E01',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E01',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E01',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E01',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E01',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E21',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E21',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E21',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E21',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E21',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E21',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E31',0.275,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E31',0.275,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E31',0.275,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E31',0.275,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E31',0.275,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E31',0.275,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E51',0.17,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E51',0.17,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E51',0.17,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E51',0.17,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E51',0.17,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E51',0.17,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','day','E70',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','inter','night','E70',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','day','E70',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','winter','night','E70',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','day','E70',0.8,''); +INSERT INTO "capacity_factor_tech" VALUES('utopia','summer','night','E70',0.8,''); +CREATE TABLE capacity_to_activity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + c2a REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +INSERT INTO "capacity_to_activity" VALUES('utopia','E01',31.54,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','E21',31.54,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','E31',31.54,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','E51',31.54,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','E70',31.54,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','RHE',1.0,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','RHO',1.0,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','RL1',1.0,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','SRE',1.0,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','TXD',1.0,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','TXE',1.0,'PJ / (GW * year)',''); +INSERT INTO "capacity_to_activity" VALUES('utopia','TXG',1.0,'PJ / (GW * year)',''); +CREATE TABLE commodity +( + name TEXT + PRIMARY KEY, + flag TEXT + REFERENCES commodity_type (label), + description TEXT, + units TEXT +); +INSERT INTO "commodity" VALUES('ethos','s','# dummy commodity to supply inputs','PJ'); +INSERT INTO "commodity" VALUES('DSL','p','# diesel','PJ'); +INSERT INTO "commodity" VALUES('ELC','p','# electricity','PJ'); +INSERT INTO "commodity" VALUES('FEQ','p','# fossil equivalent','PJ'); +INSERT INTO "commodity" VALUES('GSL','p','# gasoline','PJ'); +INSERT INTO "commodity" VALUES('HCO','p','# coal','PJ'); +INSERT INTO "commodity" VALUES('HYD','p','# water','PJ'); +INSERT INTO "commodity" VALUES('OIL','p','# crude oil','PJ'); +INSERT INTO "commodity" VALUES('URN','p','# uranium','PJ'); +INSERT INTO "commodity" VALUES('co2','e','#CO2 emissions','Mt'); +INSERT INTO "commodity" VALUES('nox','e','#NOX emissions','Mt'); +INSERT INTO "commodity" VALUES('RH','d','# residential heating','PJ'); +INSERT INTO "commodity" VALUES('RL','d','# residential lighting','PJ'); +INSERT INTO "commodity" VALUES('TX','d','# transportation','PJ'); +CREATE TABLE commodity_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +INSERT INTO "commodity_type" VALUES('w','waste commodity'); +INSERT INTO "commodity_type" VALUES('wa','waste annual commodity'); +INSERT INTO "commodity_type" VALUES('wp','waste physical commodity'); +INSERT INTO "commodity_type" VALUES('a','annual commodity'); +INSERT INTO "commodity_type" VALUES('s','source commodity'); +INSERT INTO "commodity_type" VALUES('p','physical commodity'); +INSERT INTO "commodity_type" VALUES('e','emissions commodity'); +INSERT INTO "commodity_type" VALUES('d','demand commodity'); +CREATE TABLE construction_input +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage) +); +CREATE TABLE cost_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT NOT NULL + REFERENCES commodity (name), + cost REAL NOT NULL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm) +); +CREATE TABLE cost_fixed +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1960,40.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1970,40.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1980,40.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E01',1990,40.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',1970,70.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',1980,70.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',1990,70.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E01',2000,70.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',1980,100.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',2000,100.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E01',2010,100.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E21',2010,500.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E31',2010,75.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E51',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1960,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'E70',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'RHO',1970,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'RHO',2010,1.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'RL1',1980,9.46,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'RL1',1990,9.46,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'RL1',2000,9.46,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'RL1',2010,9.46,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXD',1970,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXD',2010,52.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXE',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXE',1990,90.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXE',2000,90.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXE',2000,80.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXE',2010,80.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXG',1970,48.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',1990,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2000,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); +INSERT INTO "cost_fixed" VALUES('utopia',2010,'TXG',2010,48.0,'Mdollar / (PJ^2 / GW / year)',''); +CREATE TABLE cost_invest +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +INSERT INTO "cost_invest" VALUES('utopia','E01',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E01',2000,1300.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E01',2010,1200.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E21',1990,5000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E21',2000,5000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E21',2010,5000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E31',1990,3000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E31',2000,3000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E31',2010,3000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E51',1990,900.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E51',2000,900.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E51',2010,900.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E70',1990,1000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E70',2000,1000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','E70',2010,1000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','RHE',1990,90.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','RHE',2000,90.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','RHE',2010,90.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','RHO',1990,100.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','RHO',2000,100.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','RHO',2010,100.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','SRE',1990,100.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','SRE',2000,100.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','SRE',2010,100.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXD',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXD',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXD',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXE',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXE',2000,1750.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXE',2010,1500.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXG',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXG',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); +INSERT INTO "cost_invest" VALUES('utopia','TXG',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); +CREATE TABLE cost_variable +( + region TEXT NOT NULL, + period INTEGER NOT NULL + REFERENCES time_period (period), + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + cost REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1960,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1970,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1980,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E01',1990,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',1970,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',1980,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',1990,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E01',2000,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',1980,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',1990,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',2000,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E01',2010,0.3,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E21',1990,1.5,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E21',1990,1.5,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E21',1990,1.5,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E21',2000,1.5,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E21',2000,1.5,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E21',2010,1.5,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1960,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1970,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1980,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'E70',1990,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',1970,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',1980,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',1990,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'E70',2000,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',1980,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',1990,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',2000,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'E70',2010,0.4,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',1990,'SRE',1990,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'SRE',1990,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2000,'SRE',2000,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'SRE',1990,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'SRE',2000,10.0,'Mdollar / (PJ)',''); +INSERT INTO "cost_variable" VALUES('utopia',2010,'SRE',2010,10.0,'Mdollar / (PJ)',''); +CREATE TABLE demand +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + commodity TEXT + REFERENCES commodity (name), + demand REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, commodity) +); +INSERT INTO "demand" VALUES('utopia',1990,'RH',25.2,'PJ',''); +INSERT INTO "demand" VALUES('utopia',2000,'RH',37.8,'PJ',''); +INSERT INTO "demand" VALUES('utopia',2010,'RH',56.7,'PJ',''); +INSERT INTO "demand" VALUES('utopia',1990,'RL',5.6,'PJ',''); +INSERT INTO "demand" VALUES('utopia',2000,'RL',8.4,'PJ',''); +INSERT INTO "demand" VALUES('utopia',2010,'RL',12.6,'PJ',''); +INSERT INTO "demand" VALUES('utopia',1990,'TX',5.2,'PJ',''); +INSERT INTO "demand" VALUES('utopia',2000,'TX',7.8,'PJ',''); +INSERT INTO "demand" VALUES('utopia',2010,'TX',11.69,'PJ',''); +CREATE TABLE demand_specific_distribution +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + demand_name TEXT + REFERENCES commodity (name), + dsd REAL, + notes TEXT, + PRIMARY KEY (region, period, season, tod, demand_name), + CHECK (dsd >= 0 AND dsd <= 1) +); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RH',0.12,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RH',0.06,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RH',0.5467,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RH',0.2733,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RL',0.15,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RL',0.05,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','day','RL',0.15,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','night','RL',0.05,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RL',0.5,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RL',0.1,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RH',0.12,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RH',0.06,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RH',0.5467,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RH',0.2733,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RL',0.15,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RL',0.05,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','day','RL',0.15,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','night','RL',0.05,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RL',0.5,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RL',0.1,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RH',0.12,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RH',0.06,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RH',0.5467,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RH',0.2733,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RL',0.15,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RL',0.05,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','day','RL',0.15,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','night','RL',0.05,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RL',0.5,''); +INSERT INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RL',0.1,''); +CREATE TABLE efficiency +( + region TEXT, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +INSERT INTO "efficiency" VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,'PJ / (PJ)',''); +INSERT INTO "efficiency" VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,'PJ / (PJ)',''); +INSERT INTO "efficiency" VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,'PJ / (PJ)',''); +INSERT INTO "efficiency" VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,'PJ / (PJ)',''); +INSERT INTO "efficiency" VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,'PJ / (PJ)',''); +INSERT INTO "efficiency" VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,'PJ / (PJ)',''); +INSERT INTO "efficiency" VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,'PJ / (PJ)',''); +INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1960,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1970,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HCO','E01',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HCO','E01',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HCO','E01',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','FEQ','E21',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','FEQ','E21',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','FEQ','E21',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','URN','E21',1990,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +INSERT INTO "efficiency" VALUES('utopia','URN','E21',2000,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +INSERT INTO "efficiency" VALUES('utopia','URN','E21',2010,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +INSERT INTO "efficiency" VALUES('utopia','HYD','E31',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HYD','E31',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HYD','E31',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','HYD','E31',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1960,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1970,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1980,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +INSERT INTO "efficiency" VALUES('utopia','DSL','E70',1990,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +INSERT INTO "efficiency" VALUES('utopia','DSL','E70',2000,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +INSERT INTO "efficiency" VALUES('utopia','DSL','E70',2010,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +INSERT INTO "efficiency" VALUES('utopia','ELC','E51',1980,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +INSERT INTO "efficiency" VALUES('utopia','ELC','E51',1990,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +INSERT INTO "efficiency" VALUES('utopia','ELC','E51',2000,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +INSERT INTO "efficiency" VALUES('utopia','ELC','E51',2010,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +INSERT INTO "efficiency" VALUES('utopia','ELC','RHE',1990,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','RHE',2000,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','RHE',2010,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',1970,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',1980,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',1990,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',2000,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','RHO',2010,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',1980,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',1990,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',2000,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','RL1',2010,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','DSL','TXD',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','TXE',1990,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','TXE',2000,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','ELC','TXE',2010,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +INSERT INTO "efficiency" VALUES('utopia','GSL','TXG',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +CREATE TABLE efficiency_variable +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency REAL, + notes TEXT, + PRIMARY KEY (region, season, tod, input_comm, tech, vintage, output_comm), + CHECK (efficiency > 0) +); +CREATE TABLE emission_activity +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) +); +INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.075,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.075,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',8.9e-02,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.075,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'Mt / (PJ)',''); +INSERT INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'Mt / (PJ)',''); +CREATE TABLE emission_embodied +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE emission_end_of_life +( + region TEXT, + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, emis_comm, tech, vintage) +); +CREATE TABLE end_of_life_output +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage, output_comm) +); +CREATE TABLE existing_capacity +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +INSERT INTO "existing_capacity" VALUES('utopia','E01',1960,0.175,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','E01',1970,0.175,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','E01',1980,0.15,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','E31',1980,0.1,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','E51',1980,0.5,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','E70',1960,0.05,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','E70',1970,0.05,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','E70',1980,0.2,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','RHO',1970,12.5,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','RHO',1980,12.5,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','RL1',1980,5.6,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','TXD',1970,0.4,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','TXD',1980,0.2,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','TXG',1970,3.1,'GW',''); +INSERT INTO "existing_capacity" VALUES('utopia','TXG',1980,1.5,'GW',''); +CREATE TABLE lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +INSERT INTO "lifetime_process" VALUES('utopia','RL1',1980,20.0,'year','#forexistingcap'); +INSERT INTO "lifetime_process" VALUES('utopia','TXD',1970,30.0,'year','#forexistingcap'); +INSERT INTO "lifetime_process" VALUES('utopia','TXD',1980,30.0,'year','#forexistingcap'); +INSERT INTO "lifetime_process" VALUES('utopia','TXG',1970,30.0,'year','#forexistingcap'); +INSERT INTO "lifetime_process" VALUES('utopia','TXG',1980,30.0,'year','#forexistingcap'); +CREATE TABLE lifetime_survival_curve +( + region TEXT NOT NULL, + period INTEGER NOT NULL, + tech TEXT NOT NULL + REFERENCES technology (tech), + vintage INTEGER NOT NULL + REFERENCES time_period (period), + fraction REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, vintage) +); +CREATE TABLE lifetime_tech +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech) +); +INSERT INTO "lifetime_tech" VALUES('utopia','E01',40.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','E21',40.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','E31',100.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','E51',100.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','E70',40.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','RHE',30.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','RHO',30.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','RL1',10.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','SRE',50.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','TXD',15.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','TXE',15.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','TXG',15.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','IMPDSL1',1000.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','IMPGSL1',1000.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','IMPHCO1',1000.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','IMPOIL1',1000.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','IMPURN1',1000.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','IMPHYD',1000.0,'year',''); +INSERT INTO "lifetime_tech" VALUES('utopia','IMPFEQ',1000.0,'year',''); +CREATE TABLE limit_activity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + activity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +CREATE TABLE limit_activity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE limit_annual_capacity_factor +( + region TEXT, + tech_or_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY (region, tech_or_group, vintage, output_comm, operator), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE limit_capacity +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + capacity REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, tech_or_group, operator) +); +INSERT INTO "limit_capacity" VALUES('utopia',1990,'E31','ge',0.13,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',2000,'E31','ge',0.13,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',2010,'E31','ge',0.13,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',1990,'SRE','ge',0.1,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',1990,'E31','le',0.13,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',2000,'E31','le',0.17,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',2010,'E31','le',2.1e-01,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',1990,'RHE','le',0.0,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',1990,'TXD','le',0.6,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',2000,'TXD','le',1.76,'GW',''); +INSERT INTO "limit_capacity" VALUES('utopia',2010,'TXD','le',4.76,'GW',''); +CREATE TABLE limit_capacity_share +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + sub_group TEXT, + super_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, period, sub_group, super_group, operator) +); +CREATE TABLE limit_degrowth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE limit_degrowth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE limit_degrowth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE limit_emission +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + value REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, period, emis_comm, operator) +); +CREATE TABLE limit_growth_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE limit_growth_new_capacity +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE limit_growth_new_capacity_delta +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + rate REAL NOT NULL DEFAULT 0, + seed REAL NOT NULL DEFAULT 0, + seed_units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE limit_new_capacity +( + region TEXT, + tech_or_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + new_cap REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, vintage, operator) +); +CREATE TABLE limit_new_capacity_share +( + region TEXT, + sub_group TEXT, + super_group TEXT, + vintage INTEGER + REFERENCES time_period (period), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + share REAL, + notes TEXT, + PRIMARY KEY (region, sub_group, super_group, vintage, operator) +); +CREATE TABLE limit_resource +( + region TEXT, + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + cum_act REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech_or_group, operator) +); +CREATE TABLE limit_seasonal_capacity_factor +( + region TEXT + REFERENCES region (region), + season TEXT + REFERENCES time_season (season), + tech_or_group TEXT, + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + factor REAL, + notes TEXT, + PRIMARY KEY(region, season, tech_or_group, operator) +); +CREATE TABLE limit_storage_level_fraction +( + region TEXT, + season TEXT, + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + fraction REAL, + notes TEXT, + CHECK (fraction >= 0 AND fraction <= 1), + PRIMARY KEY(region, season, tod, tech, operator) +); +CREATE TABLE limit_tech_input_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE limit_tech_input_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, input_comm, tech, operator) +); +CREATE TABLE limit_tech_output_split +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +INSERT INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','DSL','ge',0.7,''); +INSERT INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','DSL','ge',0.7,''); +INSERT INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','DSL','ge',0.7,''); +INSERT INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','GSL','ge',0.3,''); +INSERT INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','GSL','ge',0.3,''); +INSERT INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','GSL','ge',0.3,''); +CREATE TABLE limit_tech_output_split_annual +( + region TEXT, + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + output_comm TEXT + REFERENCES commodity (name), + operator TEXT NOT NULL DEFAULT "le" + REFERENCES operator (operator), + proportion REAL, + notes TEXT, + PRIMARY KEY (region, period, tech, output_comm, operator) +); +CREATE TABLE linked_tech +( + primary_region TEXT, + primary_tech TEXT + REFERENCES technology (tech), + emis_comm TEXT + REFERENCES commodity (name), + driven_tech TEXT + REFERENCES technology (tech), + notes TEXT, + PRIMARY KEY (primary_region, primary_tech, emis_comm) +); +CREATE TABLE loan_lifetime_process +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + lifetime REAL, + units TEXT, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE loan_rate +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech, vintage) +); +CREATE TABLE metadata +( + element TEXT, + value INT, + notes TEXT, + PRIMARY KEY (element) +); +INSERT INTO "metadata" VALUES('DB_MAJOR',4,''); +INSERT INTO "metadata" VALUES('DB_MINOR',0,''); +CREATE TABLE metadata_real +( + element TEXT, + value REAL, + notes TEXT, + + PRIMARY KEY (element) +); +INSERT INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +INSERT INTO "metadata_real" VALUES('global_discount_rate',0.05,''); +CREATE TABLE myopic_efficiency +( + base_year integer, + region text, + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + efficiency real, + lifetime integer, + PRIMARY KEY (region, input_comm, tech, vintage, output_comm) +); +CREATE TABLE operator +( + operator TEXT PRIMARY KEY, + notes TEXT +); +INSERT INTO "operator" VALUES('e','equal to'); +INSERT INTO "operator" VALUES('le','less than or equal to'); +INSERT INTO "operator" VALUES('ge','greater than or equal to'); +CREATE TABLE output_built_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + PRIMARY KEY (region, scenario, tech, vintage) +); +CREATE TABLE output_cost +( + scenario TEXT, + region TEXT, + sector TEXT REFERENCES sector_label (sector), + period INTEGER REFERENCES time_period (period), + tech TEXT REFERENCES technology (tech), + vintage INTEGER REFERENCES time_period (period), + d_invest REAL, + d_fixed REAL, + d_var REAL, + d_emiss REAL, + invest REAL, + fixed REAL, + var REAL, + emiss REAL, + units TEXT, + PRIMARY KEY (scenario, region, period, tech, vintage), + FOREIGN KEY (vintage) REFERENCES time_period (period), + FOREIGN KEY (tech) REFERENCES technology (tech) +); +CREATE TABLE output_curtailment +( + scenario TEXT, + region TEXT, + sector TEXT, + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + curtailment REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE output_dual_variable +( + scenario TEXT, + constraint_name TEXT, + dual REAL, + PRIMARY KEY (constraint_name, scenario) +); +CREATE TABLE output_emission +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + emis_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + emission REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) +); +CREATE TABLE output_flow_in +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE output_flow_out +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT + REFERENCES time_season (season), + tod TEXT + REFERENCES time_of_day (tod), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) +); +CREATE TABLE output_flow_out_summary +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + input_comm TEXT + REFERENCES commodity (name), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + output_comm TEXT + REFERENCES commodity (name), + flow REAL, + PRIMARY KEY (scenario, region, period, input_comm, tech, vintage, output_comm) +); +CREATE TABLE output_net_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + capacity REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE output_objective +( + scenario TEXT, + objective_name TEXT, + total_system_cost REAL +); +CREATE TABLE output_retired_capacity +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + cap_eol REAL, + cap_early REAL, + units TEXT, + PRIMARY KEY (region, scenario, period, tech, vintage) +); +CREATE TABLE output_storage_level +( + scenario TEXT, + region TEXT, + sector TEXT + REFERENCES sector_label (sector), + period INTEGER + REFERENCES time_period (period), + season TEXT, + tod TEXT + REFERENCES time_of_day (tod), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER + REFERENCES time_period (period), + level REAL, + units TEXT, + PRIMARY KEY (scenario, region, period, season, tod, tech, vintage) +); +CREATE TABLE planning_reserve_margin +( + region TEXT + PRIMARY KEY + REFERENCES region (region), + margin REAL, + notes TEXT +); +CREATE TABLE ramp_down_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE ramp_up_hourly +( + region TEXT, + tech TEXT + REFERENCES technology (tech), + rate REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE region +( + region TEXT + PRIMARY KEY, + notes TEXT +); +INSERT INTO "region" VALUES('utopia',NULL); +CREATE TABLE reserve_capacity_derate +( + region TEXT, + season TEXT + REFERENCES time_season (season), + tech TEXT + REFERENCES technology (tech), + vintage INTEGER, + factor REAL, + notes TEXT, + PRIMARY KEY (region, season, tech, vintage), + CHECK (factor >= 0 AND factor <= 1) +); +CREATE TABLE rps_requirement +( + region TEXT NOT NULL + REFERENCES region (region), + period INTEGER NOT NULL + REFERENCES time_period (period), + tech_group TEXT NOT NULL + REFERENCES tech_group (group_name), + requirement REAL NOT NULL, + notes TEXT +); +CREATE TABLE sector_label +( + sector TEXT PRIMARY KEY, + notes TEXT +); +INSERT INTO "sector_label" VALUES('supply',NULL); +INSERT INTO "sector_label" VALUES('electric',NULL); +INSERT INTO "sector_label" VALUES('transport',NULL); +INSERT INTO "sector_label" VALUES('commercial',NULL); +INSERT INTO "sector_label" VALUES('residential',NULL); +INSERT INTO "sector_label" VALUES('industrial',NULL); +CREATE TABLE storage_duration +( + region TEXT, + tech TEXT, + duration REAL, + notes TEXT, + PRIMARY KEY (region, tech) +); +CREATE TABLE tech_group +( + group_name TEXT + PRIMARY KEY, + notes TEXT +); +CREATE TABLE tech_group_member +( + group_name TEXT + REFERENCES tech_group (group_name), + tech TEXT + REFERENCES technology (tech), + PRIMARY KEY (group_name, tech) +); +CREATE TABLE technology +( + tech TEXT NOT NULL PRIMARY KEY, + flag TEXT NOT NULL, + sector TEXT, + category TEXT, + sub_category TEXT, + unlim_cap INTEGER NOT NULL DEFAULT 0, + annual INTEGER NOT NULL DEFAULT 0, + reserve INTEGER NOT NULL DEFAULT 0, + curtail INTEGER NOT NULL DEFAULT 0, + retire INTEGER NOT NULL DEFAULT 0, + flex INTEGER NOT NULL DEFAULT 0, + exchange INTEGER NOT NULL DEFAULT 0, + seas_stor INTEGER NOT NULL DEFAULT 0, + description TEXT, + FOREIGN KEY (flag) REFERENCES technology_type (label) +); +INSERT INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); +INSERT INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); +INSERT INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); +INSERT INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); +INSERT INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); +INSERT INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); +INSERT INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); +INSERT INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); +INSERT INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); +INSERT INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); +INSERT INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); +INSERT INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); +INSERT INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); +INSERT INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); +INSERT INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); +INSERT INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); +INSERT INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); +INSERT INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); +INSERT INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); +CREATE TABLE technology_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +INSERT INTO "technology_type" VALUES('p','production technology'); +INSERT INTO "technology_type" VALUES('pb','baseload production technology'); +INSERT INTO "technology_type" VALUES('ps','storage production technology'); +CREATE TABLE time_of_day +( + sequence INTEGER UNIQUE, + tod TEXT + PRIMARY KEY, + hours REAL NOT NULL DEFAULT 1, + notes TEXT, + CHECK (hours > 0) +); +INSERT INTO "time_of_day" (sequence, tod, hours) VALUES(1,'day',16); +INSERT INTO "time_of_day" (sequence, tod, hours) VALUES(2,'night',8); +CREATE TABLE time_period +( + sequence INTEGER UNIQUE, + period INTEGER + PRIMARY KEY, + flag TEXT + REFERENCES time_period_type (label) +); +INSERT INTO "time_period" VALUES(1,1960,'e'); +INSERT INTO "time_period" VALUES(2,1970,'e'); +INSERT INTO "time_period" VALUES(3,1980,'e'); +INSERT INTO "time_period" VALUES(4,1990,'f'); +INSERT INTO "time_period" VALUES(5,2000,'f'); +INSERT INTO "time_period" VALUES(6,2010,'f'); +INSERT INTO "time_period" VALUES(7,2020,'f'); +CREATE TABLE time_period_type +( + label TEXT + PRIMARY KEY, + description TEXT +); +INSERT INTO "time_period_type" VALUES('e','existing vintages'); +INSERT INTO "time_period_type" VALUES('f','future'); +CREATE TABLE time_season +( + sequence INTEGER UNIQUE, + season TEXT, + segment_fraction REAL NOT NULL, + notes TEXT, + PRIMARY KEY (season), + CHECK (segment_fraction >= 0 AND segment_fraction <= 1) +); +INSERT INTO "time_season" VALUES(0,'inter',0.25,NULL); +INSERT INTO "time_season" VALUES(1,'summer',0.25,NULL); +INSERT INTO "time_season" VALUES(2,'winter',0.5,NULL); +CREATE TABLE time_season_sequential +( + sequence INTEGER UNIQUE, + seas_seq TEXT, + season TEXT REFERENCES time_season(season), + segment_fraction REAL NOT NULL, + notes TEXT, + PRIMARY KEY (seas_seq), + CHECK (segment_fraction >= 0 AND segment_fraction <= 1) +); +CREATE INDEX region_tech_vintage ON myopic_efficiency (region, tech, vintage); +COMMIT; diff --git a/temoa/types/__init__.py b/temoa/types/__init__.py new file mode 100644 index 000000000..022a245d9 --- /dev/null +++ b/temoa/types/__init__.py @@ -0,0 +1,161 @@ +# Types module for TEMOA + + +# Define public API for this module +# ruff: noqa: RUF022 +__all__ = [ + # Core types + 'Commodity', + 'CommoditySet', + 'Period', + 'Region', + 'RegionSet', + 'Season', + 'SparseIndex', + 'Technology', + 'TechSet', + 'TimeOfDay', + 'Vintage', + 'Sector', + # Dictionary types + 'ActiveRegionsForTechDict', + 'BaseloadVintagesDict', + 'CapacityConsumptionTechsDict', + 'CapacityFactorProcessDict', + 'CommodityStreamProcessDict', + 'CurtailmentVintagesDict', + 'EfficiencyVariableDict', + 'ExportRegionsDict', + 'ImportRegionsDict', + 'InputSplitAnnualVintagesDict', + 'InputSplitVintagesDict', + 'OutputSplitAnnualVintagesDict', + 'OutputSplitVintagesDict', + 'ProcessInputsByOutputDict', + 'ProcessInputsDict', + 'ProcessLoansDict', + 'ProcessOutputsByInputDict', + 'ProcessOutputsDict', + 'ProcessPeriodsDict', + 'ProcessReservePeriodsDict', + 'ProcessTechsDict', + 'ProcessVintagesDict', + 'RampDownVintagesDict', + 'RampUpVintagesDict', + 'RetirementPeriodsDict', + 'RetirementProductionProcessesDict', + 'SeasonalStorageDict', + 'SequentialToSeasonDict', + 'StorageVintagesDict', + 'SurvivalCurvePeriodsDict', + 'SurvivalCurveProcessDict', + 'TimeNextDict', + 'TimeNextSequentialDict', + # Index types + 'RegionPeriodSeasonTimeInputTechVintageOutput', + 'RegionPeriodTechVintage', + # Set types + 'CommodityBalancedSet', + 'ActiveActivitySet', + 'ActiveCapacityAvailableSet', + 'ActiveCapacityAvailableVintageSet', + 'ActiveCurtailmentSet', + 'ActiveFlexAnnualSet', + 'ActiveFlexSet', + 'ActiveFlowAnnualSet', + 'ActiveFlowInStorageSet', + 'ActiveFlowSet', + 'GroupRegionActiveFlowSet', + 'NewCapacitySet', + 'SeasonalStorageLevelIndicesSet', + 'SingletonDemandsSet', + 'StorageLevelIndicesSet', + # Type aliases + 'ExprLike', +] + +# Core type aliases for commonly used dimensions +# ruff: noqa: RUF022 +from .core_types import ( + Commodity, + CommoditySet, + Period, + Region, + RegionSet, + Season, + Sector, + SparseIndex, + Technology, + TechSet, + TimeOfDay, + Vintage, +) + +# Dictionary types used by the model +# ruff: noqa: RUF022 +from .dict_types import ( + ActiveRegionsForTechDict, + BaseloadVintagesDict, + CapacityConsumptionTechsDict, + CapacityFactorProcessDict, + CommodityStreamProcessDict, + CurtailmentVintagesDict, + EfficiencyVariableDict, + ExportRegionsDict, + ImportRegionsDict, + InputSplitAnnualVintagesDict, + InputSplitVintagesDict, + OutputSplitAnnualVintagesDict, + OutputSplitVintagesDict, + ProcessInputsByOutputDict, + ProcessInputsDict, + ProcessLoansDict, + ProcessOutputsByInputDict, + ProcessOutputsDict, + ProcessPeriodsDict, + ProcessReservePeriodsDict, + ProcessTechsDict, + ProcessVintagesDict, + RampDownVintagesDict, + RampUpVintagesDict, + RetirementPeriodsDict, + RetirementProductionProcessesDict, + SeasonalStorageDict, + SequentialToSeasonDict, + StorageVintagesDict, + SurvivalCurvePeriodsDict, + SurvivalCurveProcessDict, + TimeNextDict, + TimeNextSequentialDict, +) + +# Index tuple types +# ruff: noqa: RUF022 +from .index_types import ( + RegionPeriodSeasonTimeInputTechVintageOutput, + RegionPeriodTechVintage, +) + +# Set types for sparse indexing +# ruff: noqa: RUF022 +from .set_types import ( + ActiveActivitySet, + ActiveCapacityAvailableSet, + ActiveCapacityAvailableVintageSet, + ActiveCurtailmentSet, + ActiveFlexAnnualSet, + ActiveFlexSet, + ActiveFlowAnnualSet, + ActiveFlowInStorageSet, + ActiveFlowSet, + CommodityBalancedSet, + GroupRegionActiveFlowSet, + NewCapacitySet, + SeasonalStorageLevelIndicesSet, + SingletonDemandsSet, + StorageLevelIndicesSet, +) + +# Type alias for expressions that can be returned from reserve margin functions +# This covers Pyomo expressions, boolean expressions, and Constraint.Skip +ExprLike = float | bool | object # covers Pyomo expressions and Constraint.Skip diff --git a/temoa/types/core_types.py b/temoa/types/core_types.py new file mode 100644 index 000000000..bfb91037f --- /dev/null +++ b/temoa/types/core_types.py @@ -0,0 +1,142 @@ +""" +Core type aliases for Temoa energy model. + +This module contains basic type aliases for commonly used dimensions +and fundamental data structures. +""" + +from collections.abc import Callable +from dataclasses import dataclass, field +from typing import Any, NewType + +# Core type aliases for commonly used dimensions +Region = NewType('Region', str) +Period = NewType('Period', int) +Technology = NewType('Technology', str) +Sector = NewType('Sector', str) +Vintage = NewType('Vintage', int) +Season = NewType('Season', str) +TimeOfDay = NewType('TimeOfDay', str) +Commodity = NewType('Commodity', str) +Process = NewType('Process', str) + +# Type aliases for common data structures +SparseIndex = ( + tuple[Region, Period] + | tuple[Region, Period, Technology] + | tuple[Region, Period, Technology, Vintage] + | tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] + | tuple[Any, ...] +) + +# Database-related types +DatabaseConnection = Any # sqlite3.Connection or similar +DatabaseCursor = Any # sqlite3.Cursor or similar +QueryResult = list[tuple[Any, ...]] + +# Model parameter types +ParameterValue = int | float | str | bool +Parameterdict = dict[SparseIndex, ParameterValue] + +# Basic set types +StringSet = set[str] +TechSet = set[Technology] +CommoditySet = set[Commodity] +RegionSet = set[Region] +PeriodSet = set[Period] +VintageSet = set[Vintage] + +# Pyomo domain types +PyomoDomain = Any # Pyomo domain objects (NonNegativeReals, Integers, etc.) +PyomoIndexSet = Any # Pyomo set objects used for indexing + +# Configuration types +ScenarioName = str +Configdict = dict[str, Any] + +# Enhanced Configuration Types +SolverName = str +"""Type alias for solver names (e.g., 'gurobi', 'cplex', 'glpk', 'cbc').""" + + +@dataclass(slots=True) +class ScenarioConfig: + """ + Structured configuration for scenario-specific settings. + + This type represents configuration options that are specific to a particular + scenario run, including solver settings, output options, and analysis modes. + """ + + scenario: str + """Name of the scenario.""" + + input_database: str + """Path to the input database file.""" + + output_database: str + """Path to the output database file.""" + + solver_name: SolverName + """Name of the solver to use.""" + + save_excel: bool + """Whether to save results to Excel format.""" + + save_duals: bool + """Whether to save dual variables.""" + + save_storage_levels: bool + """Whether to save storage level information.""" + + +@dataclass(slots=True) +class SolverConfig: + """ + Configuration for solver-specific settings. + + This type represents solver options and parameters that control + the optimization process. + """ + + solver_name: SolverName + """Name of the solver.""" + + options: dict[str, Any] = field(default_factory=dict) + """Solver-specific options dictionary.""" + + time_limit: float | None = None + """Maximum time limit for solving (in seconds).""" + + mip_gap: float | None = None + """MIP gap tolerance for mixed-integer problems.""" + + +@dataclass(slots=True) +class OutputConfig: + """ + Configuration for output format and content settings. + + This type represents options that control what results are saved + and in what format. + """ + + save_excel: bool + """Whether to save results to Excel format.""" + + save_duals: bool + """Whether to save dual variables.""" + + save_storage_levels: bool + """Whether to save storage level information.""" + + save_lp_file: bool + """Whether to save the LP file.""" + + output_path: str + """Path where output files should be saved.""" + + +# Constraint rule types +ConstraintRule = Callable[..., object] +IndexsetRule = Callable[..., set[object]] diff --git a/temoa/types/database_types.py b/temoa/types/database_types.py new file mode 100644 index 000000000..3374ab225 --- /dev/null +++ b/temoa/types/database_types.py @@ -0,0 +1,166 @@ +""" +Database schema type definitions for Temoa energy model. + +This module provides type definitions for database tables, columns, and schema +versioning used throughout the Temoa codebase. +""" + +from collections.abc import Mapping, Sequence +from typing import Protocol + +# Type aliases for database schema elements +TableName = str +"""Type alias for database table names.""" + +ColumnName = str +"""Type alias for database column names.""" + +SchemaVersion = tuple[int, int] +"""Type alias for schema version (major, minor).""" + + +class DatabaseSchema(Protocol): + """ + Protocol defining the interface for database schema objects. + + This protocol describes the expected structure of database schema + information, allowing for type-safe access to schema metadata. + """ + + version: SchemaVersion + """Schema version as (major, minor) tuple.""" + + tables: Mapping[TableName, Sequence[ColumnName]] + """Mapping of table names to their column names.""" + + def get_table_columns(self, table: TableName) -> Sequence[ColumnName]: + """ + Get the list of columns for a given table. + + Args: + table: Name of the table + + Returns: + Sequence of column names for the table + """ + ... + + def validate_table(self, table: TableName) -> bool: + """ + Validate that a table exists in the schema. + + Args: + table: Name of the table to validate + + Returns: + True if table exists, False otherwise + """ + ... + + +# Common table row types for major Temoa tables +class TechnologyRow(Protocol): + """Protocol for Technology table rows.""" + + region: str + tech: str + flag: str + sector: str + tech_desc: str | None + + +class CommodityRow(Protocol): + """Protocol for Commodity table rows.""" + + region: str + comm_name: str + flag: str + comm_desc: str | None + + +class TimePeriodsRow(Protocol): + """Protocol for TimePeriods table rows.""" + + t_periods: int + flag: str + + +class EfficiencyRow(Protocol): + """Protocol for Efficiency table rows.""" + + region: str + input_comm: str + tech: str + vintage: int + output_comm: str + efficiency: float + eff_notes: str | None + + +class CapacityFactorRow(Protocol): + """Protocol for CapacityFactor table rows.""" + + region: str + season: str + time_of_day: str + tech: str + vintage: int + cf_process: float + cf_process_notes: str | None + + +class DemandRow(Protocol): + """Protocol for Demand table rows.""" + + region: str + periods: int + demand_comm: str + demand: float + demand_units: str + demand_notes: str | None + + +class EmissionActivityRow(Protocol): + """Protocol for EmissionActivity table rows.""" + + region: str + emis_comm: str + input_comm: str + tech: str + vintage: int + output_comm: str + emis_act: float + emis_act_units: str + emis_act_notes: str | None + + +# Query result types +TechnologyQueryResult = Sequence[Mapping[str, object]] +"""Type alias for technology query results.""" +CommodityQueryResult = Sequence[Mapping[str, object]] +"""Type alias for commodity query results.""" +EfficiencyQueryResult = Sequence[Mapping[str, object]] +"""Type alias for efficiency query results.""" +GenericQueryResult = Sequence[Mapping[str, object]] +"""Type alias for generic query results.""" + + +# Export all types +# ruff: noqa: RUF022 +__all__ = [ + 'TableName', + 'ColumnName', + 'SchemaVersion', + 'DatabaseSchema', + 'TechnologyRow', + 'CommodityRow', + 'TimePeriodsRow', + 'EfficiencyRow', + 'CapacityFactorRow', + 'DemandRow', + 'EmissionActivityRow', + 'TechnologyQueryResult', + 'CommodityQueryResult', + 'EfficiencyQueryResult', + 'GenericQueryResult', +] diff --git a/temoa/types/dict_types.py b/temoa/types/dict_types.py new file mode 100644 index 000000000..ba288ea98 --- /dev/null +++ b/temoa/types/dict_types.py @@ -0,0 +1,72 @@ +""" +Dictionary types for Temoa energy model. + +This module contains dictionary type definitions used throughout +the Temoa model for various data structures and mappings. +""" + +from .core_types import Commodity, Period, Region, Season, Technology, TimeOfDay, Vintage + +# Process-related dictionary types +ProcessInputsDict = dict[tuple[Region, Period, Technology, Vintage], set[Commodity]] +ProcessOutputsDict = dict[tuple[Region, Period, Technology, Vintage], set[Commodity]] +ProcessLoansDict = dict[tuple[Region, Technology, Vintage], float] +ProcessInputsByOutputDict = dict[ + tuple[Region, Period, Technology, Vintage, Commodity], set[Commodity] +] +ProcessOutputsByInputDict = dict[ + tuple[Region, Period, Technology, Vintage, Commodity], set[Commodity] +] +ProcessTechsDict = dict[tuple[Region, Period, Commodity], set[Technology]] +ProcessReservePeriodsDict = dict[tuple[Region, Period], set[tuple[Technology, Vintage]]] +ProcessPeriodsDict = dict[tuple[Region, Technology, Vintage], set[Period]] +RetirementPeriodsDict = dict[tuple[Region, Technology, Vintage], set[Period]] +ProcessVintagesDict = dict[tuple[Region, Period, Technology], set[Vintage]] +SurvivalCurvePeriodsDict = dict[tuple[Region, Technology, Vintage], set[Period]] +CapacityConsumptionTechsDict = dict[tuple[Region, Period, Commodity], set[Technology]] +RetirementProductionProcessesDict = dict[ + tuple[Region, Period, Commodity], set[tuple[Technology, Vintage]] +] + + +# Commodity flow dictionary types +CommodityStreamProcessDict = dict[tuple[Region, Period, Commodity], set[tuple[Technology, Vintage]]] + + +# Technology classification dictionary types +BaseloadVintagesDict = dict[tuple[Region, Period, Technology], set[Vintage]] +CurtailmentVintagesDict = dict[tuple[Region, Period, Technology], set[Vintage]] +StorageVintagesDict = dict[tuple[Region, Period, Technology], set[Vintage]] +RampUpVintagesDict = dict[tuple[Region, Period, Technology], set[Vintage]] +RampDownVintagesDict = dict[tuple[Region, Period, Technology], set[Vintage]] +InputSplitVintagesDict = dict[tuple[Region, Period, Commodity, Technology, str], set[Vintage]] +InputSplitAnnualVintagesDict = dict[tuple[Region, Period, Commodity, Technology, str], set[Vintage]] +OutputSplitVintagesDict = dict[tuple[Region, Period, Technology, Commodity, str], set[Vintage]] +OutputSplitAnnualVintagesDict = dict[ + tuple[Region, Period, Technology, Commodity, str], set[Vintage] +] + + +# Time sequencing dictionary types +TimeNextDict = dict[tuple[Season, TimeOfDay], tuple[Season, TimeOfDay]] +TimeNextSequentialDict = dict[Season, Season] +SequentialToSeasonDict = dict[Season, Season] + + +# Geography/exchange dictionary types +ExportRegionsDict = dict[ + tuple[Region, Period, Commodity], set[tuple[Region, Technology, Vintage, Commodity]] +] +ImportRegionsDict = dict[ + tuple[Region, Period, Commodity], set[tuple[Region, Technology, Vintage, Commodity]] +] +ActiveRegionsForTechDict = dict[tuple[Period, Technology], set[Region]] + + +# Switching/boolean flag dictionary types +EfficiencyVariableDict = dict[ + tuple[Region, Commodity, Technology, Vintage, Commodity], bool +] +CapacityFactorProcessDict = dict[tuple[Region, Technology, Vintage], bool] +SeasonalStorageDict = dict[Technology, bool] +SurvivalCurveProcessDict = dict[tuple[Region, Technology, Vintage], bool] diff --git a/temoa/types/index_types.py b/temoa/types/index_types.py new file mode 100644 index 000000000..74fb0666e --- /dev/null +++ b/temoa/types/index_types.py @@ -0,0 +1,35 @@ +""" +Index tuple types for Temoa energy model. + +This module contains tuple type definitions used for indexing +various data structures in the Temoa model. +""" + +from .core_types import Commodity, Period, Region, Season, Technology, TimeOfDay, Vintage + +# Basic index tuples +RegionPeriod = tuple[Region, Period] +RegionPeriodTech = tuple[Region, Period, Technology] +RegionPeriodTechVintage = tuple[Region, Period, Technology, Vintage] +RegionPeriodSeasonTimeInputTechVintageOutput = tuple[ + Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity +] + +# Extended index types +RegionTech = tuple[Region, Technology] +RegionTechVintage = tuple[Region, Technology, Vintage] +RegionPeriodCommodity = tuple[Region, Period, Commodity] +PeriodSeasonTimeOfDay = tuple[Period, Season, TimeOfDay] +RegionPeriodSeasonTimeOfDay = tuple[Region, Period, Season, TimeOfDay] +RegionPeriodSeasonTimeOfDayTech = tuple[Region, Period, Season, TimeOfDay, Technology] +RegionPeriodSeasonTimeOfDayTechVintage = tuple[ + Region, Period, Season, TimeOfDay, Technology, Vintage +] +RegionPeriodSeasonTimeOfDayCommodity = tuple[Region, Period, Season, TimeOfDay, Commodity] +RegionPeriodCommodityInputTechVintageOutput = tuple[ + Region, Period, Commodity, Technology, Vintage, Commodity +] +RegionPeriodSeasonTimeOfDayCommodityTechVintageOutput = tuple[ + Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity +] +PeriodSeasonSequential = tuple[Period, Season] diff --git a/temoa/types/model_types.py b/temoa/types/model_types.py new file mode 100644 index 000000000..108a70f62 --- /dev/null +++ b/temoa/types/model_types.py @@ -0,0 +1,343 @@ +""" +Type definitions for TemoaModel and related core classes. + +This module provides comprehensive type annotations for the core Temoa model, +including the main TemoaModel class and its associated data structures. +""" + +from enum import Enum, unique +from typing import ( + TYPE_CHECKING, + Any, + NamedTuple, + Protocol, + runtime_checkable, +) + +from . import ( + Commodity, + CommoditySet, + Period, + Region, + RegionSet, + Season, + SparseIndex, + Technology, + TechSet, + TimeOfDay, + Vintage, +) + +if TYPE_CHECKING: + from pyomo.core import ( + AbstractModel, + Constraint, + Param, + Set, + Var, + ) +else: + # Runtime fallback for non-TYPE_CHECKING contexts + Set = Any # AbstractModel.set + Param = Any # AbstractModel.Param + Var = Any # AbstractModel.Var + Constraint = Any # AbstractModel.Constraint + +# Type aliases for model data structures +ProcessInputs = dict[tuple[Region, Period, Commodity, Technology, Vintage, Commodity], float] +ProcessOutputs = dict[tuple[Region, Period, Commodity, Technology, Vintage, Commodity], float] +TechClassification = dict[Technology, str] +SparseDict = dict[SparseIndex, set[SparseIndex]] +# Model sets type definitions (avoiding naming conflicts with set import) +TimesetTyped = set[Period] +RegionsetTyped = set[Region] +TechsetTyped = set[Technology] +CommoditysetTyped = set[Commodity] +VintagesetTyped = set[Vintage] + +# Model parameters type definitions +EfficiencyParam = Param # Multi-dimensional efficiency parameter +CostParam = Param # Cost parameters (investment, fixed, variable) +CapacityParam = Param # Capacity-related parameters +EmissionParam = Param # Emission parameters + +# Model variables type definitions +if TYPE_CHECKING: + FlowVar = Var # Flow variables + CapacityVar = Var # Capacity variables + CostVar = Var # Cost variables + + # Model constraints type definitions + FlowConstraint = Constraint # Flow balance constraints + CapacityConstraint = Constraint # Capacity constraints + CostConstraint = Constraint # Cost accounting constraints +else: + # Runtime fallback + FlowVar = Any # Flow variables + CapacityVar = Any # Capacity variables + CostVar = Any # Cost variables + FlowConstraint = Any # Flow balance constraints + CapacityConstraint = Any # Capacity constraints + CostConstraint = Any # Cost accounting constraints + + +@runtime_checkable +class TemoaModelProtocol(Protocol): + """Protocol defining the interface for TemoaModel instances.""" + + # Core identification + name: str + + # Time-related sets + time_exist: Set + time_future: Set + time_optimize: Set + vintage_exist: Set + vintage_optimize: Set + time_season: Set + time_of_day: Set + + # Geography sets + regions: Set + regional_indices: Set + + # Technology sets + tech_all: Set + tech_production: Set + tech_storage: Set + tech_reserve: Set + tech_exchange: Set + + # Commodity sets + commodity_all: Set + commodity_demand: Set + commodity_physical: Set + commodity_emissions: Set + + # Model parameters + global_discount_rate: Param + demand: Param + efficiency: Param + existing_capacity: Param + capacity_to_activity: Param + + # Model variables + v_flow_out: Var + v_capacity: Var + v_new_capacity: Var + + # Model constraints + demand_constraint: Constraint + commodity_balance_constraint: Constraint + capacity_constraint: Constraint + + # Internal data structures + process_inputs: ProcessInputs + process_outputs: ProcessOutputs + active_flow_rpsditvo: ( + set[tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity]] + | None + ) + active_activity_rptv: set[tuple[Region, Period, Technology, Vintage]] | None + + def __init__(self, *args: object, **kwargs: object) -> None: ... + + +if TYPE_CHECKING: + + class TemoaModel(AbstractModel): + """ + Type stub for the main TemoaModel class. + + This provides type information for the core Temoa energy model. + """ + + # Class attributes + default_lifetime_tech: int + + # Time-related sets + time_exist: Set + time_future: Set + time_optimize: Set + vintage_exist: Set + vintage_optimize: Set + vintage_all: Set + time_season: Set + time_of_day: Set + + # Geography sets + regions: Set + regional_indices: Set + regional_global_indices: Set + + # Technology sets + tech_all: Set + tech_production: Set + tech_baseload: Set + tech_annual: Set + tech_storage: Set + tech_reserve: Set + tech_exchange: Set + tech_uncap: Set + tech_with_capacity: Set + tech_retirement: Set + + # Commodity sets + commodity_all: Set + commodity_demand: Set + commodity_physical: Set + commodity_emissions: Set + commodity_carrier: Set + + # Model parameters + global_discount_rate: Param + period_length: Param + segment_fraction: Param + demand: Param + efficiency: Param + existing_capacity: Param + capacity_to_activity: Param + cost_invest: Param + cost_fixed: Param + cost_variable: Param + + # Model variables + v_flow_out: Var + v_capacity: Var + v_new_capacity: Var + v_retired_capacity: Var + + # Model constraints + demand_constraint: Constraint + commodity_balance_constraint: Constraint + capacity_constraint: Constraint + + # Internal tracking dictionaries + process_inputs: ProcessInputs + process_outputs: ProcessOutputs + used_techs: TechSet + active_flow_rpsditvo: set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] + ] + active_activity_rptv: set[tuple[Region, Period, Technology, Vintage]] + + def __init__(self, *args: object, **kwargs: object) -> None: ... + + +else: + # Runtime alias for TemoaModel when not TYPE_CHECKING + TemoaModel = Any + + +class EI(NamedTuple): + """Emission Index""" + + r: Region + p: Period + t: Technology + v: Vintage + e: Commodity + + +class FI(NamedTuple): + """Flow Index""" + + r: Region + p: Period + s: Season + d: TimeOfDay + i: Commodity | None + t: Technology + v: Vintage + o: Commodity | None + + +class SLI(NamedTuple): + """Storage Level Index""" + + r: Region + p: Period + s: Season + d: TimeOfDay + t: Technology + v: Vintage + + +class CapData(NamedTuple): + """Capacity Data Container""" + + built: Any + net: Any + retired: Any + + +@unique +class FlowType(Enum): + """Types of flow tracked""" + + IN = 1 + OUT = 2 + CURTAIL = 3 + FLEX = 4 + LOST = 5 + + +# Data structure types for model processing +class ModelData: + """Container for model data and metadata.""" + + def __init__( + self, + regions: RegionSet, + periods: set[Period], + technologies: TechSet, + commodities: CommoditySet, + **kwargs: object, + ) -> None: ... + + +# Export types for easy importing +# ruff: noqa: RUF022 +__all__ = [ + # Protocols + 'TemoaModelProtocol', + # Core classes + 'TemoaModel', + # Data structures + 'ModelData', + 'ProcessInputs', + 'ProcessOutputs', + 'TechClassification', + 'SparseDict', + # Named tuples for indexing + 'EI', + 'FI', + 'SLI', + 'CapData', + # Enums + 'FlowType', + # Typed set aliases + 'TimesetTyped', + 'RegionsetTyped', + 'TechsetTyped', + 'CommoditysetTyped', + 'VintagesetTyped', + # Pyomo type aliases + 'Set', + 'Param', + 'Var', + 'Constraint', + # Parameter types + 'EfficiencyParam', + 'CostParam', + 'CapacityParam', + 'EmissionParam', + # Variable types + 'FlowVar', + 'CapacityVar', + 'CostVar', + # Constraint types + 'FlowConstraint', + 'CapacityConstraint', + 'CostConstraint', +] diff --git a/temoa/types/set_types.py b/temoa/types/set_types.py new file mode 100644 index 000000000..57bd18310 --- /dev/null +++ b/temoa/types/set_types.py @@ -0,0 +1,37 @@ +"""Type aliases for Temoa set types.""" + +from temoa.types.core_types import ( + Commodity, + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, +) + +# Set types for sparse indexing +ActiveFlowSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveFlowAnnualSet = set[tuple[Region, Period, Commodity, Technology, Vintage, Commodity]] +ActiveFlexSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveFlexAnnualSet = set[tuple[Region, Period, Commodity, Technology, Vintage, Commodity]] +ActiveFlowInStorageSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveCurtailmentSet = set[ + tuple[Region, Period, Season, TimeOfDay, Commodity, Technology, Vintage, Commodity] +] +ActiveActivitySet = set[tuple[Region, Period, Technology, Vintage]] +StorageLevelIndicesSet = set[tuple[Region, Period, Season, TimeOfDay, Technology, Vintage]] +SeasonalStorageLevelIndicesSet = set[tuple[Region, Period, Season, Technology, Vintage]] +NewCapacitySet = set[tuple[Region, Technology, Vintage]] +ActiveCapacityAvailableSet = set[tuple[Region, Period, Technology]] +ActiveCapacityAvailableVintageSet = set[tuple[Region, Period, Technology, Vintage]] +GroupRegionActiveFlowSet = set[tuple[Region, Period, Technology]] + +CommodityBalancedSet = set[tuple[Region, Period, Commodity]] +SingletonDemandsSet = set[tuple[Region, Period, Commodity]] diff --git a/temoa/types/solver_types.py b/temoa/types/solver_types.py new file mode 100644 index 000000000..9b7d2cffa --- /dev/null +++ b/temoa/types/solver_types.py @@ -0,0 +1,99 @@ +""" +Solver-related type definitions for Temoa energy model. + +This module provides type definitions for solver results, status, and termination +conditions used throughout the Temoa codebase. +""" + +from collections.abc import Mapping +from enum import Enum, StrEnum +from typing import TYPE_CHECKING, Any, Protocol + +if TYPE_CHECKING: + from pyomo.opt.results import SolverResults, SolverStatus, TerminationCondition +else: + # Runtime fallbacks + PyomoSolverResults = Any + PyomoSolverStatus = Any + PyomoTerminationCondition = Any + + +class SolverStatusEnum(StrEnum): + """ + Enumeration of possible solver status values. + + These represent the high-level status of the solver after attempting to solve. + """ + + OK = 'ok' + WARNING = 'warning' + ERROR = 'error' + ABORTED = 'aborted' + UNKNOWN = 'unknown' + + +class TerminationConditionEnum(int, Enum): + """ + Enumeration of possible solver termination conditions. + + These represent the specific reason why the solver terminated. + Updated to match Pyomo 6.9.2 integer-based enum values. + """ + + convergence_criteria_satisfied = 0 + max_time_limit = 1 + iteration_limit = 2 + objective_limit = 3 + min_step_length = 4 + unbounded = 5 + proven_infeasible = 6 + locally_infeasible = 7 + infeasible_or_unbounded = 8 + error = 9 + interrupted = 10 + licensing_problems = 11 + empty_model = 12 + unknown = 42 + + +class SolverResultsProtocol(Protocol): + """ + Protocol defining the interface for solver results objects. + + This protocol describes the expected structure of solver results returned + by Pyomo solvers, allowing for type-safe access to solver information. + """ + + solver: object + """Solver information and statistics.""" + + def __getitem__(self, key: str) -> object: + """Access result components by key (e.g., 'Solution', 'Problem').""" + ... + + +# Type aliases for solver-related types +SolverResults = SolverResults +"""Type alias for Pyomo SolverResults objects.""" + +SolverStatus = SolverStatus +"""Type alias for Pyomo SolverStatus enum.""" + +TerminationCondition = TerminationCondition +"""Type alias for Pyomo TerminationCondition enum.""" + +SolverOptions = Mapping[str, object] +"""Type alias for solver option dictionaries.""" + + +# Export all types +# ruff: noqa: RUF022 +__all__ = [ + 'SolverStatusEnum', + 'TerminationConditionEnum', + 'SolverResultsProtocol', + 'SolverResults', + 'SolverStatus', + 'TerminationCondition', + 'SolverOptions', +] diff --git a/temoa/types/validation_types.py b/temoa/types/validation_types.py new file mode 100644 index 000000000..a5971cab8 --- /dev/null +++ b/temoa/types/validation_types.py @@ -0,0 +1,238 @@ +""" +Validation result type definitions for Temoa energy model. + +This module provides type definitions for validation results, errors, and warnings +used in model checking and data validation throughout the Temoa codebase. +""" + +from dataclasses import dataclass +from enum import StrEnum +from typing import Any + + +class ValidationSeverity(StrEnum): + """ + Enumeration of validation message severity levels. + + These represent the severity of validation issues found during + model checking and data validation. + """ + + ERROR = 'error' + """Critical error that prevents model execution.""" + + WARNING = 'warning' + """Non-critical issue that may affect results.""" + + INFO = 'info' + """Informational message about model structure.""" + + +@dataclass(slots=True) +class ValidationError: + """ + Represents a validation error or warning. + + This dataclass encapsulates information about a validation issue, + including its severity, message, and location in the model. + + Attributes: + severity: The severity level of the validation issue + message: Human-readable description of the issue + location: Optional location information (e.g., file, line, component) + context: Optional additional context about the issue + (e.g., {'variable': 'x', 'expected': 10, 'actual': 5}) + """ + + severity: ValidationSeverity + message: str + location: str | None = None + context: dict[str, Any] | None = None + + def __str__(self) -> str: + """Return a formatted string representation of the validation error.""" + parts = [f'[{self.severity.value.upper()}]', self.message] + if self.location: + parts.append(f'at {self.location}') + return ' '.join(parts) + + +@dataclass(slots=True) +class ValidationWarning: + """ + Represents a validation warning. + + This is a convenience type for warnings, which are non-critical validation + issues that don't prevent model execution but may affect results. + + Attributes: + message: Human-readable description of the warning + location: Optional location information + context: Optional additional context + """ + + message: str + location: str | None = None + context: dict[str, Any] | None = None + + def to_validation_error(self) -> ValidationError: + """Convert this warning to a ValidationError with WARNING severity.""" + return ValidationError( + severity=ValidationSeverity.WARNING, + message=self.message, + location=self.location, + context=self.context, + ) + + def __str__(self) -> str: + """Return a formatted string representation of the validation warning.""" + parts = ['[WARNING]', self.message] + if self.location: + parts.append(f'at {self.location}') + return ' '.join(parts) + + +@dataclass(slots=True) +class ValidationResult: + """ + Represents the complete result of a validation operation. + + This dataclass aggregates all validation errors and warnings found + during a validation operation, along with summary information. + + Attributes: + errors: List of validation errors found + warnings: List of validation warnings found + is_valid: Whether the validation passed (no errors) + summary: Optional summary message + """ + + errors: list[ValidationError] + warnings: list[ValidationWarning] + is_valid: bool + summary: str | None = None + + @classmethod + def create_success(cls, summary: str | None = None) -> 'ValidationResult': + """ + Create a successful validation result with no errors or warnings. + + Args: + summary: Optional summary message + + Returns: + ValidationResult indicating success + """ + return cls(errors=[], warnings=[], is_valid=True, summary=summary) + + @classmethod + def create_failure( + cls, + errors: list[ValidationError], + warnings: list[ValidationWarning] | None = None, + summary: str | None = None, + ) -> 'ValidationResult': + """ + Create a failed validation result with errors. + + Args: + errors: List of validation errors + warnings: Optional list of validation warnings + summary: Optional summary message + + Returns: + ValidationResult indicating failure + """ + if not errors: + raise ValueError('Failure result must contain at least one error') + return cls( + errors=errors, + warnings=warnings or [], + is_valid=False, + summary=summary, + ) + + def add_error( + self, + message: str, + location: str | None = None, + context: dict[str, Any] | None = None, + ) -> None: + """ + Add an error to this validation result. + + Args: + message: Error message + location: Optional location information + context: Optional additional context + """ + self.errors.append( + ValidationError( + severity=ValidationSeverity.ERROR, + message=message, + location=location, + context=context, + ) + ) + self.is_valid = False + + def add_warning( + self, + message: str, + location: str | None = None, + context: dict[str, Any] | None = None, + ) -> None: + """ + Add a warning to this validation result. + + Args: + message: Warning message + location: Optional location information + context: Optional additional context + """ + self.warnings.append(ValidationWarning(message=message, location=location, context=context)) + + def has_errors(self) -> bool: + """Check if this result contains any errors.""" + return bool(self.errors) + + def has_warnings(self) -> bool: + """Check if this result contains any warnings.""" + return bool(self.warnings) + + def error_count(self) -> int: + """Get the number of errors in this result.""" + return len(self.errors) + + def warning_count(self) -> int: + """Get the number of warnings in this result.""" + return len(self.warnings) + + def __str__(self) -> str: + """Return a formatted string representation of the validation result.""" + lines = [] + if self.summary: + lines.append(self.summary) + lines.append( + f'Validation {"PASSED" if self.is_valid else "FAILED"}: ' + f'{self.error_count()} errors, {self.warning_count()} warnings' + ) + if self.errors: + lines.append('\nErrors:') + for error in self.errors: + lines.append(f' {error}') + if self.warnings: + lines.append('\nWarnings:') + for warning in self.warnings: + lines.append(f' {warning}') + return '\n'.join(lines) + + +# Export all types +# ruff: noqa: RUF022 +__all__ = [ + 'ValidationSeverity', + 'ValidationError', + 'ValidationWarning', + 'ValidationResult', +] diff --git a/temoa/utilities/capacity_analyzer.py b/temoa/utilities/capacity_analyzer.py index e708833bd..fb271df3a 100644 --- a/temoa/utilities/capacity_analyzer.py +++ b/temoa/utilities/capacity_analyzer.py @@ -1,75 +1,90 @@ """ Quick utility script to analyze the distribution of capacities within a scenario database -Note: this uses the MaxCapacity table for analysis, which depending on if/how that table +Note: this uses the max_capacity table for analysis, which depending on if/how that table is populated will influence the utility of using this method """ +import argparse import itertools -import os.path import sqlite3 from matplotlib import pyplot as plt -from definitions import PROJECT_ROOT - # Written by: J. F. Hyink # jeff@westernspark.us # https://westernspark.us # Created on: 7/18/23 -# filename of db to analyze... -db = 'US_9R_8D_CT500.sqlite' - -source_db_file = os.path.join(PROJECT_ROOT, 'data_files', 'untracked_data', db) -print(source_db_file) -res = [] -try: - con = sqlite3.connect(source_db_file) - cur = con.cursor() - cur.execute('SELECT max_cap FROM MaxCapacity') - for row in cur: - res.append(row) - -except sqlite3.Error as e: - print(e) - -finally: - con.close() - -# chain them together into a list -caps = list(itertools.chain(*res)) - -cutoff = 1 # GW : An arbitrary cutoff between big and small capacity systems. -small_cap_sources = [c for c in caps if c <= cutoff] -large_cap_sources = [c for c in caps if c > cutoff] - -aggregate_small_cap = sum(small_cap_sources) -aggregate_large_cap = sum(large_cap_sources) - -print(f'{len(small_cap_sources)} small cap sources account for: {aggregate_small_cap: 0.1f} GW') -print(f'{len(large_cap_sources)} large cap sources account for: {aggregate_large_cap: 0.1f} GW') - -plt.hist(caps, bins=100) -plt.show() - - -# make a cumulative contribution plot, and find a 5% cutoff -cutoff_num_sources = 0 -caps.sort() -total_cap = sum(caps) -cumulative_caps = [ - caps[0] / total_cap, -] -for i, cap in enumerate(caps[1:]): - cumulative_caps.append(cap / total_cap + cumulative_caps[i]) - if cumulative_caps[-1] < 0.05: - cutoff_num_sources += 1 - -plt.plot(range(len(cumulative_caps)), cumulative_caps) -plt.axvline(x=cutoff_num_sources, color='red', ls='--') -plt.xlabel('Aggregated Sources') -plt.ylabel('Proportion of Total Capacity') -plt.title('Aggregate Capacity vs. Number of Sources') - -plt.show() + +def analyze_capacity(db_path: str) -> None: + res = [] + con = None + try: + con = sqlite3.connect(db_path) + cur = con.cursor() + cur.execute('SELECT max_cap FROM max_capacity') + for row in cur: + res.append(row) + + except sqlite3.Error as e: + print(f'Error connecting to database: {e}') + return + + finally: + if con: + con.close() + + if not res: + print('No data found in max_capacity table.') + return + + # chain them together into a list + caps = list(itertools.chain(*res)) + + cutoff = 1 # GW : An arbitrary cutoff between big and small capacity systems. + small_cap_sources = [c for c in caps if c <= cutoff] + large_cap_sources = [c for c in caps if c > cutoff] + + aggregate_small_cap = sum(small_cap_sources) + aggregate_large_cap = sum(large_cap_sources) + + print(f'{len(small_cap_sources)} small cap sources account for: {aggregate_small_cap: 0.1f} GW') + print(f'{len(large_cap_sources)} large cap sources account for: {aggregate_large_cap: 0.1f} GW') + + plt.hist(caps, bins=100) + plt.show() + + # make a cumulative contribution plot, and find a 5% cutoff + cutoff_num_sources = 0 + caps.sort() + total_cap = sum(caps) + cumulative_caps = [ + caps[0] / total_cap, + ] + for i, cap in enumerate(caps[1:]): + cumulative_caps.append(cap / total_cap + cumulative_caps[i]) + if cumulative_caps[-1] < 0.05: + cutoff_num_sources += 1 + + plt.plot(range(len(cumulative_caps)), cumulative_caps) + plt.axvline(x=cutoff_num_sources, color='red', ls='--') + plt.xlabel('Aggregated Sources') + plt.ylabel('Proportion of Total Capacity') + plt.title('Aggregate Capacity vs. Number of Sources') + + plt.show() + + +def main() -> None: + parser = argparse.ArgumentParser( + description='Analyze capacity distribution in a Temoa database.' + ) + parser.add_argument('db_path', help='Path to the SQLite database file.') + args = parser.parse_args() + + analyze_capacity(args.db_path) + + +if __name__ == '__main__': + main() diff --git a/temoa/utilities/clear_db_outputs.py b/temoa/utilities/clear_db_outputs.py index 594c34254..d8614f141 100644 --- a/temoa/utilities/clear_db_outputs.py +++ b/temoa/utilities/clear_db_outputs.py @@ -1,60 +1,36 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/22/24 - A quick utility to clear all of the data in all of the output tables in a Temoa V3 database """ + import sqlite3 import sys from pathlib import Path basic_output_tables = [ - 'OutputBuiltCapacity', - 'OutputCost', - 'OutputCurtailment', - 'OutputDualVariable', - 'OutputEmission', - 'OutputFlowIn', - 'OutputFlowOut', - 'OutputNetCapacity', - 'OutputObjective', - 'OutputRetiredCapacity', + 'output_built_capacity', + 'output_cost', + 'output_curtailment', + 'output_dual_variable', + 'output_emissionn', + 'output_flow_in', + 'output_flow_out', + 'output_net_capacity', + 'output_objective', + 'output_retired_capacity', ] -optional_output_tables = ['OutputFlowOutSummary', 'MyopicEfficiency'] +optional_output_tables = ['output_flow_out_summary', 'myopic_efficiency'] if len(sys.argv) != 2: print('this utility file expects a CLA for the path to the database to clear') sys.exit(-1) -target_db = sys.argv[1] +target_db_str = sys.argv[1] -proceed = input('This will clear ALL output tables in ' + target_db + '? (y/n): ') +proceed = input('This will clear ALL output tables in ' + target_db_str + '? (y/n): ') if proceed == 'y': - target_db = Path(target_db) - if not Path.exists(target_db): + target_db = Path(target_db_str) + if not target_db.exists(): print(f'path provided to database is invalid: {target_db}') sys.exit(-1) try: diff --git a/temoa/utilities/database_util.py b/temoa/utilities/database_util.py new file mode 100644 index 000000000..b49002dec --- /dev/null +++ b/temoa/utilities/database_util.py @@ -0,0 +1,356 @@ +""" +Utility class for interacting with a Temoa scenario database (SQLite). + +This module provides the DatabaseUtil class to abstract the database connection +and querying process. It offers methods to retrieve specific data slices +such as technologies, commodities, capacity, and flows for given scenarios, +periods, and regions. +""" + +import os +import re +import sqlite3 +from os import PathLike + +import deprecated +import pandas as pd + + +class DatabaseUtil: + """ + A utility to connect to and query a Temoa SQLite database. + + This class provides methods to extract data from the database, handling + connection management and query construction. It can be used as a context + manager to ensure database connections are closed properly. + + Args: + database_path: The file path to the SQLite database. + scenario: The specific scenario to query within the database. + + Raises: + ValueError: If the database path does not exist or if connecting fails. + """ + + database: str + scenario: str | None + con: sqlite3.Connection + cur: sqlite3.Cursor + + def __init__(self, database_path: str | PathLike[str], scenario: str | None = None) -> None: + self.database = os.path.abspath(database_path) + self.scenario = scenario + if not os.path.exists(self.database): + raise ValueError("The database file path doesn't exist") + + if self.is_database_file(self.database): + try: + self.con = sqlite3.connect(self.database) + self.cur = self.con.cursor() + self.con.text_factory = str + except sqlite3.Error as e: + raise ValueError(f'Unable to connect to database: {e}') from e + elif self.database.endswith('.dat'): + raise ValueError('Reading .dat files is no longer supported') + + def close(self) -> None: + """Closes the database cursor and connection.""" + if self.cur: + self.cur.close() + if self.con: + self.con.close() + + @staticmethod + def is_database_file(file: str | PathLike[str]) -> bool: + """Checks if a file has a common SQLite database extension.""" + return str(file).endswith(('.db', '.sqlite', '.sqlite3')) + + @deprecated.deprecated(version='0.9.0', reason='Reading from .dat files is no longer supported') + def read_from_dat_file(self, inp_comm: str | None, inp_tech: str | None) -> pd.DataFrame: + """ + This method is deprecated and will raise a ValueError if called on a + database connection. + """ + # This check ensures the method is unreachable with a valid DB connection. + if hasattr(self, 'cur') and self.cur is not None: + raise ValueError('Invalid Operation For Database file') + + # The following code is unreachable in the current design but is kept + # to document the deprecated functionality. + inp_comm_re = inp_comm if inp_comm is not None else r'\w+' + inp_tech_re = inp_tech if inp_tech is not None else r'\w+' + + rows: list[tuple[str, ...]] = [] + eff_flag = False + with open(self.database, encoding='utf-8') as f: + for line in f: + if not eff_flag and re.search(r'^\s*param\s+efficiency\s*[:][=]', line, re.I): + eff_flag = True + elif eff_flag: + line = re.sub(r'[#].*$', ' ', line) + if re.search(r'^\s*;\s*$', line): + break + if re.search(r'^\s+$', line): + continue + line = line.strip() + row_parts = tuple(re.split(r'\s+', line)) + if ( + len(row_parts) >= 4 + and not re.search(inp_comm_re, row_parts[0]) + and not re.search(inp_comm_re, row_parts[3]) + and not re.search(inp_tech_re, row_parts[1]) + ): + continue + rows.append(row_parts) + + result = pd.DataFrame(rows, columns=['input_comm', 'tech', 'period', 'output_comm', 'flow']) + return result[['input_comm', 'tech', 'output_comm']] + + def get_time_peridos_for_flags(self, flags: list[str] | None = None) -> set[int]: + """Retrieves a set of time periods, optionally filtered by flags.""" + if (flags is None) or (not flags): + query = 'SELECT period FROM time_period' + else: + in_clause = ', '.join(f"'{flag}'" for flag in flags) + query = f'SELECT period FROM time_period WHERE flag IN ({in_clause})' + + self.cur.execute(query) + return {int(row[0]) for row in self.cur} + + def get_technologies_for_flags(self, flags: list[str] | None = None) -> set[str]: + """Retrieves a set of technologies, optionally filtered by flags.""" + if (flags is None) or (not flags): + query = 'SELECT tech FROM Technology' + else: + in_clause = ', '.join(f"'{flag}'" for flag in flags) + query = f'SELECT tech FROM Technology WHERE flag IN ({in_clause})' + + return {row[0] for row in self.cur.execute(query)} + + def get_commodities_and_tech( + self, inp_comm: str | None, inp_tech: str | None, region: str | None + ) -> pd.DataFrame: + """Retrieves technologies and commodities based on filters.""" + inp_comm_sql = f"'{inp_comm}'" if inp_comm else 'NULL' + inp_tech_sql = f"'{inp_tech}'" if inp_tech else 'NULL' + + if not inp_comm and not inp_tech: + inp_comm_sql = 'NOT NULL' + inp_tech_sql = 'NOT NULL' + + where_clause = ( + f'(input_comm IS {inp_comm_sql} OR output_comm IS {inp_comm_sql} OR tech IS ' + f'{inp_tech_sql})' + ) + if region: + where_clause = f"region LIKE '%{region}%' AND {where_clause}" + + query = f'SELECT input_comm, tech, output_comm FROM efficiency WHERE {where_clause}' + self.cur.execute(query) + return pd.DataFrame(self.cur.fetchall(), columns=['input_comm', 'tech', 'output_comm']) + + def get_existing_technologies_for_commodity( + self, comm: str, region: str | None, comm_type: str = 'input' + ) -> pd.DataFrame: + """Retrieves technologies associated with a specific commodity.""" + if comm_type == 'input': + query = f"SELECT DISTINCT tech FROM efficiency WHERE input_comm IS '{comm}'" + else: + query = f"SELECT DISTINCT tech FROM efficiency WHERE output_comm IS '{comm}'" + if region: + query += f" AND region LIKE '%{region}%'" + + self.cur.execute(query) + return pd.DataFrame(self.cur.fetchall(), columns=['tech']) + + def get_commodities_for_flags(self, flags: list[str] | None = None) -> set[str]: + """Retrieves a set of commodities, optionally filtered by flags.""" + if (flags is None) or (not flags): + query = 'SELECT name FROM Commodity' + else: + in_clause = ', '.join(f"'{flag}'" for flag in flags) + query = f'SELECT name FROM Commodity WHERE flag IN ({in_clause})' + + return {row[0] for row in self.cur.execute(query)} + + def get_commodities_by_technology( + self, region: str | None, comm_type: str = 'input' + ) -> set[tuple[str, str]]: + """Retrieves commodity-technology pairs.""" + if comm_type == 'input': + query = 'SELECT DISTINCT input_comm, tech FROM efficiency' + elif comm_type == 'output': + query = 'SELECT DISTINCT tech, output_comm FROM efficiency' + else: + raise ValueError("Invalid comm_type: can only be 'input' or 'output'") + + if region: + query += f" WHERE region LIKE '%{region}%'" + + return {tuple(row) for row in self.cur.execute(query)} + + def get_capacity_for_tech_and_period( + self, tech: str | None = None, period: int | None = None, region: str | None = None + ) -> pd.DataFrame | pd.Series: + """Retrieves capacity data, aggregated by technology.""" + if not self.scenario: + raise ValueError('A scenario must be set for output-related queries') + + columns = ['tech', 'period', 'SUM(capacity)', 'region'] + query = ( + f'SELECT {", ".join(columns)} FROM output_net_capacity WHERE scenario IS ' + f"'{self.scenario}'" + ) + + if region: + query += f" AND region LIKE '{region}%'" + if tech: + query += f" AND tech IS '{tech}'" + if period: + query += f" AND period IS '{period}'" + + query += ' GROUP BY tech, region, period, sector;' + self.cur.execute(query) + + df_columns = [col.replace('SUM(capacity)', 'capacity') for col in columns] + result = pd.DataFrame(self.cur.fetchall(), columns=df_columns) + + if region is None and not result.empty: + mask = result['region'].str.contains('-') + result.loc[mask, 'capacity'] /= 2 + + result.drop(columns=['region'], inplace=True) + if len(df_columns) == 2: + return result.sum() + else: + return result.groupby(by='tech').sum().reset_index() + + def get_output_flow_for_period( + self, + period: int, + region: str | None, + comm_type: str = 'input', + commodity: str | None = None, + ) -> pd.DataFrame: + """Retrieves aggregated output flow data.""" + if not self.scenario: + raise ValueError('A scenario must be set for output-related queries') + + columns: list[str] = [] + table = '' + if comm_type == 'input': + table = 'output_flow_in' + if commodity is None: + columns.append('input_comm') + elif comm_type == 'output': + table = 'output_flow_out' + if commodity is None: + columns.append('output_comm') + columns.append('tech') + + query = ( + f'SELECT DISTINCT {", ".join(columns)}, SUM(flow) AS flow FROM {table} WHERE ' + f"scenario IS '{self.scenario}'" + ) + + if region: + query += f" AND region LIKE '{region}%'" + query += f" AND period IS '{period}'" + + group_by = ['tech'] + if commodity: + query += f" AND {comm_type}_comm IS '{commodity}'" + if comm_type == 'output': + query += " AND input_comm != 'ethos'" + else: + group_by.append(f'{comm_type}_comm') + + query += f' GROUP BY {", ".join(group_by)}' + self.cur.execute(query) + + df_columns = columns + ['flow'] + return pd.DataFrame(self.cur.fetchall(), columns=df_columns) + + def get_emissions_activity_for_period(self, period: int, region: str | None) -> pd.DataFrame: + """Retrieves emissions activity data.""" + if not self.scenario: + raise ValueError('A scenario must be set for output-related queries') + + query = f""" + SELECT E.emis_comm, E.tech, SUM(E.activity * O.flow) + FROM emission_activity E, output_flow_out O + WHERE E.input_comm = O.input_comm + AND E.tech = O.tech + AND E.vintage = O.vintage + AND E.output_comm = O.output_comm + AND O.scenario = '{self.scenario}' + AND O.period = '{period}' + """ + if region: + query += f" AND E.region LIKE '%{region}%'" + query += ' GROUP BY E.tech, E.emis_comm' + self.cur.execute(query) + return pd.DataFrame(self.cur.fetchall(), columns=['emis_comm', 'tech', 'emis_activity']) + + def get_commodity_wise_input_and_output_flow( + self, tech: str, period: int, region: str | None + ) -> pd.DataFrame: + """Retrieves detailed input and output flows for a technology.""" + if not self.scenario: + raise ValueError('A scenario must be set for output-related queries') + + query = f""" + SELECT + OF.input_comm, OF.output_comm, OF.vintage, OF.region, + SUM(OF.vflow_in) AS vflow_in, + SUM(OFO.vflow_out) AS vflow_out, + OC.capacity + FROM ( + SELECT region, scenario, period, input_comm, tech, vintage, output_comm, + SUM(flow) AS vflow_in + FROM output_flow_in + GROUP BY region, scenario, period, input_comm, tech, vintage, output_comm + ) AS OF + INNER JOIN ( + SELECT region, scenario, period, input_comm, tech, vintage, output_comm, + SUM(flow) AS vflow_out + FROM output_flow_out + GROUP BY region, scenario, period, input_comm, tech, vintage, output_comm + ) AS OFO ON + OF.region = OFO.region AND + OF.scenario = OFO.scenario AND + OF.period = OFO.period AND + OF.tech = OFO.tech AND + OF.input_comm = OFO.input_comm AND + OF.vintage = OFO.vintage AND + OF.output_comm = OFO.output_comm + INNER JOIN output_net_capacity OC ON + OF.region = OC.region AND + OF.scenario = OC.scenario AND + OF.tech = OC.tech AND + OF.vintage = OC.vintage + WHERE + OF.period = '{period}' AND + OF.tech IS '{tech}' AND + OF.scenario IS '{self.scenario}' + """ + if region: + query += f" AND OF.region LIKE '%{region}%'" + query += ' GROUP BY OF.region, OF.vintage, OF.input_comm, OF.output_comm' + + self.cur.execute(query) + result = pd.DataFrame( + self.cur.fetchall(), + columns=[ + 'input_comm', + 'output_comm', + 'vintage', + 'region', + 'flow_in', + 'flow_out', + 'capacity', + ], + ) + return pd.DataFrame( + result.groupby(['input_comm', 'output_comm', 'vintage'], as_index=False).sum() + ) diff --git a/temoa/utilities/db_migration_to_v3.py b/temoa/utilities/db_migration_to_v3.py index 9cbcb0920..40baafb8b 100644 --- a/temoa/utilities/db_migration_to_v3.py +++ b/temoa/utilities/db_migration_to_v3.py @@ -1,28 +1,4 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/21/24 Transition a legacy database to V3 compliant. @@ -35,9 +11,9 @@ import sqlite3 import sys from collections import defaultdict +from importlib import resources from pathlib import Path - parser = argparse.ArgumentParser() parser.add_argument( '--source', @@ -48,14 +24,28 @@ ) parser.add_argument( '--schema', - help='Path to schema file (default=../../data_files/temoa_schema_v3.sql)', + help='Path to v3 schema file (defaults to bundled package resource)', required=False, dest='schema', - default='../../data_files/temoa_schema_v3.sql', + default=None, ) options = parser.parse_args() legacy_db: Path = Path(options.source_db) -schema_file = Path(options.schema) + +# Resolve schema path +if options.schema: + schema_file = Path(options.schema) +else: + try: + # Try package resources first + schema_file = Path(str(resources.files('temoa.db_schema') / 'temoa_schema_v3.sql')) + except (ModuleNotFoundError, FileNotFoundError, AttributeError): + # Fallback to local path relative to this script + schema_file = Path(__file__).parent.parent / 'db_schema' / 'temoa_schema_v3.sql' + +if not schema_file.exists(): + print(f'Error: Schema file not found: {schema_file}') + sys.exit(1) new_db_name = legacy_db.stem + '_v3.sqlite' @@ -66,7 +56,7 @@ cur = con_new.cursor() # bring in the new schema and execute -with open(schema_file, 'r') as src: +with open(schema_file) as src: sql_script = src.read() con_new.executescript(sql_script) @@ -76,71 +66,71 @@ # table mapping for DIRECT transfers # fmt: off direct_transfer_tables = [ - ('', 'CapacityCredit'), - ('', 'CapacityFactorProcess'), - ('', 'CapacityFactorTech'), - ('', 'CapacityToActivity'), - ('commodities', 'Commodity'), - ('commodity_labels', 'CommodityType'), - ('CostEmissions', 'CostEmission'), - ('', 'CostFixed'), - ('', 'CostInvest'), - ('', 'CostVariable'), - ('', 'Demand'), - ('', 'DemandSpecificDistribution'), - ('', 'Efficiency'), - ('', 'EmissionActivity'), - ('', 'EmissionLimit'), - ('', 'ExistingCapacity'), - ('', 'GrowthRateMax'), - ('', 'GrowthRateSeed'), - ('', 'LifetimeProcess'), - ('', 'LifetimeTech'), - ('LinkedTechs', 'LinkedTech'), - ('LifetimeLoanTech', 'LoanLifetimeTech'), - ('DiscountRate', 'LoanRate'), - ('', 'MaxActivity'), - ('', 'MaxActivityShare'), - ('', 'MaxAnnualCapacityFactor'), - ('', 'MaxCapacity'), - ('', 'MaxCapacityShare'), - ('', 'MaxNewCapacity'), - ('', 'MaxNewCapacityGroup'), - ('', 'MaxNewCapacityShare'), - ('', 'MaxResource'), - ('', 'MinActivity'), - ('', 'MinActivityShare'), - ('', 'MinAnnualCapacityFactor'), - ('', 'MinCapacity'), - ('', 'MinCapacityShare'), - ('', 'MinNewCapacity'), - ('', 'MinNewCapacityGroup'), - ('', 'MinNewCapacityShare'), - ('', 'PlanningReserveMargin'), - ('', 'RampDown'), - ('', 'RampUp'), - ('regions', 'Region'), - ('sector_labels', 'SectorLabel'), - ('', 'StorageDuration'), - ('', 'TechInputSplit'), - ('', 'TechInputSplitAverage'), - ('', 'TechOutputSplit'), - ('technology_labels', 'TechnologyType'), - ('time_period_labels', 'TimePeriodType'), - ('SegFrac', 'TimeSegmentFraction'), + ("", "CapacityCredit"), + ("", "CapacityFactorProcess"), + ("", "CapacityFactorTech"), + ("", "CapacityToActivity"), + ("commodities", "Commodity"), + ("commodity_labels", "CommodityType"), + ("CostEmissions", "CostEmission"), + ("", "CostFixed"), + ("", "CostInvest"), + ("", "CostVariable"), + ("", "Demand"), + ("", "DemandSpecificDistribution"), + ("", "Efficiency"), + ("", "EmissionActivity"), + ("", "EmissionLimit"), + ("", "ExistingCapacity"), + ("", "GrowthRateMax"), + ("", "GrowthRateSeed"), + ("", "LifetimeProcess"), + ("", "LifetimeTech"), + ("LinkedTechs", "LinkedTech"), + ("LifetimeLoanTech", "LoanLifetimeTech"), + ("DiscountRate", "LoanRate"), + ("", "MaxActivity"), + ("", "MaxActivityShare"), + ("", "MaxAnnualCapacityFactor"), + ("", "MaxCapacity"), + ("", "MaxCapacityShare"), + ("", "MaxNewCapacity"), + ("", "MaxNewCapacityGroup"), + ("", "MaxNewCapacityShare"), + ("", "MaxResource"), + ("", "MinActivity"), + ("", "MinActivityShare"), + ("", "MinAnnualCapacityFactor"), + ("", "MinCapacity"), + ("", "MinCapacityShare"), + ("", "MinNewCapacity"), + ("", "MinNewCapacityGroup"), + ("", "MinNewCapacityShare"), + ("", "PlanningReserveMargin"), + ("", "RampDown"), + ("", "RampUp"), + ("regions", "Region"), + ("sector_labels", "SectorLabel"), + ("", "StorageDuration"), + ("", "TechInputSplit"), + ("", "TechInputSplitAverage"), + ("", "TechOutputSplit"), + ("technology_labels", "TechnologyType"), + ("time_period_labels", "TimePeriodType"), + ("SegFrac", "TimeSegmentFraction"), ] units_added_tables = [ - ('', 'MaxActivityGroup'), - ('', 'MaxCapacityGroup'), - ('', 'MinCapacityGroup'), - ('', 'MinActivityGroup'), + ("", "MaxActivityGroup"), + ("", "MaxCapacityGroup"), + ("", "MinCapacityGroup"), + ("", "MinActivityGroup"), ] sequence_added_tables = [ - ('time_season', 'TimeSeason'), - ('time_periods', 'TimePeriod'), - ('time_of_day', 'TimeOfDay'), + ("time_season", "TimeSeason"), + ("time_periods", "time_period"), + ("time_of_day", "TimeOfDay"), ] # fmt: on @@ -150,8 +140,13 @@ old_name, new_name = name_pair if old_name == '': old_name = new_name + + new_columns = [c[1] for c in con_new.execute(f'PRAGMA table_info({new_name});').fetchall()] + old_columns = [c[1] for c in con_old.execute(f'PRAGMA table_info({old_name});').fetchall()] + cols = str(old_columns[0 : len(new_columns)])[1:-1].replace("'", '') + try: - data = con_old.execute(f'SELECT * FROM {old_name}').fetchall() + data = con_old.execute(f'SELECT {cols} FROM {old_name}').fetchall() except sqlite3.OperationalError: print('TABLE NOT FOUND: ' + old_name) data = [] @@ -174,8 +169,13 @@ old_name, new_name = name_pair if old_name == '': old_name = new_name + + new_columns = [c[1] for c in con_new.execute(f'PRAGMA table_info({new_name});').fetchall()] + old_columns = [c[1] for c in con_old.execute(f'PRAGMA table_info({old_name});').fetchall()] + cols = str(old_columns[0 : len(new_columns)])[1:-1].replace("'", '') + try: - data = con_old.execute(f'SELECT * FROM {old_name}').fetchall() + data = con_old.execute(f'SELECT {cols} FROM {old_name}').fetchall() except sqlite3.OperationalError: print('table not found: ' + old_name) data = [] @@ -186,11 +186,13 @@ # quick check for expected number of fields... if len(data[0]) != 5: print( - f'\nWARNING: unexpected number of fields in table: {old_name}. Was expecting 5: region, period, group, value, notes' + f'\nWARNING: unexpected number of fields in table: {old_name}. Was expecting 5: ' + 'region, period, group, value, notes' ) print( - '\nIt is possible that the older table you have was not indexed by REGION, which might be common' - 'for old datasets. If so, that cannot be moved automatically. You will need to do it manually.' + '\nIt is possible that the older table you have was not indexed by REGION, which might ' + 'be common for old datasets. If so, that cannot be moved automatically. You will ' + 'need to do it manually.' ) print(f'\n *** IGNORING TABLE: {old_name} in transfer!! ***\n') continue @@ -204,13 +206,18 @@ old_name, new_name = name_pair if old_name == '': old_name = new_name + + new_columns = [c[1] for c in con_new.execute(f'PRAGMA table_info({new_name});').fetchall()] + old_columns = [c[1] for c in con_old.execute(f'PRAGMA table_info({old_name});').fetchall()] + cols = str(old_columns[0 : len(new_columns) - 1])[1:-1].replace("'", '') + try: - data = con_old.execute(f'SELECT * FROM {old_name}').fetchall() + data = con_old.execute(f'SELECT {cols} FROM {old_name}').fetchall() except sqlite3.OperationalError: print(f'mandatory table: {old_name} not found. Operation Failed') sys.exit(-1) count = 1 - num_placeholders = len(data[0]) + num_placeholders = len(new_columns) - 1 for row in data: placeholders = ','.join(['?' for _ in range(num_placeholders)]) query = f'INSERT INTO {new_name} VALUES ({count}, {placeholders})' @@ -221,26 +228,27 @@ # More complicated stuff.... fixing the groups print( - '\n --- comparing RPS groups to a common set to see if they can be combined into 1 (except global) ---' + '\n --- comparing RPS groups to a common set to see if they can be combined into 1 ' + '(except global) ---' ) -groups = defaultdict(set) +groups: dict[str, set[str]] = defaultdict(set) # let's ensure all the non-global entries are consistent (same techs in each region) skip_rps = False try: - entries = con_old.execute('SELECT * FROM tech_rps').fetchall() + rps_entries = con_old.execute('SELECT * FROM tech_rps').fetchall() except sqlite3.OperationalError: print('source does not appear to include RPS techs...skipping') skip_rps = True if not skip_rps: - for region, tech, notes in entries: + for region, tech, _notes in rps_entries: groups[region].add(tech) - common = set() - for group, entries in groups.items(): + common: set[str] = set() + for group, group_entries in groups.items(): if group != 'global': - common |= set(entries) + common |= group_entries techs_common = True for group, techs in groups.items(): @@ -249,7 +257,8 @@ techs_common &= not common ^ techs if not techs_common: print( - 'combining RPS techs failed. Some regions are not same. Must be done manually.' + 'combining RPS techs failed. Some regions are not same. Must be done ' + 'manually.' ) if techs_common: @@ -309,8 +318,8 @@ skip_tech_groups = True if not skip_tech_groups: # ------- FIX TABLES THAT USED TO USE tech_groups ----------- - # We'll do this by modifying the group names similar to above (smashing the region-name together to match - # the newly renamed groups + # We'll do this by modifying the group names similar to above (smashing the region-name + # together to match the newly renamed groups tables_using_groups = [ 'MaxActivityGroup', 'MaxActivityShare', @@ -365,7 +374,7 @@ # need to convert null -> 0 for unlim_cap to match new schema that does not allow null new_data = [] for row in data: - new_row = [t for t in row] + new_row = list(row) if new_row[4] is None: new_row[4] = 0 new_data.append(tuple(new_row)) @@ -421,22 +430,22 @@ print('\n --- Moving scalar data elements ---') try: - data = con_old.execute('SELECT * FROM MyopicBaseyear').fetchone() + myopic_result = con_old.execute('SELECT * FROM MyopicBaseyear').fetchone() except sqlite3.OperationalError: - data = None -if data: - mby = data[0] + myopic_result = None +if myopic_result: + mby = myopic_result[0] cur.execute("INSERT OR REPLACE INTO MetaData VALUES ('myopic_base_year', ? , '')", (mby,)) print(f'transferred myopic base year: {mby}') else: print('no myopic base year discovered') try: - data = con_old.execute('SELECT * FROM GlobalDiscountRate').fetchone() + discount_result = con_old.execute('SELECT * FROM GlobalDiscountRate').fetchone() except sqlite3.OperationalError: - data = None -if data: - rate = data[0] + discount_result = None +if discount_result: + rate = discount_result[0] cur.execute( "INSERT OR REPLACE INTO MetaDataReal VALUES ('global_discount_rate', ?, '')", (rate,) ) diff --git a/temoa/utilities/graph_utils.py b/temoa/utilities/graph_utils.py new file mode 100644 index 000000000..7a94dba54 --- /dev/null +++ b/temoa/utilities/graph_utils.py @@ -0,0 +1,282 @@ +# temoa/utilities/graph_utils.py +""" +Utility functions for calculating node positions for network graphs. + +These functions provide deterministic starting positions for nodes based on +their layer (role) and sector, which helps the physics-based layout engine +in vis.js converge to a cleaner and more readable state faster. +""" + +from __future__ import annotations + +import json +import logging +import math +import random +import uuid +from typing import TYPE_CHECKING, Any, TypeVar, cast + +import networkx as nx + +logger = logging.getLogger(__name__) + + +if TYPE_CHECKING: + from collections.abc import Iterable, Sequence + + from temoa.model_checking.network_model_data import EdgeTuple + from temoa.types.core_types import Commodity, Sector, Technology + + GraphType = TypeVar( + 'GraphType', + nx.Graph[Commodity | Technology | str], + nx.DiGraph[Commodity | Technology | str], + nx.MultiGraph[Commodity | Technology | str], + nx.MultiDiGraph[Commodity | Technology | str], + ) +else: + # At runtime, use the base types which are not subscripted. + # The TypeVar still enforces that the graph type is one of these. + GraphType = TypeVar('GraphType', nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph) + + +def convert_graph_to_json[ + GraphType: (nx.Graph[Any], nx.DiGraph[Any], nx.MultiGraph[Any], nx.MultiDiGraph[Any]) +]( + nx_graph: GraphType, + override_node_properties: dict[str, Any] | None, + override_edge_properties: dict[str, Any] | None, + verbosity: int, +) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + """Helper to convert a single NetworkX graph to JSON-serializable lists.""" + nodes_data: list[dict[str, Any]] = [] + node_ids_map: dict[Any, str] = {} + + for node_obj, attrs in nx_graph.nodes(data=True): + node_id_str = str(node_obj) + node_ids_map[node_obj] = node_id_str + node_entry: dict[str, Any] = {'id': node_id_str} + if 'label' not in attrs: + node_entry['label'] = node_id_str + for key, value in attrs.items(): + try: + # Test if the value is JSON serializable + json.dumps(value) + node_entry[key] = value + except (TypeError, OverflowError): + # If not, fall back to a string representation + if verbosity >= 2: + logger.debug( + "Node %s attr '%s' not JSON serializable, converting to string.", + node_id_str, + key, + ) + node_entry[key] = str(value) + if override_node_properties: + node_entry.update({k: v for k, v in override_node_properties.items() if k != 'id'}) + nodes_data.append(node_entry) + + edges_data: list[dict[str, Any]] = [] + for u_obj, v_obj, attrs in nx_graph.edges(data=True): + edge_entry: dict[str, Any] = { + 'from': node_ids_map[u_obj], + 'to': node_ids_map[v_obj], + } + if 'id' not in attrs and nx_graph.is_multigraph(): + edge_entry['id'] = str(uuid.uuid4()) + for key, value in attrs.items(): + try: + # Test if the value is JSON serializable + json.dumps(value) + edge_entry[key] = value + except (TypeError, OverflowError): + # If not, fall back to a string representation + if verbosity >= 2: + logger.debug( + "Edge (%s-%s) attr '%s' not JSON serializable, converting to string.", + u_obj, + v_obj, + key, + ) + edge_entry[key] = str(value) + if override_edge_properties: + edge_entry.update( + {k: v for k, v in override_edge_properties.items() if k not in {'id', 'from', 'to'}} + ) + + edges_data.append(edge_entry) + + return nodes_data, edges_data + + +def calculate_source_positions( + node_layer_map: dict[str, int], + x_pos: int = 0, + y_separation: int = 500, +) -> dict[str, dict[str, Any]]: + """ + Calculates fixed (x, y) positions for SOURCE nodes (Layer 1) only. + + Args: + node_layer_map: Mapping from commodity name to layer ID (1, 2, or 3). + x_pos: The fixed horizontal coordinate for all source nodes. + y_separation: The vertical distance between source nodes. + + Returns: + A dictionary mapping only source node names to their position attributes. + e.g., {'SourceA': {'x': 0, 'y': 0, 'fixed': True}} + """ + positions = {} + # Filter for source nodes (layer 1) and sort them for consistent layout + source_nodes = sorted([node for node, layer in node_layer_map.items() if layer == 1]) + + if not source_nodes: + return {} + + # Calculate a starting y-offset to center the group vertically + num_nodes = len(source_nodes) + start_y = -((num_nodes - 1) * y_separation) / 2 + + for i, node_name in enumerate(source_nodes): + y_pos = start_y + i * y_separation + # Set fixed position only for these source nodes + positions[node_name] = {'x': x_pos, 'y': y_pos, 'fixed': True} + + return positions + + +def calculate_initial_positions( + node_layer_map: dict[str, int], + commodity_to_primary_sector: dict[Commodity, Sector], + unique_sectors: Sequence[Sector] | None = None, +) -> dict[str, dict[str, Any]]: + """ + Calculates an initial (x, y) layout for all nodes to provide a better + starting point for the physics engine. + + - Source nodes (Layer 1) are fixed on the left. + - Other nodes are arranged in clusters based on their primary sector, + with the clusters themselves arranged in a large circle. + """ + positions: dict[str, dict[str, Any]] = {} + + # Prepare to lay out the remaining (non-fixed) nodes: all layers except 1 + nodes_to_place = {cast('Commodity', n) for n, layer in node_layer_map.items() if layer != 1} + if not nodes_to_place: + return positions + + # Use provided unique_sectors if available; else derive from mapping + sectors_to_place = ( + sorted(unique_sectors) + if unique_sectors + else sorted({s for c, s in commodity_to_primary_sector.items() if c in nodes_to_place}) + ) + # ------------------------------------ + + if not sectors_to_place: + return positions + + # Arrange sector "anchors" in a large circle + # Scale radius based on the number of sectors and nodes to handle small models better + num_sectors = len(sectors_to_place) + num_nodes = len(nodes_to_place) + + # Base radius + incremental scaling + layout_radius = max(800, min(2000, 400 + 200 * num_sectors + 2 * num_nodes)) + jitter_radius = layout_radius // 2 + + sector_anchors = {} + + for i, sector in enumerate(sectors_to_place): + angle = (i / num_sectors) * 2 * math.pi + cx = layout_radius * math.cos(angle) + cy = layout_radius * math.sin(angle) + sector_anchors[sector] = (cx, cy) + + # Place each remaining node near its sector's anchor point + for node_name in nodes_to_place: + primary_sector = commodity_to_primary_sector.get(node_name) + if not primary_sector or primary_sector not in sector_anchors: + # Place nodes without a sector or a new sector at the center + cx, cy = 0, 0 + else: + cx, cy = sector_anchors[primary_sector] + + # Stable jitter derived from node name + seed = uuid.uuid5(uuid.NAMESPACE_DNS, node_name).int + rand_angle = ((seed % 3600) / 3600.0) * 2 * math.pi + rand_radius = (seed // 3600) % jitter_radius + x = cx + rand_radius * math.cos(rand_angle) + y = cy + rand_radius * math.sin(rand_angle) + + # Add the position but DO NOT set 'fixed: True' + positions[node_name] = {'x': x, 'y': y} + + return positions + + +def calculate_tech_graph_positions( + all_edges: Iterable[EdgeTuple], +) -> dict[Technology, dict[str, Any]]: + """ + Calculates an initial (x, y) layout for the technology graph. + All technologies are arranged in clusters by sector, with clusters + arranged in a large circle. No nodes are fixed. + """ + positions = {} + + # Materialize the iterable to avoid consumption issues + all_edges_list = list(all_edges) + + # 1. Identify all unique sectors present in the technology list + sectors_to_place = sorted({edge.sector for edge in all_edges_list if edge.sector}) + + if not sectors_to_place: + # If no sectors, just return empty positions and let physics handle it + return {} + + # 2. Arrange sector "anchors" in a large circle + # Scale radius based on the number of sectors and unique technologies + unique_techs_to_place = sorted( + {edge.tech for edge in all_edges_list if edge.tech}, key=lambda t: str(t) + ) + num_sectors = len(sectors_to_place) + num_nodes = len(unique_techs_to_place) + + if not unique_techs_to_place: + return {} + + layout_radius = max(1000, min(2500, 500 + 300 * num_sectors + 5 * num_nodes)) + jitter_radius = layout_radius // 4 + + sector_anchors = {} + + for i, sector in enumerate(sectors_to_place): + angle = (i / num_sectors) * 2 * math.pi + cx = layout_radius * math.cos(angle) + cy = layout_radius * math.sin(angle) + sector_anchors[sector] = (cx, cy) + + # 3. Place each unique technology node near its sector's anchor point with jitter + # Create a mapping of tech to its primary sector from the edges + tech_to_sector = {edge.tech: edge.sector for edge in all_edges_list if edge.tech} + + for tech in unique_techs_to_place: + primary_sector = tech_to_sector.get(tech) + if not primary_sector or primary_sector not in sector_anchors: + # Place nodes without a defined sector at the center + cx, cy = 0, 0 + else: + cx, cy = sector_anchors[primary_sector] + + # Apply deterministic "jitter" to prevent stacking (stable per-tech) + seed = uuid.uuid5(uuid.NAMESPACE_DNS, str(tech)).int + rng = random.Random(seed) + rand_angle = rng.uniform(0, 2 * math.pi) + rand_radius = rng.uniform(0, jitter_radius) + x = cx + rand_radius * math.cos(rand_angle) + y = cy + rand_radius * math.sin(rand_angle) + + positions[tech] = {'x': x, 'y': y} + + return positions diff --git a/temoa/utilities/graphviz_formats.py b/temoa/utilities/graphviz_formats.py new file mode 100644 index 000000000..a14f8dd50 --- /dev/null +++ b/temoa/utilities/graphviz_formats.py @@ -0,0 +1,287 @@ +# SVG Formats + +results_dot_fmt = """\ +strict digraph model {{ + label = "Results for {period}" + + rankdir = "LR" ; + smoothtype = "power_dist" ; + splines = "{splinevar}" ; + + node [ style="filled" ] ; + edge [ arrowhead="vee" ] ; + + subgraph unused_techs {{ + node [ + color = "{unused_color}", + fontcolor = "{unusedfont_color}", + shape = "box", + fontcolor = "{font_color}" + ] ; + + {dtechs} + }} + + subgraph unused_energy_carriers {{ + node [ + color = "{unused_color}", + fontcolor = "{unusedfont_color}", + shape = "circle", + fillcolor = "{fill_color}" + ] ; + + {dcarriers} + }} + + subgraph unused_emissions {{ + node [ + color = "{unused_color}", + fontcolor = "{unusedfont_color}", + shape = "circle", + fillcolor = "{fill_color}" + ] + + {demissions} + }} + + subgraph in_use_techs {{ + node [ + color = "{tech_color}", + fontcolor = "{usedfont_color}", + shape = "box", + fontcolor = "{font_color}" + ] ; + + {etechs} + }} + + subgraph in_use_energy_carriers {{ + node [ + color = "{commodity_color}", + fontcolor = "{usedfont_color}", + shape = "circle", + fillcolor = "{fill_color}" + ] ; + + {ecarriers} + }} + + subgraph in_use_emissions {{ + node [ + color = "{commodity_color}", + fontcolor = "{usedfont_color}", + shape = "circle", + fillcolor = "{fill_color}" + ] ; + + {eemissions} + }} + + subgraph unused_flows {{ + edge [ color="{unused_color}" ] + + {dflows} + }} + + subgraph in_use_flows {{ + subgraph inputs {{ + edge [ color="{arrowheadin_color}" ] ; + + {eflowsi} + }} + + subgraph outputs {{ + edge [ color="{arrowheadout_color}" ] ; + + {eflowso} + }} + }} + + {{rank = same; {xnodes}}} +}} +""" + + +tech_results_dot_fmt = """\ +strict digraph model {{ + label = "Results for {inp_technology} in {period}" ; + + compound = "True" ; + concentrate = "True"; + rankdir = "LR" ; + splines = "{splinevar}" ; + + node [ style="filled" ] ; + edge [ arrowhead="vee" ] ; + + subgraph cluster_vintages {{ + label = "Vintages\\nCapacity: {total_cap:.2f}" ; + + href = "{cluster_vintage_url}" ; + style = "filled" + color = "{sb_vpbackg_color}" + + node [ color="{sb_vp_color}", shape="box", fontcolor="{usedfont_color}" ] ; + + {vnodes} + }} + + subgraph energy_carriers {{ + node [ + color = "{commodity_color}", + fontcolor = "{usedfont_color}", + shape = "circle", + fillcolor = "{fill_color}" + ] ; + + {enodes} + }} + + subgraph inputs {{ + edge [ color="{arrowheadin_color}" ] ; + + {iedges} + }} + + subgraph outputs {{ + edge [ color="{arrowheadout_color}" ] ; + + {oedges} + }} +}} +""" + +slice_dot_fmt = """\ +strict digraph model {{ + label = "Activity split of process {inp_technology}, {vintage} in year {period}" ; + + compound = "True" ; + concentrate = "True"; + rankdir = "LR" ; + splines = "{splinevar}" ; + + node [ style="filled" ] ; + edge [ arrowhead="vee" ] ; + + subgraph cluster_slices {{ + label = "{vintage} Capacity: {total_cap:.2f}" ; + + color = "{vintage_cluster_color}" ; + rank = "same" ; + style = "filled" ; + + node [ color="{vintage_color}", shape="box", fontcolor="{usedfont_color}" ] ; + + {snodes} + }} + + subgraph energy_carriers {{ + node [ + color = "{commodity_color}", + fontcolor = "{usedfont_color}", + shape = "circle", + fillcolor = "{fill_color}" + ] ; + + {enodes} + }} + + subgraph inputs {{ + edge [ color="{input_color}" ] ; + + {iedges} + }} + + subgraph outputs {{ + edge [ color="{output_color}" ] ; + + {oedges} + }} +}} +""" + +commodity_dot_fmt = """\ +strict digraph result_commodity_{inp_commodity} {{ + label = "{inp_commodity} - {period}" ; + + compound = "True" ; + concentrate = "True" ; + rankdir = "LR" ; + splines = "True" ; + + node [ shape="box", style="filled", fontcolor="{font_color}" ] ; + edge [ + arrowhead = "vee", + fontsize = "8", + label = " ", + labelfloat = "False", + labelfontcolor = "lightgreen" + len = "2", + weight = "0.5", + ] ; + + {resource_node} + + subgraph used_techs {{ + node [ color="{tech_color}" ] ; + + {used_nodes} + }} + + subgraph used_techs {{ + node [ color="{unused_color}" ] ; + + {unused_nodes} + }} + + subgraph in_use_flows {{ + edge [ color="{sb_arrow_color}" ] ; + + {used_edges} + }} + + subgraph unused_flows {{ + edge [ color="{unused_color}" ] ; + + {unused_edges} + }} +}} +""" + +quick_run_dot_fmt = """\ +strict digraph model {{ + rankdir = "LR" ; + + // Default node and edge attributes + node [ style="filled" ] ; + edge [ arrowhead="vee", labelfontcolor="lightgreen" ] ; + + // Define individual nodes + subgraph techs {{ + node [ color="{tech_color}", shape="box", fontcolor="{font_color}" ] ; + + {tnodes} + }} + + subgraph energy_carriers {{ + node [ color="{commodity_color}", shape="circle", fillcolor="{fill_color}" ] ; + + {enodes} + }} + + // Define edges and any specific edge attributes + subgraph inputs {{ + edge [ color="{arrowheadin_color}" ] ; + + {iedges} + }} + + subgraph outputs {{ + edge [ color="{arrowheadout_color}" ] ; + + {oedges} + }} + + {{rank = same; {snodes}}} +}} +""" diff --git a/temoa/utilities/graphviz_generator.py b/temoa/utilities/graphviz_generator.py new file mode 100644 index 000000000..07a0568b4 --- /dev/null +++ b/temoa/utilities/graphviz_generator.py @@ -0,0 +1,570 @@ +import argparse +import os +from subprocess import call +from typing import Any, TextIO + +from temoa.utilities.graphviz_formats import ( + commodity_dot_fmt, + quick_run_dot_fmt, + results_dot_fmt, + tech_results_dot_fmt, +) +from temoa.utilities.graphviz_util import ( + create_text_edges, + create_text_nodes, + get_color_config, +) + +from .database_util import DatabaseUtil + + +def process_input(args: list[str]) -> dict[str, Any]: + """Parse command line arguments.""" + parser = argparse.ArgumentParser(description='Generate Output Plot') + parser.add_argument( + '-i', + '--input', + action='store', + dest='ifile', + help='Input Database Filename ', + required=True, + ) + parser.add_argument( + '-f', + '--format', + action='store', + dest='image_format', + help='Graphviz output format (Default: svg)', + default='svg', + ) + parser.add_argument( + '-c', + '--show_capacity', + action='store_true', + dest='show_capacity', + help='Whether capacity shows up in subgraphs (Default: not shown)', + default=False, + ) + parser.add_argument( + '-v', + '--splinevar', + action='store_true', + dest='splinevar', + help='Whether subgraph edges to be straight or curved (Default: Straight)', + default=False, + ) + parser.add_argument( + '-t', + '--graph_type', + action='store', + dest='graph_type', + help='Type of subgraph (Default: separate_vintages)', + choices=['separate_vintages', 'explicit_vintages'], + default='separate_vintages', + ) + parser.add_argument( + '-g', + '--gray', + action='store_true', + dest='grey_flag', + help='If specified, generates graph in graycale', + default=False, + ) + parser.add_argument( + '-n', + '--name', + action='store', + dest='quick_name', + help='Specify the extension you wish to give your quick run', + ) + parser.add_argument( + '-o', + '--output', + action='store', + dest='res_dir', + help='Optional output file path (to dump the images folder)', + default='./', + ) + + group1 = parser.add_mutually_exclusive_group() + group1.add_argument( + '-b', + '--technology', + action='store', + dest='inp_technology', + help='Technology for which graph to be generated', + ) + group1.add_argument( + '-a', + '--commodity', + action='store', + dest='inp_commodity', + help='Commodity for which graph to be generated', + ) + + parser.add_argument( + '-s', + '--scenario', + action='store', + dest='scenario_name', + help='Model run scenario name', + default=None, + ) + parser.add_argument( + '-y', + '--year', + action='store', + dest='period', + type=int, + help='The period for which the graph is to be generated (Used only for output plots)', + ) + parser.add_argument( + '-r', + '--region', + action='store', + dest='region', + help='The region for which the graph is to be generated', + default=None, + ) + + options = parser.parse_args(args) + + if (options.scenario_name is not None) ^ (options.period is not None): + parser.print_help() + raise ValueError('Scenario and input year must both be present or not present together') + + return vars(options) + + +class GraphvizDiagramGenerator: + """Generates Graphviz diagrams for Temoa models.""" + + db_file: str + q_name: str + scenario: str | None + region: str | None + out_dir: str + folder: dict[str, str] + verbose: int + colors: dict[str, Any] + db_util: DatabaseUtil + logger: TextIO + grey_flag: bool + + def __init__( + self, + db_file: str, + scenario: str | None = None, + region: str | None = None, + out_dir: str = '.', + verbose: int = 1, + ) -> None: + """Initialize the GraphvizDiagramGenerator.""" + self.db_file = db_file + self.q_name = os.path.splitext(os.path.basename(self.db_file))[0] + self.scenario = scenario + self.region = region + self.out_dir = out_dir + self.folder = {'results': 'whole_system', 'tech': 'processes', 'comm': 'commodities'} + self.verbose = verbose + self.colors = {} + self.grey_flag = False + + def connect(self) -> None: + """Connect to the database and set up output directories.""" + self.db_util = DatabaseUtil(self.db_file, self.scenario) + self.logger = open(os.path.join(self.out_dir, 'graphviz.log'), 'w') + self.set_graphic_options(False, False) + self.__log__('--------------------------------------') + self.__log__('GraphvizDiagramGenerator: connected') + if self.scenario: + out_dir = f'{self.q_name}_{self.scenario}_graphviz' + else: + out_dir = f'{self.q_name}_input_graphviz' + + self.out_dir = os.path.join(self.out_dir, out_dir) + if not os.path.exists(self.out_dir): + os.mkdir(self.out_dir) + + def close(self) -> None: + """Disconnect from the database and close the logger.""" + self.db_util.close() + self.__log__('GraphvizDiagramGenerator: disconnected') + self.__log__('--------------------------------------') + self.logger.close() + + def __log__(self, msg: str) -> None: + """Log a message to the console and a log file.""" + if self.verbose == 1: + print(msg) + self.logger.write(msg + '\n') + + def __generate_graph__( + self, dot_format: str, dot_args: dict[str, Any], output_name: str, output_format: str + ) -> None: + """Generate a graph from a DOT format string.""" + dot_args.update(self.colors) + with open(output_name + '.dot', 'w') as f: + f.write(dot_format.format(**dot_args)) + cmd = ( + 'dot', + f'-T{output_format}', + f'-o{output_name}.{output_format}', + f'{output_name}.dot', + ) + call(cmd) + + def set_graphic_options( + self, grey_flag: bool | None = None, splinevar: bool | None = None + ) -> None: + """Set graphic options for the diagrams.""" + if grey_flag is not None: + self.grey_flag = grey_flag + self.colors.update(get_color_config(grey_flag=self.grey_flag)) + if splinevar is not None: + self.colors['splinevar'] = 'spline' if splinevar else 'line' + self.__log__( + f'setGraphicOption: updated greyFlag = {self.grey_flag} ' + f'and splinevar = {self.colors.get("splinevar", "line")}' + ) + + def create_main_results_diagram( + self, period: int, region: str | None, output_format: str = 'svg' + ) -> tuple[str, str]: + """Create the main results diagram for a specific period.""" + self.__log__(f'CreateMainResultsDiagram: started with period = {period}') + + results_dir = os.path.join(self.out_dir, self.folder['results']) + if not os.path.exists(results_dir): + os.makedirs(results_dir) + + output_name = os.path.join(self.folder['results'], f'results{period}') + if self.region: + output_name += f'_{self.region}' + output_name = os.path.join(self.out_dir, output_name) + if self.grey_flag: + output_name += '.grey' + + tech_all = self.db_util.get_technologies_for_flags(flags=['r', 'p', 'pb', 'ps']) + commodity_carrier = self.db_util.get_commodities_for_flags(flags=['d', 'p']) + commodity_emissions = self.db_util.get_commodities_for_flags(flags=['e']) + efficiency_input = self.db_util.get_commodities_by_technology(region, comm_type='input') + efficiency_output = self.db_util.get_commodities_by_technology(region, comm_type='output') + v_cap2 = self.db_util.get_capacity_for_tech_and_period(period=period, region=region) + ei_2 = self.db_util.get_output_flow_for_period( + period=period, region=region, comm_type='input' + ) + eo_2 = self.db_util.get_output_flow_for_period( + period=period, region=region, comm_type='output' + ) + emio_2 = self.db_util.get_emissions_activity_for_period(period=period, region=region) + + self.__log__('CreateMainResultsDiagram: database fetched successfully') + + tech_attr_fmt = ( + 'label="%s\\nCapacity: %.2f", href="#", ' + "onclick=\"loadNextGraphvizGraph('results', '%s', '%s')\"" + ) + commodity_fmt = "href=\"#\", onclick=\"loadNextGraphvizGraph('results', '%s', '%s')\"" + flow_fmt = 'label="%.2f"' + epsilon = 0.005 + + etechs: set[tuple[str, str]] = set() + dtechs: set[tuple[str, str]] = set() + ecarriers: set[tuple[str, str]] = set() + xnodes: set[tuple[str, str]] = set() + eemissions: set[tuple[str, str]] = set() + eflowsi: set[tuple[str, str, str]] = set() + eflowso: set[tuple[str, str, str]] = set() + dflows: set[tuple[str, str, str]] = set() + usedc, usede = set(), set() # used carriers, used emissions + + v_cap2.index = v_cap2.tech + for tech in set(tech_all) - set(v_cap2.tech): + dtechs.add((tech, '')) + + for i in range(len(v_cap2)): + row = v_cap2.iloc[i] + etechs.add( + (row['tech'], tech_attr_fmt % (row['tech'], row['capacity'], row['tech'], period)) + ) + + udflows: set[tuple[str, str]] = set() + for i in range(len(ei_2)): + row = ei_2.iloc[i] + if row['input_comm'] != 'ethos': + eflowsi.add((row['input_comm'], row['tech'], flow_fmt % row['flow'])) + ecarriers.add((row['input_comm'], commodity_fmt % (row['input_comm'], period))) + usedc.add(row['input_comm']) + else: + tech = row['tech'] + cap = v_cap2.loc[row['tech']].capacity if tech in v_cap2.tech else 99999 + xnodes.add((row['tech'], tech_attr_fmt % (row['tech'], cap, row['tech'], period))) + udflows.add((row['input_comm'], row['tech'])) + + for row in set(efficiency_input) - udflows: + if row[0] != 'ethos': + dflows.add((row[0], row[1], '')) + else: + xnodes.add((row[1], '')) + + udflows = set() + for i in range(len(eo_2)): + row = eo_2.iloc[i] + eflowso.add((row['tech'], row['output_comm'], flow_fmt % row['flow'])) + ecarriers.add((row['output_comm'], commodity_fmt % (row['output_comm'], period))) + usedc.add(row['output_comm']) + udflows.add((row['tech'], row['output_comm'])) + + for row in set(efficiency_output) - udflows: + dflows.add((row[0], row[1], '')) + + for i in range(len(emio_2)): + row = emio_2.iloc[i] + if row['emis_activity'] >= epsilon: + eflowso.add((row['tech'], row['emis_comm'], flow_fmt % row['emis_activity'])) + eemissions.add((row['emis_comm'], '')) + usede.add(row['emis_comm']) + + dcarriers = {(cc, '') for cc in commodity_carrier if cc not in usedc and cc != 'ethos'} + demissions = {(ee, '') for ee in commodity_emissions if ee not in usede} + + self.__log__('CreateMainResultsDiagram: creating diagrams') + args = { + 'period': period, + 'splinevar': self.colors['splinevar'], + 'dtechs': create_text_nodes(dtechs, indent=2), + 'etechs': create_text_nodes(etechs, indent=2), + 'xnodes': create_text_nodes(xnodes, indent=2), + 'dcarriers': create_text_nodes(dcarriers, indent=2), + 'ecarriers': create_text_nodes(ecarriers, indent=2), + 'demissions': create_text_nodes(demissions, indent=2), + 'eemissions': create_text_nodes(eemissions, indent=2), + 'dflows': create_text_edges(dflows, indent=2), + 'eflowsi': create_text_edges(eflowsi, indent=3), + 'eflowso': create_text_edges(eflowso, indent=3), + } + + output_path = output_name + '.' + output_format + self.__generate_graph__(results_dot_fmt, args, output_name, output_format) + self.__log__('CreateMainResultsDiagram: graph generated, returning') + return self.out_dir, output_path + + def create_tech_results_diagrams( + self, period: int, region: str | None, tech: str, output_format: str = 'svg' + ) -> tuple[str, str]: + """Create technology-specific results diagrams.""" + self.__log__(f'CreateTechResultsDiagrams: started with period = {period} and tech = {tech}') + + tech_dir = os.path.join(self.out_dir, self.folder['tech']) + if not os.path.exists(tech_dir): + os.makedirs(tech_dir) + + output_name = os.path.join(self.folder['tech'], f'results_{tech}_{period}') + if self.region: + output_name += f'_{self.region}' + output_name = os.path.join(self.out_dir, output_name) + if self.grey_flag: + output_name += '.grey' + + enode_attr_fmt = "href=\"#\", onclick=\"loadNextGraphvizGraph('results', '%s', '%s')\"" + vnode_attr_fmt = ( + "href=\"#\", onclick=\"loadNextGraphvizGraph('%s', '%s', '%s')\", " + 'label="%s\\nCap: %.2f"' + ) + + total_cap = self.db_util.get_capacity_for_tech_and_period(tech, period, region) + flows = self.db_util.get_commodity_wise_input_and_output_flow(tech, period, region) + self.__log__('CreateTechResultsDiagrams: database fetched successfully') + + enodes: set[tuple[str, str]] = set() + vnodes: set[tuple[str, str]] = set() + iedges: set[tuple[str, str, str]] = set() + oedges: set[tuple[str, str, str]] = set() + + for i in range(len(flows)): + row = flows.iloc[i] + vnode = str(row['vintage']) + vnodes.add( + ( + vnode, + vnode_attr_fmt + % (tech, period, row['vintage'], row['vintage'], row['capacity']), + ) + ) + + if row['input_comm'] != 'ethos': + enodes.add((row['input_comm'], enode_attr_fmt % (row['input_comm'], period))) + iedges.add((row['input_comm'], vnode, 'label="{:.2f}"'.format(row['flow_in']))) + enodes.add((row['output_comm'], enode_attr_fmt % (row['output_comm'], period))) + oedges.add((vnode, row['output_comm'], 'label="{:.2f}"'.format(row['flow_out']))) + + output_path = output_name + '.' + output_format + if vnodes: + self.__log__('CreateTechResultsDiagrams: creating diagrams') + args: dict[str, Any] = { + 'cluster_vintage_url': '#', + 'total_cap': total_cap, + 'inp_technology': tech, + 'period': period, + 'vnodes': create_text_nodes(vnodes, indent=2), + 'enodes': create_text_nodes(enodes, indent=2), + 'iedges': create_text_edges(iedges, indent=2), + 'oedges': create_text_edges(oedges, indent=2), + } + self.__generate_graph__(tech_results_dot_fmt, args, output_name, output_format) + else: + self.__log__('CreateTechResultsDiagrams: nothing to create') + + self.__log__('CreateTechResultsDiagrams: graph generated, returning') + return self.out_dir, output_path + + def create_commodity_partial_results( + self, period: int, region: str | None, comm: str, output_format: str = 'svg' + ) -> tuple[str, str]: + """Create commodity-specific partial results diagrams.""" + self.__log__( + f'CreateCommodityPartialResults: started with period = {period} and comm = {comm}' + ) + + comm_dir = os.path.join(self.out_dir, self.folder['comm']) + if not os.path.exists(comm_dir): + os.makedirs(comm_dir) + + output_name = os.path.join(self.folder['comm'], f'rc_{comm}_{period}') + if self.region: + output_name += f'_{self.region}' + output_name = os.path.join(self.out_dir, output_name) + if self.grey_flag: + output_name += '.grey' + + input_total = set( + self.db_util.get_existing_technologies_for_commodity(comm, region, 'output')['tech'] + ) + output_total = set( + self.db_util.get_existing_technologies_for_commodity(comm, region, 'input')['tech'] + ) + flow_in = self.db_util.get_output_flow_for_period(period, region, 'input', comm) + otechs = set(flow_in['tech']) + flow_out = self.db_util.get_output_flow_for_period(period, region, 'output', comm) + itechs = set(flow_out['tech']) + self.__log__('CreateCommodityPartialResults: database fetched successfully') + + node_attr_fmt = "href=\"#\", onclick=\"loadNextGraphvizGraph('results', '%s', '%s')\"" + rc_node_fmt = 'color="%s", href="%s", shape="circle", fillcolor="%s", fontcolor="black"' + + enodes: set[tuple[str, str]] = set() + dnodes: set[tuple[str, str]] = set() + eedges: set[tuple[str, str, str]] = set() + dedges: set[tuple[str, str, str]] = set() + + rcnode = ( + (comm, rc_node_fmt % (self.colors['commodity_color'], '#', self.colors['fill_color'])), + ) + + for i in range(len(flow_in)): + t = flow_in.iloc[i]['tech'] + f = flow_in.iloc[i]['flow'] + enodes.add((t, node_attr_fmt % (t, period))) + eedges.add((comm, t, f'label="{f:.2f}"')) + for t in output_total - otechs: + dnodes.add((t, '')) + dedges.add((comm, t, '')) + for i in range(len(flow_out)): + t = flow_out.iloc[i]['tech'] + f = flow_out.iloc[i]['flow'] + enodes.add((t, node_attr_fmt % (t, period))) + eedges.add((t, comm, f'label="{f:.2f}"')) + for t in input_total - itechs: + dnodes.add((t, '')) + dedges.add((t, comm, '')) + + self.__log__('CreateCommodityPartialResults: creating diagrams') + args = { + 'inp_commodity': comm, + 'period': period, + 'resource_node': create_text_nodes(rcnode), + 'used_nodes': create_text_nodes(enodes, indent=2), + 'unused_nodes': create_text_nodes(dnodes, indent=2), + 'used_edges': create_text_edges(eedges, indent=2), + 'unused_edges': create_text_edges(dedges, indent=2), + } + output_path = output_name + '.' + output_format + self.__generate_graph__(commodity_dot_fmt, args, output_name, output_format) + self.__log__('CreateCommodityPartialResults: graph generated, returning') + return self.out_dir, output_path + + def create_complete_input_graph( + self, + region: str | None, + inp_tech: str | None = None, + inp_comm: str | None = None, + output_format: str = 'svg', + ) -> tuple[str, str]: + """Generate the complete input graph.""" + self.__log__( + f'createCompleteInputGraph: started with inp_tech = {inp_tech} ' + f'and inp_comm = {inp_comm}' + ) + output_name = self.q_name + + if inp_tech: + output_name += f'_{inp_tech}' + tech_dir = os.path.join(self.out_dir, self.folder['tech']) + if not os.path.exists(tech_dir): + os.makedirs(tech_dir) + output_name = os.path.join(self.folder['tech'], output_name) + elif inp_comm: + output_name += f'_{inp_comm}' + comm_dir = os.path.join(self.out_dir, self.folder['comm']) + if not os.path.exists(comm_dir): + os.makedirs(comm_dir) + output_name = os.path.join(self.folder['comm'], output_name) + else: + results_dir = os.path.join(self.out_dir, self.folder['results']) + if not os.path.exists(results_dir): + os.makedirs(results_dir) + output_name = os.path.join(self.folder['results'], output_name) + + if self.region: + output_name += f'_{self.region}' + + output_name = os.path.join(self.out_dir, output_name) + if self.grey_flag: + output_name += '.grey' + + nodes, tech, ltech, to_tech, from_tech = set(), set(), set(), set(), set() + + res = ( + self.db_util.get_commodities_and_tech(inp_comm, inp_tech, region) + if DatabaseUtil.is_database_file(self.db_file) + else self.db_util.read_from_dat_file(inp_comm, inp_tech) + ) + + self.__log__('createCompleteInputGraph: database fetched successfully') + for i in range(len(res)): + row = res.iloc[i] + if row['input_comm'] != 'ethos': + nodes.add(row['input_comm']) + else: + ltech.add(row['tech']) + nodes.add(row['output_comm']) + tech.add(row['tech']) + + if row['input_comm'] != 'ethos': + to_tech.add(f'"{row["input_comm"]}"\t->\t"{row["tech"]}"') + from_tech.add(f'"{row["tech"]}"\t->\t"{row["output_comm"]}"') + + self.__log__('createCompleteInputGraph: creating diagrams') + + args = { + 'enodes': ''.join(f'"{x}";\n\t\t' for x in nodes), + 'tnodes': ''.join(f'"{x}";\n\t\t' for x in tech), + 'iedges': ''.join(f'{x};\n\t\t' for x in to_tech), + 'oedges': ''.join(f'{x};\n\t\t' for x in from_tech), + 'snodes': ';'.join(f'"{x}"' for x in ltech), + } + output_path = output_name + '.' + output_format + self.__generate_graph__(quick_run_dot_fmt, args, output_name, output_format) + self.__log__('createCompleteInputGraph: graph generated, returning') + return self.out_dir, output_path diff --git a/temoa/utilities/graphviz_util.py b/temoa/utilities/graphviz_util.py new file mode 100644 index 000000000..31a320feb --- /dev/null +++ b/temoa/utilities/graphviz_util.py @@ -0,0 +1,126 @@ +from collections.abc import Callable, Iterable, Sequence, Sized + + +def get_color_config(*, grey_flag: bool) -> dict[str, str | tuple[str, ...]]: + """Return a dictionary of color configurations for the graph.""" + + is_colored = not grey_flag + kwargs: dict[str, str | tuple[str, ...]] = { + 'tech_color': 'darkseagreen' if is_colored else 'black', + 'commodity_color': 'lightsteelblue' if is_colored else 'black', + 'unused_color': 'powderblue' if is_colored else 'gray75', + 'arrowheadout_color': 'forestgreen' if is_colored else 'black', + 'arrowheadin_color': 'firebrick' if is_colored else 'black', + 'usedfont_color': 'black', + 'unusedfont_color': 'chocolate' if is_colored else 'gray75', + 'menu_color': 'hotpink', + 'home_color': 'gray75', + 'font_color': 'black' if is_colored else 'white', + 'fill_color': 'lightsteelblue' if is_colored else 'white', + # MODELDETAILED, + 'md_tech_color': 'hotpink', + 'sb_incom_color': 'lightsteelblue' if is_colored else 'black', + 'sb_outcom_color': 'lawngreen' if is_colored else 'black', + 'sb_vpbackg_color': 'lightgrey', + 'sb_vp_color': 'white', + 'sb_arrow_color': 'forestgreen' if is_colored else 'black', + # SUBGRAPH 1 ARROW COLORS + 'color_list': ( + ( + 'red', + 'orange', + 'gold', + 'green', + 'blue', + 'purple', + 'hotpink', + 'cyan', + 'burlywood', + 'coral', + 'limegreen', + 'black', + 'brown', + ) + if is_colored + else ('black', 'black') + ), + } + return kwargs + + +def _get_len(key: int) -> Callable[[Sequence[Sized]], int]: + """Return a function that gets the length of an item at a specific index in a sequence.""" + + def wrapped(obj: Sequence[Sized]) -> int: + return len(obj[key]) + + return wrapped + + +def create_text_nodes(nodes: Iterable[tuple[str, str]], indent: int = 1) -> str: + """ + Return a set of text nodes in Graphviz DOT format, optimally padded for + easier reading and debugging. + + Args: + nodes: iterable of (id, attribute) node tuples + e.g. [(node1, attr1), (node2, attr2), ...] + indent: integer, number of tabs with which to indent all Dot node lines + """ + # Convert to list to avoid consuming iterable multiple times if it's an iterator + nodes_list = list(nodes) + if not nodes_list: + return '// no nodes in this section\n' + + # Step 1: for alignment, get max item length in node list + # The `+ 2` accounts for the two extra quotes that will be added. + maxl = max(map(_get_len(0), nodes_list), default=0) + 2 + + # Step 2: prepare a text format based on max node size that pads all + # lines with attributes + nfmt_attr = f'{{0:<{maxl}}} [ {{1}} ] ;' # node text format + nfmt_noa = '{0} ;' + + # Step 3: create each node, and place string representation in a set to + # guarantee uniqueness + q = '"%s"' # enforce quoting for all nodes + gviz = {nfmt_attr.format(q % n, a) for n, a in nodes_list if a} + gviz.update(nfmt_noa.format(q % n) for n, a in nodes_list if not a) + + # Step 4: return a sorted version of nodes, as a single string + indent_str = '\n' + '\t' * indent + return indent_str.join(sorted(gviz)) + + +def create_text_edges(edges: Iterable[tuple[str, str, str]], indent: int = 1) -> str: + """ + Return a set of text edge definitions in Graphviz DOT format, optimally + padded for easier reading and debugging. + + Args: + edges: iterable of (from, to, attribute) edge tuples + e.g. [(inp1, tech1, attr1), (inp2, tech2, attr2), ...] + indent: integer, number of tabs with which to indent all Dot edge lines + """ + # Convert to list + edges_list = list(edges) + if not edges_list: + return '// no edges in this section\n' + + # Step 1: for alignment, get max length of items on left and right side of + # graph operator token ('->'). The `+ 2` accounts for the two extra quotes. + maxl = max(map(_get_len(0), edges_list), default=0) + 2 + maxr = max(map(_get_len(1), edges_list), default=0) + 2 + + # Step 2: prepare format to be "\n\tinp+PADDING -> out+PADDING [..." + efmt_attr = f'{{0:<{maxl}}} -> {{1:<{maxr}}} [ {{2}} ] ;' # with attributes + efmt_noa = f'{{0:<{maxl}}} -> {{1}} ;' # no attributes + + # Step 3: add each edge to a set (to guarantee unique entries only) + q = '"%s"' # enforce quoting for all tokens + gviz = {efmt_attr.format(q % i, q % t, a) for i, t, a in edges_list if a} + gviz.update(efmt_noa.format(q % i, q % t) for i, t, a in edges_list if not a) + + # Step 4: return a sorted version of the edges, as a single string + indent_str = '\n' + '\t' * indent + return indent_str.join(sorted(gviz)) diff --git a/temoa/utilities/master_migration.py b/temoa/utilities/master_migration.py new file mode 100644 index 000000000..154eb3927 --- /dev/null +++ b/temoa/utilities/master_migration.py @@ -0,0 +1,499 @@ +import argparse +import os +import re +import sqlite3 + +import tempfile +from pathlib import Path +from typing import Any + +# Mapping config +CUSTOM_MAP: dict[str, str] = { + 'TimeNext': 'time_manual', + 'CommodityDStreamProcess': 'commodity_down_stream_process', + 'commodityUStreamProcess': 'commodity_up_stream_process', + 'SegFrac': 'segment_fraction', + 'segfrac': 'segment_fraction', + 'MetaDataReal': 'metadata_real', + 'MetaData': 'metadata', + 'Myopicefficiency': 'myopic_efficiency', + 'DB_MAJOR': 'db_major', + 'DB_MINOR': 'db_minor', +} + +CUSTOM_EXACT_ONLY = {'time_season', 'time_season_sequential'} +CUSTOM_KEYS_SORTED = sorted( + [k for k in CUSTOM_MAP.keys() if k not in CUSTOM_EXACT_ONLY], key=lambda k: -len(k) +) + +OPERATOR_ADDED_TABLES = { + 'EmissionLimit': ('limit_emission', 'le'), + 'TechOutputSplit': ('limit_tech_output_split', 'ge'), + 'TechInputSplitAnnual': ('limit_tech_input_split_annual', 'ge'), + 'TechInputSplitAverage': ('limit_tech_input_split_annual', 'ge'), + 'TechInputSplit': ('limit_tech_input_split', 'ge'), + 'MinNewCapacityShare': ('limit_new_capacity_share', 'ge'), + 'MinNewCapacityGroupShare': ('limit_new_capacity_share', 'ge'), + 'MinNewCapacityGroup': ('limit_new_capacity', 'ge'), + 'MinNewCapacity': ('limit_new_capacity', 'ge'), + 'MinCapacityShare': ('limit_capacity_share', 'ge'), + 'MinCapacityGroup': ('limit_capacity', 'ge'), + 'MinCapacity': ('limit_capacity', 'ge'), + 'MinActivityShare': ('limit_activity_share', 'ge'), + 'MinActivityGroup': ('limit_activity', 'ge'), + 'MinActivity': ('limit_activity', 'ge'), + 'MaxNewCapacityShare': ('limit_new_capacity_share', 'le'), + 'MaxNewCapacityGroupShare': ('limit_new_capacity_share', 'le'), + 'MaxNewCapacityGroup': ('limit_new_capacity', 'le'), + 'MaxNewCapacity': ('limit_new_capacity', 'le'), + 'MaxCapacityShare': ('limit_capacity_share', 'le'), + 'MaxCapacityGroup': ('limit_capacity', 'le'), + 'MaxCapacity': ('limit_capacity', 'le'), + 'MaxActivityShare': ('limit_activity_share', 'le'), + 'MaxActivityGroup': ('limit_activity', 'le'), + 'MaxActivity': ('limit_activity', 'le'), + 'MaxResource': ('limit_resource', 'le'), +} + +PERIOD_TO_VINTAGE_TABLES = { + 'limit_new_capacity_share', + 'limit_new_capacity', +} + + +def to_snake_case(s: str) -> str: + if not s: + return s + if s == s.lower() and '_' in s: + return s + x = s.replace('-', '_').replace(' ', '_') + x = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', x) + x = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', x) + x = re.sub(r'__+', '_', x) + return x.lower() + + +def map_token_no_cascade(token: str) -> str: + if not token: + return token + mapped_values = {v.lower() for v in CUSTOM_MAP.values()} + if token.lower() in mapped_values: + return token.lower() + if token in CUSTOM_MAP: + return CUSTOM_MAP[token].lower() + tl = token.lower() + for k, v in CUSTOM_MAP.items(): + if tl == k.lower(): + return v.lower() + if any(c.isupper() for c in token): + return to_snake_case(token) + orig = token + orig_lower = orig.lower() + replacements: list[tuple[str, str]] = [ + (k, CUSTOM_MAP[k]) for k in CUSTOM_KEYS_SORTED if k.lower() in orig_lower + ] + if replacements: + out = [] + i = 0 + length = len(orig) + while i < length: + matched = False + for key, repl in replacements: + kl = len(key) + if i + kl <= length and orig[i : i + kl].lower() == key.lower(): + out.append(repl) + i += kl + matched = True + break + if not matched: + out.append(orig[i]) + i += 1 + return re.sub(r'__+', '_', ''.join(out)).lower() + return to_snake_case(token) + + +def get_table_info(conn: sqlite3.Connection, table: str) -> list[tuple[Any, ...]]: + try: + return conn.execute(f'PRAGMA table_info({table});').fetchall() + except sqlite3.OperationalError: + return [] + + +def _migrate_operator_tables(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> int: + """Migrate max/min tables to operator constraints.""" + print('--- Migrating max/min tables to operator constraints ---') + total = 0 + for old_name, (new_name, operator) in OPERATOR_ADDED_TABLES.items(): + try: + data = con_old.execute(f'SELECT * FROM {old_name}').fetchall() + except sqlite3.OperationalError: + continue + if not data: + continue + + new_cols = [c[1] for c in get_table_info(con_new, new_name)] + if 'operator' not in new_cols: + continue + + op_index = new_cols.index('operator') + assert 0 <= op_index < len(new_cols), f'Operator column missing or invalid for {new_name}' + + data = [(*row[0:op_index], operator, *row[op_index : len(new_cols) - 1]) for row in data] + + # Move period to vintage if applicable + if new_name in PERIOD_TO_VINTAGE_TABLES: + old_cols = [c[1] for c in get_table_info(con_old, old_name)] + if 'period' in old_cols and 'vintage' in new_cols: + period_index = old_cols.index('period') + vintage_index = new_cols.index('vintage') + data = [ + ( + *row[0:period_index], + *row[period_index + 1 : vintage_index + 1], + row[period_index], + *row[vintage_index + 1 :], + ) + for row in data + ] + + placeholders = ','.join(['?'] * len(data[0])) + query = f'INSERT OR REPLACE INTO {new_name} VALUES ({placeholders})' + con_new.executemany(query, data) + print(f'Migrated {len(data)} rows: {old_name} -> {new_name}') + total += len(data) + return total + + +def _migrate_standard_tables(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> int: + """Migrate standard tables by mapping names and columns.""" + print('--- Executing standard table migrations ---') + total = 0 + old_tables = [ + r[0] + for r in con_old.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + ] + new_tables = [ + r[0] + for r in con_new.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + ] + custom_handled_old_tables = { + 'MetaData', + 'MetaDataReal', + 'TimeSeason', + 'time_season', + 'time_of_day', + 'time_season_sequential', + 'TimeSeasonSequential', + 'TimeSegmentFraction', + 'LoanLifetimeTech', + 'CapacityFactorProcess', + }.union(OPERATOR_ADDED_TABLES.keys()) + + for old in old_tables: + if old.lower().startswith('sqlite_') or old in custom_handled_old_tables: + continue + + new = map_token_no_cascade(old) + if new not in new_tables: + candidates = [t for t in new_tables if t == new or t.startswith(new) or new in t] + if len(candidates) == 1: + new = candidates[0] + else: + continue + + old_cols = [c[1] for c in get_table_info(con_old, old)] + if not old_cols: + continue + new_cols = [c[1] for c in get_table_info(con_new, new)] + + selectable_old_cols, insert_new_cols = [], [] + for oc in old_cols: + mapped = map_token_no_cascade(oc) + if mapped == 'seg_frac': + mapped = 'segment_fraction' + if mapped in new_cols: + selectable_old_cols.append(oc) + insert_new_cols.append(mapped) + + if not selectable_old_cols: + continue + + sel_clause = ','.join(selectable_old_cols) + rows = con_old.execute(f'SELECT {sel_clause} FROM {old}').fetchall() + filtered = [r for r in rows if any(v is not None for v in r)] + if not filtered: + continue + + placeholders = ','.join(['?'] * len(insert_new_cols)) + q = f'INSERT OR REPLACE INTO {new} ({",".join(insert_new_cols)}) VALUES ({placeholders})' + con_new.executemany(q, filtered) + print(f'Copied {len(filtered)} rows: {old} -> {new}') + total += len(filtered) + return total + + +def _migrate_loan_lifetime(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> int: + """Migrate LoanLifetimeTech to loan_lifetime_process.""" + total = 0 + try: + data = con_old.execute( + 'SELECT region, tech, lifetime, notes FROM LoanLifetimeTech' + ).fetchall() + if data: + new_data = [] + for row in data: + vints = [ + v[0] + for v in con_old.execute( + 'SELECT vintage FROM Efficiency WHERE region=? AND tech=?', (row[0], row[1]) + ).fetchall() + ] + for v in vints: + new_data.append((row[0], row[1], v, row[2], row[3])) + con_new.executemany( + 'INSERT OR REPLACE INTO loan_lifetime_process (region, tech, vintage, lifetime, notes) VALUES (?,?,?,?,?)', + new_data, + ) + print(f'Migrated {len(new_data)} rows: LoanLifetimeTech -> loan_lifetime_process') + total += len(new_data) + except sqlite3.OperationalError: + pass + return total + + +def _migrate_time_tables(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> int: + """Migrate time-related tables (season, tod, sequential).""" + total = 0 + # 1. time_season + try: + old_data = [] + cols = [c[1] for c in get_table_info(con_old, 'TimeSegmentFraction')] + if 'period' in cols: + old_data = con_old.execute( + 'SELECT season, SUM(segfrac) / COUNT(DISTINCT period) FROM TimeSegmentFraction GROUP BY season' + ).fetchall() + else: + old_data = con_old.execute( + 'SELECT season, SUM(segfrac) FROM TimeSegmentFraction GROUP BY season' + ).fetchall() + + if old_data: + con_new.executemany( + 'INSERT OR REPLACE INTO time_season (season, segment_fraction) VALUES (?, ?)', + old_data, + ) + print(f'Propagated {len(old_data)} seasons to time_season.') + total += len(old_data) + except sqlite3.OperationalError: + pass + + # 2. time_of_day + try: + old_data = [] + cols = [c[1] for c in get_table_info(con_old, 'TimeSegmentFraction')] + if 'period' in cols: + old_data = con_old.execute( + 'SELECT tod, SUM(segfrac) FROM TimeSegmentFraction GROUP BY tod' + ).fetchall() + if old_data: + num_periods = ( + con_old.execute( + 'SELECT COUNT(DISTINCT period) FROM TimeSegmentFraction' + ).fetchone()[0] + or 1 + ) + normalized_data = [(r[0], (r[1] / num_periods) * 24.0) for r in old_data] + con_new.executemany( + 'INSERT OR REPLACE INTO time_of_day (tod, hours) VALUES (?, ?)', normalized_data + ) + print(f'Propagated {len(normalized_data)} time-of-day slots to time_of_day.') + total += len(normalized_data) + else: + old_data = con_old.execute( + 'SELECT tod, SUM(segfrac) FROM TimeSegmentFraction GROUP BY tod' + ).fetchall() + if old_data: + normalized_data = [(r[0], r[1] * 24.0) for r in old_data] + con_new.executemany( + 'INSERT OR REPLACE INTO time_of_day (tod, hours) VALUES (?, ?)', normalized_data + ) + print(f'Propagated {len(normalized_data)} time-of-day slots to time_of_day.') + total += len(normalized_data) + except sqlite3.OperationalError: + pass + + # 3. time_season_sequential + try: + old_data = [] + cols = [c[1] for c in get_table_info(con_old, 'TimeSeasonSequential')] + if 'period' in cols: + first_period = con_old.execute( + 'SELECT MIN(period) FROM TimeSeasonSequential' + ).fetchone()[0] + if first_period: + old_data = con_old.execute( + 'SELECT seas_seq, season, (num_days / 365.25) FROM TimeSeasonSequential WHERE period = ?', + (first_period,), + ).fetchall() + else: + old_data = con_old.execute( + 'SELECT seas_seq, season, (num_days / 365.25) FROM TimeSeasonSequential' + ).fetchall() + + if old_data: + con_new.executemany( + 'INSERT OR REPLACE INTO time_season_sequential ' + '(seas_seq, season, segment_fraction) VALUES (?, ?, ?)', + old_data, + ) + print(f'Propagated {len(old_data)} sequential seasons to time_season_sequential.') + total += len(old_data) + except sqlite3.OperationalError: + pass + return total + + +def _migrate_capacity_factor(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> int: + """Migrate CapacityFactorProcess.""" + total = 0 + try: + old_data = [] + cols = [c[1] for c in get_table_info(con_old, 'CapacityFactorProcess')] + if cols: + if 'period' in cols: + old_data = con_old.execute( + 'SELECT region, season, tod, tech, vintage, AVG(factor) FROM CapacityFactorProcess ' + 'GROUP BY region, season, tod, tech, vintage' + ).fetchall() + else: + old_data = con_old.execute( + 'SELECT region, season, tod, tech, vintage, factor FROM CapacityFactorProcess' + ).fetchall() + if old_data: + con_new.executemany( + 'INSERT OR REPLACE INTO capacity_factor_process (region, season, tod, tech, vintage, factor) VALUES (?,?,?,?,?,?)', + old_data, + ) + print( + f'Copied {len(old_data)} rows: CapacityFactorProcess -> capacity_factor_process' + ) + total += len(old_data) + except sqlite3.OperationalError: + pass + return total + + +def execute_v3_to_v4_migration(con_old: sqlite3.Connection, con_new: sqlite3.Connection) -> None: + """Main migration logic router.""" + total = 0 + total += _migrate_operator_tables(con_old, con_new) + total += _migrate_standard_tables(con_old, con_new) + + print('--- Processing custom migration logic ---') + total += _migrate_loan_lifetime(con_old, con_new) + total += _migrate_time_tables(con_old, con_new) + total += _migrate_capacity_factor(con_old, con_new) + + # Final Updates + con_new.execute("UPDATE technology SET flag='p' WHERE flag='r';") + con_new.execute("INSERT OR REPLACE INTO metadata VALUES ('DB_MAJOR', 4, '')") + con_new.execute("INSERT OR REPLACE INTO metadata VALUES ('DB_MINOR', 0, '')") + print(f'Total rows successfully copied: {total}') + + +def migrate_database(source_path: Path, schema_path: Path, output_path: Path) -> None: + if not source_path.is_file(): + raise FileNotFoundError(f"Input database not found: {source_path}") + if not schema_path.is_file(): + raise FileNotFoundError(f"Schema file not found: {schema_path}") + + fd, temp_path_str = tempfile.mkstemp( + suffix='.sqlite', prefix='temp_migration_', dir=output_path.parent + ) + os.close(fd) + temp_path = Path(temp_path_str) + + con_old = sqlite3.connect(source_path) + con_new = sqlite3.connect(temp_path) + + try: + with open(schema_path, encoding='utf-8') as f: + con_new.executescript(f.read()) + + con_new.execute('PRAGMA foreign_keys = 0;') + execute_v3_to_v4_migration(con_old, con_new) + + con_new.commit() + con_new.execute('VACUUM;') + con_new.execute('PRAGMA foreign_keys = 1;') + + con_old.close() + con_new.close() + os.replace(temp_path, output_path) + except Exception: + if temp_path.exists(): + os.remove(temp_path) + raise + finally: + con_old.close() + con_new.close() + + +def migrate_sql_dump(source_path: Path, schema_path: Path, output_path: Path) -> None: + if not source_path.is_file(): + raise FileNotFoundError(f"Input SQL dump not found: {source_path}") + if not schema_path.is_file(): + raise FileNotFoundError(f"Schema file not found: {schema_path}") + + con_old_in_memory = sqlite3.connect(':memory:') + with open(source_path, encoding='utf-8') as f: + con_old_in_memory.executescript(f.read()) + + con_new_in_memory = sqlite3.connect(':memory:') + with open(schema_path, encoding='utf-8') as f: + con_new_in_memory.executescript(f.read()) + + con_new_in_memory.execute('PRAGMA foreign_keys = 0;') + execute_v3_to_v4_migration(con_old_in_memory, con_new_in_memory) + + con_new_in_memory.commit() + con_new_in_memory.execute('PRAGMA foreign_keys = 1;') + + fd, temp_path_str = tempfile.mkstemp( + suffix='.sql', prefix='temp_sql_export_', dir=output_path.parent + ) + temp_path = Path(temp_path_str) + + try: + with os.fdopen(fd, 'w', encoding='utf-8') as f_out: + for line in con_new_in_memory.iterdump(): + f_out.write(line + '\n') + f_out.flush() + os.fsync(f_out.fileno()) + + os.replace(temp_path, output_path) + except Exception: + if temp_path.exists(): + os.remove(temp_path) + raise + finally: + con_old_in_memory.close() + con_new_in_memory.close() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Master Migrator for v3 to v4') + parser.add_argument('--input', '-i', required=True, help='Input DB or SQL file') + parser.add_argument('--schema', '-s', required=True, help='Path to v4 schema SQL') + parser.add_argument('--output', '-o', required=True, help='Output DB or SQL file') + parser.add_argument('--type', choices=['db', 'sql'], required=True, help='Migration type') + args = parser.parse_args() + + input_path = Path(args.input) + schema_path = Path(args.schema) + output_path = Path(args.output) + + if args.type == 'db': + migrate_database(input_path, schema_path, output_path) + elif args.type == 'sql': + migrate_sql_dump(input_path, schema_path, output_path) diff --git a/temoa/utilities/network_vis_templates/graph_script.js b/temoa/utilities/network_vis_templates/graph_script.js new file mode 100644 index 000000000..f0bf39a9b --- /dev/null +++ b/temoa/utilities/network_vis_templates/graph_script.js @@ -0,0 +1,338 @@ +document.addEventListener('DOMContentLoaded', function () { + // --- Master Datasets (Read embedded JSON safely) --- + let data = null; + const dataEl = document.getElementById('graph-data'); + if (dataEl && dataEl.textContent) { + try { + data = JSON.parse(dataEl.textContent); + } catch (e) { + console.error('Failed to parse graph data JSON:', e); + } + } + + // If data failed to load, stop immediately to prevent further errors. + if (!data) { + console.error('Could not find or parse GRAPH_DATA. Halting script.'); + return; + } + + const { + nodes_json_primary: allNodesPrimary, + edges_json_primary: allEdgesPrimary, + nodes_json_secondary: allNodesSecondary, + edges_json_secondary: allEdgesSecondary, + options_json_str: optionsRaw, + sectors_json_str: allSectors, + color_legend_json_str: colorLegendData, + style_legend_json_str: styleLegendData, + primary_view_name: primaryViewName, + secondary_view_name: secondaryViewName, + } = data; + + let optionsObject = {}; + if (typeof optionsRaw === "string") { + try { + optionsObject = JSON.parse(optionsRaw); + } catch (e) { + console.error('Failed to parse graph options JSON:', e); + optionsObject = {}; + } + } else { + optionsObject = optionsRaw || {}; + } + + // Expose for debugging only — enable in production. + const isDebug = (typeof window !== 'undefined' && window.DEBUG_GRAPH) || + (typeof URLSearchParams !== 'undefined' && new URLSearchParams(window.location.search).has('debugGraph')); + if (isDebug) { + window.__graph = { + data, + allNodesPrimary, + allEdgesPrimary, + allNodesSecondary, + allEdgesSecondary, + optionsObject, + }; + } + // --- DOM Elements --- + const fontSizeSlider = document.getElementById('font-size-slider'); + const configWrapper = document.getElementById('config-panel-wrapper'); + const configHeader = document.querySelector('.config-panel-header'); + const configToggleButton = document.querySelector('.config-toggle-btn'); + const advancedControlsToggle = document.getElementById('advanced-controls-toggle'); + const visConfigContainer = document.getElementById('vis-config-container'); + const searchInput = document.getElementById('search-input'); + + // --- Visual State --- + let currentView = 'primary'; + let primaryViewPositions = null; + let secondaryViewPositions = null; + let visualState = { + fontSize: (optionsObject?.nodes?.font?.size) || 14 + }; + + if (fontSizeSlider) { + fontSizeSlider.value = String(visualState.fontSize); + } + const resetButton = document.getElementById('reset-view-btn'); + const sectorTogglesContainer = document.getElementById('sector-toggles'); + const viewToggleButton = document.getElementById('view-toggle-btn'); + const graphContainer = document.getElementById('mynetwork'); + + // --- Config Panel Toggle --- + if (optionsObject?.configure?.enabled) { + optionsObject.configure.container = visConfigContainer; + configHeader.addEventListener('click', () => { + const isCollapsed = configWrapper.classList.toggle('collapsed'); + configToggleButton.setAttribute('aria-expanded', !isCollapsed); + }); + advancedControlsToggle.addEventListener('click', function(e) { + e.preventDefault(); + const isHidden = visConfigContainer.style.display === 'none'; + visConfigContainer.style.display = isHidden ? 'block' : 'none'; + this.textContent = isHidden ? 'Hide Advanced Physics Controls' : 'Show Advanced Physics Controls'; + }); + } + + // --- Visual Settings Sliders --- + let pendingRaf = null; + function updateVisualSettings() { + if (fontSizeSlider) visualState.fontSize = parseInt(fontSizeSlider.value, 10); + + if (pendingRaf) return; + + pendingRaf = requestAnimationFrame(() => { + pendingRaf = null; + + // Use setOptions for global font size - works for edges with smooth enabled + // Note: Don't set per-edge font as it breaks rendering with smooth edges + network.setOptions({ + nodes: { font: { size: visualState.fontSize } }, + edges: { font: { size: visualState.fontSize, align: 'top' } } + }); + + // Also update nodes individually since they have per-node font from addWithCurrentFontSize + // Note: Per-node font properties must be overwritten because they would otherwise take precedence over the global setting + const nodeUpdates = nodes.get().map(n => ({ + id: n.id, + font: { ...(n.font ?? {}), size: visualState.fontSize } + })); + nodes.update(nodeUpdates); + + network.redraw(); + }); + } + + if (fontSizeSlider) fontSizeSlider.addEventListener('input', updateVisualSettings); + + + // --- Vis.js Network Initialization --- + const nodes = new vis.DataSet(); + const edges = new vis.DataSet(); + const network = new vis.Network(graphContainer, { nodes, edges }, optionsObject); + + // --- Core Functions --- + function applyPositions(positions) { + if (!positions) return; + const updates = Object.keys(positions).map(nodeId => ({ + id: nodeId, x: positions[nodeId].x, y: positions[nodeId].y, + })); + if (updates.length > 0) nodes.update(updates); + } + + function switchView() { + if (currentView === 'primary') { + primaryViewPositions = network.getPositions(); + } else { + secondaryViewPositions = network.getPositions(); + } + nodes.clear(); edges.clear(); + + if (currentView === 'primary') { + addWithCurrentFontSize(allNodesSecondary, allEdgesSecondary); + currentView = 'secondary'; + viewToggleButton.textContent = `Switch to ${primaryViewName}`; + viewToggleButton.setAttribute('aria-pressed', 'true'); + applyPositions(secondaryViewPositions); + } else { + addWithCurrentFontSize(allNodesPrimary, allEdgesPrimary); + currentView = 'primary'; + viewToggleButton.textContent = `Switch to ${secondaryViewName}`; + viewToggleButton.setAttribute('aria-pressed', 'false'); + applyPositions(primaryViewPositions); + } + applyAllFilters(); + network.fit(); + } + + function applyAllFilters() { + // Preserve current positions so filtering doesn't reset layout + const currentPositions = network.getPositions(); + const checkedSectors = new Set(Array.from(sectorTogglesContainer.querySelectorAll('input:checked')).map(c => c.value)); + const searchQuery = searchInput.value; + let regex = null; + if (searchQuery) { + try { regex = new RegExp(searchQuery, 'i'); } catch (e) { console.error("Invalid Regex:", e); return; } + } + const activeNodesData = (currentView === 'primary') ? allNodesPrimary : allNodesSecondary; + const activeEdgesData = (currentView === 'primary') ? allEdgesPrimary : allEdgesSecondary; + const sectorFilteredNodes = activeNodesData.filter(node => { + let match = node.group === null || node.group === undefined || checkedSectors.has(node.group); + if (currentView === 'primary' && node.alwaysVisible === true) { + match = true; + } + return match; + }); + let visibleNodes, visibleEdges; + if (regex) { + const seedNodes = sectorFilteredNodes.filter(node => regex.test(node.label || node.id)); + const seedNodeIds = new Set(seedNodes.map(n => n.id)); + const nodesToShowIds = new Set(seedNodeIds); + activeEdgesData.forEach(edge => { + if (seedNodeIds.has(edge.from)) nodesToShowIds.add(edge.to); + if (seedNodeIds.has(edge.to)) nodesToShowIds.add(edge.from); + }); + visibleNodes = activeNodesData.filter(node => nodesToShowIds.has(node.id)); + visibleEdges = activeEdgesData.filter(edge => nodesToShowIds.has(edge.from) && nodesToShowIds.has(edge.to)); + } else { + visibleNodes = sectorFilteredNodes; + const visibleNodeIds = new Set(visibleNodes.map(n => n.id)); + visibleEdges = activeEdgesData.filter(edge => visibleNodeIds.has(edge.from) && visibleNodeIds.has(edge.to)); + } + + addWithCurrentFontSize(visibleNodes, visibleEdges); + applyPositions(currentPositions); + } + + function createStyleLegend() { + const container = document.getElementById('style-legend-container'); + if (!container || !styleLegendData || styleLegendData.length === 0) return; + styleLegendData.forEach(itemData => { + const item = document.createElement('div'); + item.className = 'legend-item'; + const swatch = document.createElement('div'); + swatch.className = 'legend-color-swatch'; + if (itemData.borderColor) swatch.style.borderColor = itemData.borderColor; + if (itemData.borderWidth) swatch.style.borderWidth = itemData.borderWidth + 'px'; + const label = document.createElement('span'); + label.className = 'legend-label'; + label.textContent = itemData.label; + item.append(swatch, label); + container.appendChild(item); + }); + } + + function createSectorLegend() { + const container = document.getElementById('legend-container'); + if (!container || !colorLegendData || Object.keys(colorLegendData).length === 0) return; + Object.keys(colorLegendData).sort().forEach(key => { + const item = document.createElement('div'); + item.className = 'legend-item'; + const swatch = document.createElement('div'); + swatch.className = 'legend-color-swatch'; + swatch.style.backgroundColor = colorLegendData[key]; + const label = document.createElement('span'); + label.className = 'legend-label'; + label.textContent = key; + item.append(swatch, label); + container.appendChild(item); + }); + } + + function createSectorToggles() { + if (!allSectors || allSectors.length === 0) { + sectorTogglesContainer.style.display = 'none'; + return; + } + allSectors.forEach(sector => { + const item = document.createElement('div'); + item.className = 'sector-toggle-item'; + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `toggle-${sector}`; + checkbox.value = sector; + checkbox.checked = true; + checkbox.addEventListener('change', applyAllFilters); + const swatch = document.createElement('div'); + swatch.className = 'toggle-color-swatch'; + swatch.style.backgroundColor = colorLegendData[sector] || '#ccc'; + const label = document.createElement('label'); + label.htmlFor = checkbox.id; + label.textContent = sector; + item.append(swatch, checkbox, label); + item.addEventListener('click', e => { + if (e.target.tagName === 'INPUT') return; + e.preventDefault(); + checkbox.checked = !checkbox.checked; + checkbox.dispatchEvent(new Event('change', { bubbles: true })); + }); + sectorTogglesContainer.appendChild(item); + }); + } + + function addWithCurrentFontSize(newNodes, newEdges) { + nodes.clear(); + edges.clear(); + nodes.add( + newNodes.map(n => ({ + ...n, + font: { ...(n.font ?? {}), size: visualState.fontSize }, + })), + ); + // Don't set per-edge font - let network.setOptions() handle it + // vis.js ignores global font options when edges have per-item font set + edges.add(newEdges); + } + + function resetView() { + searchInput.value = ""; + primaryViewPositions = null; + secondaryViewPositions = null; + if (currentView !== 'primary') { + switchView(); // This will switch back to primary and apply null positions + } else { + // If already on primary, just reload the original data + addWithCurrentFontSize(allNodesPrimary, allEdgesPrimary); + applyPositions(primaryViewPositions); // Apply null to reset + network.fit(); + } + sectorTogglesContainer.querySelectorAll('input[type=checkbox]').forEach(c => c.checked = true); + applyAllFilters(); + } + + function showNeighborhood(nodeId) { + const activeNodes = (currentView === 'primary') ? allNodesPrimary : allNodesSecondary; + const activeEdges = (currentView === 'primary') ? allEdgesPrimary : allEdgesSecondary; + searchInput.value = ""; + const nodesToShow = new Set([nodeId]); + activeEdges.forEach(edge => { + if (edge.from === nodeId) nodesToShow.add(edge.to); + else if (edge.to === nodeId) nodesToShow.add(edge.from); + }); + const filteredNodes = activeNodes.filter(node => nodesToShow.has(node.id)); + const filteredEdges = activeEdges.filter(edge => nodesToShow.has(edge.from) && nodesToShow.has(edge.to)); + addWithCurrentFontSize(filteredNodes, filteredEdges); + network.fit(); + } + + // --- Event Listeners & Initial Setup --- + if (allNodesSecondary && allNodesSecondary.length > 0) { + viewToggleButton.textContent = `Switch to ${secondaryViewName}`; + viewToggleButton.addEventListener('click', switchView); + } else { + document.getElementById('view-toggle-panel').style.display = 'none'; + } + resetButton.addEventListener('click', resetView); + searchInput.addEventListener('input', applyAllFilters); + network.on("doubleClick", params => { + if (params.nodes.length > 0) { + showNeighborhood(params.nodes[0]); + } + }); + + createStyleLegend(); + createSectorLegend(); + createSectorToggles(); + // Initial data load with consistent font handling + addWithCurrentFontSize(allNodesPrimary, allEdgesPrimary); +}); diff --git a/temoa/utilities/network_vis_templates/graph_styles.css b/temoa/utilities/network_vis_templates/graph_styles.css new file mode 100644 index 000000000..41e36997e --- /dev/null +++ b/temoa/utilities/network_vis_templates/graph_styles.css @@ -0,0 +1,32 @@ +body, html { + margin: 0; padding: 0; width: 100%; height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: #f4f6f8; display: flex; flex-direction: column; overflow: hidden; +} +.config-panel-wrapper { width: 100%; background-color: #ffffff; box-shadow: 0 2px 4px rgba(0,0,0,0.1); z-index: 10; flex-shrink: 0; } +.config-panel-header { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; border-bottom: 1px solid #e0e0e0; cursor: pointer; background-color: #f9f9f9; } +.config-panel-header h3 { margin: 0; font-size: 16px; font-weight: 600; } +.config-toggle-btn::after { content: '\25BC'; display: inline-block; transition: transform 0.2s ease-in-out; } +.collapsed .config-toggle-btn::after { transform: rotate(-90deg); } +#config-container-content { max-height: 40vh; overflow-y: auto; padding: 15px; box-sizing: border-box; background-color: #ffffff; transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out; } +.collapsed #config-container-content { max-height: 0; padding-top: 0; padding-bottom: 0; overflow: hidden; } +#mynetwork { width: 100%; flex-grow: 1; min-height: 0; } +.filter-panel { display: flex; align-items: center; gap: 10px; padding: 8px 15px; background-color: #e9ecef; border-bottom: 1px solid #dee2e6; } +.filter-panel input[type=text] { flex-grow: 1; padding: 6px 8px; border: 1px solid #ced4da; border-radius: 4px; } +.filter-panel button { padding: 6px 12px; border-radius: 4px; border: 1px solid #6c757d; background-color: #6c757d; color: white; cursor: pointer; } +.info-panel { padding: 8px 15px; background-color: #f8f9fa; font-size: 13px; text-align: center; border-bottom: 1px solid #dee2e6; } +.sector-toggles { padding: 10px 15px; background-color: #f8f9fa; border-bottom: 1px solid #dee2e6; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } +.sector-toggles-title { font-weight: 600; } +.sector-toggle-item { display: flex; align-items: center; gap: 8px; padding: 5px 10px; border: 1px solid #ccc; border-radius: 16px; background-color: #fff; cursor: pointer; user-select: none; } +.toggle-color-swatch { width: 14px; height: 14px; border-radius: 50%; } +.legend-section { margin-bottom: 15px; } +.legend-section h4 { margin-top: 0; margin-bottom: 10px; font-size: 14px; font-weight: 600; } +.legend-container { display: flex; flex-wrap: wrap; gap: 15px; } +.legend-item { display: flex; align-items: center; margin-bottom: 6px; } +.legend-color-swatch { width: 18px; height: 18px; margin-right: 8px; flex-shrink: 0; border: 1px solid #ccc; background-color: #f0f0f0; box-sizing: border-box; } +.legend-label { font-size: 13px; } +.control-group { display: flex; align-items: center; gap: 15px; margin-bottom: 8px; } +.control-group label { min-width: 120px; font-size: 13px; font-weight: 500; } +.control-group input[type=range] { flex-grow: 1; max-width: 250px; } +#advanced-controls-toggle { font-size: 12px; color: #007bff; cursor: pointer; text-decoration: none; margin-top: 15px; display: block; } +.view-toggle-panel { padding: 8px 15px; background-color: #343a40; color: white; display: flex; justify-content: center; align-items: center; } +.view-toggle-panel button { font-size: 14px; font-weight: 600; padding: 8px 16px; border-radius: 5px; border: 1px solid #6c757d; background-color: #495057; color: white; cursor: pointer; } diff --git a/temoa/utilities/network_vis_templates/graph_template.html b/temoa/utilities/network_vis_templates/graph_template.html new file mode 100644 index 000000000..e54218edf --- /dev/null +++ b/temoa/utilities/network_vis_templates/graph_template.html @@ -0,0 +1,62 @@ + + + + + + + __HTML_PAGE_TITLE__ + + + + + +
+
+

Configuration & Legend

+ +
+
+
+

Visual Settings

+
+ + +
+
+
+

Style Legend

+
+
+
+

Sector Legend

+
+
+ Show Advanced Physics Controls + +
+
+
+ +
+
+ Filter Sectors: +
+
+ + + +
+
+ Tip: Double-click a node to isolate. Single-click to select. Use 'Reset View' to clear. +
+
+ + + + + + + diff --git a/temoa/utilities/run_all_v4_migrations.py b/temoa/utilities/run_all_v4_migrations.py new file mode 100644 index 000000000..957bd113f --- /dev/null +++ b/temoa/utilities/run_all_v4_migrations.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +""" +run_all_v4_migrations.py + +Iterates over all .sql files in a specified directory, runs the v3.1 to v4 +SQL migration script on each, and overwrites the original file if successful. +Includes basic error handling to restore original files on failure. + +Usage: + python run_all_migrations.py --input_dir /path/to/your/sql_files \ + --migration_script ./sql_migration_v_3_1_to_v4.py \ + --v4_schema_path ./temoa_schema_v4.sql +""" + +from __future__ import annotations + +import argparse +import os +import shutil +import subprocess +import sys +import tempfile +from pathlib import Path + + +def run_command( + cmd: list[str], cwd: Path | None = None, capture_output: bool = True, silent: bool = False +) -> subprocess.CompletedProcess[str]: + """Helper to run shell commands.""" + if not silent: + print(f'Executing: {" ".join(cmd)}') + result = subprocess.run(cmd, cwd=cwd, capture_output=capture_output, text=True, check=False) + if not silent and result.returncode != 0 and capture_output: + print(f'COMMAND FAILED (exit code {result.returncode}):') + print('STDOUT:\n', result.stdout) + print('STDERR:\n', result.stderr) + return result + + +def run_migrations( + input_dir: Path, migration_script: Path, schema_path: Path, dry_run: bool = False, silent: bool = False +) -> None: + if not input_dir.is_dir(): + raise FileNotFoundError(f'Error: Input directory not found at {input_dir}') + if not migration_script.is_file(): + raise FileNotFoundError(f'Error: Migration script not found at {migration_script}') + if not schema_path.is_file(): + raise FileNotFoundError(f'Error: schema file not found at {schema_path}') + + if not silent: + print(f'Scanning for .sql and .sqlite files in: {input_dir}') + sql_files = list(input_dir.glob('*.sql')) + db_files = list(input_dir.glob('*.sqlite')) + list(input_dir.glob('*.db')) + all_files = sql_files + db_files + + if not all_files: + if not silent: + print(f'No .sql, .sqlite, or .db files found in {input_dir}. Exiting.') + return + + if dry_run: + if not silent: + print('\n--- Dry Run ---') + print(f'The following {len(all_files)} files would be processed:') + for f in all_files: + print(f' - {f.name}') + print('\nNo files will be modified in dry run mode.') + return + + if not silent: + print(f'\n--- Starting Migration of {len(all_files)} files ---') + processed_count = 0 + failed_files = [] + + for target_file in all_files: + if not silent: + print(f'\nProcessing: {target_file.name}') + + ext = target_file.suffix.lower() + fd1, path1 = tempfile.mkstemp(suffix=ext, prefix='temp_migrated_') + os.close(fd1) + temp_output_file = Path(path1) + + fd2, path2 = tempfile.mkstemp(suffix='.bak', prefix='orig_backup_') + os.close(fd2) + original_backup_file = Path(path2) + + mig_type = 'sql' if ext == '.sql' else 'db' + + try: + # 1. Back up original file + shutil.copy2(target_file, original_backup_file) + + # 2. Run migration script, outputting to a temporary file + migration_cmd = [ + sys.executable, + str(migration_script), + '--input', + str(target_file), + '--schema', + str(schema_path), + '--output', + str(temp_output_file), + '--type', + mig_type, + ] + result = run_command(migration_cmd, cwd=Path.cwd(), silent=silent) + + if result.returncode == 0: + # 3. If successful, overwrite original file + shutil.copy2(temp_output_file, target_file) + if not silent: + print(f'SUCCESS: {target_file.name} migrated and overwritten.') + processed_count += 1 + else: + # 4. On failure, restore original file + if not silent: + print(f'FAILED: Migration for {target_file.name} failed. Restoring original file.') + shutil.copy2(original_backup_file, target_file) + failed_files.append(target_file.name) + + except Exception as e: + if not silent: + print(f'CRITICAL ERROR processing {target_file.name}: {e}. Restoring original file.') + if original_backup_file.exists(): + shutil.copy2(original_backup_file, target_file) + failed_files.append(target_file.name) + finally: + if temp_output_file.exists(): + os.remove(temp_output_file) + if original_backup_file.exists(): + os.remove(original_backup_file) + + if not silent: + print('\n--- Migration Summary ---') + print(f'Total files processed: {processed_count}') + if failed_files: + raise RuntimeError(f'FAILED files: {", ".join(failed_files)}') + + if not silent: + print('All files migrated successfully.') + + +def main() -> None: + parser = argparse.ArgumentParser( + description='Run script migration on all .sql/.sqlite/.db files in a directory, overwriting originals.' + ) + parser.add_argument( + '--input_dir', + required=True, + type=Path, + help='Directory containing the files to migrate.', + ) + parser.add_argument( + '--migration_script', + required=True, + type=Path, + help='Path to the master_migration.py script.', + ) + parser.add_argument( + '--v4_schema_path', + required=True, + type=Path, + help='Path to the canonical v4 schema SQL file.', + ) + parser.add_argument( + '--dry_run', + action='store_true', + help='Perform a dry run: show which files would be processed, but do not modify.', + ) + + args = parser.parse_args() + + run_migrations( + input_dir=args.input_dir.resolve(), + migration_script=args.migration_script.resolve(), + schema_path=args.v4_schema_path.resolve(), + dry_run=args.dry_run, + ) + + +if __name__ == '__main__': + main() diff --git a/temoa/utilities/set_spitter.py b/temoa/utilities/set_spitter.py index 1f9dd5176..dbaaca05e 100644 --- a/temoa/utilities/set_spitter.py +++ b/temoa/utilities/set_spitter.py @@ -2,38 +2,12 @@ Quick utility to spit out the set members of a pyomo model """ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 1/16/24 - -""" - import pyomo.environ as pyo +from temoa.core.model import TemoaModel + -def spit_sets(model: pyo.Model, index_sets=False): +def spit_sets(model: TemoaModel, index_sets: bool = False) -> None: """ print out the set data in pyomo pprint format :param model: a built model @@ -48,7 +22,7 @@ def spit_sets(model: pyo.Model, index_sets=False): model_sets[m_set].pprint() -def spit_params(model: pyo.Model): +def spit_params(model: TemoaModel) -> None: """ Print out the parameters in pyomo-built pprint :param model: a built model diff --git a/temoa/utilities/sqlite_utils.py b/temoa/utilities/sqlite_utils.py new file mode 100644 index 000000000..953127ffb --- /dev/null +++ b/temoa/utilities/sqlite_utils.py @@ -0,0 +1,49 @@ +""" +Utilities for SQLite performance tuning in Temoa. +""" + +import logging +import sqlite3 +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from temoa.core.config import TemoaConfig + +logger = logging.getLogger(__name__) + + +def tune_sqlite_connection(con: sqlite3.Connection, config: 'TemoaConfig | None' = None) -> None: + """ + Apply performance-tuning PRAGMAs to a SQLite connection. + + Args: + con: The sqlite3.Connection object to tune. + config: Optional TemoaConfig object to override defaults. + """ + journal_mode = 'WAL' + synchronous = 'NORMAL' + temp_store = 'MEMORY' + mmap_size = 8589934592 # 8GB + cache_size = -512000 # 500MB (negative means KiB) + + if config: + journal_mode = getattr(config, 'sqlite_journal_mode', journal_mode) + synchronous = getattr(config, 'sqlite_synchronous', synchronous) + temp_store = getattr(config, 'sqlite_temp_store', temp_store) + mmap_size = getattr(config, 'sqlite_mmap_size', mmap_size) + cache_size = getattr(config, 'sqlite_cache_size', cache_size) + + pragmas = [ + ('journal_mode', journal_mode), + ('synchronous', synchronous), + ('temp_store', temp_store), + ('mmap_size', mmap_size), + ('cache_size', cache_size), + ] + + for name, value in pragmas: + try: + con.execute(f'PRAGMA {name} = {value}') + logger.debug('Applied SQLite PRAGMA: %s = %s', name, value) + except sqlite3.Error as e: + logger.warning('Failed to apply SQLite PRAGMA %s: %s', name, e) diff --git a/temoa/utilities/unit_cost_explorer.py b/temoa/utilities/unit_cost_explorer.py index fff83fa72..b17c01fe3 100644 --- a/temoa/utilities/unit_cost_explorer.py +++ b/temoa/utilities/unit_cost_explorer.py @@ -3,7 +3,19 @@ of storage capacity """ -from temoa.temoa_model.temoa_rules import * +from pyomo.environ import value + +from temoa.components.costs import total_cost_rule +from temoa.components.storage import storage_energy_upper_bound_constraint +from temoa.core.model import TemoaModel +from temoa.types.core_types import ( + Period, + Region, + Season, + Technology, + TimeOfDay, + Vintage, +) # Written by: J. F. Hyink # jeff@westernspark.us @@ -11,7 +23,7 @@ # Created on: 12/30/23 -M = TemoaModel() +model = TemoaModel() """ let's fill in what we need to cost 1 item... @@ -25,48 +37,49 @@ # indices -rtv = ('A', 'battery', 2020) # rtv -rptv = ('A', 2020, 'battery', 2020) # rptv +rtv = (Region('A'), Technology('battery'), Vintage(2020)) # rtv +rptv = (Region('A'), Period(2020), Technology('battery'), Vintage(2020)) # rptv +model.time_future.construct([2020, 2025, 2030]) # needs to go 1 period beyond optimize horizon +model.time_optimize.construct([2020, 2025]) +model.period_length.construct() +model.tech_all.construct(data=['battery']) +model.regions.construct(data=['A']) +model.regional_indices.construct(data=['A']) # make SETS -M.NewCapacityVar_rtv.construct(data=rtv) -M.CapacityVar_rptv.construct(data=rptv) -M.CostInvest_rtv.construct(data=rtv) -M.CostFixed_rptv.construct(data=rptv) -M.LoanLifetimeProcess_rtv.construct(data=rtv) -M.Loan_rtv.construct(data=rtv) -M.LoanRate_rtv.construct(data=rtv) -M.LifetimeProcess_rtv.construct(data=rtv) -M.RegionalIndices.construct(data=['A']) -M.MyopicBaseyear.construct(data={None: 0}) -M.ModelProcessLife_rptv.construct(data=rptv) +model.new_capacity_var_rtv.construct(data=rtv) +model.capacity_var_rptv.construct(data=rptv) +model.cost_invest_rtv.construct(data=rtv) +model.cost_fixed_rptv.construct(data=rptv) +model.loan_lifetime_process_rtv.construct(data=rtv) +# M.Loan_rtv.construct(data=rtv) +# M.loan_rate_rtv.construct(data=rtv) +model.lifetime_process_rtv.construct(data=rtv) +model.myopic_discounting_year.construct(data={None: 0}) +# M.ModelProcessLife_rptv.construct(data=rptv) # make PARAMS -M.time_optimize.construct([2020, 2025]) -M.time_future.construct([2020, 2025, 2030]) # needs to go 1 period beyond optimize horizon -M.PeriodLength.construct() -M.tech_all.construct(data=['battery']) -M.regions.construct(data=['A']) -M.CostInvest.construct(data={rtv: 1300}) # US_9R_8D -M.CostFixed.construct(data={rptv: 20}) # US_9R_8D -M.LoanLifetimeProcess.construct(data={rtv: 10}) -M.LoanRate.construct(data={rtv: 0.05}) -M.LoanAnnualize.construct() -M.LifetimeTech.construct(data={('A', 'battery'): 20}) -M.LifetimeProcess.construct(data={rtv: 40}) -M.ModelProcessLife.construct(data={rptv: 20}) -M.GlobalDiscountRate.construct(data={None: 0.05}) +model.cost_invest.construct(data={rtv: 1300}) # US_9R_8D +model.cost_fixed.construct(data={rptv: 20}) # US_9R_8D +model.loan_lifetime_process.construct(data={rtv: 10}) +model.loan_rate.construct(data={rtv: 0.05}) +model.loan_annualize.construct() +model.lifetime_tech.construct(data={('A', 'battery'): 20}) +model.lifetime_process.construct(data={rtv: 40}) +# M.ModelProcessLife.construct(data={rptv: 20}) +model.global_discount_rate.construct(data={None: 0.05}) +model.is_survival_curve_process[rtv] = False # make/fix VARS -M.V_NewCapacity.construct() -M.V_NewCapacity[rtv].set_value(1) +model.v_new_capacity.construct() +model.v_new_capacity[rtv].set_value(1) -M.V_Capacity.construct() -M.V_Capacity[rptv].set_value(1) +model.v_capacity.construct() +model.v_capacity[rptv].set_value(1) # run the total cost rule on our "model": -tot_cost_expr = TotalCost_rule(M) +tot_cost_expr = total_cost_rule(model) total_cost = value(tot_cost_expr) print() print(f'Total cost for building 1 capacity unit of storage: ${total_cost:0.2f} [$M]') @@ -90,42 +103,61 @@ print('building storage level constraint...') # More SETS -M.time_season.construct(data=['winter', 'summer']) +model.time_season.construct(['winter', 'summer']) +model.days_per_period.construct(data={None: 365}) tod_slices = 2 -M.time_of_day.construct(data=range(1, tod_slices + 1)) -M.tech_storage.construct(data=['battery']) -M.ProcessLifeFrac_rptv.construct(data=[rptv]) -M.StorageLevel_rpsdtv.construct( +model.time_of_day.construct(data=range(1, tod_slices + 1)) +model.tech_storage.construct(data=['battery']) +model.process_life_frac_rptv.construct(data=[rptv]) +model.storage_level_rpsdtv.construct( data=[ ('A', 2020, 'winter', 1, 'battery', 2020), ] ) -M.StorageConstraints_rpsdtv.construct( +model.storage_constraints_rpsdtv.construct( data=[ ('A', 2020, 'winter', 1, 'battery', 2020), ] ) # More PARAMS -M.CapacityToActivity.construct(data={('A', 'battery'): 31.536}) -M.StorageDuration.construct(data={('A', 'battery'): 4}) +model.capacity_to_activity.construct(data={('A', 'battery'): 31.536}) +model.storage_duration.construct(data={('A', 'battery'): 4}) seasonal_fractions = {'winter': 0.4, 'summer': 0.6} -M.SegFrac.construct( - data={(s, d): seasonal_fractions[s] / tod_slices for d in M.time_of_day for s in M.time_season} +model.segment_fraction.construct( + data={ + (s, d): seasonal_fractions[s] / tod_slices + for d in model.time_of_day + for s in model.time_season + } ) # QA the total -print(f'quality check. Total of all SegFrac: {sum(M.SegFrac.values()):0.3f}') -M.ProcessLifeFrac.construct(data={('A', 2020, 'battery', 2020): 1.0}) +print( + 'quality check. Total of all segment_fraction: ' + f'{sum(value(v) for v in model.segment_fraction.values()):0.3f}' +) +model.process_life_frac.construct(data={('A', 2020, 'battery', 2020): 1.0}) # More VARS -M.V_StorageLevel.construct() -M.SegFracPerSeason.construct() - -upper_limit = StorageEnergyUpperBound_Constraint(M, 'A', 2020, 'winter', 1, 'battery', 2020) +model.v_storage_level.construct() +model.segment_fraction_per_season.construct(data=seasonal_fractions) + +model.is_seasonal_storage[Technology('battery')] = False +upper_limit = storage_energy_upper_bound_constraint( + model, + Region('A'), + Period(2020), + Season('winter'), + TimeOfDay('1'), + Technology('battery'), + Vintage(2020), +) print('The storage level constraint for the single period in the "super day":\n', upper_limit) # cross-check the multiplier... -mulitplier = storage_dur * M.SegFracPerSeason['winter'] * 365 * c2a * c +mulitplier = ( + storage_dur * model.segment_fraction_per_season['winter'] * model.days_per_period * c2a * c +) print(f'The multiplier for the storage should be: {mulitplier}') -M.StorageEnergyUpperBoundConstraint.construct() +model.storage_energy_upper_bound_constraint.construct() diff --git a/temoa/utilities/visualizer.py b/temoa/utilities/visualizer.py new file mode 100644 index 000000000..50885e7f7 --- /dev/null +++ b/temoa/utilities/visualizer.py @@ -0,0 +1,315 @@ +# temoa/utilities/visualizer.py +""" +Tools for converting NetworkX graphs to interactive vis.js visualizations. + +This module provides the `nx_to_vis` function which takes one or two +NetworkX graphs and generates a self-contained HTML file for exploration. +It includes features for toggling between views, filtering by sector or node +name, and an interactive configuration panel. + +This code is designed to replace previous graphing dependencies like `gravis`. +""" + +from __future__ import annotations + +import copy +import json +import logging +import uuid +from collections import defaultdict +from pathlib import Path +from typing import TYPE_CHECKING, Any + +import networkx as nx + +from temoa.utilities.graph_utils import ( + GraphType, + convert_graph_to_json, +) + +if TYPE_CHECKING: + from collections.abc import Iterable, Sequence + + from temoa.types.core_types import Commodity, Sector, Technology + +logger = logging.getLogger(__name__) + + +def deep_merge_dicts(source: dict[str, Any], destination: dict[str, Any]) -> dict[str, Any]: + """Deep merge two dictionaries.""" + for key, source_value in source.items(): + dest_value = destination.get(key) + if isinstance(source_value, dict) and isinstance(dest_value, dict): + deep_merge_dicts(source_value, dest_value) + else: + destination[key] = source_value + return destination + + +def make_nx_graph( + connections: Iterable[tuple[Commodity, Technology, Commodity, Sector | None]], + edge_attributes_map: dict[tuple[str, str, str, str | None], dict[str, Any]], + node_layer_map: dict[str, int], + node_positions: dict[str, dict[str, Any]], + commodity_to_primary_sector: dict[Commodity, Sector], + driven_tech_names: set[Technology], + other_orphan_names: set[Technology], + demand_orphan_names: set[Technology], + driven_commodities: set[Commodity], + other_orphan_commodities: set[Commodity], + demand_orphan_commodities: set[Commodity], +) -> nx.MultiDiGraph[str]: + """ + Make an nx graph, grouping parallel edges to prevent label overlap. + """ + + dg: nx.MultiDiGraph[str] = nx.MultiDiGraph() + connections = tuple(connections) # Freeze for multiple iterations + + node_styles_by_layer = { + 1: {'size': 25, 'shape': 'ellipse'}, + 2: {'size': 15, 'shape': 'dot'}, + 3: {'size': 20, 'shape': 'box'}, + } + default_node_style = {'size': 15, 'shape': 'dot'} + + # 1. Add all nodes first to ensure they exist before adding edges + all_nodes_in_connections = {c[0] for c in connections} | {c[2] for c in connections} + for node_name in sorted(all_nodes_in_connections): # Sort for consistency + if node_name in dg: + continue + layer = node_layer_map.get(node_name, 2) + style = node_styles_by_layer.get(layer, default_node_style) + pos_attrs = node_positions.get(node_name, {}) + group_name = commodity_to_primary_sector.get(node_name) + + # Start with the base attributes for the node. + node_attrs: dict[str, Any] = { + 'label': node_name, + 'title': f'Commodity: {node_name}\nLayer: {layer}\nSector: {group_name or "N/A"}', + 'group': group_name, + 'size': style.get('size'), + 'shape': style.get('shape'), + 'borderWidth': 2, # Default + 'color': {}, # Initialize the color object + **pos_attrs, + } + + # Now, conditionally modify the attributes + color_obj = node_attrs['color'] + + # Set background color and alwaysVisible flag based on layer + if layer == 1: + color_obj['background'] = '#2ca02c' + node_attrs['alwaysVisible'] = True # Now this is safe to do + elif layer == 3: + color_obj['background'] = '#78facf' + + # Set border color based on special status + if node_name in demand_orphan_commodities: + color_obj['border'] = '#d62728' + node_attrs['borderWidth'] = 4 + elif node_name in other_orphan_commodities: + color_obj['border'] = '#ff7f0e' + node_attrs['borderWidth'] = 4 + elif node_name in driven_commodities: + color_obj['border'] = '#1f77b4' + node_attrs['borderWidth'] = 4 + + # Add shadow and update title for highlighted nodes + if node_attrs['borderWidth'] > 2: + node_attrs['shadow'] = {'enabled': True, 'color': 'rgba(0,0,0,0.5)', 'x': 2, 'y': 2} + status = 'Unknown' + if node_name in demand_orphan_commodities: + status = 'Connected to Demand Orphan' + elif node_name in other_orphan_commodities: + status = 'Connected to Other Orphan' + elif node_name in driven_commodities: + status = 'Connected to Driven Tech' + node_attrs['title'] += f'\nStatus: {status}' + + dg.add_node(node_name, **node_attrs) + + # 2. Group technologies by their input/output commodity pair + grouped_edges: dict[tuple[Commodity, Commodity], list[dict[str, Any]]] = defaultdict(list) + for ic, tech, oc, sector in connections: + edge_key = (ic, tech, oc, sector) + attrs = edge_attributes_map.get(edge_key, {}) + grouped_edges[(ic, oc)].append({'tech': tech, 'sector': sector, 'attrs': attrs}) + + # 3. Create a single, combined edge for each group + for (ic, oc), techs_info in grouped_edges.items(): + combined_attrs: dict[str, Any] = {} + tech_names = [info['tech'] for info in techs_info] + + # Combine labels: Show up to 2 names, then "X techs" + if len(tech_names) <= 2: + combined_attrs['label'] = ', '.join(tech_names) + else: + combined_attrs['label'] = f'{len(tech_names)} technologies' + + # Combine tooltips (titles) to list all techs + tooltip_lines = [] + # Sort by tech name for a consistent order in the tooltip + for info in sorted(techs_info, key=lambda x: x['tech']): + tech_name = info['tech'] + sector_name = info.get('sector', 'N/A') + + tech_type_info = '' + if tech_name in demand_orphan_names: + tech_type_info = ' [Demand Orphan]' + elif tech_name in other_orphan_names: + tech_type_info = ' [Other Orphan]' + elif tech_name in driven_tech_names: + tech_type_info = ' [Driven Tech]' + + tooltip_lines.append(f'- {tech_name} (Sector: {sector_name}){tech_type_info}') + + combined_attrs['title'] = f'Technologies ({ic} → {oc}):\n' + '\n'.join(tooltip_lines) + + # Combine visual properties + all_sectors = {info['sector'] for info in techs_info if info['sector']} + if len(all_sectors) > 1: + combined_attrs['color'] = '#888888' + elif len(all_sectors) == 1: + # Find a tech that actually has the sector + tech_with_sector = next((info for info in techs_info if info['sector']), techs_info[0]) + combined_attrs['color'] = tech_with_sector['attrs'].get('color', '#888888') + + # If any underlying edge is dashed, the combined edge is dashed. + if any(info['attrs'].get('dashes', False) for info in techs_info): + combined_attrs['dashes'] = True + + # Use 'width' for thickness, 'value' breaks font rendering with smooth edges + combined_attrs['width'] = 2 + len(techs_info) # Base width + 1 per tech + multi_edge_key = f'{ic}-{oc}-{uuid.uuid4().hex[:8]}' + dg.add_edge(ic, oc, key=multi_edge_key, **combined_attrs) + + return dg + + +def nx_to_vis( + nx_graph: GraphType, + output_filename: Path, + html_title: str = 'NetworkX to vis.js Graph', + vis_options: dict[str, Any] | None = None, + override_node_properties: dict[str, Any] | None = None, + override_edge_properties: dict[str, Any] | None = None, + sectors: Sequence[Sector] | None = None, + color_legend_map: dict[str, str] | None = None, + style_legend_map: list[dict[str, Any]] | None = None, + secondary_graph: GraphType | None = None, + primary_view_name: str = 'Commodity View', + secondary_view_name: str = 'Technology View', + *, + show_browser: bool = True, +) -> str | None: + """ + Generates an interactive HTML file and its assets (CSS, JS) for graph visualization. + """ + nodes_data_primary, edges_data_primary = convert_graph_to_json( + nx_graph, override_node_properties, override_edge_properties, 0 + ) + nodes_data_secondary: list[dict[str, Any]] = [] + edges_data_secondary: list[dict[str, Any]] = [] + if secondary_graph: + nodes_data_secondary, edges_data_secondary = convert_graph_to_json( + secondary_graph, override_node_properties, override_edge_properties, 0 + ) + + current_options = copy.deepcopy(DEFAULT_VIS_OPTIONS) + if vis_options: + deep_merge_dicts(vis_options, current_options) + + try: + template_dir = Path(__file__).parent / 'network_vis_templates' + html_template = (template_dir / 'graph_template.html').read_text(encoding='utf-8') + css_template = (template_dir / 'graph_styles.css').read_text(encoding='utf-8') + js_template = (template_dir / 'graph_script.js').read_text(encoding='utf-8') + except FileNotFoundError: + logger.exception( + "Template files not found. Ensure the 'network_vis_templates' directory exists next to " + 'visualizer.py.' + ) + return None + + # Prepare all data for injection into the HTML + graph_data = { + 'nodes_json_primary': nodes_data_primary, + 'edges_json_primary': edges_data_primary, + 'nodes_json_secondary': nodes_data_secondary, + 'edges_json_secondary': edges_data_secondary, + 'options_json_str': current_options, + 'sectors_json_str': sorted(sectors) if sectors else [], + 'color_legend_json_str': color_legend_map or {}, + 'style_legend_json_str': style_legend_map or [], + 'primary_view_name': primary_view_name, + 'secondary_view_name': secondary_view_name, + } + + try: + output_filename.parent.mkdir(parents=True, exist_ok=True) + output_filename.with_name('graph_styles.css').write_text(css_template, encoding='utf-8') + output_filename.with_name('graph_script.js').write_text(js_template, encoding='utf-8') + + html_content = html_template.replace('__HTML_PAGE_TITLE__', html_title).replace( + '__GRAPH_DATA_JSON__', json.dumps(graph_data) + ) + output_filename.write_text(html_content, encoding='utf-8') + abs_path = str(output_filename.resolve()) + except OSError: + logger.exception('Failed to write graph visualization files.') + return None + else: + logger.info('Generated graph HTML at: %s', abs_path) + if show_browser: + import webbrowser + + webbrowser.open('file://' + abs_path) + return abs_path + + +DEFAULT_VIS_OPTIONS = { + 'autoResize': True, + 'nodes': { + 'shape': 'dot', + 'size': 16, + 'font': {'size': 14, 'color': '#333'}, + 'borderWidth': 2, + }, + 'edges': { + 'width': 2, + 'smooth': {'type': 'continuous', 'roundness': 0.5}, + 'arrows': {'to': {'enabled': False, 'scaleFactor': 1}}, + 'font': {'align': 'top', 'size': 14}, + }, + 'physics': { + 'enabled': False, + 'barnesHut': { + 'gravitationalConstant': -15000, + 'springConstant': 0.04, + 'springLength': 200, + 'damping': 0.09, + 'avoidOverlap': 0.1, + }, + 'solver': 'barnesHut', + 'minVelocity': 0.75, + 'timestep': 0.05, + 'stabilization': {'enabled': False}, + }, + 'interaction': { + 'hover': True, + 'dragNodes': True, + 'dragView': True, + 'zoomView': True, + 'tooltipDelay': 200, + 'navigationButtons': False, + 'keyboard': {'enabled': True, 'bindToWindow': False}, + }, + 'layout': {'improvedLayout': True}, + 'configure': { + 'enabled': True, + 'showButton': False, # We have our own header, so hide the default floating button + 'container': None, # We will set this dynamically in the HTML + }, +} diff --git a/temoa/version_information.py b/temoa/version_information.py deleted file mode 100644 index be8a05734..000000000 --- a/temoa/version_information.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/1/24 - -""" - -# === INFORMATION === -# current Temoa version -TEMOA_MAJOR = 3 -TEMOA_MINOR = 0 - -# === REQUIREMENTS === -# python versions are tested internally for greater than these values -MIN_PYTHON_MAJOR = 3 -MIN_PYTHON_MINOR = 11 - -# db is tested for match on major and >= on minor -DB_MAJOR_VERSION = 3 -MIN_DB_MINOR_VERSION = 0 diff --git a/tests/conftest.py b/tests/conftest.py index 12f7ecf44..1d942ed9e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,57 +1,27 @@ -""" -pytest configuration - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/27/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -""" - import logging -import os import sqlite3 from pathlib import Path from typing import Any import pytest +from _pytest.config import Config from pyomo.opt import SolverResults -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_model import TemoaModel -from temoa.temoa_model.temoa_sequencer import TemoaSequencer +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.model import TemoaModel logger = logging.getLogger(__name__) # set the target folder for output from testing -output_path = os.path.join(PROJECT_ROOT, 'tests', 'testing_log') -if not os.path.exists(output_path): - os.mkdir(output_path) +output_path = Path(__file__).parent / 'testing_log' +if not output_path.exists(): + output_path.mkdir() # set up logger in conftest.py so that it is properly anchored in the test folder. filename = 'testing.log' logging.basicConfig( - filename=os.path.join(output_path, filename), + filename=output_path / filename, filemode='w', format='%(asctime)s | %(module)s | %(levelname)s | %(message)s', datefmt='%d-%b-%y %H:%M:%S', @@ -63,35 +33,163 @@ logging.getLogger('pyutilib').setLevel(logging.WARNING) +# Central paths +TEST_DATA_PATH = Path(__file__).parent / 'testing_data' +TEST_OUTPUT_PATH = Path(__file__).parent / 'testing_outputs' +SCHEMA_PATH = Path(__file__).parent.parent / 'temoa' / 'db_schema' / 'temoa_schema_v4.sql' + + +def _build_test_db( + db_file: Path, + data_scripts: list[Path], + modifications: list[tuple[str, tuple[Any, ...]]] | None = None, +) -> None: + """Helper to build a test database from central schema + data scripts + mods.""" + if db_file.exists(): + db_file.unlink() + + with sqlite3.connect(db_file) as con: + con.execute('PRAGMA foreign_keys = OFF') + # 1. Load central schema + con.executescript(SCHEMA_PATH.read_text(encoding='utf-8')) + # Force FK OFF again as schema file might turn it on at the end + con.execute('PRAGMA foreign_keys = OFF') + + # 2. Load data scripts + for script_path in data_scripts: + with open(script_path) as f: + con.executescript(f.read()) + + # 3. Apply modifications + if modifications: + for sql, params in modifications: + con.execute(sql, params) + + # 4. Turn foreign keys back on + con.execute('PRAGMA foreign_keys = ON') + con.commit() + + def refresh_databases() -> None: - """make new databases from source for testing... removes possibility of contamination by earlier runs""" - data_output_path = Path(PROJECT_ROOT, 'tests', 'testing_outputs') - data_source_path = Path(PROJECT_ROOT, 'tests', 'testing_data') - databases = ( - ('utopia.sql', 'utopia.sqlite'), - ('utopia.sql', 'myo_utopia.sqlite'), + """ + make new databases from source for testing... removes possibility of contamination by earlier + runs + """ + # Map source files to their locations + databases = [ + # Utopia uses the unit-compliant data-only script + ('utopia_data.sql', 'utopia.sqlite'), + ('utopia_data.sql', 'myo_utopia.sqlite'), + ('utopia_data.sql', 'utopia_mc.sqlite'), + # Other test databases ('test_system.sql', 'test_system.sqlite'), - ('storageville.sql', 'storageville.sqlite'), ('mediumville.sql', 'mediumville.sqlite'), + ('seasonal_storage.sql', 'seasonal_storage.sqlite'), + ('survival_curve.sql', 'survival_curve.sqlite'), + ('annualised_demand.sql', 'annualised_demand.sqlite'), + # Feature tests (separate for temporal consistency) ('emissions.sql', 'emissions.sqlite'), + ('materials.sql', 'materials.sqlite'), ('simple_linked_tech.sql', 'simple_linked_tech.sqlite'), - ) + ('storageville.sql', 'storageville.sqlite'), + (None, 'utopia_stochastic.sqlite'), + ] + for src, db in databases: - if Path.exists(data_output_path / db): - os.remove(data_output_path / db) - # make a new one and fill it - con = sqlite3.connect(data_output_path / db) - with open(data_source_path / src, 'r') as script: - con.executescript(script.read()) - con.close() + src_scripts = [TEST_DATA_PATH / src] if src else [] + _build_test_db(TEST_OUTPUT_PATH / db, src_scripts) + +def create_unit_test_dbs() -> None: + """Create unit test databases from SQL source for unit checking tests. -refresh_databases() + Generates databases from the single SQL source of truth (utopia_data.sql), + applying modifications for each test case. + """ + TEST_OUTPUT_PATH.mkdir(exist_ok=True) + + # Define unit test variations with their modifications + unit_test_variations = [ + # 1. Valid database (baseline) - no modifications + ('utopia_valid_units.sqlite', []), + # 2. Invalid currency (no currency dimension in cost table) + ( + 'utopia_invalid_currency.sqlite', + [ + ("UPDATE cost_invest SET units = 'PJ / (GW)' WHERE ROWID = 1", ()), + ], + ), + # 3. Energy units in capacity table (GWh instead of GW) + ( + 'utopia_energy_in_capacity.sqlite', + [ + ("UPDATE existing_capacity SET units = 'GWh' WHERE ROWID = 1", ()), + ], + ), + # 4. Missing parentheses in ratio format + ( + 'utopia_missing_parentheses.sqlite', + [ + ("UPDATE efficiency SET units = 'PJ / Mt' WHERE ROWID = 1", ()), + ], + ), + # 5. Unknown/unregistered units + ( + 'utopia_unknown_units.sqlite', + [ + ("UPDATE commodity SET units = 'catfood' WHERE name = 'co2'", ()), + ], + ), + # 6. Mismatched tech output units + ( + 'utopia_mismatched_outputs.sqlite', + [ + ( + """ + UPDATE efficiency + SET units = 'GJ / (Mt)' + WHERE tech = 'E01' AND output_comm = 'ELC' AND ROWID = + (SELECT MIN(ROWID) FROM efficiency WHERE tech = 'E01') + """, + (), + ), + ], + ), + # 7. Composite currency with nonsensical dimensions + ( + 'utopia_bad_composite_currency.sqlite', + [ + ("UPDATE cost_invest SET units = 'dollar * meter' WHERE ROWID = 1", ()), + ], + ), + ] + + for db_name, modifications in unit_test_variations: + _build_test_db( + TEST_OUTPUT_PATH / db_name, + [TEST_DATA_PATH / 'utopia_data.sql'], + modifications, + ) + logger.info('Created unit test DB: %s', db_name) + + +def pytest_configure(config: Config) -> None: # noqa: ARG001 + """Setup test databases before test collection.""" + refresh_databases() + try: + create_unit_test_dbs() + except FileNotFoundError as e: + # Source DB not available; unit tests will be skipped via pytestmark + logger.warning( + 'Unit test databases not created: source SQL not found. ' + 'Unit checking tests will be skipped.' + ) + logger.debug('DB creation skipped due to: %s', e) @pytest.fixture() def system_test_run( - request, tmp_path + request: Any, tmp_path: Path ) -> tuple[Any, SolverResults | None, TemoaModel | None, TemoaSequencer]: """ spin up the model, solve it, and hand over the model and result for inspection @@ -99,15 +197,22 @@ def system_test_run( data_name = request.param['name'] logger.info('Setting up and solving: %s', data_name) filename = request.param['filename'] - options = {'silent': True, 'debug': True} - config_file = Path(PROJECT_ROOT, 'tests', 'testing_configs', filename) + config_file = Path(__file__).parent / 'testing_configs' / filename - sequencer = TemoaSequencer( + config = TemoaConfig.build_config( config_file=config_file, output_path=tmp_path, - **options, + silent=True, ) + + sequencer = TemoaSequencer(config=config) + sequencer.start() - res = sequencer.pf_results - mdl = sequencer.pf_solved_instance - return data_name, res, mdl, sequencer + + # The rest of the fixture returns the solved instance from the sequencer + return ( + data_name, + sequencer.pf_results, + sequencer.pf_solved_instance, + sequencer, + ) diff --git a/tests/legacy_test_values.py b/tests/legacy_test_values.py index 9e55e24a0..af31669c3 100644 --- a/tests/legacy_test_values.py +++ b/tests/legacy_test_values.py @@ -2,29 +2,6 @@ a container for test values from legacy code (Python 3.7 / Pyomo 5.5) captured for continuity/development testing -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/27/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ from enum import Enum @@ -41,17 +18,106 @@ class ExpectedVals(Enum): # these values were captured on base level runs of the .dat files in the tests/testing_data folder test_vals = { 'test_system': { - ExpectedVals.OBJ_VALUE: 491977.7000753, + # reduced after removing ancient 1-year-shift obj function bug + # increased by ~25 after removing period index from storagefrac (more constraints) + ExpectedVals.OBJ_VALUE: 468575.0703, ExpectedVals.EFF_DOMAIN_SIZE: 30720, ExpectedVals.EFF_INDEX_SIZE: 74, - ExpectedVals.CONSTR_COUNT: 2826, - ExpectedVals.VAR_COUNT: 1904, + # increased by 2 when reworking storageinit. + # increased after making annualretirement derived var + # reduced 2025/07/25 by 504 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 10 after removing period index from storagefrac (more constraints) + # increased by 48 after tying v_storage_level[d_last] to v_storage_init + ExpectedVals.CONSTR_COUNT: 2868, + # reduced by 6 when reworking storageinit. + # increased after making annualretirement derived var + # reduced 2025/07/21 after removing existing vintage v_new_capacity indices + # reduced 2025/07/25 by 420 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 48 after adding v_storage_init variable + ExpectedVals.VAR_COUNT: 2008, }, 'utopia': { - ExpectedVals.OBJ_VALUE: 36535.631200, + # reduced after reworking storageinit -> storage was less constrained + # reduced after removing ancient 1-year-shift obj function bug + # increased after rework of inter-season sequencing + # reduced after changing fixed costs from MLP to PL + # reduced by <1 after changing season definition (segfrac no longer rounded) + # increased by 143 after activating CF constraints for storage techs + ExpectedVals.OBJ_VALUE: 34853.6835, ExpectedVals.EFF_DOMAIN_SIZE: 12312, ExpectedVals.EFF_INDEX_SIZE: 64, - ExpectedVals.CONSTR_COUNT: 1452, # reduced 3/27: unlim_cap techs now employed - ExpectedVals.VAR_COUNT: 1055, # reduced 3/27: unlim_cap techs now employed + # reduced 3/27: unlim_cap techs now employed. + # increased after making annualretirement derived var + # reduced 2025/07/25 by 225 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 27 after tying v_storage_level[d_last] to v_storage_init + # reduced by 14 after dropping DAC for single-tech demands + ExpectedVals.CONSTR_COUNT: 1499, + # reduced 3/27: unlim_cap techs now employed. + # reduced by 4 in storageinit rework. + # increased after making annualretirement derived var + # reduced 2025/07/21 after removing existing vintage v_new_capacity indices + # reduced 2025/07/25 by 200 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 27 after adding v_storage_init variable + ExpectedVals.VAR_COUNT: 1122, + }, + 'mediumville': { + # added 2025/06/12 prior to addition of dynamic reserve margin + # reduced 2025/06/16 after fixing bug in db + ExpectedVals.OBJ_VALUE: 7035.7275, + ExpectedVals.EFF_DOMAIN_SIZE: 2800, + ExpectedVals.EFF_INDEX_SIZE: 18, + # increased after reviving RampSeason constraints + # reduced 2025/07/25 by 24 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 2 after tying v_storage_level[d_last] to v_storage_init + # reduced by 10 after dropping DAC for single-tech demands + # reduced by 8 after disabling season ramp for seasonal_timeslices + ExpectedVals.CONSTR_COUNT: 224, + # reduced 2025/07/25 by 18 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 2 after adding v_storage_init variable + ExpectedVals.VAR_COUNT: 148, + }, + 'seasonal_storage': { + # added 2025/06/16 after addition of seasonal storage + # updated: simplified storage_init to time_next chain with d_last swap + ExpectedVals.OBJ_VALUE: 76661.0231, + ExpectedVals.EFF_DOMAIN_SIZE: 24, + ExpectedVals.EFF_INDEX_SIZE: 4, + # reduced 2025/07/25 by 7 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 2 after tying v_storage_level[d_last] to v_storage_init + # reduced by 9 after dropping DAC for single-tech demands + ExpectedVals.CONSTR_COUNT: 176, + # reduced 2025/07/25 by 7 after annualising demands + # increased 2025/08/19 after making annual demands optional + # increased by 2 after adding v_storage_init variable + ExpectedVals.VAR_COUNT: 93, + }, + 'survival_curve': { + # added 2025/06/19 after addition of survival curves + # reduced after changing fixed costs from MLP to PL + # increased after adding Periodsurvival_curve + ExpectedVals.OBJ_VALUE: 31.9423, + ExpectedVals.EFF_DOMAIN_SIZE: 64, + ExpectedVals.EFF_INDEX_SIZE: 8, + ExpectedVals.CONSTR_COUNT: 127, + # reduced 2025/07/21 after removing existing vintage v_new_capacity indices + ExpectedVals.VAR_COUNT: 127, + }, + 'annualised_demand': { + # added 2025/06/19 after addition of survival curves + # reduced after changing fixed costs from MLP to PL + # increased after adding Periodsurvival_curve + ExpectedVals.OBJ_VALUE: 1.9524, + ExpectedVals.EFF_DOMAIN_SIZE: 36, + ExpectedVals.EFF_INDEX_SIZE: 10, + ExpectedVals.CONSTR_COUNT: 15, + # reduced 2025/07/21 after removing existing vintage v_new_capacity indices + ExpectedVals.VAR_COUNT: 21, }, } diff --git a/tests/smoke_test.py b/tests/smoke_test.py new file mode 100644 index 000000000..b7fd137c6 --- /dev/null +++ b/tests/smoke_test.py @@ -0,0 +1,45 @@ +import shutil +import subprocess + +import temoa + + +def _find_temoa_path() -> str: + path = shutil.which('temoa') + if not path: + raise RuntimeError('temoa executable not found in PATH') + return path + + +def test_import() -> None: + print(f'Importing temoa version: {temoa.__version__}') + assert temoa.__version__ is not None + + +def test_cli() -> None: + print('Running temoa --version CLI command...') + temoa_path = _find_temoa_path() + + result = subprocess.run( + [temoa_path, '--version'], capture_output=True, text=True, timeout=10, check=True + ) + print(f'CLI output: {result.stdout.strip()}') + assert 'Temoa Version:' in result.stdout + assert temoa.__version__ in result.stdout + + +def test_help() -> None: + print('Running temoa --help CLI command...') + temoa_path = _find_temoa_path() + + result = subprocess.run( + [temoa_path, '--help'], capture_output=True, text=True, timeout=10, check=True + ) + assert 'The Temoa Project' in result.stdout + + +if __name__ == '__main__': + test_import() + test_cli() + test_help() + print('\n✅ Smoke test passed!') diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 000000000..00184115b --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,470 @@ +import re +import shutil +import sqlite3 +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from temoa.cli import _is_writable, app + +runner = CliRunner() + +# Path to the configuration file template we will use for tests. +TESTING_CONFIGS_DIR = Path(__file__).parent / 'testing_configs' +UTOPIA_SQL_FIXTURE = Path(__file__).parent.parent / 'temoa' / 'tutorial_assets' / 'utopia.sql' +UTOPIA_CONFIG_TEMPLATE = TESTING_CONFIGS_DIR / 'config_utopia.toml' + + +def create_test_config(tmp_path: Path, db_path: Path) -> Path: + """ + Reads the config template, replaces a placeholder path with the correct + test database path, and writes a new, runnable config file. + """ + template_content = UTOPIA_CONFIG_TEMPLATE.read_text() + # NOTE: This placeholder is specific to the `config_utopia.toml` file. + placeholder = 'input_database = "tests/testing_outputs/utopia.sqlite"' + replacement = f'input_database = "{db_path.as_posix()}"' + config_content = template_content.replace(placeholder, replacement) + test_config_path = tmp_path / 'test_config.toml' + test_config_path.write_text(config_content) + return test_config_path + + +def create_config_with_solver(tmp_path: Path, db_path: Path, solver_name: str) -> Path: + """ + Creates a test config file that points to a specific solver. + """ + test_config_path = create_test_config(tmp_path, db_path) + config_content = test_config_path.read_text() + if re.search(r'^\s*solver_name\s*=', config_content, re.MULTILINE): + config_content = re.sub( + r'^\s*solver_name\s*=\s*".*?"', + f'solver_name = "{solver_name}"', + config_content, + flags=re.MULTILINE, + ) + else: + # Add to the end if not found + config_content += f'\nsolver_name = "{solver_name}"\n' + + test_config_path.write_text(config_content) + return test_config_path + + +def test_cli_version() -> None: + """Test the `temoa --version` command.""" + result = runner.invoke(app, ['--version']) + assert result.exit_code == 0 + assert 'Temoa Version' in result.stdout + + +def test_cli_run_command_success_silent(tmp_path: Path) -> None: + """Test a successful silent run of the `temoa run` command.""" + db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' + test_config_path = create_test_config(tmp_path, db_path) + # --output is explicitly given, so it should use tmp_path + args = ['run', str(test_config_path), '--output', str(tmp_path), '--silent'] + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'CLI crashed with error: {result.exception}' + assert 'Temoa run completed successfully' not in result.stdout # Silent run + assert (tmp_path / 'temoa-run.log').exists() + + +def test_cli_run_build_only_silent(tmp_path: Path) -> None: + """Test the `temoa run --build-only --silent` flags.""" + db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' + test_config_path = create_test_config(tmp_path, db_path) + # --output is explicitly given, so it should use tmp_path + args = ['run', str(test_config_path), '--output', str(tmp_path), '--build-only', '--silent'] + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'CLI crashed with error: {result.exception}' + assert 'Model built successfully' not in result.stdout # Silent run + assert (tmp_path / 'temoa-run.log').exists() + + +# ============================================================================= +# Tests for the `validate` command +# ============================================================================= + + +def test_cli_validate_success_verbose(tmp_path: Path) -> None: + """Test a successful verbose run of the `temoa validate` command.""" + db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' + test_config_path = create_test_config(tmp_path, db_path) + args = ['validate', str(test_config_path), '--output', str(tmp_path)] + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'CLI crashed with error: {result.exception}' + assert 'Validation successful' in result.stdout + assert (tmp_path / 'temoa-run.log').exists() + + +def test_cli_validate_success_silent(tmp_path: Path) -> None: + """Test a successful silent run of the `temoa validate` command.""" + db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' + test_config_path = create_test_config(tmp_path, db_path) + args = ['validate', str(test_config_path), '--output', str(tmp_path), '--silent'] + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'CLI crashed with error: {result.exception}' + assert 'Validation successful' not in result.stdout + assert (tmp_path / 'temoa-run.log').exists() + + +def test_cli_validate_failure_on_invalid_db(tmp_path: Path) -> None: + """Test a failing run of `temoa validate` with an invalid database.""" + # Create a file that is not a valid Temoa database (an empty file). + # This will cause the version check inside the sequencer to fail. + invalid_db_path = tmp_path / 'invalid.sqlite' + invalid_db_path.touch() + + # Create a valid config file that points to this invalid database. + test_config_path = create_test_config(tmp_path, invalid_db_path) + + args = ['validate', str(test_config_path), '--output', str(tmp_path)] + result = runner.invoke(app, args) + + assert result.exit_code != 0, 'CLI should exit with a non-zero code on failure' + assert 'Validation failed' in result.stdout + # Check that the log was still created, containing the detailed error + assert (tmp_path / 'temoa-run.log').exists() + + +def test_cli_run_missing_config() -> None: + """Test graceful failure for a missing config file.""" + args = ['run', 'non_existent_file.toml'] + result = runner.invoke(app, args) + + assert result.exit_code != 0 + # Check that the error mentions the missing file (more robust than exact string match) + assert 'non_existent_file.toml' in result.stderr + + +# ============================================================================= +# Tests for the `migrate` command +# ============================================================================= + + +def test_cli_migrate_help() -> None: + """Test the `temoa migrate --help` command.""" + result = runner.invoke(app, ['migrate', '--help']) + assert result.exit_code == 0 + assert 'migrate' in result.stdout + assert 'Migrate a Temoa database file' in result.stdout + + +def test_cli_migrate_sql_file(tmp_path: Path) -> None: + """Test migrating a SQL file with explicit --output.""" + # Ensure input file is available in the test environment + input_file_src = UTOPIA_SQL_FIXTURE + input_file = tmp_path / 'test_input.sql' + shutil.copy2(input_file_src, input_file) + + output_file = tmp_path / 'migrated_explicit.sql' + args = ['migrate', str(input_file), '--output', str(output_file)] + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'Migration failed: {result.exception}\n{result.stderr}' + assert 'SQL dump migration completed' in result.stdout + assert output_file.exists() + + +def test_cli_migrate_accepts_directory_input(tmp_path: Path) -> None: + """Test that the migrate command accepts a directory as input for batch processing.""" + dummy_dir = tmp_path / 'my_dummy_dir' + dummy_dir.mkdir() + + src_file = UTOPIA_SQL_FIXTURE + input_file = dummy_dir / src_file.name + shutil.copy2(src_file, input_file) + + args = ['migrate', str(dummy_dir)] + result = runner.invoke(app, args) + + assert result.exit_code == 0 + # Normalize whitespace to handle platform-specific line breaks from rich.print() + normalized_output = ' '.join(result.stdout.split()) + assert 'Batch migrating directory' in normalized_output + # Check for the directory name in the original output (paths may be split across lines) + assert 'my_dummy_dir' in result.stdout + assert 'Total files processed: 1' in normalized_output + + +def test_cli_migrate_sql_file_auto_output_writable_input_dir(tmp_path: Path) -> None: + """ + Test migrating a SQL file without --output, + where the input directory is writable. + Output should be next to input with _v4.sql suffix. + """ + src_file = UTOPIA_SQL_FIXTURE + input_file = tmp_path / src_file.name # Input file in writable tmp_path + shutil.copy2(src_file, input_file) + + args = ['migrate', str(input_file)] + result = runner.invoke(app, args) + + assert result.exit_code == 0, ( + f'Migration failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + assert 'SQL dump migration completed' in result.stdout + expected_output = input_file.with_stem(input_file.stem + '_v4').with_suffix( + '.sql' + ) # Explicit .sql suffix + assert expected_output.exists() + + +def test_cli_migrate_sql_file_auto_output_non_writable_input_dir_fallback_cwd( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + Test migrating a SQL file without --output, + where the input directory is NOT writable. + Output should fall back to current working directory (mocked as tmp_path) + and have a _v4.sql suffix. + """ + non_writable_mock_parent = tmp_path / 'mock_non_writable_input_parent' + non_writable_mock_parent.mkdir() + + src_file = UTOPIA_SQL_FIXTURE + input_file = non_writable_mock_parent / src_file.name + shutil.copy2(src_file, input_file) + + def mock_is_writable(path: Path) -> bool: + if path == non_writable_mock_parent: + return False + if path == tmp_path: # CWD for the test runner is tmp_path + return True + return _is_writable(path) + + monkeypatch.setattr('temoa.cli._is_writable', mock_is_writable) + monkeypatch.setattr( + Path, + 'cwd', + classmethod(lambda cls: tmp_path), + ) # Ensure CWD is tmp_path for logging + + args = ['migrate', str(input_file)] + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code == 0, ( + f'Migration failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + # Normalize whitespace to handle platform-specific line breaks from rich.print() + normalized_output = ' '.join(result.stdout.split()) + clean_output = re.sub(r'\s+', '', result.stdout) + + assert 'SQL dump migration completed' in normalized_output + assert 'Warning: Input directory' in normalized_output + assert 'mock_non_writable_input_parent' in clean_output + assert 'is not writable' in normalized_output + assert 'Saving output to current directory' in normalized_output + assert tmp_path.name in clean_output + + expected_output_in_cwd = tmp_path / (input_file.stem + '_v4.sql') + assert expected_output_in_cwd.exists() + assert not (non_writable_mock_parent / (input_file.stem + '_v4.sql')).exists() + + +def test_cli_migrate_sql_file_auto_output_no_writable_location( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + Test migrating a SQL file without --output, + where neither the input directory nor the CWD are writable. + Should exit with an error. + """ + non_writable_mock_parent = tmp_path / 'mock_non_writable_input_parent_no_cwd' + non_writable_mock_parent.mkdir() + + src_file = UTOPIA_SQL_FIXTURE + input_file = non_writable_mock_parent / src_file.name + shutil.copy2(src_file, input_file) + + # Mock _is_writable to return False for both the input directory and the CWD (tmp_path) + def mock_is_writable_always_false(_path: Path) -> bool: + return False + + monkeypatch.setattr('temoa.cli._is_writable', mock_is_writable_always_false) + + args = ['migrate', str(input_file)] + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code != 0, 'Migration should fail with a non-zero exit code' + # Normalize whitespace to handle platform-specific line breaks from rich.print() + normalized_output = ' '.join(result.stdout.split()) + assert 'Error: Neither input directory' in normalized_output + assert 'nor current working directory' in normalized_output + assert 'are writable.' in normalized_output + assert not ( + tmp_path / (input_file.stem + '_v4' + input_file.suffix) + ).exists() # No output created + + +def test_cli_migrate_invalid_file() -> None: + """Test migrating a non-existent file.""" + args = ['migrate', 'non_existent.sql'] + result = runner.invoke(app, args) + + assert result.exit_code != 0 + # Typer handles file existence check, so error is in stderr + assert 'does not exist' in result.stderr or 'does not exist' in str(result.exception) + + +def test_cli_migrate_unknown_type(tmp_path: Path) -> None: + """Test migrating a file with unknown extension.""" + unknown_file = tmp_path / 'unknown.txt' + unknown_file.write_text('dummy') + args = ['migrate', str(unknown_file)] + result = runner.invoke(app, args) + + assert result.exit_code != 0 + assert 'Cannot determine migration type' in result.stdout + + +def test_cli_migrate_override_type(tmp_path: Path) -> None: + """Test migrating with explicit type override.""" + input_file_src = UTOPIA_SQL_FIXTURE + input_file = tmp_path / 'test_input_override.sql' + shutil.copy2(input_file_src, input_file) + + output_file = tmp_path / 'migrated_override.sql' + args = ['migrate', str(input_file), '--output', str(output_file), '--type', 'sql'] + result = runner.invoke(app, args) + + assert result.exit_code == 0 + assert 'SQL dump migration completed' in result.stdout + assert output_file.exists() + + +def test_cli_migrate_sql_file_silent(tmp_path: Path) -> None: + """Test migrating a SQL file with --silent flag.""" + input_file_src = UTOPIA_SQL_FIXTURE + input_file = tmp_path / 'test_input_silent.sql' + shutil.copy2(input_file_src, input_file) + + output_file = tmp_path / 'migrated_silent.sql' + args = ['migrate', str(input_file), '--output', str(output_file), '--silent'] # Use --silent + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'Migration failed: {result.exception}\n{result.stderr}' + # In silent mode, success messages should NOT be in stdout + assert 'SQL dump migration completed' not in result.stdout + assert output_file.exists() + + +def test_cli_migrate_db_file_silent(tmp_path: Path) -> None: + """Test migrating a DB file with --silent flag.""" + input_file = tmp_path / 'test_v3_1_silent.sqlite' + conn = sqlite3.connect(input_file) + conn.execute('CREATE TABLE MetaData (name TEXT, value TEXT)') + conn.execute("INSERT INTO MetaData VALUES ('DB_MAJOR', '3')") + conn.commit() + conn.close() + + output_file = tmp_path / 'migrated_silent.sqlite' + args = ['migrate', str(input_file), '--output', str(output_file), '--silent'] # Use --silent + result = runner.invoke(app, args) + + assert result.exit_code == 0, f'Migration failed: {result.exception}\n{result.stderr}' + # In silent mode, success messages should NOT be in stdout + assert 'Database migration completed' not in result.stdout + assert output_file.exists() + + +def test_cli_migrate_sql_file_auto_output_non_writable_input_dir_fallback_cwd_silent( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + Test migrating a SQL file with --silent, where input dir is not writable. + Output should fall back to CWD, and the warning should NOT be printed. + """ + non_writable_mock_parent = tmp_path / 'mock_non_writable_input_parent_silent' + non_writable_mock_parent.mkdir() + + src_file = UTOPIA_SQL_FIXTURE + input_file = non_writable_mock_parent / src_file.name + shutil.copy2(src_file, input_file) + + def mock_is_writable(path: Path) -> bool: + if path == non_writable_mock_parent: + return False + if path == tmp_path: + return True + return _is_writable(path) + + monkeypatch.setattr('temoa.cli._is_writable', mock_is_writable) + monkeypatch.setattr( + Path, + 'cwd', + classmethod(lambda cls: tmp_path), + ) + + args = ['migrate', str(input_file), '--silent'] # Use --silent here + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code == 0, ( + f'Migration failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + assert 'SQL dump migration completed' not in result.stdout # Should be silent + assert 'Warning: Input directory' not in result.stdout # Warning should be silent + + expected_output_in_cwd = tmp_path / (input_file.stem + '_v4.sql') + assert expected_output_in_cwd.exists() + assert not (non_writable_mock_parent / (input_file.stem + '_v4.sql')).exists() + + +def test_cli_validate_fails_if_solver_missing( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """ + Test that the validate command fails with SolverNotAvailableError if the configured solver is + missing. + """ + db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' + test_config_path = create_config_with_solver(tmp_path, db_path, 'nonexistent_solver') + + # Mock shutil.which to always return None for any solver check + monkeypatch.setattr(shutil, 'which', lambda _: None) + + args = ['validate', str(test_config_path), '--output', str(tmp_path)] + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code != 0, ( + f'Validate should have failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + assert isinstance(result.exception, SystemExit) + assert result.exception.code == 1 + assert '❌ Validation failed:' in result.stdout + assert 'nonexistent_solver' in result.stdout + # Use the more robust phrase for checking installation instructions + assert 'Please ensure the solver is installed and accessible.' in result.stdout + assert (tmp_path / 'temoa-run.log').exists() + + +def test_cli_run_fails_if_solver_missing(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """ + Test that the run command fails with SolverNotAvailableError if the configured solver is + missing. + """ + db_path = Path(__file__).parent / 'testing_outputs' / 'utopia.sqlite' + test_config_path = create_config_with_solver(tmp_path, db_path, 'another_nonexistent_solver') + + # Mock shutil.which to always return None for any solver check + monkeypatch.setattr(shutil, 'which', lambda _: None) + + args = ['run', str(test_config_path), '--output', str(tmp_path)] + result = runner.invoke(app, args, catch_exceptions=False) + + assert result.exit_code != 0, ( + f'Run should have failed: {result.exception}\n{result.stderr}\n{result.stdout}' + ) + assert isinstance(result.exception, SystemExit) + assert result.exception.code == 1 + assert '❌ An error occurred:' in result.stdout + assert 'another_nonexistent_solver' in result.stdout + # Use the more robust phrase for checking installation instructions + assert 'Please ensure the solver is installed and accessible.' in result.stdout + assert (tmp_path / 'temoa-run.log').exists() diff --git a/tests/test_commodity_visualizer.py b/tests/test_commodity_visualizer.py new file mode 100644 index 000000000..69e2efe5d --- /dev/null +++ b/tests/test_commodity_visualizer.py @@ -0,0 +1,89 @@ +from temoa.model_checking.commodity_graph import generate_commodity_graph +from temoa.model_checking.network_model_data import EdgeTuple, NetworkModelData +from temoa.types.core_types import Commodity, Period, Region, Sector, Technology, Vintage + + +def test_special_items_styling() -> None: + """ + Test that demand orphans, other orphans, and driven techs + are correctly styled in the commodity graph. + """ + region = Region('test_region') + period = Period(2025) + + # Concrete NetworkModelData + network_data = NetworkModelData() + network_data.physical_commodities = {Commodity('comm_inter')} + network_data.source_commodities[(region, period)] = {Commodity('comm_source')} + network_data.demand_commodities[(region, period)] = {Commodity('comm_demand')} + + # Define some special items + demand_orphans = [ + EdgeTuple( + region, + Commodity('comm_inter'), + Technology('tech_demand_orphan'), + Vintage(2020), + Commodity('comm_demand'), + sector=Sector('S1'), + ) + ] + other_orphans = [ + EdgeTuple( + region, + Commodity('comm_source'), + Technology('tech_other_orphan'), + Vintage(2020), + Commodity('comm_inter'), + sector=Sector('S2'), + ) + ] + driven_techs = [ + EdgeTuple( + region, + Commodity('comm_source'), + Technology('tech_driven'), + Vintage(2020), + Commodity('comm_demand'), + sector=Sector('S3'), + ) + ] + + # Generate the graph + dg, _sector_colors = generate_commodity_graph( + region, + period, + network_data, + demand_orphans=demand_orphans, + other_orphans=other_orphans, + driven_techs=driven_techs, + ) + + # 1. Check Node Styling + assert dg.nodes['comm_demand']['color']['border'] == '#d62728' + assert dg.nodes['comm_demand']['borderWidth'] == 4 + assert 'Connected to Demand Orphan' in dg.nodes['comm_demand']['title'] + + assert dg.nodes['comm_inter']['color']['border'] == '#d62728' + assert dg.nodes['comm_inter']['borderWidth'] == 4 + + assert dg.nodes['comm_source']['color']['border'] == '#ff7f0e' + assert dg.nodes['comm_source']['borderWidth'] == 4 + + # 2. Check Edge Styling + edges = list(dg.edges(data=True)) + + edge_do = next((e for e in edges if (e[0] == 'comm_inter' and e[1] == 'comm_demand')), None) + assert edge_do is not None, 'Edge (comm_inter -> comm_demand) not found' + assert edge_do[2].get('dashes') is True + assert edge_do[2].get('color') == '#d62728' + + edge_oo = next((e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_inter')), None) + assert edge_oo is not None, 'Edge (comm_source -> comm_inter) not found' + assert edge_oo[2].get('dashes') is True + assert edge_oo[2].get('color') == '#ff7f0e' + + edge_dt = next((e for e in edges if (e[0] == 'comm_source' and e[1] == 'comm_demand')), None) + assert edge_dt is not None, 'Edge (comm_source -> comm_demand) not found' + assert edge_dt[2].get('dashes') is True + assert edge_dt[2].get('color') == '#1f77b4' diff --git a/tests/test_cycle_limits.py b/tests/test_cycle_limits.py new file mode 100644 index 000000000..80895e4a0 --- /dev/null +++ b/tests/test_cycle_limits.py @@ -0,0 +1,194 @@ +from __future__ import annotations + +import logging +from pathlib import Path +from unittest.mock import MagicMock + +import networkx as nx +import pytest + +from temoa.core.config import TemoaConfig +from temoa.model_checking.commodity_graph import visualize_graph +from temoa.types.core_types import Period, Region + + +@pytest.fixture +def mock_config() -> MagicMock: + config = MagicMock(spec=TemoaConfig) + config.plot_commodity_network = True + config.output_path = Path('./') + config.cycle_count_limit = 100 + config.cycle_length_limit = 1 + return config + + +@pytest.fixture +def cycle_graph() -> nx.MultiDiGraph[str]: + dg: nx.MultiDiGraph[str] = nx.MultiDiGraph() + # Create two cycles: (A->B->A) length 2, (C->D->E->C) length 3 + dg.add_edge('A', 'B') + dg.add_edge('B', 'A') + dg.add_edge('C', 'D') + dg.add_edge('D', 'E') + dg.add_edge('E', 'C') + # Add some other nodes/edges to make it look like a real graph + dg.add_node('A', layer=1, sector='S1') + dg.add_node('B', layer=2, sector='S1') + dg.add_node('C', layer=1, sector='S2') + dg.add_node('D', layer=2, sector='S2') + dg.add_node('E', layer=3, sector='S2') + return dg + + +def test_cycle_limits_logging( + mock_config: MagicMock, + cycle_graph: nx.MultiDiGraph[str], + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test that cycles are logged according to length limit.""" + mock_config.cycle_length_limit = 3 + + with caplog.at_level(logging.INFO): + # We need to mock generate_commodity_graph to return our controlled graph + import temoa.model_checking.commodity_graph as cg + + monkeypatch.setattr( + cg, 'generate_commodity_graph', MagicMock(return_value=(cycle_graph, {})) + ) + monkeypatch.setattr(cg, 'generate_technology_graph', MagicMock()) + monkeypatch.setattr(cg, 'nx_to_vis', MagicMock(return_value='path')) + + visualize_graph( + region=Region('R1'), + period=Period(2020), + network_data=MagicMock(), + demand_orphans=[], + other_orphans=[], + driven_techs=[], + config=mock_config, + ) + + # Should only log cycles of length >= 3 + # (C->D->E->C) is length 3 + # (A->B->A) is length 2, should be skipped + assert any( + 'Cycle detected' in record.message + and 'C' in record.message + and 'D' in record.message + and 'E' in record.message + for record in caplog.records + ) + assert not any( + 'Cycle detected' in record.message and 'A' in record.message and 'B' in record.message + for record in caplog.records + ) + + +def test_cycle_count_limit( + mock_config: MagicMock, + cycle_graph: nx.MultiDiGraph[str], + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test that cycle detection stops after cycle_count_limit.""" + mock_config.cycle_count_limit = 1 + mock_config.cycle_length_limit = 1 + + with caplog.at_level(logging.INFO): + import temoa.model_checking.commodity_graph as cg + + monkeypatch.setattr( + cg, 'generate_commodity_graph', MagicMock(return_value=(cycle_graph, {})) + ) + monkeypatch.setattr(cg, 'generate_technology_graph', MagicMock()) + monkeypatch.setattr(cg, 'nx_to_vis', MagicMock(return_value='path')) + + visualize_graph( + region=Region('R1'), + period=Period(2020), + network_data=MagicMock(), + demand_orphans=[], + other_orphans=[], + driven_techs=[], + config=mock_config, + ) + + # Should only log 1 cycle and then the warning + # nx.simple_cycles might return them in different order depending on version/impl + # but it should only log ONE of them. + cycle_logs = [record.message for record in caplog.records if 'Cycle detected' in record.message] + assert len(cycle_logs) == 1 + assert 'Cycle detection reached limit of 1 cycles. Stopping.' in caplog.text + + +def test_cycle_count_limit_zero( + mock_config: MagicMock, + cycle_graph: nx.MultiDiGraph[str], + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test that cycle_count_limit=0 logs an error and stops immediately.""" + mock_config.cycle_count_limit = 0 + + with caplog.at_level(logging.INFO): + import temoa.model_checking.commodity_graph as cg + + monkeypatch.setattr( + cg, 'generate_commodity_graph', MagicMock(return_value=(cycle_graph, {})) + ) + monkeypatch.setattr(cg, 'generate_technology_graph', MagicMock()) + monkeypatch.setattr(cg, 'nx_to_vis', MagicMock(return_value='path')) + + visualize_graph( + region=Region('R1'), + period=Period(2020), + network_data=MagicMock(), + demand_orphans=[], + other_orphans=[], + driven_techs=[], + config=mock_config, + ) + + assert any( + 'Cycles detected but cycle_count_limit is 0. Stopping.' in record.message + for record in caplog.records + ) + assert not any('Cycle detected:' in record.message for record in caplog.records) + + +def test_cycle_unbounded( + mock_config: MagicMock, + cycle_graph: nx.MultiDiGraph[str], + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test that cycle_count_limit=-1 allows all cycles.""" + mock_config.cycle_count_limit = -1 + + with caplog.at_level(logging.INFO): + import temoa.model_checking.commodity_graph as cg + + monkeypatch.setattr( + cg, 'generate_commodity_graph', MagicMock(return_value=(cycle_graph, {})) + ) + monkeypatch.setattr(cg, 'generate_technology_graph', MagicMock()) + monkeypatch.setattr(cg, 'nx_to_vis', MagicMock(return_value='path')) + + visualize_graph( + region=Region('R1'), + period=Period(2020), + network_data=MagicMock(), + demand_orphans=[], + other_orphans=[], + driven_techs=[], + config=mock_config, + ) + + assert any( + 'Cycle detected' in record.message and 'C' in record.message for record in caplog.records + ) + assert any( + 'Cycle detected' in record.message and 'A' in record.message for record in caplog.records + ) + assert not any('Stopping' in record.message for record in caplog.records) diff --git a/tests/test_element_checker.py b/tests/test_element_checker.py index f04b935ef..5e43ed2ed 100644 --- a/tests/test_element_checker.py +++ b/tests/test_element_checker.py @@ -1,35 +1,12 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling +from typing import Any -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/25/24 - -""" import pytest -from temoa.temoa_model.model_checking.element_checker import ViableSet, filter_elements +from temoa.model_checking.element_checker import ViableSet, filter_elements -params = [ +ParamType = dict[str, Any] +ElementType = tuple[Any, ...] | str | int +params: list[ParamType] = [ { 'name': 'group 1', 'filt': ViableSet( @@ -130,8 +107,8 @@ ] -@pytest.mark.parametrize('data', params, ids=(param['name'] for param in params)) -def test_filter_elements(data): +@pytest.mark.parametrize('data', params, ids=[param['name'] for param in params]) +def test_filter_elements(data: ParamType) -> None: # use the 'tester' elements against the filter to ensure we get expected results assert ( filter_elements( @@ -141,9 +118,9 @@ def test_filter_elements(data): ) -def test_dimension_measurement(): +def test_dimension_measurement() -> None: """quick test to ensure we are getting the correct dimension esp. when elements are not tuple""" - elements = [(1, 2), (3, 4)] + elements: list[ElementType] = [(1, 2), (3, 4)] assert ViableSet(elements).dim == 2 elements = ['dog', 'pig', 'uncle bob'] diff --git a/tests/test_emission_results.py b/tests/test_emission_results.py index 129494bc1..7cd0d8e3a 100644 --- a/tests/test_emission_results.py +++ b/tests/test_emission_results.py @@ -1,68 +1,61 @@ """ Test some emissions and curtailment results for some basic technology archetypes -Written by: Ian David Elder -iandavidelder@gmail.com -Created on: 2024/06/03 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ import logging import sqlite3 +from collections.abc import Generator from pathlib import Path +from typing import TypedDict, cast import pytest -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_sequencer import TemoaSequencer +# Import TemoaConfig, which is now needed to set up the sequencer +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig logger = logging.getLogger(__name__) +type SolvedConnection = tuple[sqlite3.Connection, str, str, float] + + +# Define a TypedDict for the test parameters to provide better type hints +class TechTestParams(TypedDict): + name: str + tech: str + target: float + @pytest.fixture(scope='module') -def solved_connection(request, tmp_path_factory): +def solved_connection( + request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory +) -> Generator[SolvedConnection, None, None]: """ - spin up the model, solve it, and hand over a connection to the results db + Spins up the model, solves it, and hands over a connection to the results db. + This fixture is now updated to use the refactored TemoaSequencer API. """ - data_name = 'emissions' - logger.info('Setting up and solving: %s', data_name) - filename = 'config_emissions.toml' - options = {'silent': True, 'debug': True} - config_file = Path(PROJECT_ROOT, 'tests', 'testing_configs', filename) + param = cast('TechTestParams', request.param) + logger.info('Setting up and solving for test case: %s', param['name']) + + config_file = Path(__file__).parent / 'testing_configs' / 'config_emissions.toml' tmp_path = tmp_path_factory.mktemp('data') - sequencer = TemoaSequencer( - config_file=config_file, - output_path=tmp_path, - **options, - ) + config = TemoaConfig.build_config(config_file=config_file, output_path=tmp_path, silent=True) + sequencer = TemoaSequencer(config=config) + + # Step 3: Run the sequencer to completion. sequencer.start() - # make connection here as in your code... + con = sqlite3.connect(sequencer.config.output_database) - yield con, request.param['name'], request.param['tech'], request.param['target'] - con.close() + try: + yield con, param['name'], param['tech'], param['target'] + finally: + con.close() # List of tech archetypes to test and their correct emission value -emissions_tests = [ +emissions_tests: list[TechTestParams] = [ {'name': 'ordinary archetype', 'tech': 'TechOrdinary', 'target': 0.3}, {'name': 'curtailment archetype', 'tech': 'TechCurtailment', 'target': 0.3}, {'name': 'annual archetype', 'tech': 'TechAnnual', 'target': 1.0}, @@ -70,6 +63,12 @@ def solved_connection(request, tmp_path_factory): {'name': 'annual flex archetype', 'tech': 'TechAnnualFlex', 'target': 1.0}, {'name': 'total', 'tech': '%', 'target': 3.6}, ] +embodied_tests: list[TechTestParams] = [ + {'name': 'embodied archetype', 'tech': 'TechEmbodied', 'target': 0.3}, +] +eol_tests: list[TechTestParams] = [ + {'name': 'end of life archetype', 'tech': 'TechEndOfLife', 'target': 0.3}, +] # Emissions @@ -79,19 +78,22 @@ def solved_connection(request, tmp_path_factory): indirect=True, ids=[t['name'] for t in emissions_tests], ) -def test_emissions(solved_connection): +def test_emissions(solved_connection: SolvedConnection) -> None: """ Test that the emissions from each technology archetype are correct, and check total emissions """ con, name, tech, emis_target = solved_connection emis = ( con.cursor() - .execute(f"SELECT SUM(emission) FROM main.OutputEmission WHERE tech LIKE '{tech}'") + .execute( + f"SELECT SUM(emission) FROM main.output_emission WHERE tech LIKE '{tech}' AND " + f"tech != 'TechEmbodied' AND period == 2000" + ) .fetchone()[0] ) - assert emis == pytest.approx( - emis_target - ), f'{name} emissions were incorrect. Should be {emis_target}, got {emis}' + assert emis == pytest.approx(emis_target), ( + f'{name} emissions were incorrect. Should be {emis_target}, got {emis}' + ) # Emission costs undiscounted @@ -101,20 +103,25 @@ def test_emissions(solved_connection): indirect=True, ids=[t['name'] for t in emissions_tests], ) -def test_emissions_costs_undiscounted(solved_connection): +def test_emissions_costs_undiscounted( + solved_connection: SolvedConnection, +) -> None: """ - Test that the emission costs from each technology archetype are correct, and check total emissions + Test that the undiscounted emission costs from each technology archetype are correct """ con, name, tech, emis_target = solved_connection ec = ( con.cursor() - .execute(f"SELECT SUM(emiss) FROM main.OutputCost WHERE tech LIKE '{tech}'") + .execute( + f"SELECT SUM(emiss) FROM main.output_cost WHERE tech LIKE '{tech}' AND " + f"tech != 'TechEmbodied' AND period == 2000" + ) .fetchone()[0] ) cost_target = 0.7 * emis_target * 5 # emission cost x emissions x 5y - assert ec == pytest.approx( - cost_target - ), f'{name} undiscounted emission costs were incorrect. Should be {cost_target}, got {ec}' + assert ec == pytest.approx(cost_target), ( + f'{name} undiscounted emission costs were incorrect. Should be {cost_target}, got {ec}' + ) # Emission costs discounted @@ -124,27 +131,196 @@ def test_emissions_costs_undiscounted(solved_connection): indirect=True, ids=[t['name'] for t in emissions_tests], ) -def test_emissions_costs_discounted(solved_connection): +def test_emissions_costs_discounted( + solved_connection: SolvedConnection, +) -> None: + """ + Test that the discounted emission costs from each technology archetype are correct + """ + con, name, tech, emis_target = solved_connection + ec = ( + con.cursor() + .execute( + f"SELECT SUM(d_emiss) FROM main.output_cost WHERE tech LIKE '{tech}' AND " + f"tech != 'TechEmbodied' AND period == 2000" + ) + .fetchone()[0] + ) + cost_target = 0.7 * emis_target * 4.32947667063082 # emission cost x emissions x P/A(5%, 5y, 1) + assert ec == pytest.approx(cost_target), ( + f'{name} discounted emission costs were incorrect. Should be {cost_target}, got {ec}' + ) + + +# Embodied emissions +@pytest.mark.parametrize( + 'solved_connection', + argvalues=embodied_tests, + indirect=True, + ids=[t['name'] for t in embodied_tests], +) +def test_embodied_emissions(solved_connection: SolvedConnection) -> None: + """ + Test that the embodied emissions from each technology archetype are correct, and check total + emissions + """ + con, name, tech, emis_target = solved_connection + emis = ( + con.cursor() + .execute( + f"SELECT SUM(emission) FROM main.output_emission WHERE tech LIKE '{tech}' AND " + f'period == 2000' + ) + .fetchone()[0] + ) + assert emis == pytest.approx( + emis_target / 5 # embodied emissions are distributed over vintage period + ), f'{name} embodied emissions were incorrect. Should be {emis_target}, got {emis}' + + +# Embodied emission costs undiscounted +@pytest.mark.parametrize( + 'solved_connection', + argvalues=embodied_tests, + indirect=True, + ids=[t['name'] for t in embodied_tests], +) +def test_embodied_emissions_costs_undiscounted( + solved_connection: SolvedConnection, +) -> None: + """ + Test that the undiscounted embodied emission costs from each technology archetype are correct + """ + con, name, tech, emis_target = solved_connection + ec = ( + con.cursor() + .execute( + f"SELECT SUM(emiss) FROM main.output_cost WHERE tech LIKE '{tech}' AND period == 2000" + ) + .fetchone()[0] + ) + cost_target = 0.7 * emis_target # emission cost x embodied emissions + assert ec == pytest.approx(cost_target), ( + f'{name} undiscounted embodied emission costs were incorrect. Should be {cost_target}, ' + f'got {ec}' + ) + + +# Embodied emission costs discounted +@pytest.mark.parametrize( + 'solved_connection', + argvalues=embodied_tests, + indirect=True, + ids=[t['name'] for t in embodied_tests], +) +def test_embodied_emissions_costs_discounted( + solved_connection: SolvedConnection, +) -> None: + """ + Test that discounted embodied emission costs from each technology archetype are correct + """ + con, name, tech, emis_target = solved_connection + ec = ( + con.cursor() + .execute( + f"SELECT SUM(d_emiss) FROM main.output_cost WHERE tech LIKE '{tech}' AND period == 2000" + ) + .fetchone()[0] + ) + cost_target = ( + 0.7 * emis_target * 1 / 5 * (1.05**5 - 1) / (0.05 * 1.05**5) + ) # emission cost x embodied emissions x annual distribution x P/A(5%, 5y, 1) + assert ec == pytest.approx(cost_target), ( + f'{name} discounted emission costs were incorrect. Should be {cost_target}, got {ec}' + ) + + +# End of life emissions +@pytest.mark.parametrize( + 'solved_connection', + argvalues=eol_tests, + indirect=True, + ids=[t['name'] for t in eol_tests], +) +def test_endoflife_emissions(solved_connection: SolvedConnection) -> None: + """ + Test that the end of life emissions from each technology archetype are correct, and check total + emissions + """ + con, name, tech, emis_target = solved_connection + emis = ( + con.cursor() + .execute( + f"SELECT SUM(emission) FROM main.output_emission WHERE tech LIKE '{tech}' AND " + f'period == 2005' + ) + .fetchone()[0] + ) + assert emis == pytest.approx( + emis_target / 5 # end of life emissions are distributed over vintage period + ), f'{name} end of life emissions were incorrect. Should be {emis_target}, got {emis}' + + +# End of life emission costs undiscounted +@pytest.mark.parametrize( + 'solved_connection', + argvalues=eol_tests, + indirect=True, + ids=[t['name'] for t in eol_tests], +) +def test_endoflife_emissions_costs_undiscounted( + solved_connection: SolvedConnection, +) -> None: + """ + Test that the undiscounted end of life emission costs from each technology archetype are correct + """ + con, name, tech, emis_target = solved_connection + ec = ( + con.cursor() + .execute( + f"SELECT SUM(emiss) FROM main.output_cost WHERE tech LIKE '{tech}' AND period == 2005" + ) + .fetchone()[0] + ) + cost_target = 0.7 * emis_target # emission cost x end of life emissions + assert ec == pytest.approx(cost_target), ( + f'{name} undiscounted end of life emission costs were incorrect. Should be {cost_target}, ' + f'got {ec}' + ) + + +# End of life emission costs discounted +@pytest.mark.parametrize( + 'solved_connection', + argvalues=eol_tests, + indirect=True, + ids=[t['name'] for t in eol_tests], +) +def test_endoflife_emissions_costs_discounted( + solved_connection: SolvedConnection, +) -> None: """ - Test that the emission costs from each technology archetype are correct, and check total emissions + Test that discounted end of life emission costs from each technology archetype are correct """ con, name, tech, emis_target = solved_connection ec = ( con.cursor() - .execute(f"SELECT SUM(d_emiss) FROM main.OutputCost WHERE tech LIKE '{tech}'") + .execute( + f"SELECT SUM(d_emiss) FROM main.output_cost WHERE tech LIKE '{tech}' AND period == 2005" + ) .fetchone()[0] ) cost_target = ( - 0.7 * emis_target * 4.32947667063082 * 1.05 - ) # emission cost x emissions x P/A(5%, 5y, 1) [x F/P(5%, 1y) legacy bug?] - assert ec == pytest.approx( - cost_target - ), f'{name} discounted emission costs were incorrect. Should be {cost_target}, got {ec}' + 0.7 * emis_target * 1 / 5 * (1.05**5 - 1) / (0.05 * 1.05**5) / 1.05**5 + ) # emission cost x end of life emissions x annual distribution x P/A(5%, 5y, 1) x P/F(5%, 1y) + assert ec == pytest.approx(cost_target), ( + f'{name} discounted emission costs were incorrect. Should be {cost_target}, got {ec}' + ) # Curtailment # List of tech archetypes to test and their correct curtailment value -curtailment_tests = [ +curtailment_tests: list[TechTestParams] = [ {'name': 'curtailment archetype', 'tech': 'TechCurtailment', 'target': 0.45}, {'name': 'flex archetype', 'tech': 'TechFlex', 'target': 0.7}, {'name': 'annual flex archetype', 'tech': 'TechAnnualFlex', 'target': 0.7}, @@ -158,13 +334,17 @@ def test_emissions_costs_discounted(solved_connection): indirect=True, ids=[t['name'] for t in curtailment_tests], ) -def test_curtailment(solved_connection): +def test_curtailment(solved_connection: SolvedConnection) -> None: con, name, tech, curt_target = solved_connection + logger.info('Curtailment test: %s %s target=%s', name, tech, curt_target) curt = ( con.cursor() - .execute(f"SELECT SUM(curtailment) FROM main.OutputCurtailment WHERE tech LIKE '{tech}'") + .execute( + f"SELECT SUM(curtailment) FROM main.output_curtailment WHERE tech LIKE '{tech}' AND " + f'period == 2000' + ) .fetchone()[0] ) - assert curt == pytest.approx( - curt_target - ), f'{name} curtailment was incorrect. Should be {curt_target}, got {curt}' + assert curt == pytest.approx(curt_target), ( + f'{name} curtailment was incorrect. Should be {curt_target}, got {curt}' + ) diff --git a/tests/test_exchange_cost_ledger.py b/tests/test_exchange_cost_ledger.py index 055b03ae3..8caa1a5ff 100644 --- a/tests/test_exchange_cost_ledger.py +++ b/tests/test_exchange_cost_ledger.py @@ -1,48 +1,34 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/3/24 - -""" +from typing import TYPE_CHECKING, Any, cast import pytest -from temoa.temoa_model.exchange_tech_cost_ledger import CostType, ExchangeTechCostLedger +from temoa._internal.exchange_tech_cost_ledger import CostType, ExchangeTechCostLedger from tests.utilities.namespace_mock import Namespace +if TYPE_CHECKING: + from temoa.types.core_types import Period, Region, Technology, Vintage + +# Module-level typed constants +TEST_REGION_A = cast('Region', 'A') +TEST_REGION_B = cast('Region', 'B') +TEST_REGION_AB = cast('Region', 'A-B') +TEST_PERIOD_1 = cast('Period', 1) +TEST_PERIOD_2000 = cast('Period', 2000) +TEST_TECH_T1 = cast('Technology', 't1') +TEST_VINTAGE_2000 = cast('Vintage', 2000) + # these are the necessary Temoa elements to make the ledger work data = { - 'time_season': {1}, + 'time_season': [1], 'time_of_day': {1}, 'tech_annual': set(), - 'LifetimeProcess': {('A-B', 't1', 2000): 30, ('B-A', 't1', 2000): 30}, - 'processInputs': {('A-B', 2000, 't1', 2000): ('c1',), ('B-A', 2000, 't1', 2000): ('c1',)}, - 'ProcessOutputsByInput': { + 'lifetime_process': {('A-B', 't1', 2000): 30, ('B-A', 't1', 2000): 30}, + 'process_inputs': {('A-B', 2000, 't1', 2000): ('c1',), ('B-A', 2000, 't1', 2000): ('c1',)}, + 'process_outputs_by_input': { ('A-B', 2000, 't1', 2000, 'c1'): ('c1',), ('B-A', 2000, 't1', 2000, 'c1'): ('c1',), }, - 'V_FlowOut': { + 'v_flow_out': { ('A-B', 2000, 1, 1, 'c1', 't1', 2000, 'c1'): 60, ('B-A', 2000, 1, 1, 'c1', 't1', 2000, 'c1'): 40, }, @@ -50,20 +36,27 @@ @pytest.fixture -def fake_model(): +def fake_model() -> Namespace: """make a fake Temoa Model from data""" fake_model = Namespace(**data) return fake_model -def test_add_cost_record(fake_model): +def test_add_cost_record(fake_model: Namespace) -> None: """test adding a record to the ledger""" ledger = ExchangeTechCostLedger(fake_model) - ledger.add_cost_record('A-B', 1, 't1', 2000, 1.99, CostType.FIXED) + ledger.add_cost_record( + TEST_REGION_AB, + TEST_PERIOD_1, + TEST_TECH_T1, + TEST_VINTAGE_2000, + 1.99, + CostType.FIXED, + ) assert len(ledger.cost_records) == 1, 'should have 1 entry in the ledger' -params = [ +params: list[dict[str, Any]] = [ { 'name': 'no usage splitting', 'records': [ @@ -91,7 +84,7 @@ def test_add_cost_record(fake_model): @pytest.mark.parametrize('costs', argvalues=params, ids=[d['name'] for d in params]) -def test_cost_allocation(fake_model, costs): +def test_cost_allocation(fake_model: Namespace, costs: dict[str, Any]) -> None: """Test the accurate""" ledger = ExchangeTechCostLedger(fake_model) for record in costs['records']: @@ -99,17 +92,35 @@ def test_cost_allocation(fake_model, costs): assert len(ledger.cost_records[CostType.FIXED]) == costs['cost_entries'] # test for ratio... - ratio = ledger.get_use_ratio('A', 'B', 2000, 't1', 2000) - assert ratio == pytest.approx( - costs['B_ratio'] - ), 'B should get 60% of cost as it receives 60% of flow' - ratio = ledger.get_use_ratio('B', 'A', 2000, 't1', 2000) - assert ratio == pytest.approx( - costs['A_ratio'] - ), 'A should get 40% of cost as it receives 40% of flow' + ratio = ledger.get_use_ratio( + TEST_REGION_A, + TEST_REGION_B, + TEST_PERIOD_2000, + TEST_TECH_T1, + TEST_VINTAGE_2000, + ) + assert ratio == pytest.approx(costs['B_ratio']), ( + 'B should get 60% of cost as it receives 60% of flow' + ) + ratio = ledger.get_use_ratio( + TEST_REGION_B, + TEST_REGION_A, + TEST_PERIOD_2000, + TEST_TECH_T1, + TEST_VINTAGE_2000, + ) + assert ratio == pytest.approx(costs['A_ratio']), ( + 'A should get 40% of cost as it receives 40% of flow' + ) # test the outpt cost entries... entries = ledger.get_entries() assert len(entries) == 2, 'should produce 2 entries for A, B' - assert entries['A', 2000, 't1', 2000][CostType.FIXED] == costs['A_cost'], "costs didn't match" - assert entries['B', 2000, 't1', 2000][CostType.FIXED] == costs['B_cost'], "costs didn't match" + assert ( + entries[TEST_REGION_A, TEST_PERIOD_2000, TEST_TECH_T1, TEST_VINTAGE_2000][CostType.FIXED] + == costs['A_cost'] + ), "costs didn't match" + assert ( + entries[TEST_REGION_B, TEST_PERIOD_2000, TEST_TECH_T1, TEST_VINTAGE_2000][CostType.FIXED] + == costs['B_cost'] + ), "costs didn't match" diff --git a/tests/test_full_runs.py b/tests/test_full_runs.py index 61538e50d..01ff21532 100644 --- a/tests/test_full_runs.py +++ b/tests/test_full_runs.py @@ -1,49 +1,40 @@ """ Test a couple full-runs to match objective function value and some internals - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/27/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ import logging import sqlite3 +from pathlib import Path +from typing import TYPE_CHECKING, Any -import pyomo.environ as pyo import pytest from pyomo.core import Constraint, Var +from pyomo.environ import check_optimal_termination, value +from pyomo.opt import SolverResults # from src.temoa_model.temoa_model import temoa_create_model +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.model import TemoaModel from tests.legacy_test_values import ExpectedVals, test_vals +if TYPE_CHECKING: + import pyomo.environ as pyo + logger = logging.getLogger(__name__) # list of test scenarios for which we have captured results in legacy_test_values.py legacy_config_files = [ {'name': 'utopia', 'filename': 'config_utopia.toml'}, {'name': 'test_system', 'filename': 'config_test_system.toml'}, + {'name': 'mediumville', 'filename': 'config_mediumville.toml'}, + {'name': 'seasonal_storage', 'filename': 'config_seasonal_storage.toml'}, + {'name': 'survival_curve', 'filename': 'config_survival_curve.toml'}, + {'name': 'annualised_demand', 'filename': 'config_annualised_demand.toml'}, ] myopic_files = [{'name': 'myopic utopia', 'filename': 'config_utopia_myopic.toml'}] +mc_files = [{'name': 'utopia mc', 'filename': 'config_utopia_mc.toml'}] +stochastic_files = [{'name': 'stochastic utopia', 'filename': 'config_utopia_stochastic.toml'}] @pytest.mark.parametrize( @@ -52,28 +43,44 @@ indirect=True, ids=[d['name'] for d in legacy_config_files], ) -def test_against_legacy_outputs(system_test_run): +def test_against_legacy_outputs( + system_test_run: tuple[str, SolverResults | None, TemoaModel | None, TemoaSequencer], +) -> None: """ This test compares tests of legacy models to captured test results """ data_name, res, mdl, _ = system_test_run logger.info('Starting output test on scenario: %s', data_name) - expected_vals = test_vals.get(data_name) # a dictionary of expected results - # inspect some summary results - assert res['Solution'][0]['Status'] == 'optimal' - assert res['Solution'][0]['Objective']['TotalCost']['Value'] == pytest.approx( - expected_vals[ExpectedVals.OBJ_VALUE], 0.00001 + # Defensive checks for test fixture results + assert res is not None, f'No solver results for {data_name}' + assert isinstance(res, SolverResults), ( + f'Expected SolverResults object for {data_name}, got {type(res)}' ) + assert mdl is not None, f'No model for {data_name}' + + expected_vals = test_vals.get(data_name) # a dictionary of expected results + assert expected_vals is not None, f'No expected values for {data_name}' + + # Inspect some summary results + # Use check_optimal_termination instead of checking raw status string + # Different solvers report status differently (e.g., HiGHS reports 'unknown' but is optimal) + assert check_optimal_termination(res), f'Solver did not terminate optimally for {data_name}' + + # Get objective value from the model instance instead of results object + # HiGHS doesn't populate the results object the same way as CBC + obj_value = value(mdl.total_cost) + assert obj_value == pytest.approx(expected_vals[ExpectedVals.OBJ_VALUE], 0.00001) # inspect a couple set sizes - efficiency_param: pyo.Param = mdl.Efficiency + efficiency_param: pyo.Param = mdl.efficiency # check the set membership assert ( - len(tuple(efficiency_param.sparse_iterkeys())) == expected_vals[ExpectedVals.EFF_INDEX_SIZE] + len(tuple(efficiency_param.sparse_keys())) == expected_vals[ExpectedVals.EFF_INDEX_SIZE] ), 'should match legacy numbers' - # check the size of the domain. NOTE: The build of the domain here may be "expensive" for large models + # check the size of the domain. NOTE: The build of the domain here may be "expensive" for + # large models assert ( len(efficiency_param.index_set().domain) == expected_vals[ExpectedVals.EFF_DOMAIN_SIZE] ), 'should match legacy numbers' @@ -95,7 +102,9 @@ def test_against_legacy_outputs(system_test_run): @pytest.mark.parametrize( 'system_test_run', argvalues=myopic_files, indirect=True, ids=[d['name'] for d in myopic_files] ) -def test_myopic_utopia(system_test_run): +def test_myopic_utopia( + system_test_run: tuple[str, SolverResults | None, TemoaModel | None, TemoaSequencer], +) -> None: """ Some cursory tests to ensure Myopic is running... This is a very weak/simple test It mostly just ensures that the mode runs correctly and only checks 1 output. Much @@ -106,7 +115,115 @@ def test_myopic_utopia(system_test_run): _, _, _, sequencer = system_test_run con = sqlite3.connect(sequencer.config.output_database) cur = con.cursor() - res = cur.execute('SELECT SUM(d_invest) FROM main.OutputCost').fetchone() + res = cur.execute('SELECT SUM(d_invest) FROM main.output_cost').fetchone() invest_sum = res[0] - assert invest_sum == pytest.approx(11564.3985), 'sum of investment costs did not match expected' + # reduced this target after storageinit rework + # reduced after removing ancient 1-year shift bug from objective function + # increased after rework of inter-season sequencing + # reduced by <1 after changing season definition (segfrac no longer rounded) + # decreased by 41 after activating CF constraints for storage techs + assert invest_sum == pytest.approx(10963.1018), 'sum of investment costs did not match expected' + con.close() + + +@pytest.mark.parametrize( + 'system_test_run', + argvalues=stochastic_files, + indirect=True, + ids=[d['name'] for d in stochastic_files], +) +def test_stochastic_utopia( + system_test_run: tuple[str, Any, Any, TemoaSequencer], +) -> None: + """ + Test the stochastic integration for the utopia model. + """ + _, _, _, sequencer = system_test_run + + # Stochastic Expected Value for current utopia configuration + # reduced by <1 after changing season definition (segfrac no longer rounded) + # increased by 145 after activating CF constraints for storage techs + expected_obj = 34534.1822 + + assert sequencer.stochastic_sequencer is not None + assert sequencer.stochastic_sequencer.objective_value == pytest.approx(expected_obj, rel=1e-5) + + +def test_graphviz_integration(tmp_path: Path) -> None: + """ + Test that graphviz diagrams are generated during a full run when enabled. + """ + # Use utopia config as a base + config_file = Path(__file__).parent / 'testing_configs' / 'config_utopia.toml' + + # Build config with graphviz output enabled + config = TemoaConfig.build_config( + config_file=config_file, + output_path=tmp_path, + silent=True, + ) + + # Enable graphviz output + config.graphviz_output = True + + # Run the sequencer + sequencer = TemoaSequencer(config=config) + sequencer.start() + + # The graphviz generator creates a subdirectory like: {db_name}_{scenario}_graphviz + # Find any directory matching the pattern *_graphviz + graphviz_dirs = list(tmp_path.glob('*_graphviz')) + assert len(graphviz_dirs) > 0, 'Graphviz output directory should be created' + + graphviz_dir = graphviz_dirs[0] + assert graphviz_dir.is_dir(), 'Graphviz output should be a directory' + + # Check that at least some output files were generated (DOT or SVG files) + output_files = list(graphviz_dir.rglob('*.svg')) + list(graphviz_dir.rglob('*.dot')) + assert len(output_files) > 0, 'At least one diagram file should be generated' + + # Check that the files are not empty + for output_file in output_files: + assert output_file.stat().st_size > 0, f'{output_file.name} should not be empty' + + +@pytest.mark.parametrize( + 'system_test_run', argvalues=mc_files, indirect=True, ids=[d['name'] for d in mc_files] +) +def test_mc_utopia( + system_test_run: tuple[str, SolverResults | None, TemoaModel | None, TemoaSequencer], +) -> None: + """ + Test Monte Carlo run logic and output database contents + """ + data_name, _, _, sequencer = system_test_run + + # Connect to the output database + con = sqlite3.connect(sequencer.config.output_database) + cur = con.cursor() + + # 1. Check output_mc_delta table + res = cur.execute('SELECT run, param, old_val, new_val FROM main.output_mc_delta').fetchall() + assert len(res) == 2, 'Should have 2 tweaks recorded' + + # Tweak 1: cost_invest, utopia|TXD|2010, a, -44.0 + # The old value for TXD cost_invest in 2010 in Utopia is 1044.0 + # New value should be 1000.0 + assert res[0][0] == 1 + assert res[0][1] == 'cost_invest' + assert res[0][3] == pytest.approx(1000.0) + + # Tweak 2: demand, utopia|2010|RH, r, 0.1 + # Old value for RH demand in 2010 in Utopia is approx 56.7 + # New value should be approx 62.37 + assert res[1][0] == 2 + assert res[1][1] == 'demand' + assert res[1][3] == pytest.approx(56.7 * 1.1, rel=1e-3) + + # 2. Check output_objective table + res = cur.execute('SELECT scenario FROM main.output_objective').fetchall() + scenarios = [r[0] for r in res] + assert 'utopia_mc-1' in scenarios + assert 'utopia_mc-2' in scenarios + con.close() diff --git a/tests/test_hull.py b/tests/test_hull.py index e90f8b0e9..624b1abcf 100644 --- a/tests/test_hull.py +++ b/tests/test_hull.py @@ -1,30 +1,3 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/18/24 - -""" import numpy as np import pytest @@ -33,29 +6,25 @@ pts = np.array([[2, 2], [2, 4], [4, 2]]) r""" |\ - <-- | \ --> (to be viewed at a slightly upward angle. :/ ) + <-- | \ --> (to be viewed at a slightly upward angle. :/ ) | \ |___\ | v -A simple right triangle to start with +A simple right triangle to start with - should have 3 norms from 3 equations as a starter """ -def test_hull(): +def test_hull() -> None: """Basic build test, just see if it can be built and it rejects bad dimension inputs""" - hull = Hull(pts) - assert hull.cv_hull - with pytest.raises(ValueError): + hull = Hull(pts) # noqa F841 + with pytest.raises(ValueError, match='Insufficient points to make hull'): # transposed, the points(2) are insufficient for the dimensionality (3) - hull2 = Hull(pts.T) - print( - hull2.all_points - ) # should never get here... just to prevent warning on unused var 'hull' + hull2 = Hull(pts.T) # noqa F841 -def test_add_point(): +def test_add_point() -> None: """ test adding point to the triangle to make a square and that by doing so we get 2 new normals @@ -74,17 +43,18 @@ def test_add_point(): assert count == 5, '5 faces were available and should have been added to the available vecs' -def test_get_vector(): +def test_get_vector() -> None: """Test iteration through the 3 vectors available""" hull = Hull(pts) for _ in range(3): v = hull.get_norm() + assert v is not None assert np.linalg.norm(v) == pytest.approx(1.0) # should be no more... assert hull.get_norm() is None -def test_is_new_direction(): +def test_is_new_direction() -> None: """Test the linear algebra used to see if a new vector is different from an existing vector""" hull = Hull(pts) # make a new highly similar direction to the [-1, 0] normal @@ -93,7 +63,7 @@ def test_is_new_direction(): assert not hull.is_new_direction(sim_vec), 'this should be rejected as a new direction' -def test_valid_directions_available(): +def test_valid_directions_available() -> None: hull = Hull(pts) assert hull.norms_available == 3, '3 basic normals are available' hull.add_point(np.array([4, 4])) @@ -102,7 +72,7 @@ def test_valid_directions_available(): assert hull.norms_available == 5, '5 should be available' -def test_get_vectors(): +def test_get_vectors() -> None: hull = Hull(pts) vecs = hull.get_all_norms() assert len(vecs) == 3 diff --git a/tests/test_linked_tech.py b/tests/test_linked_tech.py index d50d6b4bf..d2850a31a 100644 --- a/tests/test_linked_tech.py +++ b/tests/test_linked_tech.py @@ -1,40 +1,19 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 6/22/24 A quick test on Linked Tech. The scenario is described in an image in the testing_data folder: simple_linked_tech_description.jpg """ + import logging import sqlite3 from pathlib import Path import pytest +from pyomo.opt import SolverResults -from definitions import PROJECT_ROOT +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.model import TemoaModel logger = logging.getLogger(__name__) config_files = [ @@ -48,25 +27,28 @@ indirect=True, ids=[d['name'] for d in config_files], ) -def test_linked_tech(system_test_run): +def test_linked_tech( + system_test_run: tuple[str, SolverResults | None, TemoaModel | None, TemoaSequencer], +) -> None: """Check a few known values. See the note above in header regarding scenario reference""" data_name, res, mdl, _ = system_test_run # test emission of CO2 - output_db_path = Path(PROJECT_ROOT, 'tests', 'testing_outputs', 'simple_linked_tech.sqlite') + output_db_path = Path(__file__).parent / 'testing_outputs' / 'simple_linked_tech.sqlite' print(output_db_path) conn = sqlite3.connect(str(output_db_path)) co2_emiss = conn.execute( - "SELECT emission FROM OutputEmission WHERE emis_comm = 'CO2'" + "SELECT emission FROM output_emission WHERE emis_comm = 'CO2'" ).fetchall() assert len(co2_emiss) == 1 co2_emiss = co2_emiss[0][0] # check the total emission - assert co2_emiss == pytest.approx( - -30.0 - ), 'the linked processes should remove have an aggregate -30 units of co2 emissions' + assert co2_emiss == pytest.approx(-30.0), ( + 'the linked processes should remove have an aggregate -30 units of co2 emissions' + ) - # check the flow out of captured carbon from the driven tech, which should output the captured carbon + # check the flow out of captured carbon from the driven tech, which should output the captured + # carbon flow_out = conn.execute( - "SELECT SUM(flow) FROM OutputFlowOut WHERE tech = 'CCS' and output_comm = 'CO2_CAP'" + "SELECT SUM(flow) FROM output_flow_out WHERE tech = 'CCS' and output_comm = 'CO2_CAP'" ).fetchone()[0] assert flow_out == pytest.approx(30.0) diff --git a/tests/test_main.py b/tests/test_main.py deleted file mode 100644 index fe2d27249..000000000 --- a/tests/test_main.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -quick test of some of the argument parsing - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/26/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . -""" - -import pytest - -import main - - -def test_parse_args(tmp_path, capsys): - config_file = tmp_path / 'config.toml' - config_file.write_text('nada') - - # missing config - with pytest.raises(AttributeError) as e: - main.parse_args(f'--output_path {tmp_path}'.split()) - - # bad file - with pytest.raises(FileNotFoundError) as e: - main.parse_args(f'--config sasquatch_bait.toml --output_path {tmp_path}'.split()) - - # good file - main.parse_args(f'--config {config_file} --output_path {tmp_path}'.split()) - - # good output path - options = main.parse_args(f'--config {config_file} --output_path {tmp_path}'.split()) - assert options.output_path == tmp_path - - # bad output path - with pytest.raises(FileNotFoundError) as e: - main.parse_args(f'--config {config_file} --output_path big_bird'.split()) - - # options setting - options = main.parse_args(f'--config {config_file} --output_path {tmp_path} -s -d -b'.split()) - assert all((options.silent, options.debug, options.build_only)) diff --git a/tests/test_material_results.py b/tests/test_material_results.py new file mode 100644 index 000000000..5fe709739 --- /dev/null +++ b/tests/test_material_results.py @@ -0,0 +1,151 @@ +import logging +import sqlite3 +from collections.abc import Generator +from pathlib import Path +from typing import Any + +import pytest + +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core import TemoaMode +from temoa.core.config import TemoaConfig + +logger = logging.getLogger(__name__) + + +@pytest.fixture(scope='module') +def solved_connection( + request: Any, tmp_path_factory: Any +) -> Generator[tuple[sqlite3.Connection, str, str, int, float, str], None, None]: + """ + spin up the model, solve it, and hand over a connection to the results db + """ + data_name = 'materials' + mode = request.param['mode'] + logger.info('Setting up and solving: %s in %s mode', data_name, mode) + filename = 'config_materials.toml' + config_file = Path(__file__).parent / 'testing_configs' / filename + tmp_path = tmp_path_factory.mktemp('data') + config = TemoaConfig.build_config( + config_file=config_file, + output_path=tmp_path, + silent=True, + ) + config.scenario_mode = TemoaMode[mode.upper()] + config.scenario = request.param['name'] + if mode == 'myopic': + assert config.myopic_inputs is not None + config.myopic_inputs['view_depth'] = request.param['view_depth'] + config.myopic_inputs['step_size'] = request.param['step_size'] + sequencer = TemoaSequencer(config=config) + + sequencer.start() + + con = sqlite3.connect(sequencer.config.output_database) + try: + # Pass all necessary params for this specific test + yield ( + con, + request.param['name'], + request.param['tech'], + request.param['period'], + request.param['target'], + mode, + ) + finally: + con.close() + + +# List of tech archetypes to test and their correct flowout value +# Parametrized for both perfect_foresight and myopic modes +flow_tests = [ + { + 'name': 'lithium import - perfect foresight', + 'tech': 'IMPORT_LI', + 'period': 2000, + 'target': 0.129291623, + 'mode': 'perfect_foresight', + }, + { + 'name': 'lithium import - myopic 1-1', + 'tech': 'IMPORT_LI', + 'period': 2000, + 'target': 0.129291623, + 'mode': 'myopic', + 'view_depth': 1, + 'step_size': 1, + }, + { + 'name': 'lithium import - myopic 2-1', + 'tech': 'IMPORT_LI', + 'period': 2000, + 'target': 0.129291623, + 'mode': 'myopic', + 'view_depth': 2, + 'step_size': 1, + }, + { + 'name': 'lithium import - myopic 3-1', + 'tech': 'IMPORT_LI', + 'period': 2000, + 'target': 0.129291623, + 'mode': 'myopic', + 'view_depth': 3, + 'step_size': 1, + }, + { + 'name': 'lithium import - myopic 2-2', + 'tech': 'IMPORT_LI', + 'period': 2000, + 'target': 0.129291623, + 'mode': 'myopic', + 'view_depth': 2, + 'step_size': 2, + }, + { + 'name': 'lithium import - myopic 3-2', + 'tech': 'IMPORT_LI', + 'period': 2000, + 'target': 0.129291623, + 'mode': 'myopic', + 'view_depth': 3, + 'step_size': 2, + }, + { + 'name': 'lithium import - myopic 3-3', + 'tech': 'IMPORT_LI', + 'period': 2000, + 'target': 0.129291623, + 'mode': 'myopic', + 'view_depth': 3, + 'step_size': 3, + }, +] + + +# Flows +@pytest.mark.parametrize( + 'solved_connection', + argvalues=flow_tests, + indirect=True, + ids=[str(t['name']) for t in flow_tests], +) +def test_flows(solved_connection: tuple[sqlite3.Connection, str, str, int, float, str]) -> None: + """ + Test that the flows from each technology archetype + are correct in both perfect foresight and myopic modes + """ + con, name, tech, period, flow_target, _mode = solved_connection + cursor = con.cursor() + row = cursor.execute( + 'SELECT SUM(flow) FROM main.output_flow_out WHERE tech = ? AND period = ? AND scenario = ?', + (tech, period, name), + ).fetchone() + # If the query returns no rows, row will be None. + # If it finds rows but the sum is NULL, row[0] will be None. + flow = row[0] if row and row[0] is not None else 0.0 + + assert flow == pytest.approx( + flow_target, + rel=1e-5, + ), f'{name} flows were incorrect. Should be {flow_target}, got {flow}' diff --git a/tests/test_mc_run.py b/tests/test_mc_run.py index 69da7e445..3fefacf4a 100644 --- a/tests/test_mc_run.py +++ b/tests/test_mc_run.py @@ -1,37 +1,10 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/9/24 - -""" import pytest -from temoa.extensions.monte_carlo.mc_run import TweakFactory, RowData +from temoa.extensions.monte_carlo.mc_run import RowData, TweakFactory @pytest.fixture(scope='module') -def tweak_factory(): +def tweak_factory() -> TweakFactory: tweak_factory = TweakFactory( data_store={'dog': {(1, 2): 3.0, (5, 6): 4.0}, 'cat': {('a', 'b'): 7.0, ('c', 'd'): 8.0}} ) @@ -39,35 +12,46 @@ def tweak_factory(): good_params = [ - ('1,dog,1|2,a,1.0,some good notes', RowData(1, 'dog', '1|2', 'a', 1.0, 'some good notes'), 1), - ( + pytest.param( + '1,dog,1|2,a,1.0,some good notes', + RowData(1, 'dog', '1|2', 'a', 1.0, 'some good notes'), + 1, + id='good_param_0', + ), + pytest.param( '1 , dog, 1|2 , a , 1.0,', RowData(1, 'dog', '1|2', 'a', 1.0, ''), 1, + id='good_param_1_strip_spaces', ), # we should be able to strip lead/trail spaces - ('22,cat,c|d/e/f|9/10,r,2,', RowData(22, 'cat', 'c|d/e/f|9/10', 'r', 2.0, ''), 6), + pytest.param( + '22,cat,c|d/e/f|9/10,r,2,', + RowData(22, 'cat', 'c|d/e/f|9/10', 'r', 2.0, ''), + 6, + id='good_param_2', + ), ] + fail_examples = [ - ('z,dog,1|2,a,1.0,'), # has 'z' for run, non integer - ('1,dog,1||2,a,1.0,'), # has empty index location - ('2,dog,5|6,x,2.0,'), # has 'x' not in r/s/a - ('3,pig,4|5|7,r,2.0,'), # no pig in data source + pytest.param('z,dog,1|2,a,1.0,', id='non-int run label'), # has 'z' for run, non integer + pytest.param('1,dog,1||2,a,1.0,', id='empty index'), # has empty index location + pytest.param('2,dog,5|6,x,2.0,', id='non r/s/a'), # has 'x' not in r/s/a + pytest.param('3,pig,4|5|7,r,2.0,', id='no-match param'), # no pig in data source ] -ids = ['non-int run label', 'empty index', 'non r/s/a', 'no-match param'] -@pytest.mark.parametrize('row, expected,_', good_params, ids=range(len(good_params))) -def test__row_parser(row, expected, _, tweak_factory): +@pytest.mark.parametrize(('row', 'expected', '_'), good_params) +def test__row_parser(row: str, expected: RowData, _: object, tweak_factory: TweakFactory) -> None: assert tweak_factory.row_parser(0, row=row) == expected -@pytest.mark.parametrize('row', fail_examples, ids=ids) -def test__row_parser_fail(row, tweak_factory): +@pytest.mark.parametrize('row', fail_examples) +def test__row_parser_fail(row: str, tweak_factory: TweakFactory) -> None: with pytest.raises(ValueError): tweak_factory.row_parser(0, row=row) -@pytest.mark.parametrize('row, _, num_tweaks', good_params, ids=range(len(good_params))) -def test_make_tweaks(row, _, num_tweaks, tweak_factory): +@pytest.mark.parametrize(('row', '_', 'num_tweaks'), good_params) +def test_make_tweaks(row: str, _: object, num_tweaks: int, tweak_factory: TweakFactory) -> None: _, tweaks = tweak_factory.make_tweaks(0, row=row) assert len(tweaks) == num_tweaks diff --git a/tests/test_model.py b/tests/test_model.py index 4710a69e9..5692333d6 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,59 +1,31 @@ """ A series of tests focused on the model entity. - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 12/6/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ import pathlib import pickle -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_mode import TemoaMode -from temoa.temoa_model.temoa_sequencer import TemoaSequencer +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.modes import TemoaMode -def test_serialization(): +def test_serialization() -> None: """ - Test to ensure the model pickles properly. This is used when employing mpi4py which requires - that jobs passed are pickle-able + Test to ensure the model pickles properly. This is used when employing mpi4py which requires + that jobs passed are pickle-able. """ - config_file = 'config_utopia.toml' - config_file = pathlib.Path(PROJECT_ROOT, 'tests', 'testing_configs', config_file) - output_path = pathlib.Path(PROJECT_ROOT, 'tests', 'testing_outputs') - options = {'silent': True, 'debug': True} - ts = TemoaSequencer( - config_file=config_file, - output_path=output_path, - mode_override=TemoaMode.BUILD_ONLY, - **options, - ) + config_filename = 'config_utopia.toml' + config_file_path = pathlib.Path(__file__).parent / 'testing_configs' / config_filename + output_path = pathlib.Path(__file__).parent / 'testing_outputs' - built_instance = ts.start() + config = TemoaConfig.build_config( + config_file=config_file_path, output_path=output_path, silent=True + ) + ts = TemoaSequencer(config=config, mode_override=TemoaMode.BUILD_ONLY) - pickled_model = pickle.dumps(built_instance) - assert pickled_model, 'model should have pickled successfully, but did not.' + # Use the correct method for build-only mode + built_instance = ts.build_model() - recovered_model = pickle.loads(pickled_model) - assert recovered_model, 'unable to recover model.' + # The actual test: try to pickle the model + pickle.dumps(built_instance) diff --git a/tests/test_myopic_sequencer.py b/tests/test_myopic_sequencer.py index d3a1be206..f0dac220a 100644 --- a/tests/test_myopic_sequencer.py +++ b/tests/test_myopic_sequencer.py @@ -1,30 +1,4 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 1/20/24 - -""" +from typing import Any import pytest @@ -33,22 +7,46 @@ params = [ { 'name': 'single_step', - 'conf_data': {'step_size': 1, 'view_depth': 3}, + 'conf_data': {'step_size': 1, 'view_depth': 3, 'evolving': False, 'evolution_script': None}, + 'expected_steps': 2, + 'expected_last_base_year': 1, + }, + { + 'name': 'triple_step', + 'conf_data': {'step_size': 3, 'view_depth': 4, 'evolving': False, 'evolution_script': None}, + 'expected_steps': 1, # see end of horizon immediately + 'expected_last_base_year': 0, + }, + { + 'name': 'single_step_evolving', + 'conf_data': {'step_size': 1, 'view_depth': 3, 'evolving': True, 'evolution_script': None}, 'expected_steps': 4, + 'expected_last_base_year': 3, }, # 4 single steps { - 'name': 'triple_step', - 'conf_data': {'step_size': 3, 'view_depth': 4}, + 'name': 'triple_step_evolving', + 'conf_data': {'step_size': 3, 'view_depth': 4, 'evolving': True, 'evolution_script': None}, 'expected_steps': 2, # 1 step of 3, followed by 1 step of 1 + 'expected_last_base_year': 3, }, ] """ assuming the periods are [0, 1, 2, 3, 4] + +[EVOLVING MODE OFF] for single step, the myopic indices should be... (base, last demand year, last year) (0, 2, 3) (1, 3, 4) + +for triple step, the myopic indices should be... +(0, 3, 4) + +[EVOLVING MODE ON] +for single step, the myopic indices should be... +(0, 2, 3) +(1, 3, 4) (2, 3, 4) (3, 3, 4) @@ -58,8 +56,8 @@ """ -@pytest.mark.parametrize('param', params, ids=[t['name'] for t in params]) -def test_characterize_run(param): +@pytest.mark.parametrize('param', params, ids=lambda p: p['name']) +def test_characterize_run(param: dict[str, Any]) -> None: """ Test the slicing up of the future periods into myopic indices """ @@ -67,14 +65,22 @@ def test_characterize_run(param): ms = MyopicSequencer(config=None) ms.view_depth = param['conf_data']['view_depth'] ms.step_size = param['conf_data']['step_size'] + ms.evolving = param['conf_data']['evolving'] + ms.evolution_script = param['conf_data']['evolution_script'] ms.characterize_run(future_periods=list(range(5))) assert len(ms.instance_queue) == param['expected_steps'], ( - 'number of myopic iterations does not match expected ' 'number of iterations' + 'number of myopic iterations does not match expected number of iterations' ) # pop the last myopic index from the queue and inspect it. Should be same for both cases last_mi = ms.instance_queue.popleft() - assert last_mi.last_year == 4 - assert last_mi.last_demand_year == 3 - assert last_mi.base_year == 3 + assert last_mi.last_year == 4, ( + 'last year in myopic index should be the end of the planning horizon' + ) + assert last_mi.last_demand_year == 3, ( + 'last demand year in myopic index should be the last planning year' + ) + assert last_mi.base_year == param['expected_last_base_year'], ( + 'base year in myopic index does not match expected base year' + ) diff --git a/tests/test_network_model_data.py b/tests/test_network_model_data.py index 854388adf..95b40e0e8 100644 --- a/tests/test_network_model_data.py +++ b/tests/test_network_model_data.py @@ -1,157 +1,159 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +import sqlite3 +from typing import TYPE_CHECKING, TypedDict, cast +from unittest.mock import MagicMock -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . +import pytest +from temoa.model_checking import network_model_data +from temoa.model_checking.commodity_network import CommodityNetwork -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/11/24 +if TYPE_CHECKING: + from temoa.types.core_types import Period, Region -""" -from itertools import chain -from unittest.mock import MagicMock +class ScenarioType(TypedDict): + name: str + db_data: dict[str, object] + expected: dict[str, object] -import pytest -from temoa.temoa_model.model_checking import network_model_data -from temoa.temoa_model.model_checking.commodity_network import CommodityNetwork - -# a couple of test cases with diagrams in the flow... -params = [ - # let's model this basic faulty network as a trial: - # - t4(2) -> p3 - # / - # s1 -> t1 -> p1 -> t2 -> d1 - # / - # p2 -> t3 - - # - # p2 -> t5 -> d2 - # the above should produce: - # 2 valid techs, t1, t2 - # 2 supply-side orphans (both instances of t4 of differing vintage) - # 1 demand-side orphan: t3 +# ============================================================================== +# Test Scenarios +# ============================================================================== +# Each scenario defines the mock database data and the expected outcomes. +# The `db_data` dictionary keys are specific, unique fragments of SQL queries. +# This makes the mock robust against changes in the order of execution, +# ensuring backwards compatibility with older versions of the code. +# ============================================================================== +test_scenarios: list[ScenarioType] = [ + # Scenario 1: A basic network with several orphan technologies. { 'name': 'basic', - 'data': [ - [(t,) for t in ['s1', 'p1', 'p2', 'p3', 'd1', 'd2']], # all commodities - [ - (t,) - for t in [ - 's1', - ] - ], # sources - [('R1', 2020, 'd1'), ('R1', 2020, 'd2')], # demands - [ + 'db_data': { + 'technology WHERE unlim_cap==1': [], + 'technology WHERE retire==1': [], + 'FROM lifetime_survival_curve': [], + 'FROM time_period': [(2020,), (2025,)], + # Unique keys for each commodity query + 'FROM main.commodity': [('s1',), ('p1',), ('p2',), ('p3',), ('d1',), ('d2',)], + "commodity WHERE flag LIKE '%p%'": [ + ('s1',), + ('p1',), + ('p2',), + ('p3',), + ('d1',), + ('d2',), + ], + "commodity WHERE flag LIKE '%w%'": [], + "commodity WHERE flag = 's'": [('s1',)], + "commodity WHERE flag LIKE '%p%' OR flag = 's' OR flag LIKE '%a%'": [ + ('s1',), + ('p1',), + ('p2',), + ('p3',), + ('d1',), + ('d2',), + ], + 'FROM main.demand': [('R1', 2020, 'd1'), ('R1', 2020, 'd2')], + # Unique keys for efficiency and optional tables + 'FROM main.efficiency': [ ('R1', 's1', 't4', 2000, 'p3', 100), ('R1', 's1', 't4', 1990, 'p3', 100), ('R1', 's1', 't1', 2000, 'p1', 100), ('R1', 'p1', 't2', 2000, 'd1', 100), ('R1', 'p2', 't3', 2000, 'd1', 100), ('R1', 'p2', 't5', 2000, 'd2', 100), - ], # techs - [ - (2020,), - (2025,), - ], # periods - [], # no linked techs - [], # no negative cost techs - ], - 'res': { - 'demands': 2, - 'techs': 6, + ], + 'FROM end_of_life_output': [], + 'FROM emission_end_of_life': [], + 'FROM construction_input': [], + 'FROM existing_capacity': [], + 'FROM main.linked_tech': [], + 'FROM cost_variable': [], + }, + 'expected': { + 'demands_count': 2, + 'techs_count': 6, 'valid_techs': 2, 'demand_orphans': 2, 'other_orphans': 2, 'unsupported_demands': {'d2'}, }, }, - # p1 -> driven -> d2 - # | - # - t4 -> p3 - # / - # s1 -> t1 -> d1 - # - # bad link tech set. t4 should be other orphan and the linked "driven" should be a demand side orphan + # Scenario 2: A network with a misconfigured linked technology. { 'name': 'bad linked tech', - 'data': [ - [(t,) for t in ['s1', 'p3', 'd1', 'd2']], # all commodities - [ - (t,) - for t in [ - 's1', - ] - ], # sources - [('R1', 2020, 'd1'), ('R1', 2020, 'd2')], # demands - [ + 'db_data': { + 'technology WHERE unlim_cap==1': [], + 'technology WHERE retire==1': [], + 'FROM lifetime_survival_curve': [], + 'FROM time_period': [(2020,), (2025,)], + 'FROM main.commodity': [('s1',), ('p1',), ('p3',), ('d1',), ('d2',)], + "commodity WHERE flag LIKE '%p%'": [('s1',), ('p3',), ('d1',), ('d2',)], + "commodity WHERE flag LIKE '%w%'": [], + "commodity WHERE flag = 's'": [('s1',)], + "commodity WHERE flag LIKE '%p%' OR flag = 's' OR flag LIKE '%a%'": [ + ('s1',), + ('p3',), + ('d1',), + ('d2',), + ], + 'FROM main.demand': [('R1', 2020, 'd1'), ('R1', 2020, 'd2')], + 'FROM main.efficiency': [ ('R1', 's1', 't4', 2000, 'p3', 100), ('R1', 'p1', 'driven', 1990, 'd2', 100), ('R1', 's1', 't1', 2000, 'd1', 100), - ], # techs - [ - (2020,), - (2025,), - ], # periods - [('R1', 't4', 'nox', 'driven')], # t4 drives 'driven' with 'nox' emission - [], # no negative cost techs - ], - 'res': { - 'demands': 2, - 'techs': 3, + ], + 'FROM end_of_life_output': [], + 'FROM emission_end_of_life': [], + 'FROM construction_input': [], + 'FROM existing_capacity': [], + 'FROM main.linked_tech': [('R1', 't4', 'nox', 'driven')], + 'FROM cost_variable': [], + }, + 'expected': { + 'demands_count': 2, + 'techs_count': 3, 'valid_techs': 1, 'demand_orphans': 0, - 'other_orphans': 2, # driven and t4 will both be culled as "other orphans" because t4 is a supply-orphan + 'other_orphans': 2, 'unsupported_demands': {'d2'}, }, }, + # Scenario 3: A network with a correctly configured linked technology. { - # iteration with a "good linked tech" - # system should give a synthetic link from t4's input to driven - # s2 -> driven -> d2 - # | - # - t4 -> d2 - # / - # s1 -> t1 -> d1 - # - # 'name': 'good linked tech', - 'data': [ - [(t,) for t in ['s1', 'd1', 'd2', 's2']], # all commodities - [(t,) for t in ['s1', 's2']], # sources - [('R1', 2020, 'd1'), ('R1', 2020, 'd2')], # demands - [ + 'db_data': { + 'technology WHERE unlim_cap==1': [], + 'technology WHERE retire==1': [], + 'FROM lifetime_survival_curve': [], + 'FROM time_period': [(2020,), (2025,)], + 'FROM main.commodity': [('s1',), ('p1',), ('d1',), ('d2',), ('s2',)], + "commodity WHERE flag LIKE '%p%'": [('s1',), ('d1',), ('d2',), ('s2',)], + "commodity WHERE flag LIKE '%w%'": [], + "commodity WHERE flag = 's'": [('s1',), ('s2',)], + "commodity WHERE flag LIKE '%p%' OR flag = 's' OR flag LIKE '%a%'": [ + ('s1',), + ('d1',), + ('d2',), + ('s2',), + ], + 'FROM main.demand': [('R1', 2020, 'd1'), ('R1', 2020, 'd2')], + 'FROM main.efficiency': [ ('R1', 's1', 't4', 2000, 'd2', 100), ('R1', 's2', 'driven', 1990, 'd2', 100), ('R1', 's1', 't1', 2000, 'd1', 100), - ], # techs - [ - (2020,), - (2025,), - ], # periods - [('R1', 't4', 'nox', 'driven')], # t4 drives 'driven' with 'nox' emission - [], # no negative cost techs - ], - 'res': { - 'demands': 2, - 'techs': 3, + ], + 'FROM end_of_life_output': [], + 'FROM emission_end_of_life': [], + 'FROM construction_input': [], + 'FROM existing_capacity': [], + 'FROM main.linked_tech': [('R1', 't4', 'nox', 'driven')], + 'FROM cost_variable': [], + }, + 'expected': { + 'demands_count': 2, + 'techs_count': 3, 'valid_techs': 3, 'demand_orphans': 0, 'other_orphans': 0, @@ -161,63 +163,287 @@ ] -# we need a small fixture to simulate the database here -@pytest.fixture() -def mock_db_connection(request): - mock_con = MagicMock() - mock_cursor = MagicMock() +# ============================================================================== +# Fixtures +# ============================================================================== +@pytest.fixture +def mock_db_connection(request: pytest.FixtureRequest) -> tuple[MagicMock, dict[str, object]]: + """ + A robust mock of a database connection. + + This fixture uses a "dispatcher" function as a side_effect. The dispatcher + inspects the SQL query and returns the corresponding data from the + test scenario's `db_data` dictionary. This makes the test independent + of the order of SQL calls in the code being tested. + """ + db_data = request.param['db_data'] + mock_con = MagicMock(name='mock_connection') + mock_cursor = MagicMock(name='mock_cursor') mock_con.cursor.return_value = mock_cursor - mock_execute = MagicMock() - mock_cursor.execute.return_value = mock_execute - mock_execute.fetchall.side_effect = request.param['data'] - return mock_con, request.param['res'] + def dispatcher(query: str, *_: object) -> MagicMock: + if 'sector FROM technology' in query: + raise sqlite3.OperationalError('no such column: sector') + for key, data in sorted(db_data.items(), key=lambda kv: -len(kv[0])): + if key in query: + m = MagicMock(name=f'execute_mock_for_{key}') + m.fetchall.return_value = data + m.fetchone.return_value = data[0] if data else None + return m + raise AssertionError('Unexpected SQL: ' + query) + mock_cursor.execute.side_effect = dispatcher + return mock_con, request.param['expected'] + + +# ============================================================================== +# Tests +# ============================================================================== @pytest.mark.parametrize( - 'mock_db_connection', params, indirect=True, ids=[d['name'] for d in params] + 'mock_db_connection', test_scenarios, indirect=True, ids=[d['name'] for d in test_scenarios] ) -def test_build_from_db(mock_db_connection): - """test a couple values in the load""" +def test_network_build_and_analysis( + mock_db_connection: tuple[MagicMock, dict[str, object]], +) -> None: + """Tests both data model construction and network analysis in one go.""" conn, expected = mock_db_connection + + # --- 1. Build the data object --- network_data = network_model_data._build_from_db(conn) + + # --- 2. Test initial data loading --- assert ( - len(tuple(chain(*network_data.demand_commodities.values()))) == expected['demands'] - ), 'demand count failed' + sum(len(s) for s in network_data.demand_commodities.values()) == expected['demands_count'] + ) assert ( - len(network_data.available_techs['R1', 2020]) == expected['techs'] - ), '6 techs are available' - + len(network_data.available_techs[(cast('Region', 'R1'), cast('Period', 2020))]) + == expected['techs_count'] + ) -@pytest.mark.parametrize( - 'mock_db_connection', params, indirect=True, ids=[d['name'] for d in params] -) -def test_source_trace(mock_db_connection): - """analyze the network and check results""" - conn, expected = mock_db_connection - network_data = network_model_data._build_from_db(conn) - cn = CommodityNetwork(region='R1', period=2020, model_data=network_data) + # --- 3. Perform network analysis --- + cn = CommodityNetwork( + region=cast('Region', 'R1'), period=cast('Period', 2020), model_data=network_data + ) cn.analyze_network() - # test the outputs - assert len(cn.get_valid_tech()) == expected['valid_techs'], 'should have this many valid techs' - assert len(cn.get_demand_side_orphans()) == expected['demand_orphans'], 'demand orphans' - assert len(cn.get_other_orphans()) == expected['other_orphans'], 'other orphans' - assert cn.unsupported_demands() == expected['unsupported_demands'], 'unsupported demands' + # --- 4. Test analysis results --- + assert len(cn.get_valid_tech()) == expected['valid_techs'], 'Incorrect number of valid techs' + assert len(cn.get_demand_side_orphans()) == expected['demand_orphans'], ( + 'Incorrect number of demand orphans' + ) + assert len(cn.get_other_orphans()) == expected['other_orphans'], ( + 'Incorrect number of other orphans' + ) + assert cn.unsupported_demands() == expected['unsupported_demands'], ( + 'Incorrect set of unsupported demands' + ) -@pytest.mark.parametrize( - 'mock_db_connection', - [ - params[0], - ], - indirect=True, -) -def test_clone(mock_db_connection): - """quick test to ensure cloning is working OK""" - conn, expected = mock_db_connection +@pytest.mark.parametrize('mock_db_connection', [test_scenarios[0]], indirect=True) +def test_clone(mock_db_connection: tuple[MagicMock, dict[str, object]]) -> None: + """Verifies that the clone() method creates a deep enough copy.""" + conn, _ = mock_db_connection network_data = network_model_data._build_from_db(conn) + clone = network_data.clone() - assert clone is not network_data, 'should be different objects' - assert network_data.available_techs == clone.available_techs, 'should be a direct copy' - clone.available_techs.pop(('R1', 2020)) # remove a known region-period - assert network_data.available_techs != clone.available_techs, 'should be different now' + + assert clone is not network_data, 'Clone should be a new object' + assert network_data.available_techs == clone.available_techs, ( + 'Data should be identical after cloning' + ) + + clone.available_techs.pop((cast('Region', 'R1'), cast('Period', 2020))) + assert network_data.available_techs != clone.available_techs, ( + 'Modifying clone should not affect original' + ) + + +def test_sector_handling_with_sectors() -> None: + """Test that sectors are properly handled when they exist in the database.""" + # Mock database with sector column + mock_con = MagicMock(name='mock_connection') + mock_cursor = MagicMock(name='mock_cursor') + mock_con.cursor.return_value = mock_cursor + + # Mock the sector column check to return True + sector_check_mock = MagicMock() + sector_check_mock.fetchall.return_value = [('Other',)] + sector_check_mock.fetchone.return_value = ('Other',) + + # Mock efficiency data with sectors + efficiency_mock = MagicMock() + efficiency_mock.fetchall.return_value = [ + ('R1', 's1', 't1', 2000, 'p1', 100, 'supply'), + ('R1', 'p1', 't2', 2000, 'd1', 100, 'demand'), + ] + + def dispatcher(query: str, *_: object) -> MagicMock: + if 'sector FROM technology' in query: + return sector_check_mock + elif 'FROM main.efficiency' in query: + return efficiency_mock + elif 'technology WHERE unlim_cap==1' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'technology WHERE retire==1' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM lifetime_survival_curve' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM time_period' in query: + m = MagicMock() + m.fetchall.return_value = [(2020,), (2025,)] + return m + elif "commodity WHERE flag LIKE '%p%'" in query: + m = MagicMock() + m.fetchall.return_value = [('s1',), ('p1',), ('d1',)] + return m + elif "commodity WHERE flag LIKE '%w%'" in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif "commodity WHERE flag = 's'" in query: + m = MagicMock() + m.fetchall.return_value = [('s1',)] + return m + elif 'FROM main.demand' in query: + m = MagicMock() + m.fetchall.return_value = [('R1', 2020, 'd1')] + return m + elif 'FROM end_of_life_output' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM emission_end_of_life' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM construction_input' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM existing_capacity' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM main.linked_tech' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM cost_variable' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + raise AssertionError('Mock database received unexpected query: ' + query) + + mock_cursor.execute.side_effect = dispatcher + + # Build network data + network_data = network_model_data._build_from_db(mock_con) + + # Verify sectors are included in efficiencyTuple + techs = list(network_data.available_techs[(cast('Region', 'R1'), cast('Period', 2020))]) + assert len(techs) == 2 + # Fields: region, ic, tech, vintage, oc, lifetime, sector + assert all(len(tech) == 7 for tech in techs) + assert any(tech.sector == 'supply' for tech in techs) + assert any(tech.sector == 'demand' for tech in techs) + + +def test_sector_handling_without_sectors() -> None: + """Test that sectors are handled gracefully when they don't exist in the database.""" + # Mock database without sector column + mock_con = MagicMock(name='mock_connection') + mock_cursor = MagicMock(name='mock_cursor') + mock_con.cursor.return_value = mock_cursor + + # Mock the sector column check to raise OperationalError (column doesn't exist) + def dispatcher(query: str, *_: object) -> MagicMock: + if 'sector FROM technology' in query: + # Simulate column not existing + raise sqlite3.OperationalError('no such column: sector') + elif 'FROM main.efficiency' in query: + # Return data without sector column + mock = MagicMock() + mock.fetchall.return_value = [ + ('R1', 's1', 't1', 2000, 'p1', 100), + ('R1', 'p1', 't2', 2000, 'd1', 100), + ] + return mock + elif 'FROM main.commodity' in query: + m = MagicMock() + m.fetchall.return_value = [('s1',), ('p1',), ('d1',)] + return m + elif 'technology WHERE unlim_cap==1' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'technology WHERE retire==1' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM lifetime_survival_curve' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM time_period' in query: + m = MagicMock() + m.fetchall.return_value = [(2020,), (2025,)] + return m + elif "commodity WHERE flag LIKE '%p%'" in query: + m = MagicMock() + m.fetchall.return_value = [('s1',), ('p1',), ('d1',)] + return m + elif "commodity WHERE flag LIKE '%w%'" in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif "commodity WHERE flag = 's'" in query: + m = MagicMock() + m.fetchall.return_value = [('s1',)] + return m + elif 'FROM main.demand' in query: + m = MagicMock() + m.fetchall.return_value = [('R1', 2020, 'd1')] + return m + elif 'FROM end_of_life_output' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM emission_end_of_life' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM construction_input' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM existing_capacity' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM main.linked_tech' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + elif 'FROM cost_variable' in query: + m = MagicMock() + m.fetchall.return_value = [] + return m + raise ValueError(f'Mock database received unexpected query: {query}') + + mock_cursor.execute.side_effect = dispatcher + + # Build network data + network_data = network_model_data._build_from_db(mock_con) + + # Verify sectors default to None + techs = list(network_data.available_techs[(cast('Region', 'R1'), cast('Period', 2020))]) + assert len(techs) == 2 + # Fields: region, ic, tech, vintage, oc, lifetime, sector (sector None here) + assert all(len(tech) == 7 for tech in techs) + assert all(tech.sector is None for tech in techs) diff --git a/tests/test_pricing_check.py b/tests/test_pricing_check.py index 40d6ee774..95aa9a97f 100644 --- a/tests/test_pricing_check.py +++ b/tests/test_pricing_check.py @@ -1,89 +1,74 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 2/2/24 - -""" +from typing import TYPE_CHECKING, cast import pytest from pyomo.environ import Any, ConcreteModel, Param, Set -from temoa.temoa_model.model_checking.pricing_check import check_tech_uncap +from temoa.model_checking.pricing_check import check_tech_uncap + +if TYPE_CHECKING: + from temoa.core.model import TemoaModel @pytest.fixture -def mock_model(): +def mock_model() -> ConcreteModel: """let's see how tough this is to work with...""" - M = ConcreteModel('mock') - M.tech_uncap = Set(initialize=['refinery']) - M.time_optimize = Set(initialize=[2000, 2010, 2020, 2030]) - M.LifetimeProcess = Param(Any, Any, Any, initialize={('CA', 'refinery', 2020): 30}) - M.Efficiency = Param(Any, Any, Any, Any, Any, initialize={('CA', 0, 'refinery', 2020, 0): 1.0}) - - M.ExistingCapacity = Param(Any, Any, Any, mutable=True) - M.CostFixed = Param(Any, Any, Any, Any, mutable=True) - M.CostInvest = Param(Any, Any, Any, mutable=True) - M.CostVariable = Param(Any, Any, Any, Any, mutable=True) - M.MaxCapacity = Param(Any, Any, Any, mutable=True) - M.MinCapacity = Param(Any, Any, Any, mutable=True) - return M - - -def test_check_tech_uncap(mock_model): + model = ConcreteModel('mock') + model.tech_uncap = Set(initialize=['refinery']) + model.time_optimize = Set(initialize=[2000, 2010, 2020, 2030]) + model.lifetime_process = Param(Any, Any, Any, initialize={('CA', 'refinery', 2020): 30}) + model.efficiency = Param( + Any, Any, Any, Any, Any, initialize={('CA', 0, 'refinery', 2020, 0): 1.0} + ) + + model.existing_capacity = Param(Any, Any, Any, mutable=True) + model.cost_fixed = Param(Any, Any, Any, Any, mutable=True) + model.cost_invest = Param(Any, Any, Any, mutable=True) + model.cost_variable = Param(Any, Any, Any, Any, mutable=True) + model.max_capacity = Param(Any, Any, Any, mutable=True) + model.min_capacity = Param(Any, Any, Any, mutable=True) + return model + + +def test_check_tech_uncap( + mock_model: ConcreteModel, +) -> None: """ test the fault checking for unlimited capacity techs :param mock_model: :return: """ - M = mock_model + model = cast('TemoaModel', mock_model) - assert check_tech_uncap(M), 'should pass for no fixed/invest/variable costs' - M.CostVariable[('CA', 2020, 'refinery', 2020)] = 42 - assert not check_tech_uncap(M), 'should fail. Has cost in 2020, but missing in 2030' + assert check_tech_uncap(model), 'should pass for no fixed/invest/variable costs' + model.cost_variable[('CA', 2020, 'refinery', 2020)] = 42 + assert not check_tech_uncap(model), 'should fail. Has cost in 2020, but missing in 2030' # add in missing cost... - M.CostVariable[('CA', 2030, 'refinery', 2020)] = 42 - assert check_tech_uncap(M), 'should pass for all periods having var cost' + model.cost_variable[('CA', 2030, 'refinery', 2020)] = 42 + assert check_tech_uncap(model), 'should pass for all periods having var cost' -def test_detect_fixed_cost(mock_model): +def test_detect_fixed_cost( + mock_model: ConcreteModel, +) -> None: """ test the fault checking for unlimited capacity techs :param mock_model: :return: """ - M = mock_model - assert check_tech_uncap(M), 'should have cleared and passed again' - M.CostFixed[('CA', 2020, 'refinery', 2020)] = 42 - assert not check_tech_uncap(M), 'should fail with any fixed cost' + model = cast('TemoaModel', mock_model) + assert check_tech_uncap(model), 'should have cleared and passed again' + model.cost_fixed[('CA', 2020, 'refinery', 2020)] = 42 + assert not check_tech_uncap(model), 'should fail with any fixed cost' -def test_detect_invest_cost(mock_model): +def test_detect_invest_cost( + mock_model: ConcreteModel, +) -> None: """ test the fault checking for unlimited capacity techs :param mock_model: :return: """ - M = mock_model - M.CostInvest['CA', 'refinery', 2020] = 42 - assert not check_tech_uncap(M), 'should fail with any investment cost' + model = cast('TemoaModel', mock_model) + model.cost_invest['CA', 'refinery', 2020] = 42 + assert not check_tech_uncap(model), 'should fail with any investment cost' diff --git a/tests/test_set_consistency.py b/tests/test_set_consistency.py index 6a0bfabc0..7f18054cb 100644 --- a/tests/test_set_consistency.py +++ b/tests/test_set_consistency.py @@ -1,33 +1,6 @@ """ These tests are designed to check the construction of the numerous sets in the 2 exemplar models: Utopia and Test System. - -They construct all the pyomo Sets associated with the model and compare them with cached results that are stored -in json files - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 9/26/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ import json @@ -36,9 +9,14 @@ import pytest from pyomo import environ as pyo -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_sequencer import TemoaMode, TemoaSequencer +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.modes import TemoaMode +from tests.utilities.hash_utils import hash_set +TESTING_CONFIGS_DIR = pathlib.Path(__file__).parent / 'testing_configs' + +# Update params to just be filenames, we will construct the path inside the test params = [ ('utopia', 'config_utopia.toml', 'utopia_sets.json'), ('test_system', 'config_test_system.toml', 'test_system_sets.json'), @@ -49,62 +27,54 @@ @pytest.mark.parametrize( argnames='data_name config_file set_file'.split(), argvalues=params, ids=[t[0] for t in params] ) -def test_set_consistency(data_name, config_file, set_file, tmp_path): +def test_set_consistency( + data_name: str, config_file: str, set_file: str, tmp_path: pathlib.Path +) -> None: """ test the set membership of the utopia model against cached values to ensure consistency """ - config_file = pathlib.Path(PROJECT_ROOT, 'tests', 'testing_configs', config_file) - options = {'silent': True, 'debug': True} - ts = TemoaSequencer( - config_file=config_file, output_path=tmp_path, mode_override=TemoaMode.BUILD_ONLY, **options + full_config_path = TESTING_CONFIGS_DIR / config_file + + config = TemoaConfig.build_config( + config_file=full_config_path, output_path=tmp_path, silent=True ) + ts = TemoaSequencer(config=config, mode_override=TemoaMode.BUILD_ONLY) + + built_instance = ts.build_model() - built_instance = ts.start() model_sets = built_instance.component_map(ctype=pyo.Set) model_sets = {k: set(v) for k, v in model_sets.items()} - # retrieve the cache and convert the set values from list -> set (json can't store sets) - cache_file = pathlib.Path(PROJECT_ROOT, 'tests', 'testing_data', set_file) - with open(cache_file, 'r') as src: + # retrieve the cache which now stores hashes + cache_file = pathlib.Path(__file__).parent / 'testing_data' / set_file + with open(cache_file) as src: cached_sets = json.load(src) - cached_sets = { - k: set(tuple(t) if isinstance(t, list) else t for t in v) for (k, v) in cached_sets.items() - } - # compare sets where they exist in the model. - overage_in_model = dict() - shortage_in_model = dict() + # compare hashes where they exist in the model. + mismatched_sets = {} for set_name, s in model_sets.items(): - if set_name == 'CostEmission_rpe': - pass - if cached_sets.get(set_name) != s: - cached_set = cached_sets.get(set_name, set()) - overage_in_model[set_name] = s - cached_set - shortage_in_model[set_name] = cached_set - s - missing_in_model = cached_sets.keys() - model_sets.keys() - # drop any set that has "_index" in the name as they are no longer reported by newer version of pyomo - missing_in_model = {s for s in missing_in_model if '_index' not in s and '_domain' not in s} - - if overage_in_model: - print('\nOverages compared to cache: ') - for k, v in overage_in_model.items(): - if len(v) > 0: - print(k, v) - if shortage_in_model: - print('\nShortages compared to cache: ') - for k, v in shortage_in_model.items(): - if len(v) > 0: - print(k, v) - - # look for new or dropped sets in EITHER - model_extra_sets = { + if '_index' in set_name or '_domain' in set_name: + continue + + model_hash = hash_set(s) + cached_hash = cached_sets.get(set_name) + if cached_hash is not None and cached_hash != model_hash: + mismatched_sets[set_name] = {'cached': cached_hash, 'model': model_hash} + + missing_in_model = { k - for k in model_sets.keys() - cached_sets.keys() + for k in cached_sets.keys() - model_sets.keys() if '_index' not in k and '_domain' not in k } - cache_extra_sets = { + + if mismatched_sets: + print('\nMismatched sets compared to cache (hashes differ): ') + for k, hashes in mismatched_sets.items(): + print(f'{k}: cached={hashes["cached"]}, model={hashes["model"]}') + + model_extra_sets = { k - for k in cached_sets.keys() - model_sets.keys() + for k in model_sets.keys() - cached_sets.keys() if '_index' not in k and '_domain' not in k } if model_extra_sets: @@ -112,16 +82,18 @@ def test_set_consistency(data_name, config_file, set_file, tmp_path): for k in model_extra_sets: print(f'{k}: {model_sets[k]}') - if cache_extra_sets: + if missing_in_model: print('\nCache extra sets compared to model: ') - for k in cache_extra_sets: + for k in missing_in_model: print(f'{k}: {cached_sets[k]}') - assert not missing_in_model, f'one or more cached set not in model: {missing_in_model}' - assert ( - not overage_in_model and not shortage_in_model - ), f'The {data_name} run-produced sets did not match cached values' - if cache_extra_sets: - assert False, 'Cache has extra sets' - if model_extra_sets: - assert False, 'Model has extra sets' + assert not missing_in_model, ( + f'The {data_name} run has cached sets missing in the model: {missing_in_model}' + ) + assert not mismatched_sets, ( + f'The {data_name} run-produced sets did not match cached values (hashes differ): ' + f'{list(mismatched_sets.keys())}' + ) + assert not model_extra_sets, ( + f'The {data_name} run has extra sets compared to the cache: {model_extra_sets}' + ) diff --git a/tests/test_source_check.py b/tests/test_source_check.py index 40c2cdd6e..5650da40a 100644 --- a/tests/test_source_check.py +++ b/tests/test_source_check.py @@ -1,85 +1,79 @@ """ -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling +Tests for the CommodityNetwork analysis class. -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. +This test suite verifies the core logic of the network analysis, ensuring that +it correctly identifies valid ("good") connections, demand-side orphans, and +other orphans under various network topologies. +""" -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +from collections import defaultdict +from typing import TypedDict, cast +from unittest.mock import MagicMock -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . +import pytest +# Assuming the refactored code is in this location +from temoa.model_checking.commodity_network import CommodityNetwork +from temoa.model_checking.network_model_data import EdgeTuple +from temoa.types.core_types import Commodity, Period, Region, Technology, Vintage -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 2/6/24 -""" +class CaseType(TypedDict): + test_id: str # Descriptive name for the test case + start_nodes: set[str] # Demand commodities to start the backward trace from + end_nodes: set[str] # Source commodities that validate a path + connections: dict[str, set[tuple[str, str]]] # The network structure {output: {(input, tech)}}. + expected_good: set[tuple[str, str, str]] # The connections that should be fully valid + expected_demand_orphans: set[ + tuple[str, str, str] + ] # Connections reachable from demand but not a source + expected_other_orphans: set[ + tuple[str, str, str] + ] # Connections not reachable from demand at all -from temoa.temoa_model.model_checking.commodity_network import _mark_good_connections, _visited_dfs - - -def test_network_analysis(): - """ - tests of the dfs connection maker - :return: - """ - # s = source commodity - # p = physical commodity - # d = demand commodity (starts for DFS) - # t = techs +TEST_CASES: list[CaseType] = [ + # s = source commodity, p = physical commodity, d = demand, t = tech + # Test 1: A simple, valid, linear chain from source to demand. # s1 -> t1 -> p1 -> t2 -> d1 - start_nodes = {'d1'} - end_nodes = {'s1'} - connections = {'d1': {('p1', 't2')}, 'p1': {('s1', 't1')}} - good_tech = {('s1', 't1', 'p1'), ('p1', 't2', 'd1')} - discovered_sources, visited = _visited_dfs( - start_nodes=start_nodes, end_nodes=end_nodes, connections=connections - ) - t = _mark_good_connections(discovered_sources, visited) - assert t == good_tech, 'should match up!' - + { + 'test_id': 'simple_linear_chain', + 'start_nodes': {'d1'}, + 'end_nodes': {'s1'}, + 'connections': {'d1': {('p1', 't2')}, 'p1': {('s1', 't1')}}, + 'expected_good': {('s1', 't1', 'p1'), ('p1', 't2', 'd1')}, + 'expected_demand_orphans': set(), + 'expected_other_orphans': set(), + }, + # Test 2: One valid chain and one orphaned branch feeding into the same demand. # s1 -> t1 -> p1 -> t2 -> d1 # / # p2 -> t3 - - - start_nodes = {'d1'} - end_nodes = {'s1'} - connections = {'d1': {('p1', 't2'), ('p2', 't3')}, 'p1': {('s1', 't1')}} - good_tech = {('s1', 't1', 'p1'), ('p1', 't2', 'd1')} - discovered_sources, visited = _visited_dfs( - start_nodes=start_nodes, end_nodes=end_nodes, connections=connections - ) - t = _mark_good_connections(discovered_sources, visited) - assert t == good_tech, 'should match up!' - + { + 'test_id': 'one_good_one_orphan_branch', + 'start_nodes': {'d1'}, + 'end_nodes': {'s1'}, + 'connections': {'d1': {('p1', 't2'), ('p2', 't3')}, 'p1': {('s1', 't1')}}, + 'expected_good': {('s1', 't1', 'p1'), ('p1', 't2', 'd1')}, + 'expected_demand_orphans': {('p2', 't3', 'd1')}, + 'expected_other_orphans': set(), + }, + # Test 3: Multiple valid paths from one intermediate commodity, plus an orphan branch. # - t4 - # / \ # s1 -> t1 -> p1 -> t2 -> d1 # / # p2 -> t3 - - - start_nodes = {'d1'} - end_nodes = {'s1'} - connections = {'d1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, 'p1': {('s1', 't1')}} - good_tech = {('s1', 't1', 'p1'), ('p1', 't2', 'd1'), ('p1', 't4', 'd1')} - discovered_sources, visited = _visited_dfs( - start_nodes=start_nodes, end_nodes=end_nodes, connections=connections - ) - t = _mark_good_connections(discovered_sources, visited) - assert t == good_tech, 'should match up!' - + { + 'test_id': 'multiple_paths_from_one_source', + 'start_nodes': {'d1'}, + 'end_nodes': {'s1'}, + 'connections': {'d1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, 'p1': {('s1', 't1')}}, + 'expected_good': {('s1', 't1', 'p1'), ('p1', 't2', 'd1'), ('p1', 't4', 'd1')}, + 'expected_demand_orphans': {('p2', 't3', 'd1')}, + 'expected_other_orphans': set(), + }, + # Test 4: Two independent, valid supply chains for two different demands. # - t4 - # / \ # s1 -> t1 -> p1 -> t2 -> d1 @@ -87,22 +81,25 @@ def test_network_analysis(): # p2 -> t3 - # # s2 -> t5 -> d2 - - start_nodes = {'d1', 'd2'} - end_nodes = {'s1', 's2'} - connections = { - 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, - 'p1': {('s1', 't1')}, - 'd2': {('s2', 't5')}, - } - good_tech = {('s1', 't1', 'p1'), ('p1', 't2', 'd1'), ('p1', 't4', 'd1'), ('s2', 't5', 'd2')} - discovered_sources, visited = _visited_dfs( - start_nodes=start_nodes, end_nodes=end_nodes, connections=connections - ) - t = _mark_good_connections(discovered_sources, visited) - assert t == good_tech, 'should match up!' - - # demand 2 (d2) with no path back to any source... + { + 'test_id': 'multiple_demands_and_sources', + 'start_nodes': {'d1', 'd2'}, + 'end_nodes': {'s1', 's2'}, + 'connections': { + 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, + 'p1': {('s1', 't1')}, + 'd2': {('s2', 't5')}, + }, + 'expected_good': { + ('s1', 't1', 'p1'), + ('p1', 't2', 'd1'), + ('p1', 't4', 'd1'), + ('s2', 't5', 'd2'), + }, + 'expected_demand_orphans': {('p2', 't3', 'd1')}, + 'expected_other_orphans': set(), + }, + # Test 5: One demand is valid, the other is completely orphaned (no path to any source). # - t4 - # / \ # s1 -> t1 -> p1 -> t2 -> d1 @@ -110,53 +107,137 @@ def test_network_analysis(): # p2 -> t3 - # # p3 -> t5 -> d2 - - start_nodes = {'d1', 'd2'} - end_nodes = {'s1'} - connections = { - 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, - 'p1': {('s1', 't1')}, - 'd2': {('p3', 't5')}, - } - good_tech = {('s1', 't1', 'p1'), ('p1', 't2', 'd1'), ('p1', 't4', 'd1')} - discovered_sources, visited = _visited_dfs( - start_nodes=start_nodes, end_nodes=end_nodes, connections=connections - ) - t = _mark_good_connections(discovered_sources, visited) - assert t == good_tech, 'should match up!' - - # test with loop: t4 is like storage with I/O the same + { + 'test_id': 'one_demand_is_fully_orphaned', + 'start_nodes': {'d1', 'd2'}, + 'end_nodes': {'s1'}, + 'connections': { + 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, + 'p1': {('s1', 't1')}, + 'd2': {('p3', 't5')}, + }, + 'expected_good': {('s1', 't1', 'p1'), ('p1', 't2', 'd1'), ('p1', 't4', 'd1')}, + 'expected_demand_orphans': {('p2', 't3', 'd1'), ('p3', 't5', 'd2')}, + 'expected_other_orphans': set(), + }, + # Test 6: A valid network that includes a loop (e.g., storage technology). # - t4 - # \ / # s1 -> t1 -> p1 -> t2 -> d1 # / # p2 -> t3 - - # - - start_nodes = {'d1'} - end_nodes = {'s1', 's2'} - connections = { - 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, - 'p1': {('s1', 't1'), ('p1', 't4')}, + { + 'test_id': 'network_with_a_loop', + 'start_nodes': {'d1'}, + 'end_nodes': {'s1', 's2'}, + 'connections': { + 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, + 'p1': {('s1', 't1'), ('p1', 't4')}, # t4 loops on p1 + }, + 'expected_good': { + ('s1', 't1', 'p1'), + ('p1', 't2', 'd1'), + ('p1', 't4', 'd1'), + ('p1', 't4', 'p1'), + }, + 'expected_demand_orphans': {('p2', 't3', 'd1')}, + 'expected_other_orphans': set(), + }, + # Test 7: No source nodes are defined, so no connections can be "good". + # s1 -> t1 -> p1 -> t2 -> d1 + # s2 -> t5 -> d2 + { + 'test_id': 'no_source_nodes_defined', + 'start_nodes': {'d1', 'd2'}, + 'end_nodes': set(), # No sources + 'connections': { + 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, + 'p1': {('s1', 't1')}, + 'd2': {('s2', 't5')}, + }, + 'expected_good': set(), # No good connections are possible + 'expected_demand_orphans': { + ('p1', 't2', 'd1'), + ('p2', 't3', 'd1'), + ('p1', 't4', 'd1'), + ('s1', 't1', 'p1'), + ('s2', 't5', 'd2'), + }, + 'expected_other_orphans': set(), + }, +] + + +@pytest.mark.parametrize( + ( + 'test_id', + 'start_nodes', + 'end_nodes', + 'connections', + 'expected_good', + 'expected_demand_orphans', + 'expected_other_orphans', + ), + [ + ( + case['test_id'], + case['start_nodes'], + case['end_nodes'], + case['connections'], + case['expected_good'], + case['expected_demand_orphans'], + case['expected_other_orphans'], + ) + for case in TEST_CASES + ], + ids=[case['test_id'] for case in TEST_CASES], +) +def test_network_analysis( + test_id: str, + start_nodes: set[str], + end_nodes: set[str], + connections: dict[Commodity, set[tuple[Commodity, Technology]]], + expected_good: set[tuple[str, str, str]], + expected_demand_orphans: set[tuple[str, str, str]], + expected_other_orphans: set[tuple[str, str, str]], +) -> None: + """ + Tests the CommodityNetwork analysis logic against various topologies. + """ + # 1. Setup mock model data for the test case + mock_model_data = MagicMock() + region = cast('Region', 'test_region') + period = cast('Period', 2025) + + # The mock needs to return the correct data for the (region, period) key + mock_model_data.demand_commodities = defaultdict(set, {(region, period): start_nodes}) + mock_model_data.source_commodities = defaultdict(set, {(region, period): end_nodes}) + mock_model_data.waste_commodities = defaultdict(set) # Assume empty + mock_model_data.available_linked_techs = set() # Assume no linked techs + + # Convert the connections dict into a set of Tech namedtuples + available_techs = { + EdgeTuple( + input_comm=ic, output_comm=oc, tech=tech, vintage=cast('Vintage', period), region=region + ) + for oc, links in connections.items() + for ic, tech in links } - good_tech = {('s1', 't1', 'p1'), ('p1', 't2', 'd1'), ('p1', 't4', 'd1'), ('p1', 't4', 'p1')} - discovered_sources, visited = _visited_dfs( - start_nodes=start_nodes, end_nodes=end_nodes, connections=connections - ) - t = _mark_good_connections(discovered_sources, visited) - assert t == good_tech, 'should match up!' + mock_model_data.available_techs = defaultdict(set, {(region, period): available_techs}) - # no good tech - start_nodes = {'d1', 'd2'} - end_nodes = set() - connections = { - 'd1': {('p1', 't2'), ('p2', 't3'), ('p1', 't4')}, - 'p1': {('s1', 't1')}, - 'd2': {('s2', 't5')}, - } - good_tech = set() - discovered_sources, visited = _visited_dfs( - start_nodes=start_nodes, end_nodes=end_nodes, connections=connections + # 2. Instantiate the class with the mock data + network = CommodityNetwork(region=region, period=period, model_data=mock_model_data) + + # 3. Run the analysis + network.analyze_network() + + # 4. Assert the results + assert network.good_connections == expected_good, ( + f'[{test_id}] Failed to identify good connections' + ) + assert network.demand_orphans == expected_demand_orphans, ( + f'[{test_id}] Failed to identify demand-side orphans' + ) + assert network.other_orphans == expected_other_orphans, ( + f'[{test_id}] Failed to identify other orphans' ) - t = _mark_good_connections(discovered_sources, visited) - assert t == good_tech, 'should match up!' diff --git a/tests/test_storage.py b/tests/test_storage.py index 8a66b78bd..30a2827fe 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -1,21 +1,14 @@ """ The intent of this file is to test the storage relationships in the model -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 12/29/23 """ import logging -import pathlib +from typing import Any import pytest -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_mode import TemoaMode -from temoa.temoa_model.temoa_model import TemoaModel -from temoa.temoa_model.temoa_sequencer import TemoaSequencer +from temoa.core.model import TemoaModel logger = logging.getLogger(__name__) # suitable scenarios for storage testing....singleton for now. @@ -25,34 +18,37 @@ ] -@pytest.mark.skip(reason='known not working...fix deferred') @pytest.mark.parametrize( 'system_test_run', argvalues=storage_config_files, indirect=True, ids=[d['name'] for d in storage_config_files], ) -def test_initialization_in_last_period(system_test_run): +def test_storage_fraction(system_test_run: tuple[str, Any, TemoaModel, Any]) -> None: """ - The last period should end up back at the initialization value + Level at the start of the time slice should equal the forced fraction """ + model: TemoaModel # helps with typing for some reason... data_name, results, model, _ = system_test_run - assert len(model.V_StorageInit.index_set()) > 0, ( - 'This model does not appear to have' 'any available storage components' + assert len(model.limit_storage_fraction_constraint_rpsdtv) > 0, ( + 'This model does not appear to have any StorageFraction constraints to test' ) - # get references to last timeslot for each prtv combo in things with storage - last_slots = { - (r, p, s, d, t, v) - for r, p, s, d, t, v in model.StorageLevel_rpsdtv - if s == model.time_season.last() and d == model.time_of_day.last() - } - # test that the last day/season combo ends up at the initialization value - # devnote: the Level variable is assessed at the end of the timeperiod - for r, p, s, d, t, v in last_slots: - assert model.V_StorageLevel[r, p, s, d, t, v].value == pytest.approx( - model.V_StorageInit[r, t, v].value, rel=1e-3 - ), f'model fails to align last season/day slot with initialization value for {r, t, v}' + + for r, p, s, d, t, v, op in model.limit_storage_fraction_constraint_rpsdtv: + energy = ( + model.limit_storage_fraction[r, s, d, t, op] + * model.v_capacity[r, p, t, v].value + * model.capacity_to_activity[r, t] + * (model.storage_duration[r, t] / 8760) + * model.segment_fraction_per_season[s] + * model.days_per_period + * model.process_life_frac[r, p, t, v] + ) + + assert model.v_storage_level[r, p, s, d, t, v].value == pytest.approx(energy, abs=1e-5), ( + f'model fails to initialise storage state at start of season {r, p, s, d, t, v}' + ) @pytest.mark.parametrize( @@ -61,49 +57,58 @@ def test_initialization_in_last_period(system_test_run): indirect=True, ids=[d['name'] for d in storage_config_files], ) -def test_initialization_in_first_period(system_test_run): +def test_state_sequencing(system_test_run: tuple[str, Any, TemoaModel, Any]) -> None: """ - The level at the end of the first period should be the initialization ± flows + Make sure that everything is looping properly. + + All storage uses the time_next chain. For non-seasonal storage at d_last, + v_storage_init replaces v_storage_level on the LHS. Seasonal storage skips + d_last (handled by seasonal_storage_energy_constraint). """ model: TemoaModel # helps with typing for some reason... data_name, results, model, _ = system_test_run - assert len(model.V_StorageInit.index_set()) > 0, ( - 'This model does not appear to have' 'any available storage components' + assert len(model.storage_level_rpsdtv) > 0, ( + 'This model does not appear to have any available storage components' ) - # test the first periods - # get references to first timeslot for each prtv combo in things with storage - first_slots = { - (r, p, s, d, t, v) - for r, p, s, d, t, v in model.StorageLevel_rpsdtv - if s == model.time_season.first() and d == model.time_of_day.first() - } - # test that the last day/season combo starts up at the initialization value - # devnote: the Level variable is assessed at the end of the timeperiod, so the init ± flows - # should total the end value - for r, p, s, d, t, v in first_slots: - inflow_indices = { - (rr, pp, ss, dd, ii, tt, vv, oo) - for rr, pp, ss, dd, ii, tt, vv, oo in model.FlowInStorage_rpsditvo - if all((rr == r, pp == p, ss == s, dd == d, tt == t, vv == v)) - } - outflow_indices = { - (rr, pp, ss, dd, ii, tt, vv, oo) - for (rr, pp, ss, dd, ii, tt, vv, oo) in model.FlowVar_rpsditvo - if all((rr == r, pp == p, ss == s, dd == d, tt == t, vv == v)) - } - # calculate the inflow and outflow. Inflow is taxed by efficiency in the model, - # so we need to do that here as well - inflow = sum( - model.V_FlowIn[r, p, s, d, i, t, v, o].value * model.Efficiency[r, i, t, v, o] - for (r, p, s, d, i, t, v, o) in inflow_indices + d_last = model.time_of_day.last() + + for r, p, s, d, t, v in model.storage_level_rpsdtv: + charge = sum( + model.v_flow_in[r, p, s, d, S_i, t, v, S_o].value * model.efficiency[r, S_i, t, v, S_o] + for S_i in model.process_inputs[r, p, t, v] + for S_o in model.process_outputs_by_input[r, p, t, v, S_i] + ) + discharge = sum( + model.v_flow_out[r, p, s, d, S_i, t, v, S_o].value + for S_o in model.process_outputs[r, p, t, v] + for S_i in model.process_inputs_by_output[r, p, t, v, S_o] ) - outflow = sum(model.V_FlowOut[idx].value for idx in outflow_indices) - start = model.V_StorageInit[r, t, v].value - assert model.V_StorageInit[r, t, v].value + inflow - outflow == pytest.approx( - model.V_StorageLevel[r, p, s, d, t, v].value, rel=1e-3 - ), f'model fails to align last season/day slot with initialization value for {r, t, v}' + + s_next, d_next = model.time_next[s, d] + stored_energy = charge - discharge + + if model.is_seasonal_storage[t]: + # Seasonal storage: time_next chain (last tod skipped) + if d == d_last: + continue # handled by seasonal_storage_energy_constraint + state = model.v_storage_level[r, p, s, d, t, v].value + next_state = model.v_storage_level[r, p, s_next, d_next, t, v].value + assert state + stored_energy == pytest.approx(next_state, abs=1e-5), ( + f'model fails to sequence storage {r, p, s, t, v} at {s, d} to {s_next, d_next}' + ) + else: + # Non-seasonal: time_next chain, v_storage_init at d_last + if d == d_last: + prev_state = model.v_storage_init[r, p, s, t, v].value + else: + prev_state = model.v_storage_level[r, p, s, d, t, v].value + next_state = model.v_storage_level[r, p, s_next, d_next, t, v].value + assert prev_state + stored_energy == pytest.approx(next_state, abs=1e-5), ( + f'model fails to sequence storage {r, p, s, t, v} at {s, d}: ' + f'state({prev_state}) + SE({stored_energy}) != next({next_state})' + ) @pytest.mark.parametrize( @@ -112,52 +117,55 @@ def test_initialization_in_first_period(system_test_run): indirect=True, ids=[d['name'] for d in storage_config_files], ) -def test_storage_flow_balance(system_test_run): +def test_storage_flow_balance(system_test_run: tuple[str, Any, TemoaModel, Any]) -> None: """ Test the balance of all inflows vs. all outflows. Note: inflows are taxed by efficiency, so that is replicated here """ model: TemoaModel # helps with typing for some reason... data_name, results, model, _ = system_test_run - assert len(model.V_StorageInit.index_set()) > 0, ( - 'This model does not appear to have' 'any available storage components' + assert len(model.storage_level_rpsdtv) > 0, ( + 'This model does not appear to haveany available storage components' ) for s_tech in model.tech_storage: inflow_indices = { (r, p, s, d, i, t, v, o) - for r, p, s, d, i, t, v, o in model.FlowInStorage_rpsditvo + for r, p, s, d, i, t, v, o in model.flow_in_storage_rpsditvo if t == s_tech } outflow_indices = { (r, p, s, d, i, t, v, o) - for r, p, s, d, i, t, v, o in model.FlowVar_rpsditvo + for r, p, s, d, i, t, v, o in model.flow_var_rpsditvo if t == s_tech } # calculate the inflow and outflow. Inflow is taxed by efficiency in the model, # so we need to do that here as well inflow = sum( - model.V_FlowIn[r, p, s, d, i, t, v, o].value * model.Efficiency[r, i, t, v, o] + model.v_flow_in[r, p, s, d, i, t, v, o].value * model.efficiency[r, i, t, v, o] for (r, p, s, d, i, t, v, o) in inflow_indices ) - outflow = sum(model.V_FlowOut[idx].value for idx in outflow_indices) - assert inflow == pytest.approx( - outflow, rel=1e-3 - ), f'the inflow and outflow of storage tech {s_tech} do not match' - - -@pytest.mark.skip('not ready for primetime') -def test_hard_initialization(): - filename = 'config_storageville.toml' - options = {'silent': True, 'debug': True} - config_file = pathlib.Path(PROJECT_ROOT, 'tests', 'testing_configs', filename) - - sequencer = TemoaSequencer( - config_file=config_file, - output_path=tmp_path, - mode_override=TemoaMode.BUILD_ONLY, - **options, - ) - # get a built, unsolved model - model = sequencer.start() - model.V_StorageInit['electricville', 'batt', 2025] = 0.5 + outflow = sum(model.v_flow_out[idx].value for idx in outflow_indices) + + assert inflow == pytest.approx(outflow, abs=1e-5), ( + f'total inflow and outflow of storage tech {s_tech} do not match', + ' - there is a discontinuity of storage states', + ) + + +# devnote: the storage_init constraint was reworked into LimitStorageLevelFraction +# @pytest.mark.skip('not ready for primetime') +# def test_hard_initialization(): +# filename = 'config_storageville.toml' +# options = {'silent': True, 'debug': True} +# config_file = pathlib.Path(PROJECT_ROOT, 'tests', 'testing_configs', filename) + +# sequencer = TemoaSequencer( +# config_file=config_file, +# output_path=tmp_path, +# mode_override=TemoaMode.BUILD_ONLY, +# **options, +# ) +# # get a built, unsolved model +# model = sequencer.start() +# model.v_storage_init['electricville', 'batt', 2025] = 0.5 diff --git a/tests/test_table_writer.py b/tests/test_table_writer.py index ac43c5f37..3f30d2e1f 100644 --- a/tests/test_table_writer.py +++ b/tests/test_table_writer.py @@ -1,102 +1,104 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling +from typing import TypedDict -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +import pytest -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . +from temoa._internal.table_data_puller import loan_costs -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/1/24 +class LoanCostInput(TypedDict): + capacity: float + invest_cost: float + loan_life: float + loan_rate: float + global_discount_rate: float + process_life: int + p_0: int + vintage: int + p_e: int -""" -import pytest +class LoanCostTestCase(TypedDict): + ID: str + input: LoanCostInput + expected_model_cost: float + expected_undiscounted_cost: float -from temoa.temoa_model.table_data_puller import loan_costs -params = [ +params: list[LoanCostTestCase] = [ { 'ID': 'near-zero GDR', - 'capacity': 100_000, # units - 'invest_cost': 1, # $/unit of capacity - 'loan_life': 40, - 'loan_rate': 0.10, - 'global_discount_rate': 0.000000000001, - 'process_life': 40, - 'p_0': 2020, # the "myopic base year" to which all prices are discounted - 'vintage': 2020, # the vintage of the new 'tech' - 'p_e': 2100, # last year in the myopic view - 'model_cost': 409037.69, - 'undiscounted_cost': 409037.69, + 'input': { + 'capacity': 100_000.0, # units + 'invest_cost': 1.0, # $/unit of capacity + 'loan_life': 40.0, + 'loan_rate': 0.10, + 'global_discount_rate': 0.000000000001, + 'process_life': 40, + 'p_0': 2020, # the "myopic base year" to which all prices are discounted + 'vintage': 2020, # the vintage of the new 'tech' + 'p_e': 2100, # last year in the myopic view + }, + 'expected_model_cost': 409037.66, + 'expected_undiscounted_cost': 409037.66, }, { 'ID': 'shortened term', - 'capacity': 100_000, - 'invest_cost': 1, - 'loan_life': 40, - 'loan_rate': 0.08, - 'global_discount_rate': 0.05, - 'process_life': 50, - 'p_0': 2020, - 'vintage': 2030, - 'p_e': 2035, - 'model_cost': 23403.85, - 'undiscounted_cost': 41930.08, + 'input': { + 'capacity': 100_000.0, + 'invest_cost': 1.0, + 'loan_life': 40.0, + 'loan_rate': 0.08, + 'global_discount_rate': 0.05, + 'process_life': 50, + 'p_0': 2020, + 'vintage': 2030, + 'p_e': 2035, + }, + 'expected_model_cost': 20950.20952, + 'expected_undiscounted_cost': 33544.06, }, ] -params_with_zero_GDR = [ +params_with_zero_gdr: list[LoanCostTestCase] = [ { 'ID': 'actual zero GDR', - 'capacity': 100_000, # units - 'invest_cost': 1, # $/unit of capacity - 'loan_life': 40, - 'loan_rate': 0.10, - 'global_discount_rate': 0, - 'process_life': 40, - 'p_0': 2020, # the "myopic base year" to which all prices are discounted - 'vintage': 2020, # the vintage of the new 'tech' - 'p_e': 2100, # last year in the myopic view - 'model_cost': 409037.657, - 'undiscounted_cost': 409037.657, + 'input': { + 'capacity': 100_000.0, # units + 'invest_cost': 1.0, # $/unit of capacity + 'loan_life': 40.0, + 'loan_rate': 0.10, + 'global_discount_rate': 0, + 'process_life': 40, + 'p_0': 2020, # the "myopic base year" to which all prices are discounted + 'vintage': 2020, # the vintage of the new 'tech' + 'p_e': 2100, # last year in the myopic view + }, + 'expected_model_cost': 409037.657, + 'expected_undiscounted_cost': 409037.657, } ] -@pytest.mark.parametrize('param', params, ids=(param['ID'] for param in params)) -def test_loan_costs(param): +@pytest.mark.parametrize('test_case', params, ids=[p['ID'] for p in params]) +def test_loan_costs(test_case: LoanCostTestCase) -> None: """ Test the loan cost calculations """ # we will test with a 1% error to accommodate the approximation of GDR=0 - model_cost, undiscounted_cost = loan_costs(**param) - assert model_cost == pytest.approx(param['model_cost'], rel=0.01) - assert undiscounted_cost == pytest.approx(param['undiscounted_cost'], rel=0.01) + model_cost, undiscounted_cost = loan_costs(**test_case['input']) + assert model_cost == pytest.approx(test_case['expected_model_cost'], rel=0.01) + assert undiscounted_cost == pytest.approx(test_case['expected_undiscounted_cost'], rel=0.01) @pytest.mark.parametrize( - 'param', params_with_zero_GDR, ids=(param['ID'] for param in params_with_zero_GDR) + 'test_case', + params_with_zero_gdr, + ids=[p['ID'] for p in params_with_zero_gdr], ) -def test_loan_costs_with_zero_GDR(param): +def test_loan_costs_with_zero_gdr(test_case: LoanCostTestCase) -> None: """ Test the formula with zero for GDR to make sure it is handled correctly. The formula risks division by zero if this is not correct. """ - model_cost, undiscounted_cost = loan_costs(**param) - assert model_cost == pytest.approx(param['model_cost'], abs=0.01) - assert undiscounted_cost == pytest.approx(param['undiscounted_cost'], abs=0.01) + model_cost, undiscounted_cost = loan_costs(**test_case['input']) + assert model_cost == pytest.approx(test_case['expected_model_cost'], abs=0.01) + assert undiscounted_cost == pytest.approx(test_case['expected_undiscounted_cost'], abs=0.01) diff --git a/tests/test_tech_activity_vectors.py b/tests/test_tech_activity_vectors.py index 258cc4bfa..7eedd7cc0 100644 --- a/tests/test_tech_activity_vectors.py +++ b/tests/test_tech_activity_vectors.py @@ -1,30 +1,3 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/16/24 - -""" import pytest from temoa.extensions.modeling_to_generate_alternatives.tech_activity_vector_manager import ( @@ -32,7 +5,7 @@ ) -def test__vector_engine(): +def test__vector_engine() -> None: """ Make sure the basis generation algorithm is accurate. In this test case, there are unequal number of members per category and differing variables per member. We should come up with @@ -47,7 +20,8 @@ def test__vector_engine(): } var_map = {'dog': ['red', 'blue'], 'pig': ['yellow', 'green'], 'cat': ['blue', 'gold']} tech_sizes = {k: len(v) for k, v in var_map.items()} - # below is just to show the mapping back to variables.... test just want the coefficients from res_values + # below is just to show the mapping back to variables.... test just want the coefficients from + # res_values # res = [ # {'red': 0.25, 'blue': 0.25, 'yellow': 0.25, 'green': 0.25}, # {'red': -0.25, 'blue': -0.25, 'yellow': -0.25, 'green': -0.25}, diff --git a/tests/test_temoa_sequencer.py b/tests/test_temoa_sequencer.py index 5f71a08a9..334df5686 100644 --- a/tests/test_temoa_sequencer.py +++ b/tests/test_temoa_sequencer.py @@ -1,57 +1,76 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 3/14/24 - -""" - from pathlib import Path +from typing import Any import pytest +from pyomo.environ import ConcreteModel + +# Import the new dependencies +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.modes import TemoaMode -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_mode import TemoaMode -from temoa.temoa_model.temoa_sequencer import TemoaSequencer +# Define the config file path once +TESTING_CONFIGS_DIR = Path(__file__).parent / 'testing_configs' +UTOPIA_MYOPIC_CONFIG = TESTING_CONFIGS_DIR / 'config_utopia_myopic.toml' -params = [ - {'name': 'build-only', 'mode': TemoaMode.BUILD_ONLY}, +# Define test parameters +# Note: Separated BUILD_ONLY as it uses a different method now +run_params = [ {'name': 'check', 'mode': TemoaMode.CHECK}, {'name': 'pf', 'mode': TemoaMode.PERFECT_FORESIGHT}, {'name': 'myopic', 'mode': TemoaMode.MYOPIC}, {'name': 'MGA', 'mode': TemoaMode.MGA}, ] -# using the myopic config file for now below, becuase (a) it is most current db, (b) it is self-referencing -# for the database, which is needed for myopic, and (c) the mode is overriden anyhow! -config_file = Path(PROJECT_ROOT, 'tests', 'testing_configs', 'config_utopia_myopic.toml') -@pytest.mark.parametrize('run_data', params, ids=lambda p: p['name']) -def test_start(run_data, tmp_path): - options = {'silent': True, 'debug': True, 'mode_override': run_data['mode']} - sequencer = TemoaSequencer( - config_file=config_file, +def id_func(p: dict[str, Any]) -> str: + return p['name'] + + +# Suppress mypy error for pytest.mark.parametrize by using a more specific ignore +@pytest.mark.parametrize('run_data', run_params, ids=id_func) +def test_sequencer_start(run_data: dict[str, Any], tmp_path: Path) -> None: + """ + Tests the main `start()` method for various run modes. + """ + # Step 1: Create the TemoaConfig object first. + # The 'silent' flag is now part of the config. + config = TemoaConfig.build_config( + config_file=UTOPIA_MYOPIC_CONFIG, output_path=tmp_path, - **options, + silent=True, ) + + # Step 2: Instantiate the sequencer with the config object. + # The mode is passed as an override. + sequencer = TemoaSequencer( + config=config, + mode_override=run_data['mode'], + ) + + # Step 3: Call the `start()` method. + # Any failure will raise an exception, which pytest will catch. sequencer.start() + + +def test_sequencer_build_model(tmp_path: Path) -> None: + """ + Tests the dedicated `build_model()` method for the BUILD_ONLY mode. + """ + # Step 1: Create the config object. + config = TemoaConfig.build_config( + config_file=UTOPIA_MYOPIC_CONFIG, + output_path=tmp_path, + silent=True, + ) + + # Step 2: Instantiate the sequencer. + sequencer = TemoaSequencer( + config=config, + mode_override=TemoaMode.BUILD_ONLY, + ) + + # Step 3: Call the `build_model()` method and check the return value. + model = sequencer.build_model() + assert model is not None, 'sequencer.build_model() should return a model' + assert isinstance(model, ConcreteModel), 'Should return a Pyomo ConcreteModel' diff --git a/tests/test_unit_checking.py b/tests/test_unit_checking.py new file mode 100644 index 000000000..4426c09d1 --- /dev/null +++ b/tests/test_unit_checking.py @@ -0,0 +1,100 @@ +""" +Unit checking module tests for Temoa v4 + +Tests for the unit checking validation using pint library + +""" + +import pytest + +from temoa.model_checking.unit_checking import ureg +from temoa.model_checking.unit_checking.common import RATIO_ELEMENT, SINGLE_ELEMENT, UnitsFormat +from temoa.model_checking.unit_checking.entry_checker import ( + validate_units_expression, + validate_units_format, +) + +cases = [ + ('PJ', SINGLE_ELEMENT, True), + (' kWh', SINGLE_ELEMENT, True), + ('dog_food ', SINGLE_ELEMENT, True), + (' G * tonne', SINGLE_ELEMENT, True), + ('Mt.steel ', SINGLE_ELEMENT, False), # period not allowed + ('PJ / day', SINGLE_ELEMENT, True), # SINGLE_ELEMENT regex allows slashes + ('PJ / (kT)', RATIO_ELEMENT, True), + ('(PJ) / (kT)', RATIO_ELEMENT, False), # numerator with parens doesn't match RATIO regex + ('PJ / kT', RATIO_ELEMENT, False), # no parens on denom + ( + 'kWh/day/(cycle)', + RATIO_ELEMENT, + True, + ), # matches: numerator has slash (allowed), denom in parens + ('(kWh/day)/(cycle)', RATIO_ELEMENT, False), # numerator parens not in RATIO regex +] + + +@pytest.mark.parametrize( + ('entry', 'units_format', 'expected'), + cases, + ids=[f'{t[0]} -> {"valid" if t[2] else "invalid"}' for t in cases], +) +def test_format_validation(entry: str, units_format: UnitsFormat, expected: bool) -> None: + """Test the regex matching for unit format + Note: The unit values here are NOT tested within the Units Registry + This test is solely to test the regex to grab the units, esp the ratio units""" + is_valid, _ = validate_units_format(expr=entry, unit_format=units_format) + assert is_valid is expected + + +# Test cases: (expression, (is_valid, unit_object)) +# These test actual unit registry validation +expression_cases = [ + ('kg', True, ureg.kg), + ('kg/m^3', True, ureg('kg/(meter*meter*meter)')), + ('m/s', True, ureg('m/s')), + ('dog_food', False, None), + ('ethos', True, ureg.ethos), + ('passenger', True, ureg.passenger), + ('seat', True, ureg.seat), + ('dollar', True, ureg.dollar), + ('dollars', True, ureg.dollar), + ('USD', True, ureg.dollar), + ('EUR', True, ureg.euro), + ('kWh', True, ureg.kWh), +] + + +@pytest.mark.parametrize( + ('expr', 'is_valid', 'expected_unit'), + expression_cases, + ids=[f'{t[0]} -> {"valid" if t[1] else "invalid"}' for t in expression_cases], +) +def test_validate_units_expression(expr: str, is_valid: bool, expected_unit: object) -> None: + """ + Test the validate_units_expression function against various unit expressions. + """ + valid, result_unit = validate_units_expression(expr) + assert valid == is_valid + if is_valid: + assert result_unit == expected_unit + else: + assert result_unit is None + + +# Time dimension exponents: power units have [time]^-3, energy units have [time]^-2 +time_dimension_cases = [('kW', -3), ('kWh', -2), ('PJ', -2), ('PJ/h', -3)] + + +@pytest.mark.parametrize( + ('expr', 'location'), + time_dimension_cases, + ids=[t[0] for t in time_dimension_cases], +) +def test_time_dimension_locator(expr: str, location: int) -> None: + valid, test_value = validate_units_expression(expr) + assert valid, f'Expression {expr} should be valid' + assert test_value is not None, f'Expected unit object for {expr}' + found = test_value.dimensionality.get('[time]') + assert found == location, ( + f'time dimension not found at expected location for units: {test_value}' + ) diff --git a/tests/test_unit_checking_integration.py b/tests/test_unit_checking_integration.py new file mode 100644 index 000000000..b7baeeeed --- /dev/null +++ b/tests/test_unit_checking_integration.py @@ -0,0 +1,97 @@ +""" +Integration tests for unit checking using pre-built test databases. + +Uses Utopia database variants with specific unit errors to test the unit checker. +Test databases are created by create_unit_test_dbs() in tests/conftest.py during +pytest setup. +""" + +import shutil +from pathlib import Path + +import pytest + +from temoa.model_checking.unit_checking.screener import screen + +# Test database paths +TEST_DB_DIR = Path(__file__).parent / 'testing_outputs' + +# Module-level skip condition - all tests require the test databases +DBS_PRESENT = (TEST_DB_DIR / 'utopia_valid_units.sqlite').exists() + +pytestmark = pytest.mark.skipif( + not DBS_PRESENT, + reason='Test databases not created. Ensure conftest.py setup completed successfully.', +) + + +def test_valid_units_pass_check() -> None: + """Test that properly configured Utopia database passes all checks. + + This includes verifying that valid composite units with currency dimensions + (e.g., 'Mdollar / (PJ^2 / GW)') are accepted. + """ + db_path = TEST_DB_DIR / 'utopia_valid_units.sqlite' + + result = screen(db_path) + + assert result is True, 'Valid Utopia database should pass all unit checks' + + +def test_invalid_currency_units_detected() -> None: + """Test that cost units without currency dimension are detected""" + db_path = TEST_DB_DIR / 'utopia_invalid_currency.sqlite' + report_dir = TEST_DB_DIR / 'reports' + + try: + result = screen(db_path, report_dir=report_dir) + finally: + if report_dir.exists(): + shutil.rmtree(report_dir) + + assert result is False, 'Should detect missing currency dimension in cost table' + + +def test_energy_units_in_capacity_table_detected() -> None: + """Test that energy units (GWh) in capacity tables are detected""" + db_path = TEST_DB_DIR / 'utopia_energy_in_capacity.sqlite' + + result = screen(db_path) + + assert result is False, 'Should detect energy units (GWh) in capacity table' + + +def test_missing_ratio_parentheses_detected() -> None: + """Test that missing parentheses in ratio format are detected""" + db_path = TEST_DB_DIR / 'utopia_missing_parentheses.sqlite' + + result = screen(db_path) + + assert result is False, 'Should detect missing parentheses in ratio format' + + +def test_unknown_units_detected() -> None: + """Test that unregistered units are detected""" + db_path = TEST_DB_DIR / 'utopia_unknown_units.sqlite' + + result = screen(db_path) + + assert result is False, 'Should detect unregistered units' + + +def test_mismatched_tech_output_units_detected() -> None: + """Test that technologies with mismatched output units are detected""" + db_path = TEST_DB_DIR / 'utopia_mismatched_outputs.sqlite' + + result = screen(db_path) + + assert result is False, 'Should detect mismatched output units for same tech' + + +def test_bad_composite_currency_rejected() -> None: + """Test that nonsensical currency composites (dollar*meter) are caught""" + db_path = TEST_DB_DIR / 'utopia_bad_composite_currency.sqlite' + + result = screen(db_path) + + assert result is False, 'Should reject nonsensical currency composite (dollar*meter)' diff --git a/tests/test_unit_propagation.py b/tests/test_unit_propagation.py new file mode 100644 index 000000000..aec882f19 --- /dev/null +++ b/tests/test_unit_propagation.py @@ -0,0 +1,223 @@ +""" +Tests for the UnitPropagator class. + +Tests unit derivation from input tables for output table population. +""" + +import sqlite3 +from collections.abc import Generator +from pathlib import Path + +import pytest + +from temoa.model_checking.unit_checking.unit_propagator import UnitPropagator + +# Use the utopia_valid_units database which has proper units +TEST_DB_DIR = Path(__file__).parent / 'testing_outputs' +VALID_UNITS_DB = TEST_DB_DIR / 'utopia_valid_units.sqlite' + +# Skip if test database doesn't exist +pytestmark = pytest.mark.skipif( + not VALID_UNITS_DB.exists(), + reason='Test database not created. Ensure conftest.py setup completed.', +) + + +@pytest.fixture +def propagator() -> Generator[UnitPropagator, None, None]: + """Create a UnitPropagator from the valid units database.""" + with sqlite3.connect(VALID_UNITS_DB) as conn: + yield UnitPropagator(conn) + + +def test_propagator_has_unit_data(propagator: UnitPropagator) -> None: + """Verify propagator detects available unit data.""" + assert propagator.has_unit_data is True + + +def test_get_flow_out_units(propagator: UnitPropagator) -> None: + """Test flow out units derived from commodity table.""" + # ELC is a commodity in Utopia with units + units = propagator.get_flow_out_units('ELC') + assert units is not None + assert 'joule' in units.lower() or 'pj' in units.lower() + + +def test_get_flow_in_units(propagator: UnitPropagator) -> None: + """Test flow in units derived from commodity table.""" + units = propagator.get_flow_in_units('DSL') + assert units is not None + + +def test_get_capacity_units(propagator: UnitPropagator) -> None: + """Test capacity units derived from existing_capacity table.""" + units = propagator.get_capacity_units('E01') + assert units == 'GW' + + +def test_get_cost_units(propagator: UnitPropagator) -> None: + """Test cost units derived from cost tables.""" + units = propagator.get_cost_units() + # Should be currency-based + assert units is not None + assert 'dollar' in units.lower() or 'usd' in units.lower() + + +def test_missing_commodity_returns_none(propagator: UnitPropagator) -> None: + """Test that missing commodities return None gracefully.""" + units = propagator.get_flow_out_units('NONEXISTENT_COMMODITY') + assert units is None + + +def test_missing_tech_capacity_returns_none(propagator: UnitPropagator) -> None: + """Test that missing tech returns None for capacity units.""" + units = propagator.get_capacity_units('NONEXISTENT_TECH') + assert units is None + + +def test_empty_database_graceful_fallback() -> None: + """Test that an empty/minimal database doesn't crash the propagator.""" + # Create in-memory database with minimal schema - use context manager to avoid leaks + with sqlite3.connect(':memory:') as conn: + conn.execute('CREATE TABLE commodity (name TEXT, flag TEXT, description TEXT, units TEXT)') + conn.execute('CREATE TABLE metadata (element TEXT, value INT)') + conn.execute("INSERT INTO metadata VALUES ('DB_MAJOR', 4)") + conn.commit() + + propagator = UnitPropagator(conn) + + # Should not crash, just return None/empty + assert propagator.get_flow_out_units('anything') is None + assert propagator.get_capacity_units('anything') is None + assert propagator.get_cost_units() is None + + +# --------------------------------------------------------------------------- +# Integration test for end-to-end unit propagation +# --------------------------------------------------------------------------- + + +@pytest.fixture(scope='module') +def solved_db_with_units(tmp_path_factory: pytest.TempPathFactory) -> Path: + """ + Run a model solve using utopia_valid_units database and return path to results. + + This fixture creates a copy of the valid units database, runs a full model + solve, and returns the path for verification. + """ + import shutil + + from temoa._internal.temoa_sequencer import TemoaSequencer + from temoa.core.config import TemoaConfig + + # Skip if source database or solver doesn't exist + if not VALID_UNITS_DB.exists(): + pytest.skip('Valid units database not available') + + is_available, _location = TemoaConfig._check_solver_availability('appsi_highs') + if not is_available: + pytest.skip('Solver appsi_highs not available') + + # Create working directory and copy database + tmp_dir = tmp_path_factory.mktemp('unit_propagation') + working_db = tmp_dir / 'utopia_propagation_test.sqlite' + shutil.copy(VALID_UNITS_DB, working_db) + + # Create a config file that points to the correct database + # Use as_posix() to ensure forward slashes, avoiding escape sequence issues on Windows in TOML + config_content = ( + f'scenario = "unit_test"\n' + f'scenario_mode = "perfect_foresight"\n' + f'input_database = "{working_db.as_posix()}"\n' + f'output_database = "{working_db.as_posix()}"\n' + f'solver_name = "appsi_highs"\n' + f'save_excel = false\n' + f'save_duals = false\n' + f'time_sequencing = "seasonal_timeslices"\n' + ) + config_file = tmp_dir / 'config_unit_propagation.toml' + config_file.write_text(config_content) + + # Build config from our temporary config file + config = TemoaConfig.build_config( + config_file=config_file, + output_path=tmp_dir, + silent=True, + ) + + # Run the model + sequencer = TemoaSequencer(config=config) + sequencer.start() + + return working_db + + +def test_units_propagated_to_output_flow_tables(solved_db_with_units: Path) -> None: + """Verify that output flow tables have units populated.""" + with sqlite3.connect(solved_db_with_units) as conn: + # Check output_flow_out + row = conn.execute( + 'SELECT COUNT(*), SUM(CASE WHEN units IS NOT NULL THEN 1 ELSE 0 END)' + ' FROM output_flow_out' + ).fetchone() + total, with_units = row + assert total > 0 + assert with_units > 0, 'No units populated in output_flow_out' + sample = conn.execute( + 'SELECT output_comm, units FROM output_flow_out WHERE units IS NOT NULL LIMIT 1' + ).fetchone() + assert sample is not None + + # Check output_curtailment (same logic) + row = conn.execute( + 'SELECT COUNT(*), SUM(CASE WHEN units IS NOT NULL THEN 1 ELSE 0 END)' + ' FROM output_curtailment' + ).fetchone() + if row[0] > 0: + assert row[1] > 0, 'No units populated in output_curtailment' + + # Check output_flow_in + row = conn.execute( + 'SELECT COUNT(*), SUM(CASE WHEN units IS NOT NULL THEN 1 ELSE 0 END)' + ' FROM output_flow_in' + ).fetchone() + if row[0] > 0: + assert row[1] > 0, 'No units populated in output_flow_in' + + +def test_units_propagated_to_capacity_tables(solved_db_with_units: Path) -> None: + """Verify that capacity output tables have units populated.""" + with sqlite3.connect(solved_db_with_units) as conn: + for table in ['output_built_capacity', 'output_net_capacity']: + row = conn.execute( + f'SELECT COUNT(*), SUM(CASE WHEN units IS NOT NULL THEN 1 ELSE 0 END) FROM {table}' + ).fetchone() + total, with_units = row + + if total > 0: # Some tables may be empty depending on model + assert with_units > 0, f'No units populated in {table}' + # Check for GW (standard capacity unit in Utopia) + sample = conn.execute( + f'SELECT tech, units FROM {table} WHERE units IS NOT NULL LIMIT 1' + ).fetchone() + assert sample is not None + assert sample[1] == 'GW', f'Expected GW, got {sample[1]} in {table}' + + +def test_units_propagated_to_cost_table(solved_db_with_units: Path) -> None: + """Verify that output_cost table has units populated.""" + with sqlite3.connect(solved_db_with_units) as conn: + row = conn.execute( + 'SELECT COUNT(*), SUM(CASE WHEN units IS NOT NULL THEN 1 ELSE 0 END) FROM output_cost' + ).fetchone() + total, with_units = row + assert total > 0 + assert with_units > 0, 'No units populated in output_cost' + + sample = conn.execute( + 'SELECT units FROM output_cost WHERE units IS NOT NULL LIMIT 1' + ).fetchone() + assert sample is not None + # Utopia uses 'Mdollar', but simplified check for non-empty string is good baseline + units = sample[0].lower() + assert ('dollar' in units) or ('usd' in units) or ('euro' in units) or ('eur' in units) diff --git a/tests/test_unit_scavenging.py b/tests/test_unit_scavenging.py new file mode 100644 index 000000000..36a523d2b --- /dev/null +++ b/tests/test_unit_scavenging.py @@ -0,0 +1,56 @@ +from temoa.model_checking.unit_checking.unit_propagator import UnitPropagator + +extract_capacity_unit_func = UnitPropagator._extract_capacity_unit + + +def test_extract_simple() -> None: + """Test extracting a simple capacity unit.""" + assert extract_capacity_unit_func('Mdollar/GW') == 'GW' + + +def test_extract_complex_utopia() -> None: + """Test extracting from a complex unit string like 'Mdollar / (PJ^2 / GW)'.""" + assert extract_capacity_unit_func('Mdollar / (PJ^2 / GW)') == 'GW' + + +def test_extract_complex_fixed() -> None: + """Test extracting from a complex unit string with a time component.""" + assert extract_capacity_unit_func('Mdollar / (PJ^2 / GW / year)') == 'GW' + + +def test_extract_MW() -> None: # noqa: N802 + """Test extracting 'MW' as a capacity unit.""" + assert extract_capacity_unit_func('Mdollar / (MW)') == 'MW' + + +def test_extract_kW() -> None: # noqa: N802 + """Test extracting 'kW' as a capacity unit.""" + assert extract_capacity_unit_func('Mdollar / (kW)') == 'kW' + + +def test_extract_gigawatt() -> None: + """Test extracting 'gigawatt' as a capacity unit.""" + assert extract_capacity_unit_func('Mdollar / (gigawatt)') == 'gigawatt' + + +def test_extract_megawatt() -> None: + """Test extracting 'megawatt' as a capacity unit.""" + assert extract_capacity_unit_func('Mdollar / (megawatt)') == 'megawatt' + + +def test_extract_kilowatt() -> None: + """Test extracting 'kilowatt' as a capacity unit.""" + assert extract_capacity_unit_func('Mdollar / (kilowatt)') == 'kilowatt' + + +def test_extract_ignores_energy() -> None: + """Test that energy units like 'GWh' are ignored.""" + assert extract_capacity_unit_func('GWh') is None + + +def test_extract_none() -> None: + """Test cases where no capacity unit can be extracted.""" + assert extract_capacity_unit_func('Mdollar/PJ') is None + assert extract_capacity_unit_func('Mdollar/kg') is None + assert extract_capacity_unit_func('Mdollar') is None + assert extract_capacity_unit_func('SomethingElse') is None diff --git a/tests/test_v4_migration.py b/tests/test_v4_migration.py new file mode 100644 index 000000000..42e649639 --- /dev/null +++ b/tests/test_v4_migration.py @@ -0,0 +1,129 @@ +import sqlite3 +import subprocess +import sys +from pathlib import Path + +import pytest + +# Constants +REPO_ROOT = Path(__file__).parents[1] +UTILITIES_DIR = REPO_ROOT / 'temoa' / 'utilities' +SCHEMA_V4 = REPO_ROOT / 'temoa' / 'db_schema' / 'temoa_schema_v4.sql' + +SCHEMA_V3 = REPO_ROOT / 'temoa' / 'db_schema' / 'temoa_schema_v3.sql' +SCHEMA_V3_1 = REPO_ROOT / 'temoa' / 'db_schema' / 'temoa_schema_v3_1.sql' +MOCK_DATA_V3 = REPO_ROOT / 'tests' / 'testing_data' / 'migration_v3_mock.sql' +MOCK_DATA_V3_1 = REPO_ROOT / 'tests' / 'testing_data' / 'migration_v3_1_mock.sql' + + +@pytest.mark.parametrize( + ('schema_file', 'mock_data_file'), + [ + (SCHEMA_V3, MOCK_DATA_V3), + (SCHEMA_V3_1, MOCK_DATA_V3_1), + ] +) +def test_v4_migrations(tmp_path: Path, schema_file: Path, mock_data_file: Path) -> None: + """Test both SQL and SQLite master migrators.""" + + # 1. Create SQLite DB + db_v3_1 = tmp_path / 'test_db.sqlite' + with sqlite3.connect(db_v3_1) as conn: + conn.execute('PRAGMA foreign_keys = OFF') + conn.executescript(schema_file.read_text()) + conn.executescript(mock_data_file.read_text()) + conn.execute('PRAGMA foreign_keys = ON') + + # 2. Dump to SQL + sql_v3_1 = tmp_path / 'test_v3_1.sql' + with open(sql_v3_1, 'w') as f: + for line in sqlite3.connect(db_v3_1).iterdump(): + f.write(line + '\n') + + # 3. Verify SQL migration script + sql_v4_migrated = tmp_path / 'test_v4_migrated.sql' + subprocess.run( + [ + sys.executable, + str(UTILITIES_DIR / 'master_migration.py'), + '--type', + 'sql', + '--input', + str(sql_v3_1), + '--schema', + str(SCHEMA_V4), + '--output', + str(sql_v4_migrated), + ], + check=True, + ) + + # Load SQL result into memory to verify + conn_sql = sqlite3.connect(':memory:') + conn_sql.executescript(sql_v4_migrated.read_text()) + + _verify_migrated_data(conn_sql) + + # 4. Verify SQLite direct migration script + db_v4_migrated = tmp_path / 'test_v4_migrated.sqlite' + subprocess.run( + [ + sys.executable, + str(UTILITIES_DIR / 'master_migration.py'), + '--type', + 'db', + '--input', + str(db_v3_1), + '--schema', + str(SCHEMA_V4), + '--output', + str(db_v4_migrated), + ], + check=True, + ) + + with sqlite3.connect(db_v4_migrated) as conn_db: + _verify_migrated_data(conn_db) + + +def _verify_migrated_data(conn: sqlite3.Connection) -> None: + # Check time_season restructuring (aggregated from TimeSegmentFraction) + # Summer: 0.4 + 0.3 = 0.7 + # Winter: 0.2 + 0.1 = 0.3 + seasons = dict(conn.execute('SELECT season, segment_fraction FROM time_season').fetchall()) + assert seasons['summer'] == pytest.approx(0.7) + assert seasons['winter'] == pytest.approx(0.3) + + # Check time_of_day restructuring (normalized to 24 hours based on weights) + # Day weight: 0.2 + 0.4 = 0.6 + # Night weight: 0.1 + 0.3 = 0.4 + # Normalized to 24: Day=0.6*24=14.4, Night=0.4*24=9.6 + tods = dict(conn.execute('SELECT tod, hours FROM time_of_day').fetchall()) + assert tods['day'] == pytest.approx(14.4) + assert tods['night'] == pytest.approx(9.6) + + # Check period removal from capacity_factor_process + # Original PK: (region, period, season, tod, tech, vintage) + # New PK: (region, season, tod, tech, vintage) + cols = [c[1] for c in conn.execute('PRAGMA table_info(capacity_factor_process)').fetchall()] + assert 'period' not in cols + + rows = conn.execute( + 'SELECT region, season, tod, tech, vintage, factor FROM capacity_factor_process' + ).fetchall() + assert len(rows) == 1 + assert rows[0] == ('R1', 'winter', 'day', 'T1', 2030, pytest.approx(0.6)) + + # Check operator-added tables (limit_capacity and limit_emission) + cap_rows = conn.execute('SELECT region, tech_or_group, period, capacity, operator FROM limit_capacity').fetchall() + assert len(cap_rows) == 1 + assert cap_rows[0] == ('R1', 'T1', 2030, 10.0, 'ge') + + emis_rows = conn.execute('SELECT region, period, value, operator FROM limit_emission').fetchall() + assert len(emis_rows) == 1 + assert emis_rows[0] == ('R1', 2030, 100.0, 'le') + + # Check metadata version + major = conn.execute("SELECT value FROM metadata WHERE element='DB_MAJOR'").fetchone()[0] + assert int(major) == 4 + assert int(conn.execute("SELECT value FROM metadata WHERE element='DB_MINOR'").fetchone()[0]) == 0 diff --git a/tests/test_validators.py b/tests/test_validators.py index 5c9b3a6e1..a3a3d9c34 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,44 +1,25 @@ """ Tests for the validators for regions, linked regions, and region groups - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 9/28/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - """ +from typing import TYPE_CHECKING, cast + import pyomo.environ as pyo import pytest -from temoa.temoa_model.model_checking.validators import ( +from temoa.model_checking.validators import ( linked_region_check, + no_slash_or_pipe, region_check, region_group_check, - no_slash_or_pipe, ) +if TYPE_CHECKING: + from temoa.core.model import TemoaModel + from temoa.types.core_types import Region + -def test_region_check(): +def test_region_check() -> None: """ Test good region names """ @@ -50,12 +31,14 @@ def test_region_check(): ' R12', # leading spaces 'global', # illegal for individual region } - assert all(region_check(None, region=r) for r in good_names) + assert all(region_check(cast('TemoaModel', None), region=cast('Region', r)) for r in good_names) for bad_name in bad_names: - assert not region_check(None, region=bad_name), f'This should fail {bad_name}' + assert not region_check(cast('TemoaModel', None), region=cast('Region', bad_name)), ( + f'This should fail {bad_name}' + ) -def test_linked_region_check(): +def test_linked_region_check() -> None: """ Test legal pairings for linked regions """ @@ -72,12 +55,14 @@ def test_linked_region_check(): 'AZ - Mexico', # bad spacing 'AZ-R2-Mexico', # triples not allowed } - assert all(linked_region_check(m, region_pair=rp) for rp in good_names) + assert all(linked_region_check(cast('TemoaModel', m), region_pair=rp) for rp in good_names) for bad_name in bad_names: - assert not linked_region_check(m, region_pair=bad_name), f'This should fail {bad_name}' + assert not linked_region_check(cast('TemoaModel', m), region_pair=bad_name), ( + f'This should fail {bad_name}' + ) -def test_region_group_check(): +def test_region_group_check() -> None: """ Test legal multi-region groupings """ @@ -92,9 +77,13 @@ def test_region_group_check(): 'Region3', # singleton not in m.R } for name in good_names: - assert region_group_check(m, name), f'This name should have been good: {name}' + assert region_group_check(cast('TemoaModel', m), rg=name), ( + f'This name should have been good: {name}' + ) for name in bad_names: - assert not region_group_check(m, name), f'This name should have failed: {name}' + assert not region_group_check(cast('TemoaModel', m), rg=name), ( + f'This name should have failed: {name}' + ) params = [ @@ -107,5 +96,5 @@ def test_region_group_check(): @pytest.mark.parametrize('value, expected', params) -def test_no_slash(value, expected): - assert no_slash_or_pipe(M=None, element=value) == expected +def test_no_slash(value: str | int, *, expected: bool) -> None: + assert no_slash_or_pipe(model=cast('TemoaModel', None), element=value) == expected diff --git a/tests/testing_configs/config_annualised_demand.toml b/tests/testing_configs/config_annualised_demand.toml new file mode 100644 index 000000000..198a7982f --- /dev/null +++ b/tests/testing_configs/config_annualised_demand.toml @@ -0,0 +1,22 @@ +scenario = "test run" +scenario_mode = "perfect_foresight" +input_database = "tests/testing_outputs/annualised_demand.sqlite" +output_database = "tests/testing_outputs/annualised_demand.sqlite" +neos = false +solver_name = "appsi_highs" +save_excel = false +save_duals = false +save_lp_file = false +time_sequencing = "representative_periods" +days_per_period = 365 +reserve_margin = "static" + +[MGA] +cost_epsilon = 0.03 +iteration_limit = 15 +time_limit_hrs = 1 +axis = "tech_category_activity" +weighting = "hull_expansion" + +[myopic] +myopic_view = 2 diff --git a/tests/testing_configs/config_emissions.toml b/tests/testing_configs/config_emissions.toml index b3c9db6d7..a3ec86a4b 100644 --- a/tests/testing_configs/config_emissions.toml +++ b/tests/testing_configs/config_emissions.toml @@ -1,36 +1,20 @@ -# this config is used for testing in test_full_runs.py scenario = "test run" scenario_mode = "perfect_foresight" - -input_database = "testing_outputs/emissions.sqlite" -output_database = "testing_outputs/emissions.sqlite" +input_database = "tests/testing_outputs/emissions.sqlite" +output_database = "tests/testing_outputs/emissions.sqlite" neos = false - -# solver -solver_name = "cbc" - -# generate an excel file in the output_files folder +solver_name = "appsi_highs" save_excel = false - -# save the duals in the output .sqlite database save_duals = false - -# save a copy of the pyomo-generated lp file to the outputs folder (may be large file!) save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- [MGA] slack = 0.1 iterations = 4 -weight = "integer" # currently supported: [integer, normalized] +weight = "integer" [myopic] -myopic_view = 2 # number of periods seen at one iteration - - - - +myopic_view = 2 diff --git a/tests/testing_configs/config_link_test.toml b/tests/testing_configs/config_link_test.toml index 2edfc0a78..88192e4f4 100644 --- a/tests/testing_configs/config_link_test.toml +++ b/tests/testing_configs/config_link_test.toml @@ -1,90 +1,24 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# ---------------------------------------------------------- -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [
] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file scenario = "test_linked_tech" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect_foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check] scenario_mode = "perfect_foresight" - -# Input database (Mandatory) -input_database = "testing_outputs/simple_linked_tech.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# For Pefrect Foresight, the user may target the same input file or a separate / -# copied sqlite file in a different location. Myopic requires that input_database = output_database -output_database = "testing_outputs/simple_linked_tech.sqlite" - -# ------------------------------------ -# DATA / MODEL CHECKS -# To check data / cost integrity -# ------------------------------------ - -# Check the pricing structure for common errors, which are reported in the log file -# Strongly recommended +input_database = "tests/testing_outputs/simple_linked_tech.sqlite" +output_database = "tests/testing_outputs/simple_linked_tech.sqlite" price_check = false - -# Check the network connectivity for processes in the model. Strongly -# recommended to ensure proper performance. Results are reported in log file -# This requires that source commodities be marked with 's' in Commodity table -# This is required for Myopic runs source_trace = true - -# Produce HTML files for Commodity Networks. Requires source_trace above plot_commodity_network = false - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve. (Currently NOT supported) neos = false - -# solver (Mandatory) -# Depending on what client machine has installed. -# [cbc, appsi_highs, gurobi, cplex, ...] -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder +solver_name = "appsi_highs" save_excel = false - -# save the duals in the output Database (may slow execution slightly?) save_duals = false - -# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!) save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- [MGA] slack = 0.1 iterations = 4 -weight = "integer" # currently supported: [integer, normalized] +weight = "integer" [myopic] -view_depth = 2 # number of periods seen/analyzed per iteration -step_size = 1 # number of periods to step by (must be <= view depth) - - - - +view_depth = 2 +step_size = 1 diff --git a/tests/testing_configs/config_materials.toml b/tests/testing_configs/config_materials.toml new file mode 100644 index 000000000..3eae4926f --- /dev/null +++ b/tests/testing_configs/config_materials.toml @@ -0,0 +1,20 @@ +scenario = "test run" +scenario_mode = "perfect_foresight" +input_database = "tests/testing_outputs/materials.sqlite" +output_database = "tests/testing_outputs/materials.sqlite" +neos = false +solver_name = "appsi_highs" +save_excel = false +save_duals = false +save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" + +[MGA] +slack = 0.1 +iterations = 4 +weight = "integer" + +[myopic] +myopic_view = 2 diff --git a/tests/testing_configs/config_mediumville.toml b/tests/testing_configs/config_mediumville.toml index 2fa7b0aaf..935abdbaa 100644 --- a/tests/testing_configs/config_mediumville.toml +++ b/tests/testing_configs/config_mediumville.toml @@ -1,70 +1,20 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [
] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file scenario = "testing" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only] -scenario_mode = "build_only" - -# Input file (Mandatory) -# Input can be a .sqlite or .dat file -# Both relative path and absolute path are accepted -input_database = "testing_outputs/mediumville.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# the user may target the same input file or a separate / -# copied sqlite file in a different location -output_database = "testing_outputs/mediumville.sqlite" - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve +scenario_mode = "perfect_foresight" +input_database = "tests/testing_outputs/mediumville.sqlite" +output_database = "tests/testing_outputs/mediumville.sqlite" neos = false - -# solver -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder +solver_name = "appsi_highs" save_excel = false - -# save the duals in the output .sqlite database save_duals = false - -# save a copy of the pyomo-generated lp file to the outputs folder (may be large file!) save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- [MGA] slack = 0.1 iterations = 4 -weight = "integer" # currently supported: [integer, normalized] +weight = "integer" [myopic] -myopic_view = 2 # number of periods seen at one iteration - - - - +myopic_view = 2 diff --git a/tests/testing_configs/config_seasonal_storage.toml b/tests/testing_configs/config_seasonal_storage.toml new file mode 100644 index 000000000..927446558 --- /dev/null +++ b/tests/testing_configs/config_seasonal_storage.toml @@ -0,0 +1,22 @@ +scenario = "test run" +scenario_mode = "perfect_foresight" +input_database = "tests/testing_outputs/seasonal_storage.sqlite" +output_database = "tests/testing_outputs/seasonal_storage.sqlite" +neos = false +solver_name = "appsi_highs" +save_excel = false +save_duals = false +save_lp_file = false +time_sequencing = "representative_periods" +days_per_period = 365 +reserve_margin = "static" + +[MGA] +cost_epsilon = 0.03 +iteration_limit = 15 +time_limit_hrs = 1 +axis = "tech_category_activity" +weighting = "hull_expansion" + +[myopic] +myopic_view = 2 diff --git a/tests/testing_configs/config_storageville.toml b/tests/testing_configs/config_storageville.toml index 35ee7b05b..79a2585d0 100644 --- a/tests/testing_configs/config_storageville.toml +++ b/tests/testing_configs/config_storageville.toml @@ -1,70 +1,20 @@ -# ---------------------------------------------------------- -# Configuration file for a Temoa Run -# Allows specification of run type and associated parameters -# -# For toml format info see: https://toml.io/en/ -# - comments may be added with hash -# - do NOT comment out table names in brackets like: [
] - -# Scenario Name (Mandatory) -# This scenario name is used to label results within the output .sqlite file scenario = "storage testing" - -# Scenaio Mode (Mandatory) -# See documentation for explanations. A standard single run is "perfect foresight" -# mode must be one of (case-insensitive): -# [perfect_foresight, MGA, myopic, method_of_morris, build_only] scenario_mode = "perfect_foresight" - -# Input file (Mandatory) -# Input can be a .sqlite or .dat file -# Both relative path and absolute path are accepted -input_database = "testing_outputs/storageville.sqlite" - -# Output file (Mandatory) -# The output file must be an existing .sqlite file -# the user may target the same input file or a separate / -# copied sqlite file in a different location -output_database = "testing_outputs/storageville.sqlite" - -# ------------------------------------ -# SOLVER -# Solver Selection -# ------------------------------------ - -# use the NEOS server to solve +input_database = "tests/testing_outputs/storageville.sqlite" +output_database = "tests/testing_outputs/storageville.sqlite" neos = false - -# solver -solver_name = "cbc" - -# ------------------------------------ -# OUTPUTS -# select desired output products/files -# ------------------------------------ - -# generate an Excel file in the output_files folder +solver_name = "appsi_highs" save_excel = false - -# save the duals in the output .sqlite database save_duals = false - -# save a copy of the pyomo-generated lp file to the outputs folder (may be large file!) save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- [MGA] slack = 0.1 iterations = 4 -weight = "integer" # currently supported: [integer, normalized] +weight = "integer" [myopic] -myopic_view = 2 # number of periods seen at one iteration - - - - +myopic_view = 2 diff --git a/tests/testing_configs/config_survival_curve.toml b/tests/testing_configs/config_survival_curve.toml new file mode 100644 index 000000000..fa28822a1 --- /dev/null +++ b/tests/testing_configs/config_survival_curve.toml @@ -0,0 +1,22 @@ +scenario = "test run" +scenario_mode = "perfect_foresight" +input_database = "tests/testing_outputs/survival_curve.sqlite" +output_database = "tests/testing_outputs/survival_curve.sqlite" +neos = false +solver_name = "appsi_highs" +save_excel = false +save_duals = false +save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" + +[MGA] +cost_epsilon = 0.03 +iteration_limit = 15 +time_limit_hrs = 1 +axis = "tech_category_activity" +weighting = "hull_expansion" + +[myopic] +myopic_view = 2 diff --git a/tests/testing_configs/config_test_system.toml b/tests/testing_configs/config_test_system.toml index 7ea66fc5e..25acdfe59 100644 --- a/tests/testing_configs/config_test_system.toml +++ b/tests/testing_configs/config_test_system.toml @@ -1,36 +1,20 @@ -# this config is used for testing in test_full_runs.py scenario = "test run" scenario_mode = "perfect_foresight" - -input_database = "testing_outputs/test_system.sqlite" -output_database = "testing_outputs/test_system.sqlite" +input_database = "tests/testing_outputs/test_system.sqlite" +output_database = "tests/testing_outputs/test_system.sqlite" neos = false - -# solver -solver_name = "cbc" - -# generate an excel file in the output_files folder +solver_name = "appsi_highs" save_excel = false - -# save the duals in the output .sqlite database save_duals = false - -# save a copy of the pyomo-generated lp file to the outputs folder (may be large file!) save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- [MGA] slack = 0.1 iterations = 4 -weight = "integer" # currently supported: [integer, normalized] +weight = "integer" [myopic] -myopic_view = 2 # number of periods seen at one iteration - - - - +myopic_view = 2 diff --git a/tests/testing_configs/config_utopia.toml b/tests/testing_configs/config_utopia.toml index 8732362b3..1e7080c4a 100644 --- a/tests/testing_configs/config_utopia.toml +++ b/tests/testing_configs/config_utopia.toml @@ -1,38 +1,22 @@ -# this config is used for testing in test_full_runs.py scenario = "test run" scenario_mode = "perfect_foresight" - -input_database = "testing_outputs/utopia.sqlite" -output_database = "testing_outputs/utopia.sqlite" +input_database = "tests/testing_outputs/utopia.sqlite" +output_database = "tests/testing_outputs/utopia.sqlite" neos = false - -# solver -solver_name = "cbc" - -# generate an excel file in the output_files folder +solver_name = "appsi_highs" save_excel = false - -# save the duals in the output .sqlite database save_duals = false - -# save a copy of the pyomo-generated lp file to the outputs folder (may be large file!) save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- [MGA] -cost_epsilon = 0.03 # 3% relaxation on optimal cost -iteration_limit = 15 # max iterations to perform -time_limit_hrs = 1 # max time -axis = "tech_category_activity" # use the tech activity Manager to control exploration based on categories in Tech -weighting = "hull_expansion" # use a convex hull expansion algorithm to weight exploration +cost_epsilon = 0.03 +iteration_limit = 15 +time_limit_hrs = 1 +axis = "tech_category_activity" +weighting = "hull_expansion" [myopic] -myopic_view = 2 # number of periods seen at one iteration - - - - +myopic_view = 2 diff --git a/tests/testing_configs/config_utopia_gv.toml b/tests/testing_configs/config_utopia_gv.toml new file mode 100644 index 000000000..cc5d93a5e --- /dev/null +++ b/tests/testing_configs/config_utopia_gv.toml @@ -0,0 +1,23 @@ +scenario = "test run" +scenario_mode = "perfect_foresight" +input_database = "tests/testing_outputs/utopia.sqlite" +output_database = "tests/testing_outputs/utopia.sqlite" +neos = false +solver_name = "appsi_highs" +save_excel = false +save_duals = false +save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" +graphviz_output = true + +[MGA] +cost_epsilon = 0.03 +iteration_limit = 15 +time_limit_hrs = 1 +axis = "tech_category_activity" +weighting = "hull_expansion" + +[myopic] +myopic_view = 2 diff --git a/tests/testing_configs/config_utopia_mc.toml b/tests/testing_configs/config_utopia_mc.toml new file mode 100644 index 000000000..40f8993dc --- /dev/null +++ b/tests/testing_configs/config_utopia_mc.toml @@ -0,0 +1,17 @@ +# MC config for testing in test_full_runs.py +scenario = "utopia_mc" +scenario_mode = "monte_carlo" + +input_database = "tests/testing_outputs/utopia_mc.sqlite" +output_database = "tests/testing_outputs/utopia_mc.sqlite" +neos = false + +# solver +solver_name = "appsi_highs" + +time_sequencing = 'seasonal_timeslices' +days_per_period = 365 +reserve_margin = 'static' + +[monte_carlo] +run_settings = "tests/testing_data/mc_settings_utopia.csv" diff --git a/tests/testing_configs/config_utopia_myopic.toml b/tests/testing_configs/config_utopia_myopic.toml index e9a14600d..ddab12b0c 100644 --- a/tests/testing_configs/config_utopia_myopic.toml +++ b/tests/testing_configs/config_utopia_myopic.toml @@ -1,37 +1,23 @@ -# this config is used for testing in test_full_runs.py scenario = "test myopic" scenario_mode = "myopic" - -# note that myopic currently only supports input = output. Test code will be responsible -# for making a fresh copy (if desired) and moving it to the output folder -input_database = "testing_outputs/myo_utopia.sqlite" -output_database = "testing_outputs/myo_utopia.sqlite" +input_database = "tests/testing_outputs/myo_utopia.sqlite" +output_database = "tests/testing_outputs/myo_utopia.sqlite" neos = false - -# solver -solver_name = "cbc" - -# generate an excel file in the output_files folder +solver_name = "appsi_highs" save_excel = true - -# save the duals in the output .sqlite database save_duals = true +save_lp_file = false +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +reserve_margin = "static" -# save a copy of the pyomo-generated lp file to the outputs folder (may be large file!) -save_lp_file = true - -# --------------------------------------------------- -# MODE OPTIONS -# options below are mode-specific and will be ignored -# if the run is not executed in that mode. -# --------------------------------------------------- [MGA] slack = 0.1 iterations = 4 -weight = "integer" # currently supported: [integer, normalized] +weight = "integer" [myopic] -view_depth = 2 # number of periods seen/analyzed per iteration -step_size = 1 # number of periods to step by - - +view_depth = 2 +step_size = 1 +evolving = false +evolution_script = '' diff --git a/tests/testing_configs/config_utopia_stochastic.toml b/tests/testing_configs/config_utopia_stochastic.toml new file mode 100644 index 000000000..a1dcefc70 --- /dev/null +++ b/tests/testing_configs/config_utopia_stochastic.toml @@ -0,0 +1,13 @@ +# Temoa configuration for utopia stochastic run +scenario = "utopia_stochastic" +scenario_mode = "stochastic" +input_database = "tests/testing_outputs/utopia.sqlite" +output_database = "tests/testing_outputs/utopia_stochastic.sqlite" +output_path = "tests/testing_outputs/utopia_stochastic" +solver_name = "appsi_highs" +time_sequencing = "seasonal_timeslices" +days_per_period = 365 +save_duals = false + +[stochastic] +stochastic_config = "../testing_data/utopia_stochastic_config.toml" diff --git a/tests/testing_data/US_9R_8D_legacy_set_sizes.json b/tests/testing_data/US_9R_8D_legacy_set_sizes.json index 014300fb5..cfe27e844 100644 --- a/tests/testing_data/US_9R_8D_legacy_set_sizes.json +++ b/tests/testing_data/US_9R_8D_legacy_set_sizes.json @@ -43,27 +43,27 @@ "tech_commercial": 0, "tech_residential": 0, "tech_PowerPlants": 0, - "SegFrac_index": 192, - "DemandDefaultDistribution_index": 192, - "DemandSpecificDistribution_index_index_0_index_0": 88128, - "DemandSpecificDistribution_index_index_0": 88128, - "DemandSpecificDistribution_index": 88128, - "Demand_index_index_0": 3213, - "Demand_index": 3213, + "segment_fraction_index": 192, + "demandDefaultDistribution_index": 192, + "demand_specific_distribution_index_index_0_index_0": 88128, + "demand_specific_distribution_index_index_0": 88128, + "demand_specific_distribution_index": 88128, + "demand_index_index_0": 3213, + "demand_index": 3213, "ResourceBound_index_index_0": 24255, "ResourceBound_index": 24255, "CapacityToActivity_index": 90639, - "ExistingCapacity_index_index_0": 7160481, - "ExistingCapacity_index": 7160481, - "Efficiency_index_index_0_index_0_index_0": 1308460978440, - "Efficiency_index_index_0_index_0": 1308460978440, - "Efficiency_index_index_0": 1308460978440, - "Efficiency_index": 1308460978440, + "existing_capacity_index_index_0": 7160481, + "existing_capacity_index": 7160481, + "efficiency_index_index_0_index_0_index_0": 1308460978440, + "efficiency_index_index_0_index_0": 1308460978440, + "efficiency_index_index_0": 1308460978440, + "efficiency_index": 1308460978440, "CapacityFactor_rsdtv": 8895936, "CapacityFactor_rsdt": 1741440, - "LifetimeTech_index": 90639, + "lifetime_tech_index": 90639, "LifetimeLoanTech_index": 90639, - "LifetimeProcess_rtv": 46333, + "lifetime_process_rtv": 46333, "LifetimeLoanProcess_rtv": 42431, "TechInputSplit_index_index_0_index_0": 27141345, "TechInputSplit_index_index_0": 27141345, @@ -74,16 +74,16 @@ "TechOutputSplit_index_index_0_index_0": 30736692, "TechOutputSplit_index_index_0": 30736692, "TechOutputSplit_index": 30736692, - "RenewablePortfolioStandard_index": 63, - "CostFixed_rptv": 154120, - "CostFixedVintageDefault_rtv": 46333, - "CostInvest_rtv": 42431, - "CostVariable_rptv": 154120, - "CostVariableVintageDefault_rtv": 46333, + "renewable_portfolio_standard_index": 63, + "cost_fixed_rptv": 154120, + "cost_fixed_vintage_default_rtv": 46333, + "cost_invest_rtv": 42431, + "cost_variable_rptv": 154120, + "cost_variable_vintage_default_rtv": 46333, "DiscountRate_rtv": 31717, "Loan_rtv": 31717, "ModelProcessLife_rptv": 154120, - "ProcessLifeFrac_rptv": 154120, + "process_life_frac_rptv": 154120, "MinCapacity_index_index_0": 634473, "MinCapacity_index": 634473, "MaxCapacity_index_index_0": 634473, @@ -107,7 +107,7 @@ "GrowthRateSeed_index": 90639, "EmissionLimit_index_index_0": 158823602, "EmissionLimit_index": 158823602, - "EmissionActivity_reitvo": 1863805, + "emission_activity_reitvo": 1863805, "MinActivityGroup_index_index_0": 0, "MinActivityGroup_index": 0, "MaxActivityGroup_index_index_0": 0, @@ -121,48 +121,48 @@ "MaxNewCapacityGroup_index_index_0": 0, "MaxNewCapacityGroup_index": 0, "MinCapShare_rptg": 0, - "LinkedTechs_index_index_0": 2084697, - "LinkedTechs_index": 2084697, + "linked_techs_index_index_0": 2084697, + "linked_techs_index": 2084697, "RampUp_index": 261, "RampDown_index": 261, - "CapacityCredit_index_index_0_index_0": 54564678, - "CapacityCredit_index_index_0": 54564678, - "CapacityCredit_index": 54564678, - "StorageDuration_index": 45, + "capacity_credit_index_index_0_index_0": 54564678, + "capacity_credit_index_index_0": 54564678, + "capacity_credit_index": 54564678, + "storage_duration_index": 45, "StorageInit_rtv": 260, - "FlowVar_rpsditvo": 18920064, - "FlowVarAnnual_rpitvo": 193276, - "FlexVar_rpsditvo": 0, - "FlexVarAnnual_rpitvo": 0, - "CurtailmentVar_rpsditvo": 4855104, - "FlowInStorage_rpsditvo": 157632, - "StorageLevel_rpsdtv": 157632, - "CapacityVar_rptv": 154120, - "NewCapacityVar_rtv": 46333, - "RetiredCapacityVar_rptv": 0, - "CapacityAvailableVar_rpt": 56272, - "CapacityConstraint_rpsdtv": 16599360, - "CapacityAnnualConstraint_rptv": 67665, - "DemandConstraint_rpsdc": 607488, - "DemandActivityConstraint_rpsdtv_dem_s0d0": 1306440, - "CommodityBalanceConstraint_rpsdc": 1813440, + "flow_var_rpsditvo": 18920064, + "flow_var_annual_rpitvo": 193276, + "flex_var_rpsditvo": 0, + "flex_var_annual_rpitvo": 0, + "curtailment_var_rpsditvo": 4855104, + "flow_in_storage_rpsditvo": 157632, + "storage_level_rpsdtv": 157632, + "capacity_var_rptv": 154120, + "new_capacity_var_rtv": 46333, + "retired_capacity_var_rptv": 0, + "capacity_available_var_rpt": 56272, + "capacity_constraint_rpsdtv": 16599360, + "capacity_annual_constraint_rptv": 67665, + "demand_constraint_rpsdc": 607488, + "demand_activity_constraint_rpsdtv_dem_s0d0": 1306440, + "commodity_balance_constraint_rpsdc": 1813440, "CommodityBalanceAnnualConstraint_rpc": 13117, "ResourceConstraint_rpr": 0, - "BaseloadDiurnalConstraint_rpsdtv": 0, - "RegionalExchangeCapacityConstraint_rrptv": 3794, - "StorageConstraints_rpsdtv": 157632, + "baseload_diurnal_constraint_rpsdtv": 0, + "regional_exchange_capacity_constraint_rrptv": 3794, + "storage_constraints_rpsdtv": 157632, "StorageInitConstraint_rtv": 0, "RampConstraintDay_rpsdtv": 369600, "RampConstraintPeriod_rptv": 1925, - "ReserveMargin_rpsd": 12096, + "reserve_margin_rpsd": 12096, "EmissionLimitConstraint_rpe": 89, "GrowthRateMaxConstraint_rtv": 0, "MaxActivityConstraint_rpt": 4424, "MinActivityConstraint_rpt": 66, "MinActivityGroup_rpg": 0, "MaxActivityGroup_rpg": 0, - "MaxCapacityConstraint_rpt": 6580, - "MaxNewCapacityConstraint_rpt": 0, + "Maxcapacity_constraint_rpt": 6580, + "MaxNewcapacity_constraint_rpt": 0, "MaxCapacityGroupConstraint_rpg": 0, "MinCapacityGroupConstraint_rpg": 0, "MinNewCapacityGroupConstraint_rpg": 0, @@ -174,8 +174,8 @@ "MinNewCapacityShareConstraint_rptg": 0, "MaxNewCapacityShareConstraint_rptg": 0, "MaxResourceConstraint_rt": 9, - "MinCapacityConstraint_rpt": 0, - "MinNewCapacityConstraint_rpt": 0, + "Mincapacity_constraint_rpt": 0, + "MinNewcapacity_constraint_rpt": 0, "MinAnnualCapacityFactorConstraint_rpto": 0, "MaxAnnualCapacityFactorConstraint_rpto": 0, "TechInputSplitConstraint_rpsditv": 924480, @@ -183,6 +183,6 @@ "TechInputSplitAverageConstraint_rpitv": 63, "TechOutputSplitConstraint_rpsdtvo": 0, "TechOutputSplitAnnualConstraint_rptvo": 0, - "RenewablePortfolioStandardConstraint_rpt": 0, - "LinkedEmissionsTechConstraint_rpsdtve": 312768 -} \ No newline at end of file + "renewable_portfolio_standard_constraint_rpt": 0, + "linked_emissions_tech_constraint_rpsdtve": 312768 +} diff --git a/tests/testing_data/US_9R_8D_set_sizes.json b/tests/testing_data/US_9R_8D_set_sizes.json index 03d89d31a..29c911cbc 100644 --- a/tests/testing_data/US_9R_8D_set_sizes.json +++ b/tests/testing_data/US_9R_8D_set_sizes.json @@ -45,33 +45,33 @@ "tech_residential": 0, "tech_PowerPlants": 0, "progress_marker_2_index": 1, - "SegFrac_index": 192, - "DemandDefaultDistribution_index": 192, - "DemandSpecificDistribution_index": 88128, - "Demand_index": 3213, + "segment_fraction_index": 192, + "demand_default_distribution_index": 192, + "demand_specific_distribution_index": 88128, + "demand_index": 3213, "ResourceBound_index": 24255, "CapacityToActivity_index": 90639, - "ExistingCapacity_index": 7160481, - "Efficiency_index": 1308460978440, - "CapacityFactorProcess_index": 166292352, + "existing_capacity_index": 7160481, + "efficiency_index": 1308460978440, + "capacity_factor_process_index": 166292352, "CapacityFactor_rsdt": 1741440, - "LifetimeTech_index": 90639, + "lifetime_tech_index": 90639, "LifetimeLoanTech_index": 90639, - "LifetimeProcess_rtv": 46333, + "lifetime_process_rtv": 46333, "LifetimeLoanProcess_rtv": 42431, "TechInputSplit_index": 27141345, "TechInputSplitAverage_index": 24255, "TechOutputSplit_index": 30736692, - "RenewablePortfolioStandard_index": 63, - "CostFixed_rptv": 154120, - "CostFixedVintageDefault_rtv": 46333, - "CostInvest_rtv": 42431, - "CostVariable_rptv": 154120, - "CostVariableVintageDefault_rtv": 46333, + "renewable_portfolio_standard_index": 63, + "cost_fixed_rptv": 154120, + "cost_fixed_vintage_default_rtv": 46333, + "cost_invest_rtv": 42431, + "cost_variable_rptv": 154120, + "cost_variable_vintage_default_rtv": 46333, "DiscountRate_rtv": 31717, "Loan_rtv": 31717, "ModelProcessLife_rptv": 154120, - "ProcessLifeFrac_rptv": 154120, + "process_life_frac_rptv": 154120, "MinCapacity_index": 634473, "MaxCapacity_index": 634473, "MinNewCapacity_index": 634473, @@ -84,7 +84,7 @@ "GrowthRateMax_index": 90639, "GrowthRateSeed_index": 90639, "EmissionLimit_index": 1610, - "EmissionActivity_reitvo": 1863805, + "emission_activity_reitvo": 1863805, "MinActivityGroup_index": 0, "MaxActivityGroup_index": 0, "MinCapacityGroup_index": 0, @@ -92,41 +92,41 @@ "MinNewCapacityGroup_index": 0, "MaxNewCapacityGroup_index": 0, "MinCapShare_rptg": 0, - "LinkedTechs_index": 2084697, + "linked_techs_index": 2084697, "RampUp_index": 261, "RampDown_index": 261, - "CapacityCredit_index": 54564678, - "StorageDuration_index": 45, + "capacity_credit_index": 54564678, + "storage_duration_index": 45, "StorageInit_rtv": 260, "progress_marker_3_index": 1, - "FlowVar_rpsditvo": 18920064, - "FlowVarAnnual_rpitvo": 193276, - "FlexVar_rpsditvo": 0, - "FlexVarAnnual_rpitvo": 0, - "CurtailmentVar_rpsditvo": 4855104, - "FlowInStorage_rpsditvo": 157632, - "StorageLevel_rpsdtv": 157632, - "CapacityVar_rptv": 154120, - "NewCapacityVar_rtv": 46333, - "RetiredCapacityVar_rptv": 0, - "CapacityAvailableVar_rpt": 56272, + "flow_var_rpsditvo": 18920064, + "flow_var_annual_rpitvo": 193276, + "flex_var_rpsditvo": 0, + "flex_var_annual_rpitvo": 0, + "curtailment_var_rpsditvo": 4855104, + "flow_in_storage_rpsditvo": 157632, + "storage_level_rpsdtv": 157632, + "capacity_var_rptv": 154120, + "new_capacity_var_rtv": 46333, + "retired_capacity_var_rptv": 0, + "capacity_available_var_rpt": 56272, "progress_marker_4_index": 1, - "CapacityConstraint_rpsdtv": 16599360, - "CapacityAnnualConstraint_rptv": 67665, + "capacity_constraint_rpsdtv": 16599360, + "capacity_annual_constraint_rptv": 67665, "progress_marker_5_index": 1, - "DemandConstraint_rpsdc": 607488, - "DemandActivityConstraint_rpsdtv_dem_s0d0": 1306440, - "CommodityBalanceConstraint_rpsdc": 1813440, + "demand_constraint_rpsdc": 607488, + "demand_activity_constraint_rpsdtv_dem_s0d0": 1306440, + "commodity_balance_constraint_rpsdc": 1813440, "CommodityBalanceAnnualConstraint_rpc": 13117, "ResourceConstraint_rpr": 0, - "BaseloadDiurnalConstraint_rpsdtv": 0, - "RegionalExchangeCapacityConstraint_rrptv": 3794, + "baseload_diurnal_constraint_rpsdtv": 0, + "regional_exchange_capacity_constraint_rrptv": 3794, "progress_marker_6_index": 1, - "StorageConstraints_rpsdtv": 157632, + "storage_constraints_rpsdtv": 157632, "StorageInitConstraint_rtv": 0, "RampConstraintDay_rpsdtv": 369600, "RampConstraintPeriod_rptv": 1925, - "ReserveMargin_rpsd": 12096, + "reserve_margin_rpsd": 12096, "EmissionLimitConstraint_rpe": 89, "progress_marker_7_index": 1, "GrowthRateMaxConstraint_rtv": 0, @@ -134,8 +134,8 @@ "MinActivityConstraint_rpt": 66, "MinActivityGroup_rpg": 0, "MaxActivityGroup_rpg": 0, - "MaxCapacityConstraint_rpt": 6580, - "MaxNewCapacityConstraint_rpt": 0, + "Maxcapacity_constraint_rpt": 6580, + "MaxNewcapacity_constraint_rpt": 0, "MaxCapacityGroupConstraint_rpg": 0, "MinCapacityGroupConstraint_rpg": 0, "MinNewCapacityGroupConstraint_rpg": 0, @@ -148,8 +148,8 @@ "MaxNewCapacityShareConstraint_rptg": 0, "progress_marker_8_index": 1, "MaxResourceConstraint_rt": 9, - "MinCapacityConstraint_rpt": 0, - "MinNewCapacityConstraint_rpt": 0, + "Mincapacity_constraint_rpt": 0, + "MinNewcapacity_constraint_rpt": 0, "MinAnnualCapacityFactorConstraint_rpto": 0, "MaxAnnualCapacityFactorConstraint_rpto": 0, "TechInputSplitConstraint_rpsditv": 924480, @@ -157,7 +157,7 @@ "TechInputSplitAverageConstraint_rpitv": 63, "TechOutputSplitConstraint_rpsdtvo": 0, "TechOutputSplitAnnualConstraint_rptvo": 0, - "RenewablePortfolioStandardConstraint_rpt": 0, - "LinkedEmissionsTechConstraint_rpsdtve": 312768, + "renewable_portfolio_standard_constraint_rpt": 0, + "linked_emissions_tech_constraint_rpsdtve": 312768, "progress_marker_9_index": 1 -} \ No newline at end of file +} diff --git a/tests/testing_data/annualised_demand.sql b/tests/testing_data/annualised_demand.sql new file mode 100644 index 000000000..76d910927 --- /dev/null +++ b/tests/testing_data/annualised_demand.sql @@ -0,0 +1,53 @@ +REPLACE INTO "commodity" VALUES('source', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('annual', 'a', NULL, NULL); +REPLACE INTO "commodity" VALUES('physical', 'p', NULL, NULL); +REPLACE INTO "commodity" VALUES('demand', 'd', NULL, NULL); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "cost_invest" VALUES('region','annual',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('region','non_annual',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2000,'annual',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2000,'non_annual',2000,1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2000,'demand',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('region', 'annual', 'annual', 2000, 'physical', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'annual', 'annual', 2000, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'physical', 'annual', 2000, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'physical', 'annual', 2000, 'annual', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'import', 2000, 'physical', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'import', 2000, 'annual', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'annual', 'non_annual', 2000, 'physical', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'annual', 'non_annual', 2000, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'physical', 'non_annual', 2000, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'physical', 'non_annual', 2000, 'annual', 1.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('region', 'annual', 1.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('region', 'non_annual', 1.0, NULL, NULL); +REPLACE INTO "limit_activity" VALUES('region',2000,'annual','le',0.5,NULL,NULL); +REPLACE INTO "limit_activity" VALUES('region',2000,'non_annual','le',0.5,NULL,NULL); +REPLACE INTO "loan_lifetime_process" VALUES('region', 'annual', 2000, 1.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('region', 'non_annual', 2000, 1.0, NULL, NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('region',NULL); +REPLACE INTO "technology" VALUES('annual','p','energy',NULL,NULL,0,1,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('import','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('non_annual','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(0,'D1',24,NULL); +REPLACE INTO "time_period" VALUES(0,2000,'f'); +REPLACE INTO "time_period" VALUES(1,2001,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(0,'S1',1.0,NULL); diff --git a/tests/testing_data/emissions.sql b/tests/testing_data/emissions.sql index 23203e965..0aaa11b02 100644 --- a/tests/testing_data/emissions.sql +++ b/tests/testing_data/emissions.sql @@ -1,957 +1,97 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('myopic_base_year',2000,'Base Year for Myopic Analysis'); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05000000000000000277,'Discount Rate for future costs'); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in LoanRate table'); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -INSERT INTO OutputObjective VALUES('test run','TotalCost',3.600000000000003641); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorTech VALUES('TestRegion','S1','TOD1','TechCurtailment',1,NULL); -INSERT INTO CapacityFactorTech VALUES('TestRegion','S1','TOD2','TechCurtailment',0.5,NULL); -INSERT INTO CapacityFactorTech VALUES('TestRegion','S1','TOD1','TechOrdinary',1,NULL); -INSERT INTO CapacityFactorTech VALUES('TestRegion','S1','TOD2','TechOrdinary',0.5,NULL); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('annual_in','s',NULL); -INSERT INTO Commodity VALUES('flex_in','s',NULL); -INSERT INTO Commodity VALUES('ordinary_in','s',NULL); -INSERT INTO Commodity VALUES('curtailment_in','s',NULL); -INSERT INTO Commodity VALUES('annual_out','d',NULL); -INSERT INTO Commodity VALUES('flex_out','p',NULL); -INSERT INTO Commodity VALUES('ordinary_out','d',NULL); -INSERT INTO Commodity VALUES('curtailment_out','d',NULL); -INSERT INTO Commodity VALUES('emission','e',NULL); -INSERT INTO Commodity VALUES('flex_null','d',NULL); -INSERT INTO Commodity VALUES('annual_flex_out','p',NULL); -INSERT INTO Commodity VALUES('annual_flex_in','s',NULL); -INSERT INTO Commodity VALUES('annual_flex_null','d',NULL); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -INSERT INTO CommodityType VALUES('s','source commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -INSERT INTO CostEmission VALUES('TestRegion',2000,'emission',0.7,NULL,NULL); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('TestRegion',2000,'annual_out',1.0,NULL,NULL); -INSERT INTO Demand VALUES('TestRegion',2000,'ordinary_out',0.3,NULL,NULL); -INSERT INTO Demand VALUES('TestRegion',2000,'curtailment_out',0.3,NULL,NULL); -INSERT INTO Demand VALUES('TestRegion',2000,'flex_null',0.3,NULL,NULL); -INSERT INTO Demand VALUES('TestRegion',2000,'annual_flex_null',0.3,NULL,NULL); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('TestRegion','annual_in','TechAnnual',2000,'annual_out',1.0,NULL); -INSERT INTO Efficiency VALUES('TestRegion','flex_in','TechFlex',2000,'flex_out',1.0,NULL); -INSERT INTO Efficiency VALUES('TestRegion','ordinary_in','TechOrdinary',2000,'ordinary_out',1.0,NULL); -INSERT INTO Efficiency VALUES('TestRegion','curtailment_in','TechCurtailment',2000,'curtailment_out',1.0,NULL); -INSERT INTO Efficiency VALUES('TestRegion','flex_out','TechFlexNull',2000,'flex_null',1.0,NULL); -INSERT INTO Efficiency VALUES('TestRegion','annual_flex_out','TechFlexNull',2000,'annual_flex_null',1.0,NULL); -INSERT INTO Efficiency VALUES('TestRegion','annual_flex_in','TechAnnualFlex',2000,'annual_flex_out',1.0,NULL); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('TestRegion','emission','annual_in','TechAnnual',2000,'annual_out',1.0,NULL,NULL); -INSERT INTO EmissionActivity VALUES('TestRegion','emission','flex_in','TechFlex',2000,'flex_out',1.0,NULL,NULL); -INSERT INTO EmissionActivity VALUES('TestRegion','emission','ordinary_in','TechOrdinary',2000,'ordinary_out',1.0,NULL,NULL); -INSERT INTO EmissionActivity VALUES('TestRegion','emission','curtailment_in','TechCurtailment',2000,'curtailment_out',1.0,NULL,NULL); -INSERT INTO EmissionActivity VALUES('TestRegion','emission','annual_flex_in','TechAnnualFlex',2000,'annual_flex_out',1.0,NULL,NULL); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxActivity VALUES('TestRegion',2000,'TechFlex',1.0,NULL,NULL); -INSERT INTO MaxActivity VALUES('TestRegion',2000,'TechAnnualFlex',1.0,NULL,NULL); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxCapacity VALUES('TestRegion',2000,'TechOrdinary',1.0,NULL,NULL); -INSERT INTO MaxCapacity VALUES('TestRegion',2000,'TechCurtailment',1.0,NULL,NULL); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinActivity VALUES('TestRegion',2000,'TechFlex',1.0,NULL,NULL); -INSERT INTO MinActivity VALUES('TestRegion',2000,'TechAnnualFlex',1.0,NULL,NULL); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinCapacity VALUES('TestRegion',2000,'TechOrdinary',1.0,NULL,NULL); -INSERT INTO MinCapacity VALUES('TestRegion',2000,'TechCurtailment',1.0,NULL,NULL); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('TestRegion',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('S1','TOD1',0.5,NULL); -INSERT INTO TimeSegmentFraction VALUES('S1','TOD2',0.5,NULL); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'TOD1'); -INSERT INTO TimeOfDay VALUES(2,'TOD2'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,1999,'e'); -INSERT INTO TimePeriod VALUES(2,2000,'f'); -INSERT INTO TimePeriod VALUES(3,2005,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'S1'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('TechAnnual','p','energy',NULL,NULL,0,1,0,0,0,0,0,0,NULL); -INSERT INTO Technology VALUES('TechFlex','p','energy',NULL,NULL,0,0,0,0,0,1,0,0,NULL); -INSERT INTO Technology VALUES('TechOrdinary','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -INSERT INTO Technology VALUES('TechCurtailment','p','energy',NULL,NULL,0,0,0,1,0,0,0,0,NULL); -INSERT INTO Technology VALUES('TechFlexNull','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -INSERT INTO Technology VALUES('TechAnnualFlex','p','energy',NULL,NULL,0,1,0,0,0,1,0,0,NULL); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -COMMIT; +REPLACE INTO "capacity_factor_tech" VALUES('Testregion','S1','TOD1','TechCurtailment',1.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('Testregion','S1','TOD2','TechCurtailment',0.5,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('Testregion','S1','TOD1','TechOrdinary',1.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('Testregion','S1','TOD2','TechOrdinary',0.5,NULL); +REPLACE INTO "commodity" VALUES('annual_in', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('flex_in', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('ordinary_in', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('curtailment_in', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('annual_out', 'd', NULL, NULL); +REPLACE INTO "commodity" VALUES('flex_out', 'p', NULL, NULL); +REPLACE INTO "commodity" VALUES('ordinary_out', 'd', NULL, NULL); +REPLACE INTO "commodity" VALUES('curtailment_out', 'd', NULL, NULL); +REPLACE INTO "commodity" VALUES('emission', 'e', NULL, NULL); +REPLACE INTO "commodity" VALUES('flex_null', 'd', NULL, NULL); +REPLACE INTO "commodity" VALUES('annual_flex_out', 'p', NULL, NULL); +REPLACE INTO "commodity" VALUES('annual_flex_in', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('annual_flex_null', 'd', NULL, NULL); +REPLACE INTO "commodity" VALUES('embodied_in', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('embodied_out', 'd', NULL, NULL); +REPLACE INTO "commodity" VALUES('eol_in', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('eol_out', 'd', NULL, NULL); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "cost_emission" VALUES('Testregion',2000,'emission',0.7,NULL,NULL); +REPLACE INTO "cost_emission" VALUES('Testregion',2005,'emission',0.7,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2000,'annual_out',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2000,'ordinary_out',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2000,'curtailment_out',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2000,'flex_null',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2000,'annual_flex_null',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2000,'embodied_out',0.6,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2000,'eol_out',0.6,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2005,'ordinary_out',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2005,'annual_out',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2005,'curtailment_out',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2005,'flex_null',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2005,'annual_flex_null',0.3,NULL,NULL); +REPLACE INTO "demand" VALUES('Testregion',2005,'embodied_out',0.6,NULL,NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'annual_in', 'TechAnnual', 2000, 'annual_out', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'flex_in', 'TechFlex', 2000, 'flex_out', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'ordinary_in', 'TechOrdinary', 2000, 'ordinary_out', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'curtailment_in', 'TechCurtailment', 2000, 'curtailment_out', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'flex_out', 'TechFlexNull', 2000, 'flex_null', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'annual_flex_out', 'TechFlexNull', 2000, 'annual_flex_null', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'annual_flex_in', 'TechAnnualFlex', 2000, 'annual_flex_out', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'embodied_in', 'TechEmbodied', 2000, 'embodied_out', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('Testregion', 'eol_in', 'TechEndOfLife', 2000, 'eol_out', 1.0, NULL, NULL); +REPLACE INTO "emission_activity" VALUES('Testregion','emission','annual_in','TechAnnual',2000,'annual_out',1.0,NULL,NULL); +REPLACE INTO "emission_activity" VALUES('Testregion','emission','flex_in','TechFlex',2000,'flex_out',1.0,NULL,NULL); +REPLACE INTO "emission_activity" VALUES('Testregion','emission','ordinary_in','TechOrdinary',2000,'ordinary_out',1.0,NULL,NULL); +REPLACE INTO "emission_activity" VALUES('Testregion','emission','curtailment_in','TechCurtailment',2000,'curtailment_out',1.0,NULL,NULL); +REPLACE INTO "emission_activity" VALUES('Testregion','emission','annual_flex_in','TechAnnualFlex',2000,'annual_flex_out',1.0,NULL,NULL); +REPLACE INTO "emission_embodied" VALUES('Testregion','emission','TechEmbodied',2000,0.5,NULL,NULL); +REPLACE INTO "emission_end_of_life" VALUES('Testregion','emission','TechEndOfLife',2000,0.5,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('Testregion', 'TechEndOfLife', 5.0, NULL, NULL); +REPLACE INTO "limit_activity" VALUES('Testregion',2000,'TechFlex','ge',1.0,NULL,NULL); +REPLACE INTO "limit_activity" VALUES('Testregion',2000,'TechAnnualFlex','ge',1.0,NULL,NULL); +REPLACE INTO "limit_activity" VALUES('Testregion',2000,'TechFlex','le',1.0,NULL,NULL); +REPLACE INTO "limit_activity" VALUES('Testregion',2000,'TechAnnualFlex','le',1.0,NULL,NULL); +REPLACE INTO "limit_capacity" VALUES('Testregion',2000,'TechOrdinary','ge',1.0,NULL,NULL); +REPLACE INTO "limit_capacity" VALUES('Testregion',2000,'TechCurtailment','ge',1.0,NULL,NULL); +REPLACE INTO "limit_capacity" VALUES('Testregion',2000,'TechOrdinary','le',1.0,NULL,NULL); +REPLACE INTO "limit_capacity" VALUES('Testregion',2000,'TechCurtailment','le',1.0,NULL,NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('Testregion',NULL); +REPLACE INTO "technology" VALUES('TechAnnual','p','energy',NULL,NULL,0,1,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechFlex','p','energy',NULL,NULL,0,0,0,0,0,1,0,0,NULL); +REPLACE INTO "technology" VALUES('TechOrdinary','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechCurtailment','p','energy',NULL,NULL,0,0,0,1,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechFlexNull','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechAnnualFlex','p','energy',NULL,NULL,0,1,0,0,0,1,0,0,NULL); +REPLACE INTO "technology" VALUES('TechEmbodied','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('TechEndOfLife','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(1,'TOD1',12,NULL); +REPLACE INTO "time_of_day" VALUES(2,'TOD2',12,NULL); +REPLACE INTO "time_period" VALUES(1,1999,'e'); +REPLACE INTO "time_period" VALUES(2,2000,'f'); +REPLACE INTO "time_period" VALUES(3,2005,'f'); +REPLACE INTO "time_period" VALUES(4,2010,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(1,'S1',1.0,NULL); diff --git a/tests/testing_data/materials.sql b/tests/testing_data/materials.sql new file mode 100644 index 000000000..95f24b4fa --- /dev/null +++ b/tests/testing_data/materials.sql @@ -0,0 +1,384 @@ +REPLACE INTO "capacity_factor_tech" VALUES('regionA','summer','morning','SOL_PV',0.3,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','autumn','morning','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','winter','morning','SOL_PV',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','spring','morning','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','summer','afternoon','SOL_PV',0.3,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','autumn','afternoon','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','winter','afternoon','SOL_PV',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','spring','afternoon','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','summer','evening','SOL_PV',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','autumn','evening','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','winter','evening','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','spring','evening','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','summer','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','autumn','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','winter','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionA','spring','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','summer','morning','SOL_PV',0.3,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','autumn','morning','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','winter','morning','SOL_PV',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','spring','morning','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','summer','afternoon','SOL_PV',0.3,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','autumn','afternoon','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','winter','afternoon','SOL_PV',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','spring','afternoon','SOL_PV',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','summer','evening','SOL_PV',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','autumn','evening','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','winter','evening','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','spring','evening','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','summer','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','autumn','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','winter','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('regionB','spring','overnight','SOL_PV',0.0,NULL); +REPLACE INTO "commodity" VALUES('ethos', 's', 'import dummy source', NULL); +REPLACE INTO "commodity" VALUES('electricity', 'p', 'grid electricity', NULL); +REPLACE INTO "commodity" VALUES('passenger_km', 'd', 'demand for passenger km', NULL); +REPLACE INTO "commodity" VALUES('battery_nmc', 'a', 'battery - lithium nickel manganese cobalt oxide', NULL); +REPLACE INTO "commodity" VALUES('battery_lfp', 'a', 'battery - lithium iron phosphate', NULL); +REPLACE INTO "commodity" VALUES('lithium', 'a', 'lithium', NULL); +REPLACE INTO "commodity" VALUES('cobalt', 'a', 'cobalt', NULL); +REPLACE INTO "commodity" VALUES('phosphorous', 'a', 'phosphorous', NULL); +REPLACE INTO "commodity" VALUES('diesel', 'a', 'diesel', NULL); +REPLACE INTO "commodity" VALUES('heating', 'd', 'demand for residential heating', NULL); +REPLACE INTO "commodity" VALUES('nickel', 'a', 'nickel', NULL); +REPLACE INTO "commodity" VALUES('used_batt_nmc', 'wa', 'used battery - lithium nickel manganese cobalt oxide', NULL); +REPLACE INTO "commodity" VALUES('used_batt_lfp', 'wa', 'used battery - lithium iron phosphate', NULL); +REPLACE INTO "commodity" VALUES('co2e', 'e', 'emitted co2-equivalent GHGs', NULL); +REPLACE INTO "commodity" VALUES('waste_steel', 'w', 'waste steel from cars', NULL); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "construction_input" VALUES('regionA','battery_nmc','CAR_BEV',2000,1.0,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionA','battery_lfp','CAR_PHEV',2000,0.1,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionA','battery_nmc','CAR_BEV',2010,1.0,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionA','battery_lfp','CAR_PHEV',2010,0.1,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionA','battery_nmc','CAR_BEV',2020,1.0,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionA','battery_lfp','CAR_PHEV',2020,0.1,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionB','battery_nmc','CAR_BEV',2000,1.0,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionB','battery_lfp','CAR_PHEV',2000,0.1,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionB','battery_nmc','CAR_BEV',2010,1.0,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionB','battery_lfp','CAR_PHEV',2010,0.1,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionB','battery_nmc','CAR_BEV',2020,1.0,NULL,NULL); +REPLACE INTO "construction_input" VALUES('regionB','battery_lfp','CAR_PHEV',2020,0.1,NULL,NULL); +REPLACE INTO "cost_emission" VALUES('regionA',2000,'co2e',1.0,NULL,NULL); +REPLACE INTO "cost_emission" VALUES('regionA',2010,'co2e',1.0,NULL,NULL); +REPLACE INTO "cost_emission" VALUES('regionA',2020,'co2e',1.0,NULL,NULL); +REPLACE INTO "cost_emission" VALUES('regionB',2000,'co2e',1.0,NULL,NULL); +REPLACE INTO "cost_emission" VALUES('regionB',2010,'co2e',1.0,NULL,NULL); +REPLACE INTO "cost_emission" VALUES('regionB',2020,'co2e',1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_BEV',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_BEV',2010,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_BEV',2020,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_PHEV',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_PHEV',2010,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_PHEV',2020,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_ICE',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_ICE',2010,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','CAR_ICE',2020,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','RECYCLE_NMC',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','RECYCLE_LFP',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','MANUFAC_NMC',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','MANUFAC_LFP',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','BATT_GRID',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','SOL_PV',2000,10.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA','GEN_DSL',2000,2.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_BEV',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_BEV',2010,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_BEV',2020,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_PHEV',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_PHEV',2010,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_PHEV',2020,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_ICE',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_ICE',2010,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','CAR_ICE',2020,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','RECYCLE_NMC',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','RECYCLE_LFP',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','MANUFAC_NMC',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','MANUFAC_LFP',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','BATT_GRID',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','GEN_DSL',2000,2.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionA-regionB','ELEC_INTERTIE',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB-regionA','ELEC_INTERTIE',2000,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('regionB','SOL_PV',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2000,'IMPORT_DSL',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2010,'IMPORT_DSL',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2020,'IMPORT_DSL',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2000,'IMPORT_LI',2000,2.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2010,'IMPORT_LI',2000,2.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2020,'IMPORT_LI',2000,2.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2000,'IMPORT_NI',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2010,'IMPORT_NI',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2020,'IMPORT_NI',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2000,'IMPORT_CO',2000,5.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2010,'IMPORT_CO',2000,5.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2020,'IMPORT_CO',2000,5.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2000,'IMPORT_P',2000,3.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2010,'IMPORT_P',2000,3.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2020,'IMPORT_P',2000,3.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2000,'DOMESTIC_NI',2000,0.5,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2010,'DOMESTIC_NI',2000,0.5,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionA',2020,'DOMESTIC_NI',2000,0.5,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2000,'IMPORT_DSL',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2010,'IMPORT_DSL',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2020,'IMPORT_DSL',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2000,'IMPORT_LI',2000,2.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2010,'IMPORT_LI',2000,2.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2020,'IMPORT_LI',2000,2.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2000,'IMPORT_NI',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2010,'IMPORT_NI',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2020,'IMPORT_NI',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2000,'IMPORT_CO',2000,5.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2010,'IMPORT_CO',2000,5.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2020,'IMPORT_CO',2000,5.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2000,'IMPORT_P',2000,3.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2010,'IMPORT_P',2000,3.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2020,'IMPORT_P',2000,3.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2000,'DOMESTIC_NI',2000,0.5,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2010,'DOMESTIC_NI',2000,0.5,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('regionB',2020,'DOMESTIC_NI',2000,0.5,NULL,NULL); +REPLACE INTO "demand" VALUES('regionA',2000,'passenger_km',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionA',2010,'passenger_km',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionA',2020,'passenger_km',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionA',2000,'heating',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionA',2010,'heating',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionA',2020,'heating',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionB',2000,'passenger_km',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionB',2010,'passenger_km',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionB',2020,'passenger_km',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionB',2000,'heating',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionB',2010,'heating',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('regionB',2020,'heating',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'ethos', 'DOMESTIC_NI', 2000, 'nickel', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'ethos', 'IMPORT_LI', 2000, 'lithium', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'ethos', 'IMPORT_NI', 2000, 'nickel', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'ethos', 'IMPORT_CO', 2000, 'cobalt', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'ethos', 'IMPORT_P', 2000, 'phosphorous', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'used_batt_nmc', 'RECYCLE_NMC', 2000, 'battery_nmc', 0.2, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'used_batt_lfp', 'RECYCLE_LFP', 2000, 'battery_lfp', 0.2, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'lithium', 'MANUFAC_NMC', 2000, 'battery_nmc', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'nickel', 'MANUFAC_NMC', 2000, 'battery_nmc', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'cobalt', 'MANUFAC_NMC', 2000, 'battery_nmc', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'lithium', 'MANUFAC_LFP', 2000, 'battery_lfp', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'phosphorous', 'MANUFAC_LFP', 2000, 'battery_lfp', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'RECYCLE_NMC', 2000, 'battery_nmc', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'RECYCLE_LFP', 2000, 'battery_lfp', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'MANUFAC_NMC', 2000, 'battery_nmc', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'MANUFAC_LFP', 2000, 'battery_lfp', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'GEN_DSL', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'ethos', 'SOL_PV', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'BATT_GRID', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'ethos', 'IMPORT_DSL', 2000, 'diesel', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'FURNACE', 2000, 'heating', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'HEATPUMP', 2000, 'heating', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_BEV', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_PHEV', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_PHEV', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_ICE', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_BEV', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_PHEV', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_PHEV', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_ICE', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_BEV', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_PHEV', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_PHEV', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_ICE', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_BEV', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'electricity', 'CAR_PHEV', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_PHEV', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA', 'diesel', 'CAR_ICE', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'ethos', 'DOMESTIC_NI', 2000, 'nickel', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'ethos', 'IMPORT_LI', 2000, 'lithium', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'ethos', 'IMPORT_NI', 2000, 'nickel', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'ethos', 'IMPORT_CO', 2000, 'cobalt', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'ethos', 'IMPORT_P', 2000, 'phosphorous', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'used_batt_nmc', 'RECYCLE_NMC', 2000, 'battery_nmc', 0.2, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'used_batt_lfp', 'RECYCLE_LFP', 2000, 'battery_lfp', 0.2, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'lithium', 'MANUFAC_NMC', 2000, 'battery_nmc', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'nickel', 'MANUFAC_NMC', 2000, 'battery_nmc', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'cobalt', 'MANUFAC_NMC', 2000, 'battery_nmc', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'lithium', 'MANUFAC_LFP', 2000, 'battery_lfp', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'phosphorous', 'MANUFAC_LFP', 2000, 'battery_lfp', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'RECYCLE_NMC', 2000, 'battery_nmc', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'RECYCLE_LFP', 2000, 'battery_lfp', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'MANUFAC_NMC', 2000, 'battery_nmc', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'MANUFAC_LFP', 2000, 'battery_lfp', 0.001, NULL, 'Effectively zero'); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'GEN_DSL', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'ethos', 'SOL_PV', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'BATT_GRID', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'ethos', 'IMPORT_DSL', 2000, 'diesel', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'FURNACE', 2000, 'heating', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'HEATPUMP', 2000, 'heating', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_BEV', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_PHEV', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_PHEV', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_ICE', 1990, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_BEV', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_PHEV', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_PHEV', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_ICE', 2000, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_BEV', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_PHEV', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_PHEV', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_ICE', 2010, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_BEV', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'electricity', 'CAR_PHEV', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_PHEV', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB', 'diesel', 'CAR_ICE', 2020, 'passenger_km', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionA-regionB', 'electricity', 'ELEC_INTERTIE', 2000, 'electricity', 0.9, NULL, NULL); +REPLACE INTO "efficiency" VALUES('regionB-regionA', 'electricity', 'ELEC_INTERTIE', 2000, 'electricity', 0.9, NULL, NULL); +REPLACE INTO "emission_activity" VALUES('regionA','co2e','ethos','IMPORT_DSL',2000,'diesel',1.0,NULL,'assumed combusted'); +REPLACE INTO "emission_activity" VALUES('regionB','co2e','ethos','IMPORT_DSL',2000,'diesel',1.0,NULL,'assumed combusted'); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_BEV',1990,'used_batt_nmc',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_PHEV',1990,'used_batt_lfp',0.1,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_BEV',2000,'used_batt_nmc',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_PHEV',2000,'used_batt_lfp',0.1,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_BEV',2010,'used_batt_nmc',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_PHEV',2010,'used_batt_lfp',0.1,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_BEV',1990,'used_batt_nmc',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_PHEV',1990,'used_batt_lfp',0.1,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_BEV',2000,'used_batt_nmc',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_PHEV',2000,'used_batt_lfp',0.1,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_BEV',2010,'used_batt_nmc',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_PHEV',2010,'used_batt_lfp',0.1,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_BEV',1990,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_ICE',1990,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_PHEV',1990,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_BEV',2000,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_ICE',2000,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_PHEV',2000,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_BEV',2010,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_ICE',2010,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionA','CAR_PHEV',2010,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_BEV',1990,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_ICE',1990,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_PHEV',1990,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_BEV',2000,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_ICE',2000,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_PHEV',2000,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_BEV',2010,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_ICE',2010,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "end_of_life_output" VALUES('regionB','CAR_PHEV',2010,'waste_steel',1.0,NULL,NULL); +REPLACE INTO "existing_capacity" VALUES('regionA','CAR_BEV',1990,1.0,NULL,NULL); +REPLACE INTO "existing_capacity" VALUES('regionA','CAR_PHEV',1990,1.0,NULL,NULL); +REPLACE INTO "existing_capacity" VALUES('regionA','CAR_ICE',1990,1.0,NULL,NULL); +REPLACE INTO "existing_capacity" VALUES('regionB','CAR_BEV',1990,1.0,NULL,NULL); +REPLACE INTO "existing_capacity" VALUES('regionB','CAR_PHEV',1990,1.0,NULL,NULL); +REPLACE INTO "existing_capacity" VALUES('regionB','CAR_ICE',1990,1.0,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('regionA', 'CAR_BEV', 10.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('regionA', 'CAR_PHEV', 10.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('regionA', 'CAR_ICE', 10.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('regionB', 'CAR_BEV', 10.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('regionB', 'CAR_PHEV', 10.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('regionB', 'CAR_ICE', 10.0, NULL, NULL); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'lithium','MANUFAC_NMC','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'nickel','MANUFAC_NMC','le',0.15,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'cobalt','MANUFAC_NMC','le',0.04,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'electricity','MANUFAC_NMC','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'lithium','MANUFAC_LFP','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'phosphorous','MANUFAC_LFP','le',0.19,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'electricity','MANUFAC_LFP','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'lithium','MANUFAC_NMC','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'nickel','MANUFAC_NMC','le',0.15,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'cobalt','MANUFAC_NMC','le',0.04,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'electricity','MANUFAC_NMC','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'lithium','MANUFAC_LFP','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'phosphorous','MANUFAC_LFP','le',0.19,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'electricity','MANUFAC_LFP','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'lithium','MANUFAC_NMC','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'nickel','MANUFAC_NMC','le',0.15,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'cobalt','MANUFAC_NMC','le',0.04,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'electricity','MANUFAC_NMC','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'lithium','MANUFAC_LFP','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'phosphorous','MANUFAC_LFP','le',0.19,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'electricity','MANUFAC_LFP','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'electricity','CAR_PHEV','le',0.2,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2000,'diesel','CAR_PHEV','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'electricity','CAR_PHEV','le',0.2,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2010,'diesel','CAR_PHEV','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'electricity','CAR_PHEV','le',0.2,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionA',2020,'diesel','CAR_PHEV','le',0.8,NULL); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'lithium','MANUFAC_NMC','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'nickel','MANUFAC_NMC','le',0.15,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'cobalt','MANUFAC_NMC','le',0.04,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'electricity','MANUFAC_NMC','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'lithium','MANUFAC_LFP','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'phosphorous','MANUFAC_LFP','le',0.19,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'electricity','MANUFAC_LFP','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'lithium','MANUFAC_NMC','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'nickel','MANUFAC_NMC','le',0.15,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'cobalt','MANUFAC_NMC','le',0.04,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'electricity','MANUFAC_NMC','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'lithium','MANUFAC_LFP','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'phosphorous','MANUFAC_LFP','le',0.19,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'electricity','MANUFAC_LFP','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'lithium','MANUFAC_NMC','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'nickel','MANUFAC_NMC','le',0.15,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'cobalt','MANUFAC_NMC','le',0.04,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'electricity','MANUFAC_NMC','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'lithium','MANUFAC_LFP','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'phosphorous','MANUFAC_LFP','le',0.19,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'electricity','MANUFAC_LFP','le',0.01,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'electricity','CAR_PHEV','le',0.2,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2000,'diesel','CAR_PHEV','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'electricity','CAR_PHEV','le',0.2,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2010,'diesel','CAR_PHEV','le',0.8,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'electricity','CAR_PHEV','le',0.2,''); +REPLACE INTO "limit_tech_input_split_annual" VALUES('regionB',2020,'diesel','CAR_PHEV','le',0.8,NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('regionA',NULL); +REPLACE INTO "region" VALUES('regionB',NULL); +REPLACE INTO "sector_label" VALUES('materials',NULL); +REPLACE INTO "sector_label" VALUES('transportation',NULL); +REPLACE INTO "sector_label" VALUES('electricity',NULL); +REPLACE INTO "sector_label" VALUES('residential',NULL); +REPLACE INTO "sector_label" VALUES('fuels',NULL); +REPLACE INTO "storage_duration" VALUES('regionA','BATT_GRID',2.0,'2 hours energy storage'); +REPLACE INTO "storage_duration" VALUES('regionB','BATT_GRID',2.0,'2 hours energy storage'); +REPLACE INTO "technology" VALUES('IMPORT_LI','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'lithium importer'); +REPLACE INTO "technology" VALUES('IMPORT_CO','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'cobalt importer'); +REPLACE INTO "technology" VALUES('IMPORT_P','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'phosphorous importer'); +REPLACE INTO "technology" VALUES('CAR_BEV','p','transportation',NULL,NULL,0,0,0,0,0,0,0,0,'car - battery electric'); +REPLACE INTO "technology" VALUES('CAR_PHEV','p','transportation',NULL,NULL,0,0,0,0,0,0,0,0,'car - plug in hybrid'); +REPLACE INTO "technology" VALUES('CAR_ICE','p','transportation',NULL,NULL,0,0,0,0,0,0,0,0,'car - internal combustion'); +REPLACE INTO "technology" VALUES('RECYCLE_NMC','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'nmc battery recycler'); +REPLACE INTO "technology" VALUES('RECYCLE_LFP','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'lfp battery recycler'); +REPLACE INTO "technology" VALUES('MANUFAC_NMC','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'nmc battery manufacturing'); +REPLACE INTO "technology" VALUES('MANUFAC_LFP','p','materials',NULL,NULL,0,1,0,0,0,0,0,0,'lfp battery manufacturing'); +REPLACE INTO "technology" VALUES('IMPORT_NI','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'nickel importer'); +REPLACE INTO "technology" VALUES('DOMESTIC_NI','p','materials',NULL,NULL,1,1,0,0,0,0,0,0,'domestic nickel production'); +REPLACE INTO "technology" VALUES('GEN_DSL','p','electricity',NULL,NULL,0,0,0,0,0,0,0,0,'diesel generators'); +REPLACE INTO "technology" VALUES('SOL_PV','p','electricity',NULL,NULL,0,0,0,1,0,0,0,0,'solar panels'); +REPLACE INTO "technology" VALUES('BATT_GRID','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,0,'grid battery storage'); +REPLACE INTO "technology" VALUES('FURNACE','p','residential',NULL,NULL,1,0,0,0,0,0,0,0,'diesel furnace heater'); +REPLACE INTO "technology" VALUES('HEATPUMP','p','residential',NULL,NULL,1,0,0,0,0,0,0,0,'heat pump'); +REPLACE INTO "technology" VALUES('IMPORT_DSL','p','fuels',NULL,NULL,1,1,0,0,0,0,0,0,'diesel importer'); +REPLACE INTO "technology" VALUES('ELEC_INTERTIE','p','electricity',NULL,NULL,0,0,0,0,0,0,1,0,'dummy tech to make landfill feasible'); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(1,'morning',6,NULL); +REPLACE INTO "time_of_day" VALUES(2,'afternoon',6,NULL); +REPLACE INTO "time_of_day" VALUES(3,'evening',6,NULL); +REPLACE INTO "time_of_day" VALUES(4,'overnight',6,NULL); +REPLACE INTO "time_period" VALUES(1,1990,'e'); +REPLACE INTO "time_period" VALUES(2,2000,'f'); +REPLACE INTO "time_period" VALUES(3,2010,'f'); +REPLACE INTO "time_period" VALUES(4,2020,'f'); +REPLACE INTO "time_period" VALUES(5,2030,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(1,'summer',0.25,NULL); +REPLACE INTO "time_season" VALUES(2,'autumn',0.25,NULL); +REPLACE INTO "time_season" VALUES(3,'winter',0.25,NULL); +REPLACE INTO "time_season" VALUES(4,'spring',0.25,NULL); diff --git a/tests/testing_data/mc_settings_utopia.csv b/tests/testing_data/mc_settings_utopia.csv new file mode 100644 index 000000000..f4b6b6ebb --- /dev/null +++ b/tests/testing_data/mc_settings_utopia.csv @@ -0,0 +1,3 @@ +run,param,index,mod,value,notes +1,cost_invest,utopia|TXD|2010,a,-44.0,reduce invest cost to 1000 +2,demand,utopia|2010|RH,r,0.1,increase demand by 10% diff --git a/tests/testing_data/mediumville.sql b/tests/testing_data/mediumville.sql index 6d3315fb2..9df6b3f7a 100644 --- a/tests/testing_data/mediumville.sql +++ b/tests/testing_data/mediumville.sql @@ -1,1061 +1,190 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -INSERT INTO MetaData VALUES('myopic_base_year',2000,''); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.4199999999999999845,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -INSERT INTO CapacityCredit VALUES('A',2025,'EF',2025,0.5999999999999999778,NULL); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorProcess VALUES('A','s2','d1','EFL',2025,0.8000000000000000444,NULL); -INSERT INTO CapacityFactorProcess VALUES('A','s1','d2','EFL',2025,0.9000000000000000222,NULL); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorTech VALUES('A','s1','d1','EF',0.8000000000000000444,NULL); -INSERT INTO CapacityFactorTech VALUES('B','s2','d2','bulbs',0.75,NULL); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO CapacityToActivity VALUES('A','bulbs',1.0,''); -INSERT INTO CapacityToActivity VALUES('B','bulbs',1.0,NULL); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ELC','p','electricity'); -INSERT INTO Commodity VALUES('HYD','p','water'); -INSERT INTO Commodity VALUES('co2','e','CO2 emissions'); -INSERT INTO Commodity VALUES('RL','d','residential lighting'); -INSERT INTO Commodity VALUES('earth','p','the source of stuff'); -INSERT INTO Commodity VALUES('RH','d','residential heat'); -INSERT INTO Commodity VALUES('FusionGas','e','mystery emission'); -INSERT INTO Commodity VALUES('FusionGasFuel','p','converted mystery gas to fuel'); -INSERT INTO Commodity VALUES('GeoHyd','p','Hot water from geo'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -INSERT INTO CommodityType VALUES('s','source commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -INSERT INTO CostEmission VALUES ('A', 2025, 'co2', 1.99, 'dollars', 'none' ); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostFixed VALUES('A',2025,'EH',2025,3.299999999999999823,'',''); -INSERT INTO CostFixed VALUES('A',2025,'EF',2025,2.0,NULL,NULL); -INSERT INTO CostFixed VALUES('A',2025,'EFL',2025,3.0,NULL,NULL); -INSERT INTO CostFixed VALUES('B',2025,'batt',2025,1.0,NULL,NULL); -INSERT INTO CostFixed VALUES('B',2025,'EF',2025,2.0,NULL,NULL); -INSERT INTO CostFixed VALUES('A',2025,'bulbs',2025,1.0,NULL,NULL); -INSERT INTO CostFixed VALUES('B',2025,'bulbs',2025,1.0,NULL,NULL); -INSERT INTO CostFixed VALUES('A',2025,'heater',2025,2.0,NULL,NULL); -INSERT INTO CostFixed VALUES('B',2025,'heater',2025,2.0,NULL,NULL); -INSERT INTO CostFixed VALUES('B',2025,'GeoThermal',2025,6.0,NULL,NULL); -INSERT INTO CostFixed VALUES('B',2025,'GeoHeater',2025,1.0,NULL,NULL); -INSERT INTO CostFixed VALUES('B',2025,'EH',2025,3.299999999999999823,NULL,NULL); -INSERT INTO CostFixed VALUES('A',2025,'GeoThermal',2025,4.0,NULL,NULL); -INSERT INTO CostFixed VALUES('A',2025,'GeoHeater',2025,4.5,NULL,NULL); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('A','EF',2025,1.0,NULL,NULL); -INSERT INTO CostInvest VALUES('A','EH',2025,3.0,NULL,NULL); -INSERT INTO CostInvest VALUES('A','bulbs',2025,4.0,NULL,NULL); -INSERT INTO CostInvest VALUES('A','heater',2025,5.0,NULL,NULL); -INSERT INTO CostInvest VALUES('B','EF',2025,6.0,NULL,NULL); -INSERT INTO CostInvest VALUES('B','batt',2025,7.0,NULL,NULL); -INSERT INTO CostInvest VALUES('B','bulbs',2025,8.0,NULL,NULL); -INSERT INTO CostInvest VALUES('B','heater',2025,9.0,NULL,NULL); -INSERT INTO CostInvest VALUES('A','EFL',2025,2.0,NULL,NULL); -INSERT INTO CostInvest VALUES('B','GeoThermal',2025,3.0,NULL,NULL); -INSERT INTO CostInvest VALUES('B','GeoHeater',2025,4.0,NULL,NULL); -INSERT INTO CostInvest VALUES('B','EH',2025,3.299999999999999823,NULL,NULL); -INSERT INTO CostInvest VALUES('A','GeoThermal',2025,5.599999999999999645,NULL,NULL); -INSERT INTO CostInvest VALUES('A','GeoHeater',2025,4.200000000000000177,NULL,NULL); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('A',2025,'EF',2025,9.0,NULL,NULL); -INSERT INTO CostVariable VALUES('A',2025,'EFL',2025,8.0,NULL,NULL); -INSERT INTO CostVariable VALUES('A',2025,'EH',2025,7.0,NULL,NULL); -INSERT INTO CostVariable VALUES('A',2025,'bulbs',2025,6.0,NULL,NULL); -INSERT INTO CostVariable VALUES('A',2025,'heater',2025,5.0,NULL,NULL); -INSERT INTO CostVariable VALUES('B',2025,'EF',2025,4.0,NULL,NULL); -INSERT INTO CostVariable VALUES('B',2025,'batt',2025,3.0,NULL,NULL); -INSERT INTO CostVariable VALUES('B',2025,'bulbs',2025,2.0,NULL,NULL); -INSERT INTO CostVariable VALUES('B',2025,'heater',2025,1.0,NULL,NULL); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('A',2025,'RL',100.0,'',''); -INSERT INTO Demand VALUES('B',2025,'RL',100.0,NULL,NULL); -INSERT INTO Demand VALUES('A',2025,'RH',50.0,NULL,NULL); -INSERT INTO Demand VALUES('B',2025,'RH',50.0,NULL,NULL); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('A','s1','d1','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('A','s1','d2','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('A','s2','d1','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('A','s2','d2','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s1','d1','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s1','d2','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s2','d1','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s2','d2','RL',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('A','s1','d1','RH',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('A','s2','d1','RH',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s1','d1','RH',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s2','d1','RH',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('A','s1','d2','RH',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('A','s2','d2','RH',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s1','d2','RH',0.25,NULL); -INSERT INTO DemandSpecificDistribution VALUES('B','s2','d2','RH',0.25,NULL); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('A','ELC','bulbs',2025,'RL',1.0,NULL); -INSERT INTO Efficiency VALUES('A','HYD','EH',2025,'ELC',1.0,NULL); -INSERT INTO Efficiency VALUES('A','HYD','EF',2025,'ELC',1.0,NULL); -INSERT INTO Efficiency VALUES('B','ELC','bulbs',2025,'RL',1.0,NULL); -INSERT INTO Efficiency VALUES('B','HYD','EH',2025,'ELC',1.0,NULL); -INSERT INTO Efficiency VALUES('B','ELC','batt',2025,'ELC',1.0,NULL); -INSERT INTO Efficiency VALUES('B','HYD','EF',2025,'ELC',1.0,NULL); -INSERT INTO Efficiency VALUES('A','earth','well',2025,'HYD',1.0,NULL); -INSERT INTO Efficiency VALUES('B','earth','well',2025,'HYD',1.0,NULL); -INSERT INTO Efficiency VALUES('A','earth','EFL',2025,'FusionGasFuel',1.0,NULL); -INSERT INTO Efficiency VALUES('A','FusionGasFuel','heater',2025,'RH',0.9000000000000000222,NULL); -INSERT INTO Efficiency VALUES('A-B','FusionGasFuel','FGF_pipe',2025,'FusionGasFuel',0.949999999999999956,NULL); -INSERT INTO Efficiency VALUES('B','FusionGasFuel','heater',2025,'RH',0.9000000000000000222,NULL); -INSERT INTO Efficiency VALUES('B','GeoHyd','GeoHeater',2025,'RH',0.979999999999999983,NULL); -INSERT INTO Efficiency VALUES('B','earth','GeoThermal',2025,'GeoHyd',1.0,NULL); -INSERT INTO Efficiency VALUES('B-A','FusionGasFuel','FGF_pipe',2025,'FusionGasFuel',0.949999999999999956,NULL); -INSERT INTO Efficiency VALUES('A','GeoHyd','GeoHeater',2025,'RH',0.9000000000000000222,NULL); -INSERT INTO Efficiency VALUES('A','earth','GeoThermal',2025,'GeoHyd',1.0,NULL); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('A','co2','HYD','EH',2025,'ELC',0.02000000000000000041,NULL,NULL); -INSERT INTO EmissionActivity VALUES('A','FusionGas','HYD','EF',2025,'ELC',-0.2000000000000000111,NULL,'needs to be negative as a driver of linked tech...don''t ask'); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO ExistingCapacity VALUES('A','EH',2020,200.0,'things',NULL); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO TechGroup VALUES('RPS_global',''); -INSERT INTO TechGroup VALUES('RPS_common',''); -INSERT INTO TechGroup VALUES('(A)_tech_grp_1','converted from old db'); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO GrowthRateMax VALUES('A','GeoHeater',0.2000000000000000111,NULL); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO GrowthRateSeed VALUES('A','GeoHeater',1000.0,'jobs','unk'); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LoanLifetimeTech VALUES('A','EF',57.0,NULL); -INSERT INTO LoanLifetimeTech VALUES('A','EFL',68.0,NULL); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO LifetimeProcess VALUES('B','EF',2025,200.0,NULL); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('A','EH',60.0,''); -INSERT INTO LifetimeTech VALUES('B','bulbs',100.0,'super LED!'); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -INSERT INTO LinkedTech VALUES('A','EF','FusionGas','EFL',NULL); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxActivity VALUES('B',2025,'EH',10000.0,'stuff',NULL); -INSERT INTO MaxActivity VALUES('A',2025,'EF',10000.0,'stuff',NULL); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxCapacity VALUES('A',2025,'EH',20000.0,'',''); -INSERT INTO MaxCapacity VALUES('B',2025,'EH',20000.0,'',''); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO MaxResource VALUES('B','EF',9000.0,'clumps',NULL); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinActivity VALUES('A',2025,'EF',0.00100000000000000002,'PJ/CY','goofy units'); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -INSERT INTO MaxCapacityGroup VALUES('A',2025,'(A)_tech_grp_1',6000.0,'',NULL); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinCapacity VALUES('A',2025,'EH',0.1000000000000000055,'',''); -INSERT INTO MinCapacity VALUES('B',2025,'batt',0.1000000000000000055,'',''); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -INSERT INTO MinCapacityGroup VALUES('A',2025,'(A)_tech_grp_1',0.2000000000000000111,'',NULL); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -INSERT INTO PlanningReserveMargin VALUES('A',0.05000000000000000277); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -INSERT INTO RampDown VALUES('A','EH',0.2000000000000000111); -INSERT INTO RampDown VALUES('B','EH',0.2000000000000000111); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -INSERT INTO RampUp VALUES('B','EH',100.0); -INSERT INTO RampUp VALUES('A','EH',100.0); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('A','main region'); -INSERT INTO Region VALUES('B','just a 2nd region'); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('s2','d1',0.25,NULL); -INSERT INTO TimeSegmentFraction VALUES('s2','d2',0.25,NULL); -INSERT INTO TimeSegmentFraction VALUES('s1','d1',0.25,NULL); -INSERT INTO TimeSegmentFraction VALUES('s1','d2',0.25,NULL); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO StorageDuration VALUES('B','batt',15.0,NULL); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -INSERT INTO TechInputSplit VALUES('A',2025,'HYD','EH',0.949999999999999956,'95% HYD reqt. (other not specified...)'); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -INSERT INTO TechInputSplitAverage VALUES('A',2025,'GeoHyd','GeoHeater',0.8000000000000000444,'80% geothermal'); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -INSERT INTO TechOutputSplit VALUES('B',2025,'EH','ELC',0.949999999999999956,'95% ELC output (there are not others, this is a min)'); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'d1'); -INSERT INTO TimeOfDay VALUES(2,'d2'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,2020,'e'); -INSERT INTO TimePeriod VALUES(2,2025,'f'); -INSERT INTO TimePeriod VALUES(3,2030,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'s1'); -INSERT INTO TimeSeason VALUES(2,'s2'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -INSERT INTO MinActivityGroup VALUES('A',2025,'(A)_tech_grp_1',0.05000000000000000277,'',NULL); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -INSERT INTO EmissionLimit VALUES('A',2025,'co2',10000.0,'gulps',NULL); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -INSERT INTO MaxActivityGroup VALUES('A',2025,'(A)_tech_grp_1',10000.0,'',NULL); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -INSERT INTO RPSRequirement VALUES('B',2025,'RPS_common',0.2999999999999999889,NULL); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -INSERT INTO TechGroupMember VALUES('RPS_common','EF'); -INSERT INTO TechGroupMember VALUES('(A)_tech_grp_1','EH'); -INSERT INTO TechGroupMember VALUES('(A)_tech_grp_1','EF'); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('well','r','supply','water','',0,0,0,0,0,0,0,0,'plain old water'); -INSERT INTO Technology VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,0,'residential lighting'); -INSERT INTO Technology VALUES('EH','pb','electric','hydro','',0,0,1,1,1,0,0,0,'hydro power electric plant'); -INSERT INTO Technology VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,0,'big battery'); -INSERT INTO Technology VALUES('EF','p','electric','electric','',0,0,0,0,0,0,0,0,'fusion plant'); -INSERT INTO Technology VALUES('EFL','p','electric','electric','',0,0,0,0,0,1,0,0,'linked (to Fusion) producer'); -INSERT INTO Technology VALUES('heater','p','residential','electric','',0,0,0,0,0,0,0,0,'heater'); -INSERT INTO Technology VALUES('FGF_pipe','p','transport',NULL,'',0,0,0,0,0,0,0,1,'transportation line A->B'); -INSERT INTO Technology VALUES('GeoThermal','p','residential','hydro','',0,1,0,0,0,0,0,0,'geothermal hot water source'); -INSERT INTO Technology VALUES('GeoHeater','p','residential','hydro','',0,0,0,0,0,0,1,0,'geothermal heater from geo hyd'); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -COMMIT; +REPLACE INTO "capacity_credit" VALUES('A',2025,'EF',2025,0.6,NULL); +REPLACE INTO "capacity_factor_process" VALUES('A','s2','d1','EFL',2025,0.8,NULL); +REPLACE INTO "capacity_factor_process" VALUES('A','s1','d2','EFL',2025,0.9,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('A','s1','d1','EF',0.8,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('B','s2','d2','bulbs',0.75,NULL); +REPLACE INTO "capacity_to_activity" VALUES('A', 'bulbs', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('B', 'bulbs', 1.0, NULL, NULL); +REPLACE INTO "commodity" VALUES('ELC', 'p', 'electricity', NULL); +REPLACE INTO "commodity" VALUES('HYD', 'p', 'water', NULL); +REPLACE INTO "commodity" VALUES('co2', 'e', 'CO2 emissions', NULL); +REPLACE INTO "commodity" VALUES('RL', 'd', 'residential lighting', NULL); +REPLACE INTO "commodity" VALUES('earth', 's', 'the source of stuff', NULL); +REPLACE INTO "commodity" VALUES('RH', 'd', 'residential heat', NULL); +REPLACE INTO "commodity" VALUES('FusionGas', 'e', 'mystery emission', NULL); +REPLACE INTO "commodity" VALUES('FusionGasFuel', 'p', 'converted mystery gas to fuel', NULL); +REPLACE INTO "commodity" VALUES('GeoHyd', 'p', 'Hot water from geo', NULL); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "cost_emission" VALUES('A',2025,'co2',1.99,'dollars','none'); +REPLACE INTO "cost_fixed" VALUES('A',2025,'EH',2025,3.3,'',''); +REPLACE INTO "cost_fixed" VALUES('A',2025,'EF',2025,2.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('A',2025,'EFL',2025,3.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('B',2025,'batt',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('B',2025,'EF',2025,2.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('A',2025,'bulbs',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('B',2025,'bulbs',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('A',2025,'heater',2025,2.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('B',2025,'heater',2025,2.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('B',2025,'GeoThermal',2025,6.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('B',2025,'GeoHeater',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('B',2025,'EH',2025,3.3,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('A',2025,'GeoThermal',2025,4.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('A',2025,'GeoHeater',2025,4.5,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('A','EF',2025,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('A','EH',2025,3.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('A','bulbs',2025,4.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('A','heater',2025,5.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('B','EF',2025,6.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('B','batt',2025,7.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('B','bulbs',2025,8.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('B','heater',2025,9.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('A','EFL',2025,2.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('B','GeoThermal',2025,3.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('B','GeoHeater',2025,4.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('B','EH',2025,3.3,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('A','GeoThermal',2025,5.6,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('A','GeoHeater',2025,4.2,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('A',2025,'EF',2025,9.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('A',2025,'EFL',2025,8.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('A',2025,'EH',2025,7.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('A',2025,'bulbs',2025,6.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('A',2025,'heater',2025,5.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('B',2025,'EF',2025,4.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('B',2025,'batt',2025,3.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('B',2025,'bulbs',2025,2.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('B',2025,'heater',2025,1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('A',2025,'RL',100.0,'',''); +REPLACE INTO "demand" VALUES('B',2025,'RL',100.0,NULL,NULL); +REPLACE INTO "demand" VALUES('A',2025,'RH',50.0,NULL,NULL); +REPLACE INTO "demand" VALUES('B',2025,'RH',50.0,NULL,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s1','d1','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s1','d2','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s2','d1','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s2','d2','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s1','d1','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s1','d2','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s2','d1','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s2','d2','RL',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s1','d1','RH',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s2','d1','RH',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s1','d1','RH',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s2','d1','RH',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s1','d2','RH',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('A',2025,'s2','d2','RH',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s1','d2','RH',0.25,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('B',2025,'s2','d2','RH',0.25,NULL); +REPLACE INTO "efficiency" VALUES('A', 'ELC', 'bulbs', 2025, 'RL', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A', 'HYD', 'EH', 2025, 'ELC', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A', 'HYD', 'EF', 2025, 'ELC', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'ELC', 'bulbs', 2025, 'RL', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'HYD', 'EH', 2025, 'ELC', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'ELC', 'batt', 2025, 'ELC', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'HYD', 'EF', 2025, 'ELC', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A', 'earth', 'well', 2025, 'HYD', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'earth', 'well', 2025, 'HYD', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A', 'earth', 'EFL', 2025, 'FusionGasFuel', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A', 'FusionGasFuel', 'heater', 2025, 'RH', 0.9, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A-B', 'FusionGasFuel', 'FGF_pipe', 2025, 'FusionGasFuel', 0.95, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'FusionGasFuel', 'heater', 2025, 'RH', 0.9, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'GeoHyd', 'GeoHeater', 2025, 'RH', 9.80000000000000093e-01, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B', 'earth', 'GeoThermal', 2025, 'GeoHyd', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('B-A', 'FusionGasFuel', 'FGF_pipe', 2025, 'FusionGasFuel', 0.95, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A', 'GeoHyd', 'GeoHeater', 2025, 'RH', 0.9, NULL, NULL); +REPLACE INTO "efficiency" VALUES('A', 'earth', 'GeoThermal', 2025, 'GeoHyd', 1.0, NULL, NULL); +REPLACE INTO "emission_activity" VALUES('A','co2','HYD','EH',2025,'ELC',0.02,NULL,NULL); +REPLACE INTO "emission_activity" VALUES('A','FusionGas','HYD','EF',2025,'ELC',-0.2,NULL,'emission_activity specifies emission activity coefficients (not efficiency values), negative coefficients represent emissions removal/capture, this coupling is essential for the linked tech constraint that converts CO2 from emissions commodity to physical commodity input.'); +REPLACE INTO "lifetime_process" VALUES('B', 'EF', 2025, 200.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('A', 'EH', 60.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('B', 'bulbs', 100.0, NULL, 'super LED!'); +REPLACE INTO "limit_activity" VALUES('A',2025,'EF','ge',0.001,'PJ/CY','goofy units'); +REPLACE INTO "limit_activity" VALUES('B',2025,'EH','le',10000.0,'stuff',NULL); +REPLACE INTO "limit_activity" VALUES('A',2025,'EF','le',10000.0,'stuff',NULL); +REPLACE INTO "limit_activity" VALUES('A',2025,'A_tech_grp_1','ge',0.05,'',NULL); +REPLACE INTO "limit_activity" VALUES('A',2025,'A_tech_grp_1','le',10000.0,'',NULL); +REPLACE INTO "limit_capacity" VALUES('A',2025,'EH','ge',0.1,'',''); +REPLACE INTO "limit_capacity" VALUES('B',2025,'batt','ge',0.1,'',''); +REPLACE INTO "limit_capacity" VALUES('A',2025,'EH','le',20000.0,'',''); +REPLACE INTO "limit_capacity" VALUES('B',2025,'EH','le',20000.0,'',''); +REPLACE INTO "limit_capacity" VALUES('A',2025,'A_tech_grp_1','ge',0.2,'',NULL); +REPLACE INTO "limit_capacity" VALUES('A',2025,'A_tech_grp_1','le',6000.0,'',NULL); +REPLACE INTO "limit_emission" VALUES('A',2025,'co2','le',10000.0,'gulps',NULL); +REPLACE INTO "limit_new_capacity_share" VALUES('A','RPS_common','A_tech_grp_1',2025,'ge',0.0,''); +REPLACE INTO "limit_new_capacity_share" VALUES('global','RPS_common','A_tech_grp_1',2025,'le',1.0,''); +REPLACE INTO "limit_resource" VALUES('B','EF','le',9000.0,'clumps',NULL); +REPLACE INTO "limit_tech_input_split" VALUES('A',2025,'HYD','EH','ge',0.95,'95% HYD reqt. (other not specified...)'); +REPLACE INTO "limit_tech_output_split" VALUES('B',2025,'EH','ELC','ge',0.95,'95% ELC output (there are not others, this is a min)'); +REPLACE INTO "linked_tech" VALUES('A','EF','FusionGas','EFL',NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'EF', 2025, 57.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'EFL', 2025, 68.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'bulbs', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'EH', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'well', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'heater', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'GeoHeater', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A', 'GeoThermal', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'EF', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'bulbs', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'EH', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'batt', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'well', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'heater', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'GeoHeater', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B', 'GeoThermal', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('A-B', 'FGF_pipe', 2025, 10.0, NULL, NULL); +REPLACE INTO "loan_lifetime_process" VALUES('B-A', 'FGF_pipe', 2025, 10.0, NULL, NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',4.2000000000000004e-01,''); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "planning_reserve_margin" VALUES('A',0.05,NULL); +REPLACE INTO "ramp_down_hourly" VALUES('A','EH',0.05,NULL); +REPLACE INTO "ramp_down_hourly" VALUES('B','EH',0.05,NULL); +REPLACE INTO "ramp_up_hourly" VALUES('B','EH',0.05,NULL); +REPLACE INTO "ramp_up_hourly" VALUES('A','EH',0.05,NULL); +REPLACE INTO "region" VALUES('A','main region'); +REPLACE INTO "region" VALUES('B','just a 2nd region'); +REPLACE INTO "rps_requirement" VALUES('B',2025,'RPS_common',0.3,NULL); +REPLACE INTO "sector_label" VALUES('supply',NULL); +REPLACE INTO "sector_label" VALUES('electric',NULL); +REPLACE INTO "sector_label" VALUES('transport',NULL); +REPLACE INTO "sector_label" VALUES('commercial',NULL); +REPLACE INTO "sector_label" VALUES('residential',NULL); +REPLACE INTO "sector_label" VALUES('industrial',NULL); +REPLACE INTO "storage_duration" VALUES('B','batt',15.0,NULL); +REPLACE INTO "tech_group" VALUES('RPS_common',''); +REPLACE INTO "tech_group" VALUES('A_tech_grp_1','converted from old db'); +REPLACE INTO "tech_group_member" VALUES('RPS_common','EF'); +REPLACE INTO "tech_group_member" VALUES('A_tech_grp_1','EH'); +REPLACE INTO "tech_group_member" VALUES('A_tech_grp_1','EF'); +REPLACE INTO "technology" VALUES('well','p','supply','water','',0,0,0,0,0,0,0,0,'plain old water'); +REPLACE INTO "technology" VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,0,'residential lighting'); +REPLACE INTO "technology" VALUES('EH','pb','electric','hydro','',0,0,0,1,1,0,0,0,'hydro power electric plant'); +REPLACE INTO "technology" VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,0,'big battery'); +REPLACE INTO "technology" VALUES('EF','p','electric','electric','',0,0,1,0,0,0,0,0,'fusion plant'); +REPLACE INTO "technology" VALUES('EFL','p','electric','electric','',0,0,0,0,0,1,0,0,'linked (to Fusion) producer'); +REPLACE INTO "technology" VALUES('heater','p','residential','electric','',0,0,0,0,0,0,0,0,'heater'); +REPLACE INTO "technology" VALUES('FGF_pipe','p','transport',NULL,'',0,0,0,0,0,0,1,0,'transportation line A->B'); +REPLACE INTO "technology" VALUES('GeoThermal','p','residential','hydro','',0,1,0,0,0,0,0,0,'geothermal hot water source'); +REPLACE INTO "technology" VALUES('GeoHeater','p','residential','hydro','',0,0,0,0,0,0,0,0,'geothermal heater from geo hyd'); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(1,'d1',12,NULL); +REPLACE INTO "time_of_day" VALUES(2,'d2',12,NULL); +REPLACE INTO "time_period" VALUES(1,2020,'e'); +REPLACE INTO "time_period" VALUES(2,2025,'f'); +REPLACE INTO "time_period" VALUES(3,2030,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(1,'s1',0.5,NULL); +REPLACE INTO "time_season" VALUES(2,'s2',0.5,NULL); diff --git a/tests/testing_data/mediumville_sets.json b/tests/testing_data/mediumville_sets.json index 74d7ac75f..82bf63afd 100644 --- a/tests/testing_data/mediumville_sets.json +++ b/tests/testing_data/mediumville_sets.json @@ -1,4175 +1,115 @@ { - "time_exist": [ - 2020 - ], - "time_future": [ - 2025, - 2030 - ], - "time_optimize": [ - 2025 - ], - "vintage_exist": [ - 2020 - ], - "vintage_optimize": [ - 2025 - ], - "vintage_all": [ - 2020, - 2025 - ], - "time_season": [ - "s1", - "s2" - ], - "time_of_day": [ - "d1", - "d2" - ], - "regions": [ - "A", - "B" - ], - "RegionalIndices": [ - "A", - "A-B", - "B", - "B-A" - ], - "RegionalGlobalIndices": [ - "A", - "B" - ], - "tech_resource": [ - "well" - ], - "tech_production": [ - "bulbs", - "EH", - "batt", - "EF", - "EFL", - "heater", - "FGF_pipe", - "GeoThermal", - "GeoHeater" - ], - "tech_all": [ - "well", - "bulbs", - "EH", - "batt", - "EF", - "EFL", - "heater", - "FGF_pipe", - "GeoThermal", - "GeoHeater" - ], - "tech_baseload": [ - "EH" - ], - "tech_annual": [ - "GeoThermal" - ], - "tech_storage": [ - "batt" - ], - "tech_reserve": [ - "EH" - ], - "tech_ramping": [ - "EH" - ], - "tech_curtailment": [ - "EH" - ], - "tech_flex": [ - "EFL" - ], - "tech_exchange": [ - "FGF_pipe" - ], - "tech_group_names": [ - "(A)_tech_grp_1", - "RPS_common", - "RPS_global" - ], - "tech_group_members": [ - "(A)_tech_grp_1", - "RPS_common", - "RPS_global" - ], - "tech_uncap": [], - "tech_with_capacity": [ - "well", - "bulbs", - "EH", - "batt", - "EF", - "EFL", - "heater", - "FGF_pipe", - "GeoThermal", - "GeoHeater" - ], - "tech_variable": [ - "GeoHeater" - ], - "tech_retirement": [ - "EH" - ], - "commodity_demand": [ - "RL", - "RH" - ], - "commodity_emissions": [ - "co2", - "FusionGas" - ], - "commodity_physical": [ - "ELC", - "HYD", - "earth", - "FusionGasFuel", - "GeoHyd" - ], - "commodity_source": [], - "commodity_carrier": [ - "ELC", - "HYD", - "earth", - "FusionGasFuel", - "GeoHyd", - "RL", - "RH" - ], - "commodity_all": [ - "ELC", - "HYD", - "earth", - "FusionGasFuel", - "GeoHyd", - "RL", - "RH", - "co2", - "FusionGas" - ], - "tech_mga": [], - "tech_electric": [], - "tech_transport": [], - "tech_industrial": [], - "tech_commercial": [], - "tech_residential": [], - "tech_PowerPlants": [], - "ResourceConstraint_rpr": [], - "CapacityFactor_rsdt": [ - [ - "A", - "s2", - "d1", - "bulbs" - ], - [ - "A", - "s1", - "d1", - "EF" - ], - [ - "B-A", - "s2", - "d1", - "FGF_pipe" - ], - [ - "B", - "s1", - "d2", - "EH" - ], - [ - "B", - "s2", - "d2", - "batt" - ], - [ - "B-A", - "s1", - "d1", - "FGF_pipe" - ], - [ - "B", - "s2", - "d2", - "well" - ], - [ - "A", - "s2", - "d2", - "EH" - ], - [ - "B", - "s2", - "d2", - "GeoThermal" - ], - [ - "B", - "s2", - "d2", - "heater" - ], - [ - "A", - "s1", - "d2", - "well" - ], - [ - "A-B", - "s1", - "d2", - "FGF_pipe" - ], - [ - "B", - "s1", - "d1", - "batt" - ], - [ - "B", - "s1", - "d1", - "well" - ], - [ - "A", - "s1", - "d2", - "GeoThermal" - ], - [ - "A", - "s1", - "d2", - "heater" - ], - [ - "A", - "s1", - "d2", - "EFL" - ], - [ - "B", - "s2", - "d1", - "bulbs" - ], - [ - "A", - "s2", - "d1", - "well" - ], - [ - "B", - "s1", - "d1", - "GeoThermal" - ], - [ - "B", - "s1", - "d1", - "heater" - ], - [ - "A", - "s1", - "d1", - "bulbs" - ], - [ - "A", - "s2", - "d1", - "GeoThermal" - ], - [ - "B", - "s1", - "d2", - "GeoHeater" - ], - [ - "A", - "s2", - "d1", - "heater" - ], - [ - "A", - "s1", - "d2", - "EH" - ], - [ - "B", - "s2", - "d2", - "EH" - ], - [ - "A", - "s2", - "d1", - "EFL" - ], - [ - "B", - "s1", - "d2", - "EF" - ], - [ - "B-A", - "s2", - "d2", - "FGF_pipe" - ], - [ - "B", - "s1", - "d1", - "EH" - ], - [ - "A-B", - "s2", - "d1", - "FGF_pipe" - ], - [ - "B", - "s2", - "d1", - "batt" - ], - [ - "A", - "s2", - "d2", - "GeoHeater" - ], - [ - "B", - "s2", - "d1", - "well" - ], - [ - "A", - "s2", - "d1", - "EH" - ], - [ - "B", - "s2", - "d1", - "GeoThermal" - ], - [ - "B", - "s2", - "d1", - "heater" - ], - [ - "A", - "s2", - "d2", - "EF" - ], - [ - "A-B", - "s1", - "d1", - "FGF_pipe" - ], - [ - "A", - "s1", - "d1", - "well" - ], - [ - "A", - "s1", - "d1", - "GeoThermal" - ], - [ - "A", - "s1", - "d1", - "heater" - ], - [ - "A", - "s1", - "d1", - "EFL" - ], - [ - "B", - "s2", - "d2", - "GeoHeater" - ], - [ - "B", - "s2", - "d2", - "EF" - ], - [ - "B", - "s1", - "d2", - "bulbs" - ], - [ - "B", - "s1", - "d1", - "GeoHeater" - ], - [ - "A", - "s1", - "d2", - "GeoHeater" - ], - [ - "B", - "s2", - "d1", - "EH" - ], - [ - "A", - "s2", - "d2", - "bulbs" - ], - [ - "A", - "s1", - "d2", - "EF" - ], - [ - "A", - "s1", - "d1", - "EH" - ], - [ - "B-A", - "s1", - "d2", - "FGF_pipe" - ], - [ - "B", - "s1", - "d1", - "EF" - ], - [ - "A", - "s2", - "d1", - "GeoHeater" - ], - [ - "A-B", - "s2", - "d2", - "FGF_pipe" - ], - [ - "A", - "s2", - "d1", - "EF" - ], - [ - "B", - "s1", - "d2", - "batt" - ], - [ - "B", - "s2", - "d2", - "bulbs" - ], - [ - "B", - "s1", - "d2", - "well" - ], - [ - "B", - "s1", - "d2", - "GeoThermal" - ], - [ - "B", - "s1", - "d2", - "heater" - ], - [ - "B", - "s2", - "d1", - "GeoHeater" - ], - [ - "A", - "s1", - "d2", - "bulbs" - ], - [ - "A", - "s2", - "d2", - "well" - ], - [ - "A", - "s2", - "d2", - "EFL" - ], - [ - "B", - "s2", - "d1", - "EF" - ], - [ - "B", - "s1", - "d1", - "bulbs" - ], - [ - "A", - "s1", - "d1", - "GeoHeater" - ], - [ - "A", - "s2", - "d2", - "GeoThermal" - ], - [ - "A", - "s2", - "d2", - "heater" - ] - ], - "LifetimeProcess_rtv": [ - [ - "A", - "EH", - 2025 - ], - [ - "A", - "GeoThermal", - 2025 - ], - [ - "A", - "EF", - 2025 - ], - [ - "B", - "heater", - 2025 - ], - [ - "B", - "EF", - 2025 - ], - [ - "A", - "bulbs", - 2025 - ], - [ - "A-B", - "FGF_pipe", - 2025 - ], - [ - "B", - "GeoHeater", - 2025 - ], - [ - "B", - "batt", - 2025 - ], - [ - "A", - "heater", - 2025 - ], - [ - "B-A", - "FGF_pipe", - 2025 - ], - [ - "B", - "GeoThermal", - 2025 - ], - [ - "A", - "GeoHeater", - 2025 - ], - [ - "B", - "well", - 2025 - ], - [ - "B", - "EH", - 2025 - ], - [ - "A", - "EFL", - 2025 - ], - [ - "A", - "well", - 2025 - ], - [ - "B", - "bulbs", - 2025 - ] - ], - "LoanLifetimeProcess_rtv": [ - [ - "A", - "EH", - 2025 - ], - [ - "A", - "GeoThermal", - 2025 - ], - [ - "A", - "EF", - 2025 - ], - [ - "B", - "heater", - 2025 - ], - [ - "B", - "EF", - 2025 - ], - [ - "A", - "bulbs", - 2025 - ], - [ - "A-B", - "FGF_pipe", - 2025 - ], - [ - "B", - "GeoHeater", - 2025 - ], - [ - "B", - "batt", - 2025 - ], - [ - "A", - "heater", - 2025 - ], - [ - "B-A", - "FGF_pipe", - 2025 - ], - [ - "B", - "GeoThermal", - 2025 - ], - [ - "A", - "GeoHeater", - 2025 - ], - [ - "B", - "well", - 2025 - ], - [ - "B", - "EH", - 2025 - ], - [ - "A", - "EFL", - 2025 - ], - [ - "A", - "well", - 2025 - ], - [ - "B", - "bulbs", - 2025 - ] - ], - "RenewablePortfolioStandardConstraint_rpg": [ - [ - "B", - 2025, - "RPS_common" - ] - ], - "CostFixed_rptv": [ - [ - "B", - 2025, - "EH", - 2025 - ], - [ - "B", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "well", - 2025 - ], - [ - "A-B", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "EH", - 2025 - ], - [ - "B", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "heater", - 2025 - ], - [ - "B", - 2025, - "batt", - 2025 - ], - [ - "A", - 2025, - "GeoHeater", - 2025 - ], - [ - "A", - 2025, - "heater", - 2025 - ], - [ - "B-A", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "GeoThermal", - 2025 - ], - [ - "B", - 2025, - "well", - 2025 - ], - [ - "A", - 2025, - "GeoThermal", - 2025 - ], - [ - "A", - 2025, - "EFL", - 2025 - ] - ], - "CostInvest_rtv": [ - [ - "A", - "EF", - 2025 - ], - [ - "A", - "EH", - 2025 - ], - [ - "A", - "bulbs", - 2025 - ], - [ - "A", - "heater", - 2025 - ], - [ - "B", - "EF", - 2025 - ], - [ - "B", - "batt", - 2025 - ], - [ - "B", - "bulbs", - 2025 - ], - [ - "B", - "heater", - 2025 - ], - [ - "A", - "EFL", - 2025 - ], - [ - "B", - "GeoThermal", - 2025 - ], - [ - "B", - "GeoHeater", - 2025 - ], - [ - "B", - "EH", - 2025 - ], - [ - "A", - "GeoThermal", - 2025 - ], - [ - "A", - "GeoHeater", - 2025 - ] - ], - "CostVariable_rptv": [ - [ - "B", - 2025, - "EH", - 2025 - ], - [ - "A", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "GeoThermal", - 2025 - ], - [ - "A", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "well", - 2025 - ], - [ - "A-B", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "EH", - 2025 - ], - [ - "B", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "heater", - 2025 - ], - [ - "B", - 2025, - "batt", - 2025 - ], - [ - "A", - 2025, - "heater", - 2025 - ], - [ - "B-A", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "GeoThermal", - 2025 - ], - [ - "B", - 2025, - "well", - 2025 - ], - [ - "A", - 2025, - "EFL", - 2025 - ] - ], - "CostEmission_rpe": [ - [ - "A", - 2025, - "co2" - ] - ], - "ModelProcessLife_rptv": [ - [ - "B", - 2025, - "EH", - 2025 - ], - [ - "A", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "GeoThermal", - 2025 - ], - [ - "A", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "well", - 2025 - ], - [ - "A-B", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "EH", - 2025 - ], - [ - "B", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "heater", - 2025 - ], - [ - "B", - 2025, - "batt", - 2025 - ], - [ - "A", - 2025, - "heater", - 2025 - ], - [ - "B-A", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "GeoThermal", - 2025 - ], - [ - "B", - 2025, - "well", - 2025 - ], - [ - "A", - 2025, - "EFL", - 2025 - ] - ], - "ProcessLifeFrac_rptv": [ - [ - "B", - 2025, - "EH", - 2025 - ], - [ - "A", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "GeoThermal", - 2025 - ], - [ - "A", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "well", - 2025 - ], - [ - "A-B", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "EH", - 2025 - ], - [ - "B", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "heater", - 2025 - ], - [ - "B", - 2025, - "batt", - 2025 - ], - [ - "A", - 2025, - "heater", - 2025 - ], - [ - "B-A", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "GeoThermal", - 2025 - ], - [ - "B", - 2025, - "well", - 2025 - ], - [ - "A", - 2025, - "EFL", - 2025 - ] - ], - "MinCapacityConstraint_rpt": [ - [ - "A", - 2025, - "EH" - ], - [ - "B", - 2025, - "batt" - ] - ], - "MaxCapacityConstraint_rpt": [ - [ - "A", - 2025, - "EH" - ], - [ - "B", - 2025, - "EH" - ] - ], - "MinNewCapacityConstraint_rpt": [], - "MaxNewCapacityConstraint_rpt": [], - "MaxResourceConstraint_rt": [ - [ - "B", - "EF" - ] - ], - "MaxActivityConstraint_rpt": [ - [ - "B", - 2025, - "EH" - ], - [ - "A", - 2025, - "EF" - ] - ], - "MinActivityConstraint_rpt": [ - [ - "A", - 2025, - "EF" - ] - ], - "MinAnnualCapacityFactorConstraint_rpto": [], - "MaxAnnualCapacityFactorConstraint_rpto": [], - "EmissionLimitConstraint_rpe": [ - [ - "A", - 2025, - "co2" - ] - ], - "EmissionActivity_reitvo": [ - [ - "B", - "FusionGas", - "earth", - "GeoThermal", - 2025, - "GeoHyd" - ], - [ - "A", - "co2", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "B", - "co2", - "earth", - "well", - 2025, - "HYD" - ], - [ - "A", - "co2", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "B", - "co2", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "A", - "co2", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B", - "FusionGas", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - "co2", - "earth", - "GeoThermal", - 2025, - "GeoHyd" - ], - [ - "B", - "FusionGas", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "A", - "FusionGas", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "B", - "FusionGas", - "earth", - "well", - 2025, - "HYD" - ], - [ - "B", - "co2", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "A", - "FusionGas", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B", - "FusionGas", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "A", - "FusionGas", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "B", - "FusionGas", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "B", - "FusionGas", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "A", - "co2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - "co2", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - "FusionGas", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "B", - "co2", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "A", - "co2", - "earth", - "well", - 2025, - "HYD" - ], - [ - "B", - "co2", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "A", - "FusionGas", - "earth", - "GeoThermal", - 2025, - "GeoHyd" - ], - [ - "B", - "co2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "B", - "co2", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "B", - "co2", - "earth", - "GeoThermal", - 2025, - "GeoHyd" - ], - [ - "A", - "co2", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "A", - "FusionGas", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - "FusionGas", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - "FusionGas", - "earth", - "well", - 2025, - "HYD" - ], - [ - "B", - "FusionGas", - "FusionGasFuel", - "heater", - 2025, - "RH" - ] - ], - "MinActivityGroup_rpg": [ - [ - "A", - 2025, - "(A)_tech_grp_1" - ] - ], - "MaxActivityGroup_rpg": [ - [ - "A", - 2025, - "(A)_tech_grp_1" - ] - ], - "MinCapacityGroupConstraint_rpg": [ - [ - "A", - 2025, - "(A)_tech_grp_1" - ] - ], - "MaxCapacityGroupConstraint_rpg": [ - [ - "A", - 2025, - "(A)_tech_grp_1" - ] - ], - "MinNewCapacityGroupConstraint_rpg": [], - "MaxNewCapacityGroupConstraint_rpg": [], - "GroupShareIndices": [ - [ - "B", - 2025, - "EH", - "(A)_tech_grp_1" - ], - [ - "B", - 2025, - "EF", - "RPS_common" - ], - [ - "A", - 2025, - "EF", - "(A)_tech_grp_1" - ], - [ - "B", - 2025, - "EF", - "(A)_tech_grp_1" - ], - [ - "A", - 2025, - "EH", - "(A)_tech_grp_1" - ], - [ - "A", - 2025, - "EF", - "RPS_common" - ] - ], - "MinCapacityShareConstraint_rptg": [], - "MaxCapacityShareConstraint_rptg": [], - "MinActivityShareConstraint_rptg": [], - "MaxActivityShareConstraint_rptg": [], - "MinNewCapacityShareConstraint_rptg": [], - "MaxNewCapacityShareConstraint_rptg": [], - "StorageInit_rtv": [ - [ - "B", - "batt", - 2025 - ] - ], - "FlowVar_rpsditvo": [ - [ - "A-B", - 2025, - "s1", - "d2", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "B", - 2025, - "s2", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "earth", - "well", - 2025, - "HYD" - ], - [ - "B", - 2025, - "s2", - "d2", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "B", - 2025, - "s2", - "d2", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s2", - "d1", - "earth", - "well", - 2025, - "HYD" - ], - [ - "A", - 2025, - "s2", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s2", - "d1", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B-A", - 2025, - "s2", - "d1", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "A-B", - 2025, - "s2", - "d1", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s2", - "d1", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "A", - 2025, - "s1", - "d1", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "A", - 2025, - "s2", - "d2", - "earth", - "well", - 2025, - "HYD" - ], - [ - "B", - 2025, - "s2", - "d1", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "B", - 2025, - "s2", - "d1", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s1", - "d1", - "earth", - "well", - 2025, - "HYD" - ], - [ - "A", - 2025, - "s2", - "d2", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s2", - "d2", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "A-B", - 2025, - "s1", - "d1", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s1", - "d2", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "A", - 2025, - "s1", - "d2", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "A", - 2025, - "s1", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d2", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "B", - 2025, - "s2", - "d1", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "B-A", - 2025, - "s1", - "d1", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "B", - 2025, - "s1", - "d2", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "A-B", - 2025, - "s2", - "d2", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "B", - 2025, - "s2", - "d1", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d2", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "A", - 2025, - "s1", - "d2", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s1", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s2", - "d2", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "A", - 2025, - "s2", - "d1", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "B", - 2025, - "s1", - "d2", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d2", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s2", - "d2", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s2", - "d1", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "A", - 2025, - "s2", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s1", - "d1", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s1", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s2", - "d1", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s2", - "d2", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "B", - 2025, - "s2", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s2", - "d2", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s2", - "d2", - "earth", - "well", - 2025, - "HYD" - ], - [ - "A", - 2025, - "s1", - "d1", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "A", - 2025, - "s2", - "d2", - "GeoHyd", - "GeoHeater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s2", - "d2", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s2", - "d1", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "FusionGasFuel", - "heater", - 2025, - "RH" - ], - [ - "B", - 2025, - "s2", - "d1", - "earth", - "well", - 2025, - "HYD" - ], - [ - "B", - 2025, - "s1", - "d2", - "earth", - "well", - 2025, - "HYD" - ], - [ - "B-A", - 2025, - "s2", - "d2", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "B-A", - 2025, - "s1", - "d2", - "FusionGasFuel", - "FGF_pipe", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s1", - "d2", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "HYD", - "EF", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d2", - "ELC", - "bulbs", - 2025, - "RL" - ], - [ - "A", - 2025, - "s1", - "d2", - "earth", - "well", - 2025, - "HYD" - ] - ], - "FlowVarAnnual_rpitvo": [ - [ - "A", - 2025, - "earth", - "GeoThermal", - 2025, - "GeoHyd" - ], - [ - "B", - 2025, - "earth", - "GeoThermal", - 2025, - "GeoHyd" - ] - ], - "FlexVar_rpsditvo": [ - [ - "A", - 2025, - "s2", - "d2", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s1", - "d2", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s1", - "d1", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ], - [ - "A", - 2025, - "s2", - "d1", - "earth", - "EFL", - 2025, - "FusionGasFuel" - ] - ], - "FlexVarAnnual_rpitvo": [], - "CurtailmentVar_rpsditvo": [ - [ - "B", - 2025, - "s2", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s2", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s1", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s2", - "d2", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "A", - 2025, - "s2", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "HYD", - "EH", - 2025, - "ELC" - ] - ], - "FlowInStorage_rpsditvo": [ - [ - "B", - 2025, - "s2", - "d2", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d2", - "ELC", - "batt", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s2", - "d1", - "ELC", - "batt", - 2025, - "ELC" - ] - ], - "StorageLevel_rpsdtv": [ - [ - "B", - 2025, - "s2", - "d2", - "batt", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "batt", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "batt", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "batt", - 2025 - ] - ], - "CapacityVar_rptv": [ - [ - "B", - 2025, - "EH", - 2025 - ], - [ - "B", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "well", - 2025 - ], - [ - "A-B", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "EF", - 2025 - ], - [ - "A", - 2025, - "bulbs", - 2025 - ], - [ - "A", - 2025, - "EH", - 2025 - ], - [ - "B", - 2025, - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "heater", - 2025 - ], - [ - "B", - 2025, - "batt", - 2025 - ], - [ - "A", - 2025, - "GeoHeater", - 2025 - ], - [ - "A", - 2025, - "heater", - 2025 - ], - [ - "B-A", - 2025, - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "GeoThermal", - 2025 - ], - [ - "B", - 2025, - "well", - 2025 - ], - [ - "A", - 2025, - "GeoThermal", - 2025 - ], - [ - "A", - 2025, - "EFL", - 2025 - ] - ], - "NewCapacityVar_rtv": [ - [ - "A", - "EH", - 2025 - ], - [ - "A", - "GeoThermal", - 2025 - ], - [ - "A", - "EF", - 2025 - ], - [ - "B", - "heater", - 2025 - ], - [ - "B", - "EF", - 2025 - ], - [ - "A", - "bulbs", - 2025 - ], - [ - "A-B", - "FGF_pipe", - 2025 - ], - [ - "B", - "GeoHeater", - 2025 - ], - [ - "B", - "batt", - 2025 - ], - [ - "A", - "heater", - 2025 - ], - [ - "B-A", - "FGF_pipe", - 2025 - ], - [ - "B", - "GeoThermal", - 2025 - ], - [ - "A", - "GeoHeater", - 2025 - ], - [ - "B", - "well", - 2025 - ], - [ - "B", - "EH", - 2025 - ], - [ - "A", - "EFL", - 2025 - ], - [ - "A", - "well", - 2025 - ], - [ - "B", - "bulbs", - 2025 - ] - ], - "RetiredCapacityVar_rptv": [], - "CapacityAvailableVar_rpt": [ - [ - "B", - 2025, - "heater" - ], - [ - "A", - 2025, - "EF" - ], - [ - "A", - 2025, - "GeoThermal" - ], - [ - "A", - 2025, - "bulbs" - ], - [ - "A", - 2025, - "EFL" - ], - [ - "A", - 2025, - "heater" - ], - [ - "B-A", - 2025, - "FGF_pipe" - ], - [ - "A", - 2025, - "GeoHeater" - ], - [ - "B", - 2025, - "EH" - ], - [ - "B", - 2025, - "GeoHeater" - ], - [ - "B", - 2025, - "batt" - ], - [ - "B", - 2025, - "well" - ], - [ - "B", - 2025, - "GeoThermal" - ], - [ - "B", - 2025, - "bulbs" - ], - [ - "A", - 2025, - "EH" - ], - [ - "A-B", - 2025, - "FGF_pipe" - ], - [ - "B", - 2025, - "EF" - ], - [ - "A", - 2025, - "well" - ] - ], - "CapacityConstraint_rpsdtv": [ - [ - "A", - 2025, - "s2", - "d2", - "heater", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "heater", - 2025 - ], - [ - "B-A", - 2025, - "s2", - "d2", - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "well", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "bulbs", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "well", - 2025 - ], - [ - "B-A", - 2025, - "s2", - "d1", - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "heater", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "bulbs", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "batt", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "heater", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "bulbs", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "EF", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "EF", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "bulbs", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "bulbs", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "EF", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "EF", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "batt", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "well", - 2025 - ], - [ - "A", - 2025, - "s2", - "d2", - "bulbs", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "EFL", - 2025 - ], - [ - "A-B", - 2025, - "s1", - "d1", - "FGF_pipe", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "well", - 2025 - ], - [ - "A", - 2025, - "s2", - "d2", - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "GeoHeater", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "EFL", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "EH", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "EF", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "well", - 2025 - ], - [ - "A-B", - 2025, - "s1", - "d2", - "FGF_pipe", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "EH", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "bulbs", - 2025 - ], - [ - "A", - 2025, - "s2", - "d2", - "EF", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "heater", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "EF", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "bulbs", - 2025 - ], - [ - "A", - 2025, - "s2", - "d2", - "well", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "well", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "heater", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "EH", - 2025 - ], - [ - "A", - 2025, - "s2", - "d2", - "EH", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "GeoHeater", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "heater", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "well", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "batt", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "EH", - 2025 - ], - [ - "A", - 2025, - "s2", - "d2", - "EFL", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "EF", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "heater", - 2025 - ], - [ - "A-B", - 2025, - "s2", - "d1", - "FGF_pipe", - 2025 - ], - [ - "B-A", - 2025, - "s1", - "d2", - "FGF_pipe", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "batt", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "EFL", - 2025 - ], - [ - "A-B", - 2025, - "s2", - "d2", - "FGF_pipe", - 2025 - ], - [ - "B-A", - 2025, - "s1", - "d1", - "FGF_pipe", - 2025 - ] - ], - "CapacityAnnualConstraint_rptv": [ - [ - "B", - 2025, - "GeoThermal", - 2025 - ], - [ - "A", - 2025, - "GeoThermal", - 2025 - ] - ], - "DemandConstraint_rpsdc": [ - [ - "B", - 2025, - "s2", - "d2", - "RH" - ], - [ - "B", - 2025, - "s1", - "d1", - "RL" - ], - [ - "A", - 2025, - "s1", - "d1", - "RH" - ], - [ - "A", - 2025, - "s2", - "d2", - "RH" - ], - [ - "B", - 2025, - "s1", - "d2", - "RH" - ], - [ - "B", - 2025, - "s2", - "d1", - "RL" - ], - [ - "A", - 2025, - "s2", - "d1", - "RL" - ], - [ - "A", - 2025, - "s1", - "d2", - "RH" - ], - [ - "A", - 2025, - "s1", - "d2", - "RL" - ], - [ - "B", - 2025, - "s1", - "d1", - "RH" - ], - [ - "B", - 2025, - "s2", - "d1", - "RH" - ], - [ - "A", - 2025, - "s2", - "d1", - "RH" - ], - [ - "A", - 2025, - "s1", - "d1", - "RL" - ], - [ - "B", - 2025, - "s1", - "d2", - "RL" - ], - [ - "B", - 2025, - "s2", - "d2", - "RL" - ], - [ - "A", - 2025, - "s2", - "d2", - "RL" - ] - ], - "DemandActivityConstraint_rpsdtv_dem_s0d0": [ - [ - "B", - 2025, - "s1", - "d2", - "heater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "B", - 2025, - "s2", - "d1", - "heater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "B", - 2025, - "s2", - "d2", - "heater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "B", - 2025, - "s1", - "d2", - "GeoHeater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "B", - 2025, - "s2", - "d1", - "GeoHeater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "B", - 2025, - "s2", - "d2", - "GeoHeater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "A", - 2025, - "s1", - "d2", - "heater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "A", - 2025, - "s2", - "d1", - "heater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "A", - 2025, - "s2", - "d2", - "heater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "A", - 2025, - "s1", - "d2", - "GeoHeater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "A", - 2025, - "s2", - "d1", - "GeoHeater", - 2025, - "RH", - "s1", - "d1" - ], - [ - "A", - 2025, - "s2", - "d2", - "GeoHeater", - 2025, - "RH", - "s1", - "d1" - ] - ], - "CommodityBalanceConstraint_rpsdc": [ - [ - "A", - 2025, - "s1", - "d1", - "FusionGasFuel" - ], - [ - "A", - 2025, - "s2", - "d1", - "HYD" - ], - [ - "A", - 2025, - "s1", - "d2", - "HYD" - ], - [ - "A", - 2025, - "s2", - "d2", - "ELC" - ], - [ - "B", - 2025, - "s1", - "d2", - "HYD" - ], - [ - "A", - 2025, - "s2", - "d2", - "FusionGasFuel" - ], - [ - "B", - 2025, - "s2", - "d1", - "HYD" - ], - [ - "B", - 2025, - "s2", - "d2", - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "HYD" - ], - [ - "A", - 2025, - "s2", - "d1", - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "HYD" - ], - [ - "A", - 2025, - "s1", - "d2", - "ELC" - ], - [ - "A", - 2025, - "s2", - "d1", - "FusionGasFuel" - ], - [ - "B", - 2025, - "s1", - "d2", - "ELC" - ], - [ - "A", - 2025, - "s1", - "d2", - "FusionGasFuel" - ], - [ - "A", - 2025, - "s2", - "d2", - "HYD" - ], - [ - "B", - 2025, - "s2", - "d1", - "ELC" - ], - [ - "A", - 2025, - "s1", - "d1", - "ELC" - ], - [ - "B", - 2025, - "s2", - "d2", - "HYD" - ], - [ - "B", - 2025, - "s1", - "d1", - "ELC" - ] - ], - "CommodityBalanceAnnualConstraint_rpc": [ - [ - "B", - 2025, - "GeoHyd" - ], - [ - "A", - 2025, - "GeoHyd" - ] - ], - "BaseloadDiurnalConstraint_rpsdtv": [ - [ - "A", - 2025, - "s2", - "d2", - "EH", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "EH", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "EH", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "EH", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "EH", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "EH", - 2025 - ] - ], - "RegionalExchangeCapacityConstraint_rrptv": [ - [ - "B", - "A", - 2025, - "FGF_pipe", - 2025 - ], - [ - "A", - "B", - 2025, - "FGF_pipe", - 2025 - ] - ], - "StorageConstraints_rpsdtv": [ - [ - "B", - 2025, - "s2", - "d2", - "batt", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "batt", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "batt", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "batt", - 2025 - ] - ], - "StorageInitConstraint_rtv": [], - "RampConstraintDay_rpsdtv": [ - [ - "A", - 2025, - "s2", - "d2", - "EH", - 2025 - ], - [ - "B", - 2025, - "s2", - "d2", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d2", - "EH", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "EH", - 2025 - ], - [ - "B", - 2025, - "s1", - "d2", - "EH", - 2025 - ], - [ - "B", - 2025, - "s2", - "d1", - "EH", - 2025 - ], - [ - "B", - 2025, - "s1", - "d1", - "EH", - 2025 - ] - ], - "RampConstraintPeriod_rptv": [ - [ - "B", - 2025, - "EH", - 2025 - ], - [ - "A", - 2025, - "EH", - 2025 - ] - ], - "ReserveMargin_rpsd": [ - [ - "A", - 2025, - "s2", - "d2" - ], - [ - "A", - 2025, - "s2", - "d1" - ], - [ - "B", - 2025, - "s1", - "d2" - ], - [ - "A", - 2025, - "s1", - "d2" - ], - [ - "B", - 2025, - "s1", - "d1" - ], - [ - "A", - 2025, - "s1", - "d1" - ], - [ - "B", - 2025, - "s2", - "d2" - ], - [ - "B", - 2025, - "s2", - "d1" - ] - ], - "GrowthRateMaxConstraint_rtv": [ - [ - 2025, - "A", - "GeoHeater" - ] - ], - "TechInputSplitConstraint_rpsditv": [ - [ - "A", - 2025, - "s1", - "d2", - "HYD", - "EH", - 2025 - ], - [ - "A", - 2025, - "s1", - "d1", - "HYD", - "EH", - 2025 - ], - [ - "A", - 2025, - "s2", - "d2", - "HYD", - "EH", - 2025 - ], - [ - "A", - 2025, - "s2", - "d1", - "HYD", - "EH", - 2025 - ] - ], - "TechInputSplitAnnualConstraint_rpitv": [], - "TechInputSplitAverageConstraint_rpitv": [ - [ - "A", - 2025, - "GeoHyd", - "GeoHeater", - 2025 - ] - ], - "TechOutputSplitConstraint_rpsdtvo": [ - [ - "B", - 2025, - "s2", - "d1", - "EH", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d2", - "EH", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s2", - "d2", - "EH", - 2025, - "ELC" - ], - [ - "B", - 2025, - "s1", - "d1", - "EH", - 2025, - "ELC" - ] - ], - "TechOutputSplitAnnualConstraint_rptvo": [], - "LinkedEmissionsTechConstraint_rpsdtve": [ - [ - "A", - 2025, - "s1", - "d2", - "EF", - 2025, - "FusionGas" - ], - [ - "A", - 2025, - "s1", - "d1", - "EF", - 2025, - "FusionGas" - ], - [ - "A", - 2025, - "s2", - "d2", - "EF", - 2025, - "FusionGas" - ], - [ - "A", - 2025, - "s2", - "d1", - "EF", - 2025, - "FusionGas" - ] - ] -} \ No newline at end of file + "annual_commodity_balance_constraint_rpc": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "annual_retirement_var_rptv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "baseload_diurnal_constraint_rpsdtv": "d766f05fa377b69768214641fda5d15617b697a43e64c863fe53503f11db4a89", + "capacity_annual_constraint_rptv": "6bd365e2e2f3267c7b838774484507a0812b25077f3006bcbfe135eceda11a28", + "capacity_available_var_rpt": "34f71aae4b6b3a07130b57fa7910379bc1ecc3e12a6b4930fca5717bf54f8f56", + "capacity_constraint_rpsdtv": "02dcd6b84019270beb4b4fcac4062d1554bcc62ea9ed37c9b7f99d351b307898", + "capacity_factor_rsdt": "596087531ddbb1b27bc63b78e85b3d4fbb56c621ed2dbe35ef4f9f0ffcc49f68", + "capacity_var_rptv": "fdb177f119430de12c8b1083c9275e3a256f45f859d6b6819a84bff5c8e92184", + "commodity_all": "1967c4b68c028414d99b55bfe9e03efd2fff370d15540a95282b721875d41460", + "commodity_annual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "commodity_balance_constraint_rpsdc": "a1d06a815047ba07b42ced6dc65df3511301af2f30791b3094fb7f25ccdd4217", + "commodity_carrier": "1b95135b3de272d1ecab3761bdb976f1e48c044dcb2b5b6d320bc19dae0038cd", + "commodity_demand": "f0ecafabcdc5015dab09f4b23f3c742a27b536f1c5634057a902a9b9c4a741b8", + "commodity_emissions": "849d0ab948e289950369de3a31be985d420635fa93525416da6d3ee9149019fc", + "commodity_flex": "bfbe6f995c3a9fd19c92c7b6ffc526035a773a24d302c7eae9951b7327aef904", + "commodity_physical": "7dd03855822916eb73171efbb05042535e0f89c26614139ed357a002b8a80fd1", + "commodity_sink": "f0ecafabcdc5015dab09f4b23f3c742a27b536f1c5634057a902a9b9c4a741b8", + "commodity_source": "ab3705f3ba0b69baa453c70d1bdfde3faa4eaeabc007204278258b4aacd861f3", + "commodity_waste": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "cost_emission_rpe": "14c414e00f949816cbdbbfc01ab7df2fea18cd7f0ab4e84a33571e41667bd057", + "cost_fixed_rptv": "fdb177f119430de12c8b1083c9275e3a256f45f859d6b6819a84bff5c8e92184", + "cost_invest_rtv": "cc795cbbeaaae0c046247752c104269e877cd4c402391b63ce6fe4d6ed4cf46b", + "cost_variable_rptv": "fdb177f119430de12c8b1083c9275e3a256f45f859d6b6819a84bff5c8e92184", + "curtailment_var_rpsditvo": "541097cce6a976a20b73a2ffd8c4edd665a8497b066545d37318641b10484e95", + "demand_activity_constraint_rpsdtv_dem": "91570a9fbe4b071c37f1985c1671de2f33d9dfa7847f791a2933b8b3f262f8e8", + "demand_constraint_rpc": "65996df03f7f3d85067ae18d36bdffe8596881473ae847052dc5df735cd01262", + "emission_activity_reitvo": "1292a5003c98c9ae6b7476807aca4bcc528b3dc28aec79827a47aeaaaaab74e4", + "flex_var_annual_rpitvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "flex_var_rpsditvo": "e4a8d7519d3e9deda0a32a471bdfa825eb893f10ba5781b895c9e3a3ef5faaa3", + "flow_in_storage_rpsditvo": "ff680754a218ee843941e77bd07cbc5e5afd74e2b7b29a76ea86621624e8c0fd", + "flow_var_annual_rpitvo": "6f5c569787fbf1e0a4076011f75fcd50a840bac85bb57b026f4fc5682739f888", + "flow_var_rpsditvo": "e8c35c3b7b1f0c0e10e6f53e94d0c6b8a884c0dd048675cbeb9e7df33794161b", + "lifetime_process_rtv": "212639322f29aa7687eb9962fb9d74ef60961d720a2a753e2af53473b9e1648f", + "limit_activity_constraint_rpt": "90461349933d0b32167abdca242233004b66212b57ed57213dce6f6818203963", + "limit_activity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_annual_capacity_factor_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_annual_capacity_factor_constraint_rtvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_capacity_constraint_rpt": "118826dabd14af75ae09adbd5bdbba750528b3d59907cbfba3690223ffc7def7", + "limit_capacity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_emission_constraint_rpe": "9bf30514ecc261370f67cd0c2e18194f45ff14c15764d274cc342a5a27eaf2a0", + "limit_growth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_growth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_growth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_new_capacity_constraint_rtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_new_capacity_share_constraint_rggv": "296e4148565f752973655692bae04ebf50877bd13fe9cec8f9563f347a61d885", + "limit_resource_constraint_rt": "b9b44ae8bc9642da0a81202877bd69f0b35ce193e31590dd3325ac0828edb5cf", + "limit_seasonal_capacity_factor_constraint_rpst": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_seasonal_capacity_factor_constraint_rst": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_storage_fraction_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_storage_fraction_param_rsdt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_annual_constraint_rpitv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_average_constraint_rpitv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_constraint_rpsditv": "7a0e9b6c8271bcc30ccd6588da984c4aa8afe5d768ce5035fe6d8e62b31f8b3e", + "limit_tech_output_split_annual_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_output_split_average_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_output_split_constraint_rpsdtvo": "7cf9f5c111231f4e5c3f0ee8acfcddf52f5b1af3474d4cb949b969997602c8d7", + "linked_emissions_tech_constraint_rpsdtve": "f5accc0ee9eaaf16e584566a50e576405f8b5e1eb7d13815b87cb2f8b33a19b8", + "loan_lifetime_process_rtv": "212639322f29aa7687eb9962fb9d74ef60961d720a2a753e2af53473b9e1648f", + "new_capacity_var_rtv": "212639322f29aa7687eb9962fb9d74ef60961d720a2a753e2af53473b9e1648f", + "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", + "ordered_season_sequential": "53bc15b6510e5bad389153a661165e5a434fea3f17ba2c8b3919568edf124f72", + "process_life_frac_rptv": "fdb177f119430de12c8b1083c9275e3a256f45f859d6b6819a84bff5c8e92184", + "ramp_down_day_constraint_rpsdtv": "d766f05fa377b69768214641fda5d15617b697a43e64c863fe53503f11db4a89", + "ramp_down_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "ramp_up_day_constraint_rpsdtv": "d766f05fa377b69768214641fda5d15617b697a43e64c863fe53503f11db4a89", + "ramp_up_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "regional_exchange_capacity_constraint_rrptv": "cf641ca7d7b664fda48e76f03b37c3b81aaa130ce821f919e6a8ac4860dad100", + "regional_global_indices": "a4bd2969735cb437072971a2fa02a2d8fbb20a707a1bdfa695b92922daeb5d10", + "regional_indices": "6d8dc3bc6dc8cd485bcf2a59d752df20974b0fd78cc623e3a51e705a01edeea4", + "regions": "f2314439597c11e286fd3c2fdf8eb70d3739e136f83e9533249ce3ecc43ece3e", + "renewable_portfolio_standard_constraint_rpg": "ab39415b5df1f906b11f06034cbfca390f7332b6a17adae60a71a74d7b421f67", + "reserve_margin_method": "7869283c0d14273f720716309207a8f0c24606d03c679d6b68e656ed8d86241d", + "reserve_margin_rpsd": "9bd91053e57055456e04648c00d8e581addc9aa503cf31cf44c7a439c6f4fe0f", + "retired_capacity_var_rptv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "seasonal_storage_constraints_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "seasonal_storage_level_rpstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "storage_constraints_rpsdtv": "e1fe21679519e4410954d233e22d9490b182f21fda460b31a8d02538856fb85c", + "storage_init_rpstv": "90db44be71af3d9d75369791755570cf888e63e3544be22800077a4a2871175c", + "storage_level_rpsdtv": "e1fe21679519e4410954d233e22d9490b182f21fda460b31a8d02538856fb85c", + "tech_all": "24f75b6f705a36033456d02638e7c50667908c45c474151fb8490666d928c63f", + "tech_annual": "04ce850e0c3d3cb6608accab041c052be13c33d3f9d546242743b948989a043f", + "tech_baseload": "ddcf6ff7665c2a8acc4dff1b43655fae1b5a265135cbee18cec638df4e954346", + "tech_curtailment": "ddcf6ff7665c2a8acc4dff1b43655fae1b5a265135cbee18cec638df4e954346", + "tech_demand": "b9e4a17c6dbf50597b7fd89aa581889bc19b262bd61629465295a52d91208096", + "tech_downramping": "ddcf6ff7665c2a8acc4dff1b43655fae1b5a265135cbee18cec638df4e954346", + "tech_exchange": "4ad32a8fc9b95e840aa771b2dabc6fb35fae4904c089d4552659a4a9267883a7", + "tech_exist": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_flex": "658ce9e7b9c3d7bcc751d8d49b9ece9c57623b3e25470c4f849af44b61ea5e20", + "tech_group_members": "6b923047f11d1c00a828b0f02aed79e853d81fd35ffd4667fbdc828ce356e515", + "tech_group_names": "6b923047f11d1c00a828b0f02aed79e853d81fd35ffd4667fbdc828ce356e515", + "tech_or_group": "4899ca300eb8ed5ba168b1f2ee669cacd36d041f493fb3dd00218d6764598cf9", + "tech_production": "24f75b6f705a36033456d02638e7c50667908c45c474151fb8490666d928c63f", + "tech_reserve": "b28d0fd7ec11abdd0e645c2a9d83b05c08ecadcfb944d4a7bbd844ff4b83bfbf", + "tech_retirement": "ddcf6ff7665c2a8acc4dff1b43655fae1b5a265135cbee18cec638df4e954346", + "tech_seasonal_storage": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_storage": "e4e91128cadba8c633bf98029cf6666adc3959c63967c6f396028b78e2e9f0cf", + "tech_uncap": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_upramping": "ddcf6ff7665c2a8acc4dff1b43655fae1b5a265135cbee18cec638df4e954346", + "tech_with_capacity": "24f75b6f705a36033456d02638e7c50667908c45c474151fb8490666d928c63f", + "time_exist": "91a2461c25439830d94bf4b3d3a3b020343f75e74561a913b1f972b2ac42e943", + "time_future": "6b150df8e12ac4dd15396b52f304c7935a41d2cc4da498186552819973171389", + "time_manual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "time_of_day": "f9bbfe9130c510cba59a13e8b385b4d0206196abbdfe8032b995502bb7215f76", + "time_optimize": "9c9380fb50cd4f4f9e2032bd9a18645ae4fc1d30335672c62c897bcb9e099ba5", + "time_season": "1e413891065e24bbf66543d4747ff12b1c65bf23c4ff9857b0bf0520eccd7f21", + "time_season_sequential": "1e413891065e24bbf66543d4747ff12b1c65bf23c4ff9857b0bf0520eccd7f21", + "time_sequencing": "91f69c8abab9959c1f8c90f5aaa56db29bccc67e37d12673ab41c54e4179d7ca", + "vintage_all": "947dd08ad4812a98649b4306e4a0ca1b51374d86f1a8e27c3a8af3b189bcc0f6", + "vintage_exist": "91a2461c25439830d94bf4b3d3a3b020343f75e74561a913b1f972b2ac42e943", + "vintage_optimize": "9c9380fb50cd4f4f9e2032bd9a18645ae4fc1d30335672c62c897bcb9e099ba5" +} diff --git a/tests/testing_data/migration_v3_1_mock.sql b/tests/testing_data/migration_v3_1_mock.sql new file mode 100644 index 000000000..9a282962f --- /dev/null +++ b/tests/testing_data/migration_v3_1_mock.sql @@ -0,0 +1,32 @@ +-- Mock data for v3.1 -> v4 migration testing +INSERT INTO Region (region) VALUES ('R1'); +INSERT OR IGNORE INTO TechnologyType (label, description) VALUES ('p', 'production'); +INSERT INTO Technology (tech, flag, unlim_cap, annual, reserve, curtail, retire, flex, exchange, seas_stor) VALUES ('T1', 'p', 0, 0, 0, 0, 0, 0, 0, 0); + +INSERT INTO TimePeriod (sequence, period, flag) VALUES (1, 2020, 'e'); +INSERT INTO TimePeriod (sequence, period, flag) VALUES (2, 2030, 'f'); +INSERT INTO TimePeriod (sequence, period, flag) VALUES (3, 2040, 'f'); + +INSERT INTO SeasonLabel (season) VALUES ('winter'); +INSERT INTO SeasonLabel (season) VALUES ('summer'); + +INSERT INTO TimeOfDay (sequence, tod) VALUES (1, 'day'); +INSERT INTO TimeOfDay (sequence, tod) VALUES (2, 'night'); + +INSERT INTO TimeSeason (period, sequence, season) VALUES (2030, 1, 'winter'); +INSERT INTO TimeSeason (period, sequence, season) VALUES (2030, 2, 'summer'); + +INSERT INTO TimeSegmentFraction (period, season, tod, segfrac) VALUES (2030, 'winter', 'day', 0.2); +INSERT INTO TimeSegmentFraction (period, season, tod, segfrac) VALUES (2030, 'winter', 'night', 0.1); +INSERT INTO TimeSegmentFraction (period, season, tod, segfrac) VALUES (2030, 'summer', 'day', 0.4); +INSERT INTO TimeSegmentFraction (period, season, tod, segfrac) VALUES (2030, 'summer', 'night', 0.3); + +INSERT OR IGNORE INTO CommodityType (label, description) VALUES ('p', 'physical'); +INSERT INTO Commodity (name, flag) VALUES ('In', 'p'); +INSERT INTO Commodity (name, flag) VALUES ('Out', 'p'); + +INSERT INTO CapacityFactorProcess (region, period, season, tod, tech, vintage, factor) VALUES ('R1', 2030, 'winter', 'day', 'T1', 2030, 0.5); +INSERT INTO CapacityFactorProcess (region, period, season, tod, tech, vintage, factor) VALUES ('R1', 2040, 'winter', 'day', 'T1', 2030, 0.7); +INSERT INTO Efficiency (region, input_comm, tech, vintage, output_comm, efficiency) VALUES ('R1', 'In', 'T1', 2030, 'Out', 0.9); +INSERT INTO LimitCapacity (region, period, tech_or_group, operator, capacity, units, notes) VALUES ('R1', 2030, 'T1', 'ge', 10.0, 'GW', 'test op'); +INSERT INTO LimitEmission (region, period, emis_comm, operator, value, units, notes) VALUES ('R1', 2030, 'Out', 'le', 100.0, 'kt', 'test op'); diff --git a/tests/testing_data/migration_v3_mock.sql b/tests/testing_data/migration_v3_mock.sql new file mode 100644 index 000000000..28973eb24 --- /dev/null +++ b/tests/testing_data/migration_v3_mock.sql @@ -0,0 +1,32 @@ +-- Mock data for v3 -> v4 migration testing +INSERT INTO Region (region) VALUES ('R1'); +INSERT OR IGNORE INTO TechnologyType (label, description) VALUES ('p', 'production'); +INSERT INTO Technology (tech, flag, unlim_cap, annual, reserve, curtail, retire, flex, exchange) VALUES ('T1', 'p', 0, 0, 0, 0, 0, 0, 0); + +INSERT INTO TimePeriodType (label, description) VALUES ('e', 'existing'); +INSERT INTO TimePeriodType (label, description) VALUES ('f', 'future'); +INSERT INTO TimePeriod (sequence, period, flag) VALUES (1, 2020, 'e'); +INSERT INTO TimePeriod (sequence, period, flag) VALUES (2, 2030, 'f'); +INSERT INTO TimePeriod (sequence, period, flag) VALUES (3, 2040, 'f'); + + + +INSERT INTO TimeOfDay (sequence, tod) VALUES (1, 'day'); +INSERT INTO TimeOfDay (sequence, tod) VALUES (2, 'night'); + +INSERT INTO TimeSeason (sequence, season) VALUES (1, 'winter'); +INSERT INTO TimeSeason (sequence, season) VALUES (2, 'summer'); + +INSERT INTO TimeSegmentFraction (season, tod, segfrac) VALUES ('winter', 'day', 0.2); +INSERT INTO TimeSegmentFraction (season, tod, segfrac) VALUES ('winter', 'night', 0.1); +INSERT INTO TimeSegmentFraction (season, tod, segfrac) VALUES ('summer', 'day', 0.4); +INSERT INTO TimeSegmentFraction (season, tod, segfrac) VALUES ('summer', 'night', 0.3); + +INSERT OR IGNORE INTO CommodityType (label, description) VALUES ('p', 'physical'); +INSERT INTO Commodity (name, flag) VALUES ('In', 'p'); +INSERT INTO Commodity (name, flag) VALUES ('Out', 'p'); + +INSERT INTO CapacityFactorProcess (region, season, tod, tech, vintage, factor) VALUES ('R1', 'winter', 'day', 'T1', 2030, 0.6); +INSERT INTO Efficiency (region, input_comm, tech, vintage, output_comm, efficiency) VALUES ('R1', 'In', 'T1', 2030, 'Out', 0.9); +INSERT INTO MinCapacity (region, tech, period, min_cap, units, notes) VALUES ('R1', 'T1', 2030, 10.0, 'GW', 'test op'); +INSERT INTO EmissionLimit (region, period, emis_comm, value, units, notes) VALUES ('R1', 2030, 'Out', 100.0, 'kt', 'test op'); diff --git a/tests/testing_data/seasonal_storage.sql b/tests/testing_data/seasonal_storage.sql new file mode 100644 index 000000000..19b394b39 --- /dev/null +++ b/tests/testing_data/seasonal_storage.sql @@ -0,0 +1,86 @@ +REPLACE INTO "capacity_factor_tech" VALUES('region','charge','a','generator',1.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('region','charge','b','generator',1.0,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('region','charge','c','generator',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('region','charge','d','generator',0.2,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('region','discharge','a','generator',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('region','discharge','b','generator',0.1,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('region','discharge','c','generator',0.01,NULL); +REPLACE INTO "capacity_factor_tech" VALUES('region','discharge','d','generator',0.01,NULL); +REPLACE INTO "capacity_to_activity" VALUES('region', 'generator', 8760.0, NULL, 'MWh/MWy'); +REPLACE INTO "capacity_to_activity" VALUES('region', 'dly_stor', 8760.0, NULL, 'MWh/MWy'); +REPLACE INTO "capacity_to_activity" VALUES('region', 'seas_stor', 8760.0, NULL, 'MWh/MWy'); +REPLACE INTO "capacity_to_activity" VALUES('region', 'demand', 8760.0, NULL, 'MWh/MWy'); +REPLACE INTO "commodity" VALUES('ethos', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('electricity', 'p', NULL, NULL); +REPLACE INTO "commodity" VALUES('demand', 'd', NULL, NULL); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "cost_invest" VALUES('region','generator',2000,1000.0,'',NULL); +REPLACE INTO "cost_invest" VALUES('region','dly_stor',2000,1.0,'',NULL); +REPLACE INTO "cost_invest" VALUES('region','seas_stor',2000,100.0,'',NULL); +REPLACE INTO "cost_invest" VALUES('region','demand',2000,1.0,'',NULL); +REPLACE INTO "cost_variable" VALUES('region',2000,'generator',2000,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2000,'demand',2000,1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2000,'demand',8760.0,'MWh',NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'charge','a','demand',0.0,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'charge','b','demand',0.05,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'charge','c','demand',0.05,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'charge','d','demand',0.1,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'discharge','a','demand',0.0,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'discharge','b','demand',0.2,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'discharge','c','demand',0.2,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('region',2000,'discharge','d','demand',0.4,NULL); +REPLACE INTO "efficiency" VALUES('region', 'ethos', 'generator', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'electricity', 'dly_stor', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'electricity', 'seas_stor', 2000, 'electricity', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'electricity', 'demand', 2000, 'demand', 1.0, NULL, NULL); +REPLACE INTO "limit_storage_level_fraction" VALUES('region','winter','b','seas_stor','e',0.5,NULL); +REPLACE INTO "limit_storage_level_fraction" VALUES('region','charge','b','dly_stor','e',0.5,NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('region',NULL); +REPLACE INTO "sector_label" VALUES('electricity',NULL); +REPLACE INTO "storage_duration" VALUES('region','dly_stor',4.0,NULL); +REPLACE INTO "storage_duration" VALUES('region','seas_stor',8760.0,NULL); +REPLACE INTO "technology" VALUES('generator','p','electricity',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('dly_stor','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('seas_stor','ps','electricity',NULL,NULL,0,0,0,0,0,0,0,1,NULL); +REPLACE INTO "technology" VALUES('demand','p','electricity',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(0,'a',6,NULL); +REPLACE INTO "time_of_day" VALUES(1,'b',6,NULL); +REPLACE INTO "time_of_day" VALUES(2,'c',6,NULL); +REPLACE INTO "time_of_day" VALUES(3,'d',6,NULL); +REPLACE INTO "time_period" VALUES(0,2000,'f'); +REPLACE INTO "time_period" VALUES(1,2005,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(0,'charge',0.5,NULL); +REPLACE INTO "time_season" VALUES(1,'discharge',0.5,NULL); +REPLACE INTO "time_season_sequential" VALUES(1,'summer','charge',0.417808,NULL); +REPLACE INTO "time_season_sequential" VALUES(2,'sept_w1','discharge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(3,'sept_w2','charge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(4,'sept_w3','discharge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(5,'sept_w4','charge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(6,'sept_29th','discharge',0.002740,NULL); +REPLACE INTO "time_season_sequential" VALUES(7,'sept_30th','charge',0.002740,NULL); +REPLACE INTO "time_season_sequential" VALUES(8,'winter','discharge',0.417808,NULL); +REPLACE INTO "time_season_sequential" VALUES(9,'apr_w1','charge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(10,'apr_w2','discharge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(11,'apr_w3','charge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(12,'apr_w4','discharge',0.019178,NULL); +REPLACE INTO "time_season_sequential" VALUES(13,'apr_29th','charge',0.002740,NULL); +REPLACE INTO "time_season_sequential" VALUES(14,'apr_30th','discharge',0.002740,NULL); diff --git a/tests/testing_data/simple_linked_tech.sql b/tests/testing_data/simple_linked_tech.sql index ce9a7fc71..25760ebc9 100644 --- a/tests/testing_data/simple_linked_tech.sql +++ b/tests/testing_data/simple_linked_tech.sql @@ -1,957 +1,58 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -INSERT INTO MetaData VALUES('myopic_base_year',1990,''); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ELC','d','electricity'); -INSERT INTO Commodity VALUES('NGA','p','natural gas'); -INSERT INTO Commodity VALUES('CO2','e','CO2 emission'); -INSERT INTO Commodity VALUES('CO2_CAP','d','captured CO2'); -INSERT INTO Commodity VALUES('ETHOS','s','source'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('s','source commodity'); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -INSERT INTO CostEmission VALUES('linkville',2000,'CO2',2.0,NULL,NULL); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('linkville','PLANT',2000,100.0,'',''); -INSERT INTO CostInvest VALUES('linkville','CCS',2000,50.0,'',''); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('linkville',2000,'PLANT',2000,10.0,NULL,NULL); -INSERT INTO CostVariable VALUES('linkville',2000,'CCS',2000,10.0,NULL,NULL); -INSERT INTO CostVariable VALUES('linkville',2000,'FAKE_SOURCE',2000,0.0,NULL,NULL); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('linkville',2000,'CO2_CAP',1000.0,NULL,NULL); -INSERT INTO Demand VALUES('linkville',2000,'ELC',10.0,NULL,NULL); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('linkville','summer','day','ELC',0.5,''); -INSERT INTO DemandSpecificDistribution VALUES('linkville','winter','day','ELC',0.5,''); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('linkville','ETHOS','MINE',2000,'NGA',1.0,''); -INSERT INTO Efficiency VALUES('linkville','ETHOS','CCS',2000,'CO2_CAP',1.0,'capture eff'); -INSERT INTO Efficiency VALUES('linkville','ETHOS','FAKE_SOURCE',2000,'CO2_CAP',1.0,''); -INSERT INTO Efficiency VALUES('linkville','NGA','PLANT',2000,'ELC',0.5,NULL); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('linkville','CO2','NGA','PLANT',2000,'ELC',-3.0,'',''); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('linkville','CCS',100.0,''); -INSERT INTO LifetimeTech VALUES('linkville','PLANT',100.0,''); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -INSERT INTO LinkedTech VALUES('linkville','PLANT','CO2','CCS',NULL); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('linkville',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('summer','day',0.5,'# S-D'); -INSERT INTO TimeSegmentFraction VALUES('winter','day',0.5,'# W-D'); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'day'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(0,1995,'e'); -INSERT INTO TimePeriod VALUES(1,2000,'f'); -INSERT INTO TimePeriod VALUES(2,2005,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'summer'); -INSERT INTO TimeSeason VALUES(2,'winter'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('PLANT','p','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -INSERT INTO Technology VALUES('CCS','r','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -INSERT INTO Technology VALUES('MINE','r','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); -INSERT INTO Technology VALUES('FAKE_SOURCE','r','supply',NULL,NULL,1,0,0,0,0,0,0,0,NULL); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -CREATE TABLE MyopicEfficiency -( - base_year integer, - region text, - input_comm text, - tech text, - vintage integer, - output_comm text, - efficiency real, - lifetime integer, - - FOREIGN KEY (tech) REFERENCES Technology (tech), - PRIMARY KEY (region, input_comm, tech, vintage, output_comm) -); -CREATE INDEX region_tech_vintage ON MyopicEfficiency (region, tech, vintage); -COMMIT; +REPLACE INTO "commodity" VALUES('ELC', 'd', 'electricity', NULL); +REPLACE INTO "commodity" VALUES('NGA', 'p', 'natural gas', NULL); +REPLACE INTO "commodity" VALUES('CO2', 'e', 'CO2 emission', NULL); +REPLACE INTO "commodity" VALUES('CO2_CAP', 'd', 'captured CO2', NULL); +REPLACE INTO "commodity" VALUES('ETHOS', 's', 'source', NULL); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "cost_emission" VALUES('linkville',2000,'CO2',2.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('linkville','PLANT',2000,100.0,'',''); +REPLACE INTO "cost_invest" VALUES('linkville','CCS',2000,50.0,'',''); +REPLACE INTO "cost_variable" VALUES('linkville',2000,'PLANT',2000,10.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('linkville',2000,'CCS',2000,10.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('linkville',2000,'FAKE_SOURCE',2000,0.0,NULL,NULL); +REPLACE INTO "demand" VALUES('linkville',2000,'CO2_CAP',1000.0,NULL,NULL); +REPLACE INTO "demand" VALUES('linkville',2000,'ELC',10.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('linkville', 'ETHOS', 'MINE', 2000, 'NGA', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('linkville', 'ETHOS', 'CCS', 2000, 'CO2_CAP', 1.0, NULL, 'capture eff'); +REPLACE INTO "efficiency" VALUES('linkville', 'ETHOS', 'FAKE_SOURCE', 2000, 'CO2_CAP', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('linkville', 'NGA', 'PLANT', 2000, 'ELC', 0.5, NULL, NULL); +REPLACE INTO "emission_activity" VALUES('linkville','CO2','NGA','PLANT',2000,'ELC',-3.0,'',''); +REPLACE INTO "lifetime_tech" VALUES('linkville', 'CCS', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('linkville', 'PLANT', 100.0, NULL, ''); +REPLACE INTO "linked_tech" VALUES('linkville','PLANT','CO2','CCS',NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('linkville',NULL); +REPLACE INTO "sector_label" VALUES('supply',NULL); +REPLACE INTO "sector_label" VALUES('electric',NULL); +REPLACE INTO "sector_label" VALUES('transport',NULL); +REPLACE INTO "sector_label" VALUES('commercial',NULL); +REPLACE INTO "sector_label" VALUES('residential',NULL); +REPLACE INTO "sector_label" VALUES('industrial',NULL); +REPLACE INTO "technology" VALUES('PLANT','p','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('CCS','p','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('MINE','p','supply',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('FAKE_SOURCE','p','supply',NULL,NULL,1,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(1,'day',24,NULL); +REPLACE INTO "time_period" VALUES(0,1995,'e'); +REPLACE INTO "time_period" VALUES(1,2000,'f'); +REPLACE INTO "time_period" VALUES(2,2005,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(1,'summer',0.5,NULL); +REPLACE INTO "time_season" VALUES(2,'winter',0.5,NULL); diff --git a/tests/testing_data/storageville.sql b/tests/testing_data/storageville.sql index 0cf789f65..e643378ba 100644 --- a/tests/testing_data/storageville.sql +++ b/tests/testing_data/storageville.sql @@ -1,966 +1,77 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -INSERT INTO MetaData VALUES('myopic_base_year',2000,''); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05000000000000000277,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05000000000000000277,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO CapacityToActivity VALUES('electricville','bulbs',1.0,''); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ELC','p','# electricity'); -INSERT INTO Commodity VALUES('HYD','p','# water'); -INSERT INTO Commodity VALUES('co2','e','#CO2 emissions'); -INSERT INTO Commodity VALUES('RL','d','# residential lighting'); -INSERT INTO Commodity VALUES('earth','p','# the source of stuff'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -INSERT INTO CommodityType VALUES('s','source commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostFixed VALUES('electricville',2025,'EH',2025,100.0,'',''); -INSERT INTO CostFixed VALUES('electricville',2025,'batt',2025,1.0,'',''); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('electricville','EH',2025,100.0,'',''); -INSERT INTO CostInvest VALUES('electricville','batt',2025,1.0,NULL,NULL); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('electricville',2025,'EH',2025,1000.0,'',''); -INSERT INTO CostVariable VALUES('electricville',2025,'batt',2025,1.0,'',''); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('electricville',2025,'RL',100.0,'',''); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s1','d1','RL',0.07499999999999999723,''); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s1','d2','RL',0.07499999999999999723,''); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s1','d3','RL',0.07499999999999999723,NULL); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s2','d1','RL',0.07499999999999999723,NULL); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s2','d2','RL',0.07499999999999999723,NULL); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s2','d3','RL',0.07499999999999999723,NULL); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s1','d4','RL',0.07499999999999999723,NULL); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s1','d5','RL',0.2000000000000000111,NULL); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s2','d4','RL',0.2000000000000000111,NULL); -INSERT INTO DemandSpecificDistribution VALUES('electricville','s2','d5','RL',0.07499999999999999723,NULL); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('electricville','HYD','EH',2025,'ELC',1.0,NULL); -INSERT INTO Efficiency VALUES('electricville','ELC','bulbs',2025,'RL',1.0,NULL); -INSERT INTO Efficiency VALUES('electricville','earth','well',2025,'HYD',1.0,'water source'); -INSERT INTO Efficiency VALUES('electricville','ELC','batt',2025,'ELC',0.75,NULL); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('electricville','co2','HYD','EH',2025,'ELC',0.02000000000000000041,NULL,NULL); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('electricville','EH',60.0,''); -INSERT INTO LifetimeTech VALUES('electricville','bulbs',100.0,'super LED!'); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxCapacity VALUES('electricville',2025,'EH',200.0,'',''); -INSERT INTO MaxCapacity VALUES('electricville',2025,'batt',100.0,'',''); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinCapacity VALUES('electricville',2025,'EH',0.1000000000000000055,'',''); -INSERT INTO MinCapacity VALUES('electricville',2025,'batt',0.1000000000000000055,'',''); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('electricville',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('s1','d3',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s2','d1',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s2','d2',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s2','d3',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s1','d1',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s1','d2',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s1','d4',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s1','d5',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s2','d4',0.1000000000000000055,NULL); -INSERT INTO TimeSegmentFraction VALUES('s2','d5',0.1000000000000000055,NULL); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO StorageDuration VALUES('electricville','batt',10.0,NULL); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'d1'); -INSERT INTO TimeOfDay VALUES(2,'d2'); -INSERT INTO TimeOfDay VALUES(3,'d3'); -INSERT INTO TimeOfDay VALUES(4,'d4'); -INSERT INTO TimeOfDay VALUES(5,'d5'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,2020,'e'); -INSERT INTO TimePeriod VALUES(2,2025,'f'); -INSERT INTO TimePeriod VALUES(3,2030,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'s1'); -INSERT INTO TimeSeason VALUES(2,'s2'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('well','r','supply','water','',0,0,0,0,0,0,0,0,'plain old water'); -INSERT INTO Technology VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); -INSERT INTO Technology VALUES('EH','pb','electric','hydro','',0,0,0,0,0,0,0,0,'hydro power electric plant'); -INSERT INTO Technology VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,0,'big battery'); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -COMMIT; +REPLACE INTO "capacity_to_activity" VALUES('electricville', 'bulbs', 1.0, NULL, ''); +REPLACE INTO "commodity" VALUES('ELC', 'p', '# electricity', NULL); +REPLACE INTO "commodity" VALUES('HYD', 'p', '# water', NULL); +REPLACE INTO "commodity" VALUES('co2', 'e', '#CO2 emissions', NULL); +REPLACE INTO "commodity" VALUES('RL', 'd', '# residential lighting', NULL); +REPLACE INTO "commodity" VALUES('earth', 's', '# the source of stuff', NULL); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "cost_fixed" VALUES('electricville',2025,'EH',2025,100.0,'',''); +REPLACE INTO "cost_fixed" VALUES('electricville',2025,'batt',2025,1.0,'',''); +REPLACE INTO "cost_invest" VALUES('electricville','EH',2025,100.0,'',''); +REPLACE INTO "cost_invest" VALUES('electricville','batt',2025,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('electricville',2025,'EH',2025,1000.0,'',''); +REPLACE INTO "cost_variable" VALUES('electricville',2025,'batt',2025,1.0,'',''); +REPLACE INTO "demand" VALUES('electricville',2025,'RL',100.0,'',''); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s1','d1','RL',0.075,''); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s1','d2','RL',0.075,''); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s1','d3','RL',0.075,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s2','d1','RL',0.075,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s2','d2','RL',0.075,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s2','d3','RL',0.075,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s1','d4','RL',0.075,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s1','d5','RL',0.2,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s2','d4','RL',0.2,NULL); +REPLACE INTO "demand_specific_distribution" VALUES('electricville',2025,'s2','d5','RL',0.075,NULL); +REPLACE INTO "efficiency" VALUES('electricville', 'HYD', 'EH', 2025, 'ELC', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('electricville', 'ELC', 'bulbs', 2025, 'RL', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('electricville', 'earth', 'well', 2025, 'HYD', 1.0, NULL, 'water source'); +REPLACE INTO "efficiency" VALUES('electricville', 'ELC', 'batt', 2025, 'ELC', 0.75, NULL, NULL); +REPLACE INTO "emission_activity" VALUES('electricville','co2','HYD','EH',2025,'ELC',0.02,NULL,NULL); +REPLACE INTO "lifetime_tech" VALUES('electricville', 'EH', 60.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('electricville', 'bulbs', 100.0, NULL, 'super LED!'); +REPLACE INTO "limit_capacity" VALUES('electricville',2025,'EH','ge',0.1,'',''); +REPLACE INTO "limit_capacity" VALUES('electricville',2025,'batt','ge',0.1,'',''); +REPLACE INTO "limit_capacity" VALUES('electricville',2025,'EH','le',200.0,'',''); +REPLACE INTO "limit_capacity" VALUES('electricville',2025,'batt','le',100.0,'',''); +REPLACE INTO "limit_storage_level_fraction" VALUES('electricville','s1','d1','batt','e',0.5,NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('electricville',NULL); +REPLACE INTO "sector_label" VALUES('supply',NULL); +REPLACE INTO "sector_label" VALUES('electric',NULL); +REPLACE INTO "sector_label" VALUES('transport',NULL); +REPLACE INTO "sector_label" VALUES('commercial',NULL); +REPLACE INTO "sector_label" VALUES('residential',NULL); +REPLACE INTO "sector_label" VALUES('industrial',NULL); +REPLACE INTO "storage_duration" VALUES('electricville','batt',10.0,NULL); +REPLACE INTO "technology" VALUES('well','p','supply','water','',0,0,0,0,0,0,0,0,'plain old water'); +REPLACE INTO "technology" VALUES('bulbs','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); +REPLACE INTO "technology" VALUES('EH','pb','electric','hydro','',0,0,0,0,0,0,0,0,'hydro power electric plant'); +REPLACE INTO "technology" VALUES('batt','ps','electric','electric','',0,0,0,0,0,0,0,0,'big battery'); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(1,'d1', 4.8, NULL); +REPLACE INTO "time_of_day" VALUES(2,'d2', 4.8, NULL); +REPLACE INTO "time_of_day" VALUES(3,'d3', 4.8, NULL); +REPLACE INTO "time_of_day" VALUES(4,'d4', 4.8, NULL); +REPLACE INTO "time_of_day" VALUES(5,'d5', 4.8, NULL); +REPLACE INTO "time_period" VALUES(1,2020,'e'); +REPLACE INTO "time_period" VALUES(2,2025,'f'); +REPLACE INTO "time_period" VALUES(3,2030,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(1,'s1',0.5,NULL); +REPLACE INTO "time_season" VALUES(2,'s2',0.5,NULL); diff --git a/tests/testing_data/survival_curve.sql b/tests/testing_data/survival_curve.sql new file mode 100644 index 000000000..a0f32dc16 --- /dev/null +++ b/tests/testing_data/survival_curve.sql @@ -0,0 +1,172 @@ +REPLACE INTO "commodity" VALUES('source', 's', NULL, NULL); +REPLACE INTO "commodity" VALUES('demand', 'd', NULL, NULL); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "cost_fixed" VALUES('region',2025,'tech_ancient',1994,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2025,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2030,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2035,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2040,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2025,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2030,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2035,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2040,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2045,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2050,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2030,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2035,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2040,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2045,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2050,'tech_future',2050,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2035,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2040,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2045,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2050,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2040,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2045,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2050,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2045,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2050,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_fixed" VALUES('region',2050,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('region','tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('region','tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('region','tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('region','tech_future',2040,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('region','tech_future',2045,1.0,NULL,NULL); +REPLACE INTO "cost_invest" VALUES('region','tech_future',2050,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2025,'tech_ancient',1994,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2025,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2030,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2035,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2040,'tech_old',2010,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2025,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2030,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2035,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2040,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2045,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2050,'tech_current',2025,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2030,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2035,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2040,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2045,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2050,'tech_future',2050,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2035,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2040,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2045,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2050,'tech_future',2045,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2040,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2045,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2050,'tech_future',2040,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2045,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2050,'tech_future',2035,1.0,NULL,NULL); +REPLACE INTO "cost_variable" VALUES('region',2050,'tech_future',2030,1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2025,'demand',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2030,'demand',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2035,'demand',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2040,'demand',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2045,'demand',1.0,NULL,NULL); +REPLACE INTO "demand" VALUES('region',2050,'demand',1.0,NULL,NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_ancient', 1994, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_old', 2010, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_current', 2025, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_future', 2030, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_future', 2035, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_future', 2040, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_future', 2045, 'demand', 1.0, NULL, NULL); +REPLACE INTO "efficiency" VALUES('region', 'source', 'tech_future', 2050, 'demand', 1.0, NULL, NULL); +REPLACE INTO "existing_capacity" VALUES('region','tech_ancient',1994,3.0,NULL,NULL); +REPLACE INTO "existing_capacity" VALUES('region','tech_old',2010,0.7,NULL,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',1994,'tech_ancient',1994,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',1999,'tech_ancient',1994,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2004,'tech_ancient',1994,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2009,'tech_ancient',1994,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2014,'tech_ancient',1994,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2019,'tech_ancient',1994,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2029,'tech_ancient',1994,0.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2010,'tech_old',2010,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2015,'tech_old',2010,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2020,'tech_old',2010,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2025,'tech_old',2010,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2030,'tech_old',2010,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2035,'tech_old',2010,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2045,'tech_old',2010,0.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2025,'tech_current',2025,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2030,'tech_current',2025,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2035,'tech_current',2025,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2040,'tech_current',2025,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2045,'tech_current',2025,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2050,'tech_current',2025,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2060,'tech_current',2025,0.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2030,'tech_future',2030,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2035,'tech_future',2030,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2040,'tech_future',2030,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2045,'tech_future',2030,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2050,'tech_future',2030,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2055,'tech_future',2030,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2065,'tech_future',2030,0.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2035,'tech_future',2035,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2040,'tech_future',2035,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2045,'tech_future',2035,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2050,'tech_future',2035,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2055,'tech_future',2035,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2060,'tech_future',2035,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2070,'tech_future',2035,0.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2040,'tech_future',2040,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2045,'tech_future',2040,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2050,'tech_future',2040,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2055,'tech_future',2040,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2060,'tech_future',2040,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2065,'tech_future',2040,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2075,'tech_future',2040,0.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2045,'tech_future',2045,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2050,'tech_future',2045,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2055,'tech_future',2045,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2060,'tech_future',2045,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2065,'tech_future',2045,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2070,'tech_future',2045,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2080,'tech_future',2045,0.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2050,'tech_future',2050,1.0,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2055,'tech_future',2050,0.97,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2060,'tech_future',2050,8.80000000000000115e-01,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2065,'tech_future',2050,0.62,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2070,'tech_future',2050,0.27,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2075,'tech_future',2050,0.08,NULL); +REPLACE INTO "lifetime_survival_curve" VALUES('region',2085,'tech_future',2050,0.0,NULL); +REPLACE INTO "lifetime_tech" VALUES('region', 'tech_ancient', 35.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('region', 'tech_old', 35.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('region', 'tech_current', 35.0, NULL, NULL); +REPLACE INTO "lifetime_tech" VALUES('region', 'tech_future', 35.0, NULL, NULL); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,'Discount Rate for future costs'); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('region',NULL); +REPLACE INTO "technology" VALUES('tech_ancient','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('tech_old','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('tech_current','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology" VALUES('tech_future','p','energy',NULL,NULL,0,0,0,0,0,0,0,0,NULL); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(0,'d',24,NULL); +REPLACE INTO "time_period" VALUES(-2,1994,'e'); +REPLACE INTO "time_period" VALUES(-1,2010,'e'); +REPLACE INTO "time_period" VALUES(0,2025,'f'); +REPLACE INTO "time_period" VALUES(1,2030,'f'); +REPLACE INTO "time_period" VALUES(2,2035,'f'); +REPLACE INTO "time_period" VALUES(3,2040,'f'); +REPLACE INTO "time_period" VALUES(4,2045,'f'); +REPLACE INTO "time_period" VALUES(5,2050,'f'); +REPLACE INTO "time_period" VALUES(6,2055,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(0,'s',1.0,NULL); diff --git a/tests/testing_data/test_system.sql b/tests/testing_data/test_system.sql index d2a9b2939..a50226c85 100644 --- a/tests/testing_data/test_system.sql +++ b/tests/testing_data/test_system.sql @@ -1,1372 +1,488 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('myopic_base_year',2000,'Base Year for Myopic Analysis'); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorTech VALUES('R1','spring','day','E_SOLPV',0.5999999999999999778,''); -INSERT INTO CapacityFactorTech VALUES('R1','spring','night','E_SOLPV',0.0,''); -INSERT INTO CapacityFactorTech VALUES('R1','summer','day','E_SOLPV',0.5999999999999999778,''); -INSERT INTO CapacityFactorTech VALUES('R1','summer','night','E_SOLPV',0.0,''); -INSERT INTO CapacityFactorTech VALUES('R1','fall','day','E_SOLPV',0.5999999999999999778,''); -INSERT INTO CapacityFactorTech VALUES('R1','fall','night','E_SOLPV',0.0,''); -INSERT INTO CapacityFactorTech VALUES('R1','winter','day','E_SOLPV',0.5999999999999999778,''); -INSERT INTO CapacityFactorTech VALUES('R1','winter','night','E_SOLPV',0.0,''); -INSERT INTO CapacityFactorTech VALUES('R2','spring','day','E_SOLPV',0.4799999999999999823,''); -INSERT INTO CapacityFactorTech VALUES('R2','spring','night','E_SOLPV',0.0,''); -INSERT INTO CapacityFactorTech VALUES('R2','summer','day','E_SOLPV',0.4799999999999999823,''); -INSERT INTO CapacityFactorTech VALUES('R2','summer','night','E_SOLPV',0.0,''); -INSERT INTO CapacityFactorTech VALUES('R2','fall','day','E_SOLPV',0.4799999999999999823,''); -INSERT INTO CapacityFactorTech VALUES('R2','fall','night','E_SOLPV',0.0,''); -INSERT INTO CapacityFactorTech VALUES('R2','winter','day','E_SOLPV',0.4799999999999999823,''); -INSERT INTO CapacityFactorTech VALUES('R2','winter','night','E_SOLPV',0.0,''); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO CapacityToActivity VALUES('R1','S_IMPETH',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','S_IMPOIL',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','S_IMPNG',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','S_IMPURN',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','S_OILREF',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','E_NGCC',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R1','E_SOLPV',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R1','E_BATT',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R1','E_NUCLEAR',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R1','T_BLND',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','T_DSL',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','T_GSL',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','T_EV',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','R_EH',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1','R_NGH',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','S_IMPETH',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','S_IMPOIL',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','S_IMPNG',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','S_IMPURN',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','S_OILREF',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','E_NGCC',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R2','E_SOLPV',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R2','E_BATT',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R2','E_NUCLEAR',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R2','T_BLND',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','T_DSL',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','T_GSL',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','T_EV',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','R_EH',1.0,''); -INSERT INTO CapacityToActivity VALUES('R2','R_NGH',1.0,''); -INSERT INTO CapacityToActivity VALUES('R1-R2','E_TRANS',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('R2-R1','E_TRANS',31.53999999999999915,''); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ethos','s','dummy commodity to supply inputs (makes graph easier to read)'); -INSERT INTO Commodity VALUES('OIL','p','crude oil'); -INSERT INTO Commodity VALUES('NG','p','natural gas'); -INSERT INTO Commodity VALUES('URN','p','uranium'); -INSERT INTO Commodity VALUES('ETH','p','ethanol'); -INSERT INTO Commodity VALUES('SOL','p','solar insolation'); -INSERT INTO Commodity VALUES('GSL','p','gasoline'); -INSERT INTO Commodity VALUES('DSL','p','diesel'); -INSERT INTO Commodity VALUES('ELC','p','electricity'); -INSERT INTO Commodity VALUES('E10','p','gasoline blend with 10% ethanol'); -INSERT INTO Commodity VALUES('VMT','d','travel demand for vehicle-miles traveled'); -INSERT INTO Commodity VALUES('RH','d','demand for residential heating'); -INSERT INTO Commodity VALUES('CO2','e','CO2 emissions commodity'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('s','source commodity'); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostFixed VALUES('R1',2020,'E_NGCC',2020,30.60000000000000142,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_NGCC',2020,9.77999999999999937,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_NGCC',2025,9.77999999999999937,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_NGCC',2020,9.77999999999999937,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_NGCC',2025,9.77999999999999937,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_NGCC',2030,9.77999999999999937,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2020,'E_SOLPV',2020,10.40000000000000035,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_SOLPV',2020,10.40000000000000035,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_SOLPV',2025,9.099999999999999645,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_SOLPV',2020,10.40000000000000035,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_SOLPV',2025,9.099999999999999645,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_SOLPV',2030,9.099999999999999645,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2020,'E_NUCLEAR',2020,98.0999999999999944,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_NUCLEAR',2020,98.0999999999999944,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_NUCLEAR',2025,98.0999999999999944,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_NUCLEAR',2020,98.0999999999999944,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_NUCLEAR',2025,98.0999999999999944,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_NUCLEAR',2030,98.0999999999999944,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2020,'E_BATT',2020,7.049999999999999823,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_BATT',2020,7.049999999999999823,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2025,'E_BATT',2025,7.049999999999999823,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_BATT',2020,7.049999999999999823,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_BATT',2025,7.049999999999999823,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R1',2030,'E_BATT',2030,7.049999999999999823,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2020,'E_NGCC',2020,24.48000000000000042,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_NGCC',2020,7.823999999999999844,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_NGCC',2025,7.823999999999999844,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_NGCC',2020,7.823999999999999844,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_NGCC',2025,7.823999999999999844,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_NGCC',2030,7.823999999999999844,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2020,'E_SOLPV',2020,8.320000000000000284,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_SOLPV',2020,8.320000000000000284,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_SOLPV',2025,7.280000000000000248,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_SOLPV',2020,8.320000000000000284,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_SOLPV',2025,7.280000000000000248,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_SOLPV',2030,7.280000000000000248,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2020,'E_NUCLEAR',2020,78.48000000000000397,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_NUCLEAR',2020,78.48000000000000397,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_NUCLEAR',2025,78.48000000000000397,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_NUCLEAR',2020,78.48000000000000397,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_NUCLEAR',2025,78.48000000000000397,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_NUCLEAR',2030,78.48000000000000397,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2020,'E_BATT',2020,5.639999999999999681,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_BATT',2020,5.639999999999999681,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2025,'E_BATT',2025,5.639999999999999681,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_BATT',2020,5.639999999999999681,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_BATT',2025,5.639999999999999681,'$M/GWyr',''); -INSERT INTO CostFixed VALUES('R2',2030,'E_BATT',2030,5.639999999999999681,'$M/GWyr',''); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('R1','E_NGCC',2020,1050.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_NGCC',2025,1025.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_NGCC',2030,1000.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_SOLPV',2020,900.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_SOLPV',2025,560.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_SOLPV',2030,800.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_NUCLEAR',2020,6145.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_NUCLEAR',2025,6045.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_NUCLEAR',2030,5890.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_BATT',2020,1150.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_BATT',2025,720.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','E_BATT',2030,480.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R1','T_GSL',2020,2570.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_GSL',2025,2700.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_GSL',2030,2700.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_DSL',2020,2715.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_DSL',2025,2810.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_DSL',2030,2810.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_EV',2020,3100.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_EV',2025,3030.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','T_EV',2030,2925.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R1','R_EH',2020,4.099999999999999644,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R1','R_EH',2025,4.099999999999999644,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R1','R_EH',2030,4.099999999999999644,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R1','R_NGH',2020,7.599999999999999645,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R1','R_NGH',2025,7.599999999999999645,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R1','R_NGH',2030,7.599999999999999645,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R2','E_NGCC',2020,840.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_NGCC',2025,820.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_NGCC',2030,800.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_SOLPV',2020,720.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_SOLPV',2025,448.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_SOLPV',2030,640.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_NUCLEAR',2020,4916.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_NUCLEAR',2025,4836.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_NUCLEAR',2030,4712.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_BATT',2020,920.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_BATT',2025,576.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','E_BATT',2030,384.0,'$M/GW',''); -INSERT INTO CostInvest VALUES('R2','T_GSL',2020,2056.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_GSL',2025,2160.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_GSL',2030,2160.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_DSL',2020,2172.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_DSL',2025,2248.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_DSL',2030,2248.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_EV',2020,2480.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_EV',2025,2424.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','T_EV',2030,2340.0,'$/bvmt/yr',''); -INSERT INTO CostInvest VALUES('R2','R_EH',2020,3.279999999999999805,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R2','R_EH',2025,3.279999999999999805,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R2','R_EH',2030,3.279999999999999805,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R2','R_NGH',2020,6.080000000000000071,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R2','R_NGH',2025,6.080000000000000071,'$/PJ/yr',''); -INSERT INTO CostInvest VALUES('R2','R_NGH',2030,6.080000000000000071,'$/PJ/yr',''); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('R1',2020,'S_IMPETH',2020,32.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'S_IMPETH',2020,32.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'S_IMPETH',2020,32.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2020,'S_IMPOIL',2020,20.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'S_IMPOIL',2020,20.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'S_IMPOIL',2020,20.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2020,'S_IMPNG',2020,4.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'S_IMPNG',2020,4.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'S_IMPNG',2020,4.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2020,'S_OILREF',2020,1.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'S_OILREF',2020,1.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'S_OILREF',2020,1.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2020,'E_NGCC',2020,1.600000000000000088,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'E_NGCC',2020,1.600000000000000088,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'E_NGCC',2025,1.699999999999999956,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'E_NGCC',2020,1.600000000000000088,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'E_NGCC',2025,1.699999999999999956,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'E_NGCC',2030,1.800000000000000044,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2020,'E_NUCLEAR',2020,0.2399999999999999912,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'E_NUCLEAR',2020,0.2399999999999999912,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2025,'E_NUCLEAR',2025,0.25,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'E_NUCLEAR',2020,0.2399999999999999912,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'E_NUCLEAR',2025,0.25,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1',2030,'E_NUCLEAR',2030,0.2600000000000000088,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2020,'S_IMPETH',2020,25.60000000000000142,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'S_IMPETH',2020,25.60000000000000142,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'S_IMPETH',2020,25.60000000000000142,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2020,'S_IMPOIL',2020,16.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'S_IMPOIL',2020,16.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'S_IMPOIL',2020,16.0,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2020,'S_IMPNG',2020,3.200000000000000177,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'S_IMPNG',2020,3.200000000000000177,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'S_IMPNG',2020,3.200000000000000177,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2020,'S_OILREF',2020,0.8000000000000000444,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'S_OILREF',2020,0.8000000000000000444,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'S_OILREF',2020,0.8000000000000000444,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2020,'E_NGCC',2020,1.280000000000000026,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'E_NGCC',2020,1.280000000000000026,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'E_NGCC',2025,1.360000000000000097,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'E_NGCC',2020,1.280000000000000026,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'E_NGCC',2025,1.360000000000000097,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'E_NGCC',2030,1.439999999999999947,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2020,'E_NUCLEAR',2020,0.1920000000000000039,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'E_NUCLEAR',2020,0.1920000000000000039,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2025,'E_NUCLEAR',2025,0.2000000000000000111,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'E_NUCLEAR',2020,0.1920000000000000039,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'E_NUCLEAR',2025,0.2000000000000000111,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2',2030,'E_NUCLEAR',2030,0.2079999999999999905,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1-R2',2020,'E_TRANS',2015,0.1000000000000000055,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1-R2',2025,'E_TRANS',2015,0.1000000000000000055,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R1-R2',2030,'E_TRANS',2015,0.1000000000000000055,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2-R1',2020,'E_TRANS',2015,0.1000000000000000055,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2-R1',2025,'E_TRANS',2015,0.1000000000000000055,'$M/PJ',''); -INSERT INTO CostVariable VALUES('R2-R1',2030,'E_TRANS',2015,0.1000000000000000055,'$M/PJ',''); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('R1',2020,'RH',30.0,'',''); -INSERT INTO Demand VALUES('R1',2025,'RH',33.0,'',''); -INSERT INTO Demand VALUES('R1',2030,'RH',36.0,'',''); -INSERT INTO Demand VALUES('R1',2020,'VMT',84.0,'',''); -INSERT INTO Demand VALUES('R1',2025,'VMT',91.0,'',''); -INSERT INTO Demand VALUES('R1',2030,'VMT',98.0,'',''); -INSERT INTO Demand VALUES('R2',2020,'RH',70.0,'',''); -INSERT INTO Demand VALUES('R2',2025,'RH',77.0,'',''); -INSERT INTO Demand VALUES('R2',2030,'RH',84.0,'',''); -INSERT INTO Demand VALUES('R2',2020,'VMT',36.0,'',''); -INSERT INTO Demand VALUES('R2',2025,'VMT',39.0,'',''); -INSERT INTO Demand VALUES('R2',2030,'VMT',42.0,'',''); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('R1','spring','day','RH',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('R1','spring','night','RH',0.1000000000000000055,''); -INSERT INTO DemandSpecificDistribution VALUES('R1','summer','day','RH',0.0,''); -INSERT INTO DemandSpecificDistribution VALUES('R1','summer','night','RH',0.0,''); -INSERT INTO DemandSpecificDistribution VALUES('R1','fall','day','RH',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('R1','fall','night','RH',0.1000000000000000055,''); -INSERT INTO DemandSpecificDistribution VALUES('R1','winter','day','RH',0.2999999999999999889,''); -INSERT INTO DemandSpecificDistribution VALUES('R1','winter','night','RH',0.4000000000000000222,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','spring','day','RH',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','spring','night','RH',0.1000000000000000055,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','summer','day','RH',0.0,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','summer','night','RH',0.0,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','fall','day','RH',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','fall','night','RH',0.1000000000000000055,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','winter','day','RH',0.2999999999999999889,''); -INSERT INTO DemandSpecificDistribution VALUES('R2','winter','night','RH',0.4000000000000000222,''); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('R1','ethos','S_IMPETH',2020,'ETH',1.0,''); -INSERT INTO Efficiency VALUES('R1','ethos','S_IMPOIL',2020,'OIL',1.0,''); -INSERT INTO Efficiency VALUES('R1','ethos','S_IMPNG',2020,'NG',1.0,''); -INSERT INTO Efficiency VALUES('R1','ethos','S_IMPURN',2020,'URN',1.0,''); -INSERT INTO Efficiency VALUES('R1','OIL','S_OILREF',2020,'GSL',1.0,''); -INSERT INTO Efficiency VALUES('R1','OIL','S_OILREF',2020,'DSL',1.0,''); -INSERT INTO Efficiency VALUES('R1','ETH','T_BLND',2020,'E10',1.0,''); -INSERT INTO Efficiency VALUES('R1','GSL','T_BLND',2020,'E10',1.0,''); -INSERT INTO Efficiency VALUES('R1','NG','E_NGCC',2020,'ELC',0.5500000000000000444,''); -INSERT INTO Efficiency VALUES('R1','NG','E_NGCC',2025,'ELC',0.5500000000000000444,''); -INSERT INTO Efficiency VALUES('R1','NG','E_NGCC',2030,'ELC',0.5500000000000000444,''); -INSERT INTO Efficiency VALUES('R1','SOL','E_SOLPV',2020,'ELC',1.0,''); -INSERT INTO Efficiency VALUES('R1','SOL','E_SOLPV',2025,'ELC',1.0,''); -INSERT INTO Efficiency VALUES('R1','SOL','E_SOLPV',2030,'ELC',1.0,''); -INSERT INTO Efficiency VALUES('R1','URN','E_NUCLEAR',2015,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R1','URN','E_NUCLEAR',2020,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R1','URN','E_NUCLEAR',2025,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R1','URN','E_NUCLEAR',2030,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R1','ELC','E_BATT',2020,'ELC',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R1','ELC','E_BATT',2025,'ELC',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R1','ELC','E_BATT',2030,'ELC',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R1','E10','T_GSL',2020,'VMT',0.25,''); -INSERT INTO Efficiency VALUES('R1','E10','T_GSL',2025,'VMT',0.25,''); -INSERT INTO Efficiency VALUES('R1','E10','T_GSL',2030,'VMT',0.25,''); -INSERT INTO Efficiency VALUES('R1','DSL','T_DSL',2020,'VMT',0.2999999999999999889,''); -INSERT INTO Efficiency VALUES('R1','DSL','T_DSL',2025,'VMT',0.2999999999999999889,''); -INSERT INTO Efficiency VALUES('R1','DSL','T_DSL',2030,'VMT',0.2999999999999999889,''); -INSERT INTO Efficiency VALUES('R1','ELC','T_EV',2020,'VMT',0.8900000000000000133,''); -INSERT INTO Efficiency VALUES('R1','ELC','T_EV',2025,'VMT',0.8900000000000000133,''); -INSERT INTO Efficiency VALUES('R1','ELC','T_EV',2030,'VMT',0.8900000000000000133,''); -INSERT INTO Efficiency VALUES('R1','ELC','R_EH',2020,'RH',1.0,''); -INSERT INTO Efficiency VALUES('R1','ELC','R_EH',2025,'RH',1.0,''); -INSERT INTO Efficiency VALUES('R1','ELC','R_EH',2030,'RH',1.0,''); -INSERT INTO Efficiency VALUES('R1','NG','R_NGH',2020,'RH',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R1','NG','R_NGH',2025,'RH',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R1','NG','R_NGH',2030,'RH',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R2','ethos','S_IMPETH',2020,'ETH',1.0,''); -INSERT INTO Efficiency VALUES('R2','ethos','S_IMPOIL',2020,'OIL',1.0,''); -INSERT INTO Efficiency VALUES('R2','ethos','S_IMPNG',2020,'NG',1.0,''); -INSERT INTO Efficiency VALUES('R2','ethos','S_IMPURN',2020,'URN',1.0,''); -INSERT INTO Efficiency VALUES('R2','OIL','S_OILREF',2020,'GSL',1.0,''); -INSERT INTO Efficiency VALUES('R2','OIL','S_OILREF',2020,'DSL',1.0,''); -INSERT INTO Efficiency VALUES('R2','ETH','T_BLND',2020,'E10',1.0,''); -INSERT INTO Efficiency VALUES('R2','GSL','T_BLND',2020,'E10',1.0,''); -INSERT INTO Efficiency VALUES('R2','NG','E_NGCC',2020,'ELC',0.5500000000000000444,''); -INSERT INTO Efficiency VALUES('R2','NG','E_NGCC',2025,'ELC',0.5500000000000000444,''); -INSERT INTO Efficiency VALUES('R2','NG','E_NGCC',2030,'ELC',0.5500000000000000444,''); -INSERT INTO Efficiency VALUES('R2','SOL','E_SOLPV',2020,'ELC',1.0,''); -INSERT INTO Efficiency VALUES('R2','SOL','E_SOLPV',2025,'ELC',1.0,''); -INSERT INTO Efficiency VALUES('R2','SOL','E_SOLPV',2030,'ELC',1.0,''); -INSERT INTO Efficiency VALUES('R2','URN','E_NUCLEAR',2015,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R2','URN','E_NUCLEAR',2020,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R2','URN','E_NUCLEAR',2025,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R2','URN','E_NUCLEAR',2030,'ELC',0.4000000000000000222,''); -INSERT INTO Efficiency VALUES('R2','ELC','E_BATT',2020,'ELC',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R2','ELC','E_BATT',2025,'ELC',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R2','ELC','E_BATT',2030,'ELC',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R2','E10','T_GSL',2020,'VMT',0.25,''); -INSERT INTO Efficiency VALUES('R2','E10','T_GSL',2025,'VMT',0.25,''); -INSERT INTO Efficiency VALUES('R2','E10','T_GSL',2030,'VMT',0.25,''); -INSERT INTO Efficiency VALUES('R2','DSL','T_DSL',2020,'VMT',0.2999999999999999889,''); -INSERT INTO Efficiency VALUES('R2','DSL','T_DSL',2025,'VMT',0.2999999999999999889,''); -INSERT INTO Efficiency VALUES('R2','DSL','T_DSL',2030,'VMT',0.2999999999999999889,''); -INSERT INTO Efficiency VALUES('R2','ELC','T_EV',2020,'VMT',0.8900000000000000133,''); -INSERT INTO Efficiency VALUES('R2','ELC','T_EV',2025,'VMT',0.8900000000000000133,''); -INSERT INTO Efficiency VALUES('R2','ELC','T_EV',2030,'VMT',0.8900000000000000133,''); -INSERT INTO Efficiency VALUES('R2','ELC','R_EH',2020,'RH',1.0,''); -INSERT INTO Efficiency VALUES('R2','ELC','R_EH',2025,'RH',1.0,''); -INSERT INTO Efficiency VALUES('R2','ELC','R_EH',2030,'RH',1.0,''); -INSERT INTO Efficiency VALUES('R2','NG','R_NGH',2020,'RH',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R2','NG','R_NGH',2025,'RH',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R2','NG','R_NGH',2030,'RH',0.8499999999999999778,''); -INSERT INTO Efficiency VALUES('R1-R2','ELC','E_TRANS',2015,'ELC',0.9000000000000000222,''); -INSERT INTO Efficiency VALUES('R2-R1','ELC','E_TRANS',2015,'ELC',0.9000000000000000222,''); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('R1','CO2','ethos','S_IMPNG',2020,'NG',50.29999999999999716,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO EmissionActivity VALUES('R1','CO2','OIL','S_OILREF',2020,'GSL',67.20000000000000284,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO EmissionActivity VALUES('R1','CO2','OIL','S_OILREF',2020,'DSL',69.40000000000000569,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO EmissionActivity VALUES('R2','CO2','ethos','S_IMPNG',2020,'NG',50.29999999999999716,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO EmissionActivity VALUES('R2','CO2','OIL','S_OILREF',2020,'GSL',67.20000000000000284,'kT/PJ','taken from MIT Energy Fact Sheet'); -INSERT INTO EmissionActivity VALUES('R2','CO2','OIL','S_OILREF',2020,'DSL',69.40000000000000569,'kT/PJ','taken from MIT Energy Fact Sheet'); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO ExistingCapacity VALUES('R1','E_NUCLEAR',2015,0.07000000000000000667,'GW',''); -INSERT INTO ExistingCapacity VALUES('R2','E_NUCLEAR',2015,0.02999999999999999889,'GW',''); -INSERT INTO ExistingCapacity VALUES('R1-R2','E_TRANS',2015,10.0,'GW',''); -INSERT INTO ExistingCapacity VALUES('R2-R1','E_TRANS',2015,10.0,'GW',''); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LoanLifetimeTech VALUES('R1','S_IMPETH',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','S_IMPOIL',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','S_IMPNG',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','S_IMPURN',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','S_OILREF',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','E_NGCC',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','E_SOLPV',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','E_BATT',20.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','E_NUCLEAR',50.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','T_BLND',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','T_DSL',12.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','T_GSL',12.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','T_EV',12.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','R_EH',20.0,''); -INSERT INTO LoanLifetimeTech VALUES('R1','R_NGH',20.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','S_IMPETH',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','S_IMPOIL',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','S_IMPNG',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','S_IMPURN',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','S_OILREF',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','E_NGCC',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','E_SOLPV',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','E_BATT',20.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','E_NUCLEAR',50.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','T_BLND',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','T_DSL',12.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','T_GSL',12.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','T_EV',12.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','R_EH',20.0,''); -INSERT INTO LoanLifetimeTech VALUES('R2','R_NGH',20.0,''); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('R1','S_IMPETH',100.0,''); -INSERT INTO LifetimeTech VALUES('R1','S_IMPOIL',100.0,''); -INSERT INTO LifetimeTech VALUES('R1','S_IMPNG',100.0,''); -INSERT INTO LifetimeTech VALUES('R1','S_IMPURN',100.0,''); -INSERT INTO LifetimeTech VALUES('R1','S_OILREF',100.0,''); -INSERT INTO LifetimeTech VALUES('R1','E_NGCC',30.0,''); -INSERT INTO LifetimeTech VALUES('R1','E_SOLPV',30.0,''); -INSERT INTO LifetimeTech VALUES('R1','E_BATT',20.0,''); -INSERT INTO LifetimeTech VALUES('R1','E_NUCLEAR',50.0,''); -INSERT INTO LifetimeTech VALUES('R1','T_BLND',100.0,''); -INSERT INTO LifetimeTech VALUES('R1','T_DSL',12.0,''); -INSERT INTO LifetimeTech VALUES('R1','T_GSL',12.0,''); -INSERT INTO LifetimeTech VALUES('R1','T_EV',12.0,''); -INSERT INTO LifetimeTech VALUES('R1','R_EH',20.0,''); -INSERT INTO LifetimeTech VALUES('R1','R_NGH',20.0,''); -INSERT INTO LifetimeTech VALUES('R2','S_IMPETH',100.0,''); -INSERT INTO LifetimeTech VALUES('R2','S_IMPOIL',100.0,''); -INSERT INTO LifetimeTech VALUES('R2','S_IMPNG',100.0,''); -INSERT INTO LifetimeTech VALUES('R2','S_IMPURN',100.0,''); -INSERT INTO LifetimeTech VALUES('R2','S_OILREF',100.0,''); -INSERT INTO LifetimeTech VALUES('R2','E_NGCC',30.0,''); -INSERT INTO LifetimeTech VALUES('R2','E_SOLPV',30.0,''); -INSERT INTO LifetimeTech VALUES('R2','E_BATT',20.0,''); -INSERT INTO LifetimeTech VALUES('R2','E_NUCLEAR',50.0,''); -INSERT INTO LifetimeTech VALUES('R2','T_BLND',100.0,''); -INSERT INTO LifetimeTech VALUES('R2','T_DSL',12.0,''); -INSERT INTO LifetimeTech VALUES('R2','T_GSL',12.0,''); -INSERT INTO LifetimeTech VALUES('R2','T_EV',12.0,''); -INSERT INTO LifetimeTech VALUES('R2','R_EH',20.0,''); -INSERT INTO LifetimeTech VALUES('R2','R_NGH',20.0,''); -INSERT INTO LifetimeTech VALUES('R1-R2','E_TRANS',30.0,''); -INSERT INTO LifetimeTech VALUES('R2-R1','E_TRANS',30.0,''); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinActivity VALUES('R1',2020,'T_GSL',35.0,'',''); -INSERT INTO MinActivity VALUES('R1',2025,'T_GSL',35.0,'',''); -INSERT INTO MinActivity VALUES('R1',2030,'T_GSL',35.0,'',''); -INSERT INTO MinActivity VALUES('R2',2020,'T_GSL',15.0,'',''); -INSERT INTO MinActivity VALUES('R2',2025,'T_GSL',15.0,'',''); -INSERT INTO MinActivity VALUES('R2',2030,'T_GSL',15.0,'',''); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('R1',NULL); -INSERT INTO Region VALUES('R2',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('spring','day',0.125,'Spring - Day'); -INSERT INTO TimeSegmentFraction VALUES('spring','night',0.125,'Spring - Night'); -INSERT INTO TimeSegmentFraction VALUES('summer','day',0.125,'Summer - Day'); -INSERT INTO TimeSegmentFraction VALUES('summer','night',0.125,'Summer - Night'); -INSERT INTO TimeSegmentFraction VALUES('fall','day',0.125,'Fall - Day'); -INSERT INTO TimeSegmentFraction VALUES('fall','night',0.125,'Fall - Night'); -INSERT INTO TimeSegmentFraction VALUES('winter','day',0.125,'Winter - Day'); -INSERT INTO TimeSegmentFraction VALUES('winter','night',0.125,'Winter - Night'); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO StorageDuration VALUES('R1','E_BATT',8.0,'8-hour duration specified as fraction of a day'); -INSERT INTO StorageDuration VALUES('R2','E_BATT',8.0,'8-hour duration specified as fraction of a day'); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -INSERT INTO TechInputSplit VALUES('R1',2020,'GSL','T_BLND',0.9000000000000000222,''); -INSERT INTO TechInputSplit VALUES('R1',2020,'ETH','T_BLND',0.1000000000000000055,''); -INSERT INTO TechInputSplit VALUES('R1',2025,'GSL','T_BLND',0.9000000000000000222,''); -INSERT INTO TechInputSplit VALUES('R1',2025,'ETH','T_BLND',0.1000000000000000055,''); -INSERT INTO TechInputSplit VALUES('R1',2030,'GSL','T_BLND',0.9000000000000000222,''); -INSERT INTO TechInputSplit VALUES('R1',2030,'ETH','T_BLND',0.1000000000000000055,''); -INSERT INTO TechInputSplit VALUES('R2',2020,'GSL','T_BLND',0.7199999999999999734,''); -INSERT INTO TechInputSplit VALUES('R2',2020,'ETH','T_BLND',0.08000000000000000166,''); -INSERT INTO TechInputSplit VALUES('R2',2025,'GSL','T_BLND',0.7199999999999999734,''); -INSERT INTO TechInputSplit VALUES('R2',2025,'ETH','T_BLND',0.08000000000000000166,''); -INSERT INTO TechInputSplit VALUES('R2',2030,'GSL','T_BLND',0.7199999999999999734,''); -INSERT INTO TechInputSplit VALUES('R2',2030,'ETH','T_BLND',0.08000000000000000166,''); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -INSERT INTO TechOutputSplit VALUES('R1',2020,'S_OILREF','GSL',0.9000000000000000222,''); -INSERT INTO TechOutputSplit VALUES('R1',2020,'S_OILREF','DSL',0.1000000000000000055,''); -INSERT INTO TechOutputSplit VALUES('R1',2025,'S_OILREF','GSL',0.9000000000000000222,''); -INSERT INTO TechOutputSplit VALUES('R1',2025,'S_OILREF','DSL',0.1000000000000000055,''); -INSERT INTO TechOutputSplit VALUES('R1',2030,'S_OILREF','GSL',0.9000000000000000222,''); -INSERT INTO TechOutputSplit VALUES('R1',2030,'S_OILREF','DSL',0.1000000000000000055,''); -INSERT INTO TechOutputSplit VALUES('R2',2020,'S_OILREF','GSL',0.7199999999999999734,''); -INSERT INTO TechOutputSplit VALUES('R2',2020,'S_OILREF','DSL',0.08000000000000000166,''); -INSERT INTO TechOutputSplit VALUES('R2',2025,'S_OILREF','GSL',0.7199999999999999734,''); -INSERT INTO TechOutputSplit VALUES('R2',2025,'S_OILREF','DSL',0.08000000000000000166,''); -INSERT INTO TechOutputSplit VALUES('R2',2030,'S_OILREF','GSL',0.7199999999999999734,''); -INSERT INTO TechOutputSplit VALUES('R2',2030,'S_OILREF','DSL',0.08000000000000000166,''); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'day'); -INSERT INTO TimeOfDay VALUES(2,'night'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,2015,'e'); -INSERT INTO TimePeriod VALUES(2,2020,'f'); -INSERT INTO TimePeriod VALUES(3,2025,'f'); -INSERT INTO TimePeriod VALUES(4,2030,'f'); -INSERT INTO TimePeriod VALUES(5,2035,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'spring'); -INSERT INTO TimeSeason VALUES(2,'summer'); -INSERT INTO TimeSeason VALUES(3,'fall'); -INSERT INTO TimeSeason VALUES(4,'winter'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -INSERT INTO EmissionLimit VALUES('R1',2020,'CO2',25000.0,'kT CO2',''); -INSERT INTO EmissionLimit VALUES('R1',2025,'CO2',24000.0,'kT CO2',''); -INSERT INTO EmissionLimit VALUES('R1',2030,'CO2',23000.0,'kT CO2',''); -INSERT INTO EmissionLimit VALUES('global',2020,'CO2',37500.0,'kT CO2',''); -INSERT INTO EmissionLimit VALUES('global',2025,'CO2',36000.0,'kT CO2',''); -INSERT INTO EmissionLimit VALUES('global',2030,'CO2',34500.0,'kT CO2',''); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('S_IMPETH','r','supply','','',1,0,0,0,0,0,0,0,' imported ethanol'); -INSERT INTO Technology VALUES('S_IMPOIL','r','supply','','',1,0,0,0,0,0,0,0,' imported crude oil'); -INSERT INTO Technology VALUES('S_IMPNG','r','supply','','',1,0,0,0,0,0,0,0,' imported natural gas'); -INSERT INTO Technology VALUES('S_IMPURN','r','supply','','',1,0,0,0,0,0,0,0,' imported uranium'); -INSERT INTO Technology VALUES('S_OILREF','p','supply','','',0,0,0,1,0,0,0,0,' crude oil refinery'); -INSERT INTO Technology VALUES('E_NGCC','p','electric','','',0,0,0,0,0,0,0,0,' natural gas combined-cycle'); -INSERT INTO Technology VALUES('E_SOLPV','p','electric','','',0,0,0,0,0,0,0,0,' solar photovoltaic'); -INSERT INTO Technology VALUES('E_BATT','ps','electric','','',0,0,0,0,0,0,0,0,' lithium-ion battery'); -INSERT INTO Technology VALUES('E_NUCLEAR','pb','electric','','',0,0,0,0,0,0,0,0,' nuclear power plant'); -INSERT INTO Technology VALUES('T_BLND','p','transport','','',0,0,0,0,0,0,0,0,'ethanol - gasoline blending process'); -INSERT INTO Technology VALUES('T_DSL','p','transport','','',0,0,0,0,0,0,0,0,'diesel vehicle'); -INSERT INTO Technology VALUES('T_GSL','p','transport','','',0,0,0,0,0,0,0,0,'gasoline vehicle'); -INSERT INTO Technology VALUES('T_EV','p','transport','','',0,0,0,0,0,0,0,0,'electric vehicle'); -INSERT INTO Technology VALUES('R_EH','p','residential','','',0,0,0,0,0,0,0,0,' electric residential heating'); -INSERT INTO Technology VALUES('R_NGH','p','residential','','',0,0,0,0,0,0,0,0,' natural gas residential heating'); -INSERT INTO Technology VALUES('E_TRANS','p','electric','','',0,0,0,0,0,0,0,1,'electric transmission'); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -COMMIT; +REPLACE INTO "capacity_factor_tech" VALUES('R1','spring','day','E_SOLPV',0.6,''); +REPLACE INTO "capacity_factor_tech" VALUES('R1','spring','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_factor_tech" VALUES('R1','summer','day','E_SOLPV',0.6,''); +REPLACE INTO "capacity_factor_tech" VALUES('R1','summer','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_factor_tech" VALUES('R1','fall','day','E_SOLPV',0.6,''); +REPLACE INTO "capacity_factor_tech" VALUES('R1','fall','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_factor_tech" VALUES('R1','winter','day','E_SOLPV',0.6,''); +REPLACE INTO "capacity_factor_tech" VALUES('R1','winter','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','spring','day','E_SOLPV',0.48,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','spring','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','summer','day','E_SOLPV',0.48,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','summer','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','fall','day','E_SOLPV',0.48,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','fall','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','winter','day','E_SOLPV',0.48,''); +REPLACE INTO "capacity_factor_tech" VALUES('R2','winter','night','E_SOLPV',0.0,''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'S_IMPETH', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'S_IMPOIL', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'S_IMPNG', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'S_IMPURN', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'S_OILREF', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'E_NGCC', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'E_SOLPV', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'E_BATT', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'E_NUCLEAR', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'T_BLND', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'T_DSL', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'T_GSL', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'T_EV', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'R_EH', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1', 'R_NGH', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'S_IMPETH', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'S_IMPOIL', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'S_IMPNG', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'S_IMPURN', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'S_OILREF', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'E_NGCC', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'E_SOLPV', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'E_BATT', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'E_NUCLEAR', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'T_BLND', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'T_DSL', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'T_GSL', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'T_EV', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'R_EH', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2', 'R_NGH', 1.0, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R1-R2', 'E_TRANS', 31.54, NULL, ''); +REPLACE INTO "capacity_to_activity" VALUES('R2-R1', 'E_TRANS', 31.54, NULL, ''); +REPLACE INTO "commodity" VALUES('ethos', 's', 'dummy commodity to supply inputs (makes graph easier to read)', NULL); +REPLACE INTO "commodity" VALUES('OIL', 'p', 'crude oil', NULL); +REPLACE INTO "commodity" VALUES('NG', 'p', 'natural gas', NULL); +REPLACE INTO "commodity" VALUES('URN', 'p', 'uranium', NULL); +REPLACE INTO "commodity" VALUES('ETH', 'p', 'ethanol', NULL); +REPLACE INTO "commodity" VALUES('SOL', 's', 'solar insolation', NULL); +REPLACE INTO "commodity" VALUES('GSL', 'p', 'gasoline', NULL); +REPLACE INTO "commodity" VALUES('DSL', 'p', 'diesel', NULL); +REPLACE INTO "commodity" VALUES('ELC', 'p', 'electricity', NULL); +REPLACE INTO "commodity" VALUES('E10', 'p', 'gasoline blend with 10% ethanol', NULL); +REPLACE INTO "commodity" VALUES('VMT', 'd', 'travel demand for vehicle-miles traveled', NULL); +REPLACE INTO "commodity" VALUES('RH', 'd', 'demand for residential heating', NULL); +REPLACE INTO "commodity" VALUES('CO2', 'e', 'CO2 emissions commodity', NULL); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "cost_fixed" VALUES('R1',2020,'E_NGCC',2020,30.6,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_NGCC',2020,9.78,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_NGCC',2025,9.78,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_NGCC',2020,9.78,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_NGCC',2025,9.78,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_NGCC',2030,9.78,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2020,'E_SOLPV',2020,10.4,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_SOLPV',2020,10.4,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_SOLPV',2025,9.1,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_SOLPV',2020,10.4,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_SOLPV',2025,9.1,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_SOLPV',2030,9.1,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2020,'E_NUCLEAR',2020,9.809999999999998e+01,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_NUCLEAR',2020,9.809999999999998e+01,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_NUCLEAR',2025,9.809999999999998e+01,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_NUCLEAR',2020,9.809999999999998e+01,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_NUCLEAR',2025,9.809999999999998e+01,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_NUCLEAR',2030,9.809999999999998e+01,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2020,'E_BATT',2020,7.05,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_BATT',2020,7.05,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2025,'E_BATT',2025,7.05,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_BATT',2020,7.05,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_BATT',2025,7.05,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R1',2030,'E_BATT',2030,7.05,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2020,'E_NGCC',2020,24.48,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_NGCC',2020,7.824,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_NGCC',2025,7.824,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_NGCC',2020,7.824,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_NGCC',2025,7.824,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_NGCC',2030,7.824,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2020,'E_SOLPV',2020,8.32,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_SOLPV',2020,8.32,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_SOLPV',2025,7.28,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_SOLPV',2020,8.32,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_SOLPV',2025,7.28,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_SOLPV',2030,7.28,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2020,'E_NUCLEAR',2020,78.48,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_NUCLEAR',2020,78.48,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_NUCLEAR',2025,78.48,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_NUCLEAR',2020,78.48,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_NUCLEAR',2025,78.48,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_NUCLEAR',2030,78.48,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2020,'E_BATT',2020,5.64,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_BATT',2020,5.64,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2025,'E_BATT',2025,5.64,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_BATT',2020,5.64,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_BATT',2025,5.64,'$M/GWyr',''); +REPLACE INTO "cost_fixed" VALUES('R2',2030,'E_BATT',2030,5.64,'$M/GWyr',''); +REPLACE INTO "cost_invest" VALUES('R1','E_NGCC',2020,1050.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_NGCC',2025,1025.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_NGCC',2030,1000.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_SOLPV',2020,900.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_SOLPV',2025,560.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_SOLPV',2030,800.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_NUCLEAR',2020,6145.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_NUCLEAR',2025,6045.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_NUCLEAR',2030,5890.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_BATT',2020,1150.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_BATT',2025,720.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','E_BATT',2030,480.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R1','T_GSL',2020,2570.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_GSL',2025,2700.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_GSL',2030,2700.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_DSL',2020,2715.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_DSL',2025,2810.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_DSL',2030,2810.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_EV',2020,3100.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_EV',2025,3030.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','T_EV',2030,2925.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','R_EH',2020,4.1,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','R_EH',2025,4.1,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','R_EH',2030,4.1,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','R_NGH',2020,7.6,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','R_NGH',2025,7.6,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R1','R_NGH',2030,7.6,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','E_NGCC',2020,840.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_NGCC',2025,820.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_NGCC',2030,800.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_SOLPV',2020,720.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_SOLPV',2025,448.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_SOLPV',2030,640.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_NUCLEAR',2020,4916.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_NUCLEAR',2025,4836.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_NUCLEAR',2030,4712.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_BATT',2020,920.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_BATT',2025,576.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','E_BATT',2030,384.0,'$M/GW',''); +REPLACE INTO "cost_invest" VALUES('R2','T_GSL',2020,2056.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_GSL',2025,2160.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_GSL',2030,2160.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_DSL',2020,2172.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_DSL',2025,2248.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_DSL',2030,2248.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_EV',2020,2480.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_EV',2025,2424.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','T_EV',2030,2340.0,'$/bvmt/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','R_EH',2020,3.28,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','R_EH',2025,3.28,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','R_EH',2030,3.28,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','R_NGH',2020,6.08,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','R_NGH',2025,6.08,'$/PJ/yr',''); +REPLACE INTO "cost_invest" VALUES('R2','R_NGH',2030,6.08,'$/PJ/yr',''); +REPLACE INTO "cost_variable" VALUES('R1',2020,'S_IMPETH',2020,32.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'S_IMPETH',2020,32.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'S_IMPETH',2020,32.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2020,'S_IMPOIL',2020,20.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'S_IMPOIL',2020,20.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'S_IMPOIL',2020,20.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2020,'S_IMPNG',2020,4.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'S_IMPNG',2020,4.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'S_IMPNG',2020,4.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2020,'S_OILREF',2020,1.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'S_OILREF',2020,1.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'S_OILREF',2020,1.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2020,'E_NGCC',2020,1.6,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'E_NGCC',2020,1.6,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'E_NGCC',2025,1.7,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'E_NGCC',2020,1.6,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'E_NGCC',2025,1.7,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'E_NGCC',2030,1.8,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2020,'E_NUCLEAR',2020,0.24,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'E_NUCLEAR',2020,0.24,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2025,'E_NUCLEAR',2025,0.25,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'E_NUCLEAR',2020,0.24,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'E_NUCLEAR',2025,0.25,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1',2030,'E_NUCLEAR',2030,0.26,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2020,'S_IMPETH',2020,25.6,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'S_IMPETH',2020,25.6,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'S_IMPETH',2020,25.6,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2020,'S_IMPOIL',2020,16.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'S_IMPOIL',2020,16.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'S_IMPOIL',2020,16.0,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2020,'S_IMPNG',2020,3.2,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'S_IMPNG',2020,3.2,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'S_IMPNG',2020,3.2,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2020,'S_OILREF',2020,0.8,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'S_OILREF',2020,0.8,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'S_OILREF',2020,0.8,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2020,'E_NGCC',2020,1.28,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'E_NGCC',2020,1.28,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'E_NGCC',2025,1.36,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'E_NGCC',2020,1.28,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'E_NGCC',2025,1.36,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'E_NGCC',2030,1.44,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2020,'E_NUCLEAR',2020,0.192,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'E_NUCLEAR',2020,0.192,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2025,'E_NUCLEAR',2025,0.2,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'E_NUCLEAR',2020,0.192,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'E_NUCLEAR',2025,0.2,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2',2030,'E_NUCLEAR',2030,2.08000000000000018e-01,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1-R2',2020,'E_TRANS',2015,0.1,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1-R2',2025,'E_TRANS',2015,0.1,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R1-R2',2030,'E_TRANS',2015,0.1,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2-R1',2020,'E_TRANS',2015,0.1,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2-R1',2025,'E_TRANS',2015,0.1,'$M/PJ',''); +REPLACE INTO "cost_variable" VALUES('R2-R1',2030,'E_TRANS',2015,0.1,'$M/PJ',''); +REPLACE INTO "demand" VALUES('R1',2020,'RH',30.0,'',''); +REPLACE INTO "demand" VALUES('R1',2025,'RH',33.0,'',''); +REPLACE INTO "demand" VALUES('R1',2030,'RH',36.0,'',''); +REPLACE INTO "demand" VALUES('R1',2020,'VMT',84.0,'',''); +REPLACE INTO "demand" VALUES('R1',2025,'VMT',91.0,'',''); +REPLACE INTO "demand" VALUES('R1',2030,'VMT',98.0,'',''); +REPLACE INTO "demand" VALUES('R2',2020,'RH',70.0,'',''); +REPLACE INTO "demand" VALUES('R2',2025,'RH',77.0,'',''); +REPLACE INTO "demand" VALUES('R2',2030,'RH',84.0,'',''); +REPLACE INTO "demand" VALUES('R2',2020,'VMT',36.0,'',''); +REPLACE INTO "demand" VALUES('R2',2025,'VMT',39.0,'',''); +REPLACE INTO "demand" VALUES('R2',2030,'VMT',42.0,'',''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'spring','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'spring','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'summer','day','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'summer','night','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'fall','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'fall','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'winter','day','RH',0.3,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2020,'winter','night','RH',0.4,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'spring','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'spring','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'summer','day','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'summer','night','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'fall','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'fall','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'winter','day','RH',0.3,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2020,'winter','night','RH',0.4,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'spring','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'spring','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'summer','day','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'summer','night','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'fall','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'fall','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'winter','day','RH',0.3,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2025,'winter','night','RH',0.4,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'spring','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'spring','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'summer','day','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'summer','night','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'fall','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'fall','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'winter','day','RH',0.3,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2025,'winter','night','RH',0.4,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'spring','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'spring','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'summer','day','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'summer','night','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'fall','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'fall','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'winter','day','RH',0.3,''); +REPLACE INTO "demand_specific_distribution" VALUES('R1',2030,'winter','night','RH',0.4,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'spring','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'spring','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'summer','day','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'summer','night','RH',0.0,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'fall','day','RH',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'fall','night','RH',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'winter','day','RH',0.3,''); +REPLACE INTO "demand_specific_distribution" VALUES('R2',2030,'winter','night','RH',0.4,''); +REPLACE INTO "efficiency" VALUES('R1', 'ethos', 'S_IMPETH', 2020, 'ETH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ethos', 'S_IMPOIL', 2020, 'OIL', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ethos', 'S_IMPNG', 2020, 'NG', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ethos', 'S_IMPURN', 2020, 'URN', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'OIL', 'S_OILREF', 2020, 'GSL', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'OIL', 'S_OILREF', 2020, 'DSL', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ETH', 'T_BLND', 2020, 'E10', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'GSL', 'T_BLND', 2020, 'E10', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'NG', 'E_NGCC', 2020, 'ELC', 0.55, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'NG', 'E_NGCC', 2025, 'ELC', 0.55, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'NG', 'E_NGCC', 2030, 'ELC', 0.55, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'SOL', 'E_SOLPV', 2020, 'ELC', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'SOL', 'E_SOLPV', 2025, 'ELC', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'SOL', 'E_SOLPV', 2030, 'ELC', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'URN', 'E_NUCLEAR', 2015, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'URN', 'E_NUCLEAR', 2020, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'URN', 'E_NUCLEAR', 2025, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'URN', 'E_NUCLEAR', 2030, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'E_BATT', 2020, 'ELC', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'E_BATT', 2025, 'ELC', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'E_BATT', 2030, 'ELC', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'E10', 'T_GSL', 2020, 'VMT', 0.25, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'E10', 'T_GSL', 2025, 'VMT', 0.25, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'E10', 'T_GSL', 2030, 'VMT', 0.25, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'DSL', 'T_DSL', 2020, 'VMT', 0.3, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'DSL', 'T_DSL', 2025, 'VMT', 0.3, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'DSL', 'T_DSL', 2030, 'VMT', 0.3, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'T_EV', 2020, 'VMT', 0.89, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'T_EV', 2025, 'VMT', 0.89, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'T_EV', 2030, 'VMT', 0.89, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'R_EH', 2020, 'RH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'R_EH', 2025, 'RH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'ELC', 'R_EH', 2030, 'RH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'NG', 'R_NGH', 2020, 'RH', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'NG', 'R_NGH', 2025, 'RH', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1', 'NG', 'R_NGH', 2030, 'RH', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ethos', 'S_IMPETH', 2020, 'ETH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ethos', 'S_IMPOIL', 2020, 'OIL', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ethos', 'S_IMPNG', 2020, 'NG', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ethos', 'S_IMPURN', 2020, 'URN', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'OIL', 'S_OILREF', 2020, 'GSL', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'OIL', 'S_OILREF', 2020, 'DSL', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ETH', 'T_BLND', 2020, 'E10', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'GSL', 'T_BLND', 2020, 'E10', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'NG', 'E_NGCC', 2020, 'ELC', 0.55, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'NG', 'E_NGCC', 2025, 'ELC', 0.55, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'NG', 'E_NGCC', 2030, 'ELC', 0.55, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'SOL', 'E_SOLPV', 2020, 'ELC', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'SOL', 'E_SOLPV', 2025, 'ELC', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'SOL', 'E_SOLPV', 2030, 'ELC', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'URN', 'E_NUCLEAR', 2015, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'URN', 'E_NUCLEAR', 2020, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'URN', 'E_NUCLEAR', 2025, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'URN', 'E_NUCLEAR', 2030, 'ELC', 0.4, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'E_BATT', 2020, 'ELC', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'E_BATT', 2025, 'ELC', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'E_BATT', 2030, 'ELC', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'E10', 'T_GSL', 2020, 'VMT', 0.25, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'E10', 'T_GSL', 2025, 'VMT', 0.25, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'E10', 'T_GSL', 2030, 'VMT', 0.25, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'DSL', 'T_DSL', 2020, 'VMT', 0.3, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'DSL', 'T_DSL', 2025, 'VMT', 0.3, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'DSL', 'T_DSL', 2030, 'VMT', 0.3, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'T_EV', 2020, 'VMT', 0.89, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'T_EV', 2025, 'VMT', 0.89, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'T_EV', 2030, 'VMT', 0.89, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'R_EH', 2020, 'RH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'R_EH', 2025, 'RH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'ELC', 'R_EH', 2030, 'RH', 1.0, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'NG', 'R_NGH', 2020, 'RH', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'NG', 'R_NGH', 2025, 'RH', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2', 'NG', 'R_NGH', 2030, 'RH', 0.85, NULL, ''); +REPLACE INTO "efficiency" VALUES('R1-R2', 'ELC', 'E_TRANS', 2015, 'ELC', 0.9, NULL, ''); +REPLACE INTO "efficiency" VALUES('R2-R1', 'ELC', 'E_TRANS', 2015, 'ELC', 0.9, NULL, ''); +REPLACE INTO "emission_activity" VALUES('R1','CO2','ethos','S_IMPNG',2020,'NG',5.029999999999999e+01,'kT/PJ','taken from MIT Energy Fact Sheet'); +REPLACE INTO "emission_activity" VALUES('R1','CO2','OIL','S_OILREF',2020,'GSL',67.2,'kT/PJ','taken from MIT Energy Fact Sheet'); +REPLACE INTO "emission_activity" VALUES('R1','CO2','OIL','S_OILREF',2020,'DSL',69.4,'kT/PJ','taken from MIT Energy Fact Sheet'); +REPLACE INTO "emission_activity" VALUES('R2','CO2','ethos','S_IMPNG',2020,'NG',5.029999999999999e+01,'kT/PJ','taken from MIT Energy Fact Sheet'); +REPLACE INTO "emission_activity" VALUES('R2','CO2','OIL','S_OILREF',2020,'GSL',67.2,'kT/PJ','taken from MIT Energy Fact Sheet'); +REPLACE INTO "emission_activity" VALUES('R2','CO2','OIL','S_OILREF',2020,'DSL',69.4,'kT/PJ','taken from MIT Energy Fact Sheet'); +REPLACE INTO "existing_capacity" VALUES('R1','E_NUCLEAR',2015,0.07,'GW',''); +REPLACE INTO "existing_capacity" VALUES('R2','E_NUCLEAR',2015,0.03,'GW',''); +REPLACE INTO "existing_capacity" VALUES('R1-R2','E_TRANS',2015,10.0,'GW',''); +REPLACE INTO "existing_capacity" VALUES('R2-R1','E_TRANS',2015,10.0,'GW',''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'S_IMPETH', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'S_IMPOIL', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'S_IMPNG', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'S_IMPURN', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'S_OILREF', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'E_NGCC', 30.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'E_SOLPV', 30.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'E_BATT', 20.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'E_NUCLEAR', 50.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'T_BLND', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'T_DSL', 12.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'T_GSL', 12.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'T_EV', 12.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'R_EH', 20.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1', 'R_NGH', 20.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'S_IMPETH', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'S_IMPOIL', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'S_IMPNG', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'S_IMPURN', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'S_OILREF', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'E_NGCC', 30.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'E_SOLPV', 30.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'E_BATT', 20.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'E_NUCLEAR', 50.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'T_BLND', 100.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'T_DSL', 12.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'T_GSL', 12.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'T_EV', 12.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'R_EH', 20.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2', 'R_NGH', 20.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R1-R2', 'E_TRANS', 30.0, NULL, ''); +REPLACE INTO "lifetime_tech" VALUES('R2-R1', 'E_TRANS', 30.0, NULL, ''); +REPLACE INTO "limit_activity" VALUES('R1',2020,'T_GSL','ge',35.0,'',''); +REPLACE INTO "limit_activity" VALUES('R1',2025,'T_GSL','ge',35.0,'',''); +REPLACE INTO "limit_activity" VALUES('R1',2030,'T_GSL','ge',35.0,'',''); +REPLACE INTO "limit_activity" VALUES('R2',2020,'T_GSL','ge',15.0,'',''); +REPLACE INTO "limit_activity" VALUES('R2',2025,'T_GSL','ge',15.0,'',''); +REPLACE INTO "limit_activity" VALUES('R2',2030,'T_GSL','ge',15.0,'',''); +REPLACE INTO "limit_emission" VALUES('R1',2020,'CO2','le',25000.0,'kT CO2',''); +REPLACE INTO "limit_emission" VALUES('R1',2025,'CO2','le',24000.0,'kT CO2',''); +REPLACE INTO "limit_emission" VALUES('R1',2030,'CO2','le',23000.0,'kT CO2',''); +REPLACE INTO "limit_emission" VALUES('global',2020,'CO2','le',37500.0,'kT CO2',''); +REPLACE INTO "limit_emission" VALUES('global',2025,'CO2','le',36000.0,'kT CO2',''); +REPLACE INTO "limit_emission" VALUES('global',2030,'CO2','le',34500.0,'kT CO2',''); +REPLACE INTO "limit_storage_level_fraction" VALUES('R1','winter','day','E_BATT','e',0.5,''); +REPLACE INTO "limit_storage_level_fraction" VALUES('R2','summer','day','E_BATT','e',0.5,''); +REPLACE INTO "limit_tech_input_split" VALUES('R1',2020,'GSL','T_BLND','ge',0.9,''); +REPLACE INTO "limit_tech_input_split" VALUES('R1',2020,'ETH','T_BLND','ge',0.1,''); +REPLACE INTO "limit_tech_input_split" VALUES('R1',2025,'GSL','T_BLND','ge',0.9,''); +REPLACE INTO "limit_tech_input_split" VALUES('R1',2025,'ETH','T_BLND','ge',0.1,''); +REPLACE INTO "limit_tech_input_split" VALUES('R1',2030,'GSL','T_BLND','ge',0.9,''); +REPLACE INTO "limit_tech_input_split" VALUES('R1',2030,'ETH','T_BLND','ge',0.1,''); +REPLACE INTO "limit_tech_input_split" VALUES('R2',2020,'GSL','T_BLND','ge',0.72,''); +REPLACE INTO "limit_tech_input_split" VALUES('R2',2020,'ETH','T_BLND','ge',0.08,''); +REPLACE INTO "limit_tech_input_split" VALUES('R2',2025,'GSL','T_BLND','ge',0.72,''); +REPLACE INTO "limit_tech_input_split" VALUES('R2',2025,'ETH','T_BLND','ge',0.08,''); +REPLACE INTO "limit_tech_input_split" VALUES('R2',2030,'GSL','T_BLND','ge',0.72,''); +REPLACE INTO "limit_tech_input_split" VALUES('R2',2030,'ETH','T_BLND','ge',0.08,''); +REPLACE INTO "limit_tech_output_split" VALUES('R1',2020,'S_OILREF','GSL','ge',0.9,''); +REPLACE INTO "limit_tech_output_split" VALUES('R1',2020,'S_OILREF','DSL','ge',0.1,''); +REPLACE INTO "limit_tech_output_split" VALUES('R1',2025,'S_OILREF','GSL','ge',0.9,''); +REPLACE INTO "limit_tech_output_split" VALUES('R1',2025,'S_OILREF','DSL','ge',0.1,''); +REPLACE INTO "limit_tech_output_split" VALUES('R1',2030,'S_OILREF','GSL','ge',0.9,''); +REPLACE INTO "limit_tech_output_split" VALUES('R1',2030,'S_OILREF','DSL','ge',0.1,''); +REPLACE INTO "limit_tech_output_split" VALUES('R2',2020,'S_OILREF','GSL','ge',0.72,''); +REPLACE INTO "limit_tech_output_split" VALUES('R2',2020,'S_OILREF','DSL','ge',0.08,''); +REPLACE INTO "limit_tech_output_split" VALUES('R2',2025,'S_OILREF','GSL','ge',0.72,''); +REPLACE INTO "limit_tech_output_split" VALUES('R2',2025,'S_OILREF','DSL','ge',0.08,''); +REPLACE INTO "limit_tech_output_split" VALUES('R2',2030,'S_OILREF','GSL','ge',0.72,''); +REPLACE INTO "limit_tech_output_split" VALUES('R2',2030,'S_OILREF','DSL','ge',0.08,''); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('R1',NULL); +REPLACE INTO "region" VALUES('R2',NULL); +REPLACE INTO "sector_label" VALUES('supply',NULL); +REPLACE INTO "sector_label" VALUES('electric',NULL); +REPLACE INTO "sector_label" VALUES('transport',NULL); +REPLACE INTO "sector_label" VALUES('commercial',NULL); +REPLACE INTO "sector_label" VALUES('residential',NULL); +REPLACE INTO "sector_label" VALUES('industrial',NULL); +REPLACE INTO "storage_duration" VALUES('R1','E_BATT',8.0,'8-hour duration specified as fraction of a day'); +REPLACE INTO "storage_duration" VALUES('R2','E_BATT',8.0,'8-hour duration specified as fraction of a day'); +REPLACE INTO "technology" VALUES('S_IMPETH','p','supply','','',1,0,0,0,0,0,0,0,' imported ethanol'); +REPLACE INTO "technology" VALUES('S_IMPOIL','p','supply','','',1,0,0,0,0,0,0,0,' imported crude oil'); +REPLACE INTO "technology" VALUES('S_IMPNG','p','supply','','',1,0,0,0,0,0,0,0,' imported natural gas'); +REPLACE INTO "technology" VALUES('S_IMPURN','p','supply','','',1,0,0,0,0,0,0,0,' imported uranium'); +REPLACE INTO "technology" VALUES('S_OILREF','p','supply','','',0,0,0,1,0,0,0,0,' crude oil refinery'); +REPLACE INTO "technology" VALUES('E_NGCC','p','electric','','',0,0,0,0,0,0,0,0,' natural gas combined-cycle'); +REPLACE INTO "technology" VALUES('E_SOLPV','p','electric','','',0,0,0,0,0,0,0,0,' solar photovoltaic'); +REPLACE INTO "technology" VALUES('E_BATT','ps','electric','','',0,0,0,0,0,0,0,0,' lithium-ion battery'); +REPLACE INTO "technology" VALUES('E_NUCLEAR','pb','electric','','',0,0,0,0,0,0,0,0,' nuclear power plant'); +REPLACE INTO "technology" VALUES('T_BLND','p','transport','','',0,0,0,0,0,0,0,0,'ethanol - gasoline blending process'); +REPLACE INTO "technology" VALUES('T_DSL','p','transport','','',0,0,0,0,0,0,0,0,'diesel vehicle'); +REPLACE INTO "technology" VALUES('T_GSL','p','transport','','',0,0,0,0,0,0,0,0,'gasoline vehicle'); +REPLACE INTO "technology" VALUES('T_EV','p','transport','','',0,0,0,0,0,0,0,0,'electric vehicle'); +REPLACE INTO "technology" VALUES('R_EH','p','residential','','',0,0,0,0,0,0,0,0,' electric residential heating'); +REPLACE INTO "technology" VALUES('R_NGH','p','residential','','',0,0,0,0,0,0,0,0,' natural gas residential heating'); +REPLACE INTO "technology" VALUES('E_TRANS','p','electric','','',0,0,0,0,0,0,1,0,'electric transmission'); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(1,'day',12,NULL); +REPLACE INTO "time_of_day" VALUES(2,'night',12,NULL); +REPLACE INTO "time_period" VALUES(1,2015,'e'); +REPLACE INTO "time_period" VALUES(2,2020,'f'); +REPLACE INTO "time_period" VALUES(3,2025,'f'); +REPLACE INTO "time_period" VALUES(4,2030,'f'); +REPLACE INTO "time_period" VALUES(5,2035,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(1,'spring',0.25,NULL); +REPLACE INTO "time_season" VALUES(2,'summer',0.25,NULL); +REPLACE INTO "time_season" VALUES(3,'fall',0.25,NULL); +REPLACE INTO "time_season" VALUES(4,'winter',0.25,NULL); diff --git a/tests/testing_data/test_system_sets.json b/tests/testing_data/test_system_sets.json index d49b39b14..35cd0b4cf 100644 --- a/tests/testing_data/test_system_sets.json +++ b/tests/testing_data/test_system_sets.json @@ -1,45035 +1,115 @@ { - "time_exist": [ - 2015 - ], - "time_future": [ - 2020, - 2025, - 2030, - 2035 - ], - "time_optimize": [ - 2020, - 2025, - 2030 - ], - "vintage_exist": [ - 2015 - ], - "vintage_optimize": [ - 2020, - 2025, - 2030 - ], - "vintage_all": [ - 2015, - 2020, - 2025, - 2030 - ], - "time_season": [ - "spring", - "summer", - "fall", - "winter" - ], - "time_of_day": [ - "day", - "night" - ], - "regions": [ - "R1", - "R2" - ], - "RegionalIndices": [ - "R1", - "R1-R2", - "R2", - "R2-R1" - ], - "RegionalGlobalIndices": [ - "R1", - "R2", - "global" - ], - "tech_resource": [ - "S_IMPETH", - "S_IMPOIL", - "S_IMPNG", - "S_IMPURN" - ], - "tech_production": [ - "S_OILREF", - "E_NGCC", - "E_SOLPV", - "E_BATT", - "E_NUCLEAR", - "T_BLND", - "T_DSL", - "T_GSL", - "T_EV", - "R_EH", - "R_NGH", - "E_TRANS" - ], - "tech_all": [ - "S_IMPETH", - "S_IMPOIL", - "S_IMPNG", - "S_IMPURN", - "S_OILREF", - "E_NGCC", - "E_SOLPV", - "E_BATT", - "E_NUCLEAR", - "T_BLND", - "T_DSL", - "T_GSL", - "T_EV", - "R_EH", - "R_NGH", - "E_TRANS" - ], - "tech_baseload": [ - "E_NUCLEAR" - ], - "tech_annual": [], - "tech_storage": [ - "E_BATT" - ], - "tech_reserve": [], - "tech_ramping": [], - "tech_curtailment": [ - "S_OILREF" - ], - "tech_flex": [], - "tech_exchange": [ - "E_TRANS" - ], - "tech_group_names": [], - "tech_group_members": [], - "tech_uncap": [ - "S_IMPETH", - "S_IMPOIL", - "S_IMPNG", - "S_IMPURN" - ], - "tech_with_capacity": [ - "S_OILREF", - "E_NGCC", - "E_SOLPV", - "E_BATT", - "E_NUCLEAR", - "T_BLND", - "T_DSL", - "T_GSL", - "T_EV", - "R_EH", - "R_NGH", - "E_TRANS" - ], - "tech_variable": [], - "tech_retirement": [], - "commodity_demand": [ - "VMT", - "RH" - ], - "commodity_emissions": [ - "CO2" - ], - "commodity_physical": [ - "ethos", - "OIL", - "NG", - "URN", - "ETH", - "SOL", - "GSL", - "DSL", - "ELC", - "E10" - ], - "commodity_source": [ - "ethos" - ], - "commodity_carrier": [ - "ethos", - "OIL", - "NG", - "URN", - "ETH", - "SOL", - "GSL", - "DSL", - "ELC", - "E10", - "VMT", - "RH" - ], - "commodity_all": [ - "ethos", - "OIL", - "NG", - "URN", - "ETH", - "SOL", - "GSL", - "DSL", - "ELC", - "E10", - "VMT", - "RH", - "CO2" - ], - "tech_mga": [], - "tech_electric": [], - "tech_transport": [], - "tech_industrial": [], - "tech_commercial": [], - "tech_residential": [], - "tech_PowerPlants": [], - "ResourceConstraint_rpr": [], - "CapacityFactor_rsdt": [ - [ - "R1", - "fall", - "night", - "E_NUCLEAR" - ], - [ - "R1-R2", - "winter", - "night", - "E_TRANS" - ], - [ - "R1", - "summer", - "night", - "E_BATT" - ], - [ - "R1", - "winter", - "night", - "E_SOLPV" - ], - [ - "R1", - "summer", - "day", - "R_NGH" - ], - [ - "R1", - "fall", - "day", - "E_SOLPV" - ], - [ - "R1", - "fall", - "night", - "S_IMPETH" - ], - [ - "R2", - "winter", - "day", - "S_IMPNG" - ], - [ - "R1", - "winter", - "night", - "T_DSL" - ], - [ - "R1-R2", - "fall", - "day", - "E_TRANS" - ], - [ - "R2", - "winter", - "night", - "E_NUCLEAR" - ], - [ - "R1", - "fall", - "day", - "T_DSL" - ], - [ - "R2", - "winter", - "day", - "E_BATT" - ], - [ - "R2", - "summer", - "day", - "S_IMPURN" - ], - [ - "R2", - "spring", - "night", - "T_BLND" - ], - [ - "R1", - "spring", - "day", - "E_NGCC" - ], - [ - "R1", - "spring", - "night", - "S_OILREF" - ], - [ - "R2", - "winter", - "day", - "E_SOLPV" - ], - [ - "R2-R1", - "fall", - "night", - "E_TRANS" - ], - [ - "R2", - "summer", - "day", - "S_IMPOIL" - ], - [ - "R2", - "winter", - "night", - "E_NGCC" - ], - [ - "R2-R1", - "spring", - "day", - "E_TRANS" - ], - [ - "R1-R2", - "winter", - "day", - "E_TRANS" - ], - [ - "R2", - "fall", - "night", - "S_OILREF" - ], - [ - "R2", - "fall", - "day", - "E_NGCC" - ], - [ - "R1", - "winter", - "day", - "T_GSL" - ], - [ - "R2", - "spring", - "day", - "S_IMPNG" - ], - [ - "R2", - "summer", - "night", - "R_NGH" - ], - [ - "R1", - "spring", - "night", - "T_DSL" - ], - [ - "R2", - "summer", - "day", - "T_EV" - ], - [ - "R2", - "spring", - "night", - "E_NUCLEAR" - ], - [ - "R2", - "fall", - "night", - "E_SOLPV" - ], - [ - "R2", - "summer", - "day", - "R_EH" - ], - [ - "R1", - "winter", - "day", - "S_IMPNG" - ], - [ - "R2", - "spring", - "day", - "E_BATT" - ], - [ - "R2", - "summer", - "day", - "S_IMPETH" - ], - [ - "R1", - "winter", - "day", - "E_BATT" - ], - [ - "R2", - "fall", - "night", - "T_DSL" - ], - [ - "R2", - "spring", - "day", - "E_SOLPV" - ], - [ - "R1", - "summer", - "day", - "S_IMPURN" - ], - [ - "R2", - "spring", - "night", - "E_NGCC" - ], - [ - "R1-R2", - "spring", - "day", - "E_TRANS" - ], - [ - "R1", - "summer", - "day", - "S_IMPOIL" - ], - [ - "R1", - "winter", - "night", - "E_NGCC" - ], - [ - "R1", - "fall", - "day", - "E_NGCC" - ], - [ - "R1", - "spring", - "day", - "T_GSL" - ], - [ - "R1", - "spring", - "day", - "S_IMPNG" - ], - [ - "R1", - "summer", - "night", - "R_NGH" - ], - [ - "R1", - "summer", - "day", - "R_EH" - ], - [ - "R1", - "summer", - "day", - "T_EV" - ], - [ - "R1", - "spring", - "night", - "E_NUCLEAR" - ], - [ - "R1", - "fall", - "night", - "E_SOLPV" - ], - [ - "R2", - "fall", - "day", - "T_GSL" - ], - [ - "R2", - "winter", - "night", - "S_IMPNG" - ], - [ - "R1-R2", - "fall", - "night", - "E_TRANS" - ], - [ - "R1", - "spring", - "day", - "E_BATT" - ], - [ - "R1", - "summer", - "day", - "S_IMPETH" - ], - [ - "R2", - "fall", - "day", - "S_IMPNG" - ], - [ - "R1", - "fall", - "night", - "T_DSL" - ], - [ - "R2", - "winter", - "night", - "E_BATT" - ], - [ - "R2", - "summer", - "night", - "S_IMPURN" - ], - [ - "R2", - "winter", - "day", - "R_NGH" - ], - [ - "R2", - "fall", - "day", - "E_BATT" - ], - [ - "R1", - "spring", - "night", - "E_NGCC" - ], - [ - "R2", - "winter", - "night", - "E_SOLPV" - ], - [ - "R2", - "summer", - "night", - "S_IMPOIL" - ], - [ - "R2-R1", - "spring", - "night", - "E_TRANS" - ], - [ - "R2", - "fall", - "night", - "E_NGCC" - ], - [ - "R2", - "summer", - "day", - "S_OILREF" - ], - [ - "R2", - "spring", - "night", - "S_OILREF" - ], - [ - "R1", - "winter", - "night", - "S_IMPNG" - ], - [ - "R1", - "fall", - "day", - "T_GSL" - ], - [ - "R2", - "summer", - "night", - "T_EV" - ], - [ - "R2", - "summer", - "night", - "R_EH" - ], - [ - "R1", - "fall", - "day", - "S_IMPNG" - ], - [ - "R2", - "spring", - "night", - "E_BATT" - ], - [ - "R1", - "winter", - "night", - "E_BATT" - ], - [ - "R2", - "spring", - "night", - "E_SOLPV" - ], - [ - "R1", - "summer", - "night", - "S_IMPURN" - ], - [ - "R1", - "winter", - "day", - "R_NGH" - ], - [ - "R1", - "fall", - "day", - "E_BATT" - ], - [ - "R2", - "winter", - "day", - "T_GSL" - ], - [ - "R2", - "spring", - "night", - "T_DSL" - ], - [ - "R1-R2", - "spring", - "night", - "E_TRANS" - ], - [ - "R1", - "summer", - "night", - "S_IMPOIL" - ], - [ - "R1", - "fall", - "night", - "E_NGCC" - ], - [ - "R1", - "summer", - "day", - "S_OILREF" - ], - [ - "R2", - "summer", - "day", - "T_BLND" - ], - [ - "R1", - "spring", - "night", - "S_IMPNG" - ], - [ - "R1-R2", - "summer", - "day", - "E_TRANS" - ], - [ - "R1", - "summer", - "night", - "R_EH" - ], - [ - "R1", - "summer", - "night", - "T_EV" - ], - [ - "R1", - "spring", - "night", - "E_BATT" - ], - [ - "R1", - "summer", - "night", - "S_IMPETH" - ], - [ - "R2", - "fall", - "night", - "S_IMPNG" - ], - [ - "R1", - "spring", - "night", - "E_SOLPV" - ], - [ - "R1", - "spring", - "day", - "R_NGH" - ], - [ - "R2", - "spring", - "day", - "T_GSL" - ], - [ - "R2", - "summer", - "day", - "E_NUCLEAR" - ], - [ - "R2", - "fall", - "night", - "E_BATT" - ], - [ - "R2", - "winter", - "day", - "T_EV" - ], - [ - "R2", - "fall", - "day", - "R_NGH" - ], - [ - "R1", - "summer", - "day", - "T_BLND" - ], - [ - "R1", - "winter", - "day", - "S_IMPURN" - ], - [ - "R2", - "summer", - "night", - "S_OILREF" - ], - [ - "R1", - "winter", - "day", - "S_IMPOIL" - ], - [ - "R1", - "fall", - "night", - "T_GSL" - ], - [ - "R1", - "fall", - "night", - "S_IMPNG" - ], - [ - "R1", - "summer", - "day", - "E_NUCLEAR" - ], - [ - "R1", - "winter", - "night", - "R_NGH" - ], - [ - "R1", - "fall", - "night", - "E_BATT" - ], - [ - "R1", - "winter", - "day", - "T_EV" - ], - [ - "R2", - "spring", - "day", - "T_EV" - ], - [ - "R1", - "winter", - "day", - "R_EH" - ], - [ - "R1", - "fall", - "day", - "R_NGH" - ], - [ - "R2", - "winter", - "night", - "T_GSL" - ], - [ - "R1", - "spring", - "day", - "S_IMPURN" - ], - [ - "R1", - "summer", - "night", - "S_OILREF" - ], - [ - "R2", - "summer", - "night", - "T_BLND" - ], - [ - "R1", - "summer", - "day", - "E_NGCC" - ], - [ - "R1", - "spring", - "day", - "S_IMPOIL" - ], - [ - "R1-R2", - "summer", - "night", - "E_TRANS" - ], - [ - "R2", - "fall", - "day", - "S_IMPURN" - ], - [ - "R2-R1", - "summer", - "day", - "E_TRANS" - ], - [ - "R2", - "fall", - "day", - "S_IMPOIL" - ], - [ - "R1", - "spring", - "night", - "R_NGH" - ], - [ - "R1", - "spring", - "day", - "R_EH" - ], - [ - "R1", - "spring", - "day", - "T_EV" - ], - [ - "R2", - "spring", - "night", - "T_GSL" - ], - [ - "R1", - "winter", - "night", - "T_GSL" - ], - [ - "R2", - "spring", - "night", - "S_IMPNG" - ], - [ - "R2", - "summer", - "night", - "E_NUCLEAR" - ], - [ - "R2", - "winter", - "night", - "T_EV" - ], - [ - "R2", - "fall", - "night", - "R_NGH" - ], - [ - "R2", - "fall", - "day", - "R_EH" - ], - [ - "R2", - "fall", - "day", - "T_EV" - ], - [ - "R2", - "summer", - "day", - "E_SOLPV" - ], - [ - "R2", - "summer", - "night", - "S_IMPETH" - ], - [ - "R1", - "summer", - "night", - "T_BLND" - ], - [ - "R2", - "spring", - "day", - "R_NGH" - ], - [ - "R1", - "winter", - "night", - "S_IMPURN" - ], - [ - "R2", - "summer", - "day", - "T_DSL" - ], - [ - "R1", - "fall", - "day", - "S_IMPURN" - ], - [ - "R1", - "winter", - "night", - "S_IMPOIL" - ], - [ - "R1", - "fall", - "day", - "S_IMPOIL" - ], - [ - "R1", - "winter", - "day", - "S_OILREF" - ], - [ - "R2", - "winter", - "day", - "T_BLND" - ], - [ - "R1", - "spring", - "night", - "T_GSL" - ], - [ - "R2", - "winter", - "day", - "S_IMPURN" - ], - [ - "R1", - "summer", - "night", - "E_NUCLEAR" - ], - [ - "R1", - "winter", - "night", - "T_EV" - ], - [ - "R1", - "fall", - "night", - "R_NGH" - ], - [ - "R1", - "fall", - "day", - "R_EH" - ], - [ - "R1", - "fall", - "day", - "T_EV" - ], - [ - "R1", - "summer", - "day", - "E_SOLPV" - ], - [ - "R2", - "fall", - "night", - "T_GSL" - ], - [ - "R2", - "winter", - "day", - "S_IMPOIL" - ], - [ - "R1", - "fall", - "day", - "S_IMPETH" - ], - [ - "R1", - "summer", - "day", - "T_DSL" - ], - [ - "R2", - "winter", - "night", - "R_NGH" - ], - [ - "R2", - "winter", - "day", - "R_EH" - ], - [ - "R2", - "fall", - "night", - "S_IMPURN" - ], - [ - "R2-R1", - "summer", - "night", - "E_TRANS" - ], - [ - "R2", - "spring", - "day", - "T_BLND" - ], - [ - "R2", - "winter", - "day", - "S_IMPETH" - ], - [ - "R2", - "spring", - "day", - "S_IMPURN" - ], - [ - "R2", - "fall", - "night", - "S_IMPOIL" - ], - [ - "R1", - "winter", - "day", - "T_BLND" - ], - [ - "R1", - "spring", - "night", - "T_EV" - ], - [ - "R2", - "summer", - "day", - "E_NGCC" - ], - [ - "R2", - "fall", - "day", - "S_OILREF" - ], - [ - "R2", - "spring", - "day", - "S_IMPOIL" - ], - [ - "R2", - "fall", - "night", - "T_EV" - ], - [ - "R2", - "summer", - "night", - "E_SOLPV" - ], - [ - "R2", - "spring", - "night", - "R_NGH" - ], - [ - "R1", - "winter", - "day", - "E_NUCLEAR" - ], - [ - "R2", - "spring", - "day", - "R_EH" - ], - [ - "R2", - "summer", - "night", - "T_DSL" - ], - [ - "R1", - "fall", - "night", - "S_IMPURN" - ], - [ - "R2", - "spring", - "day", - "S_IMPETH" - ], - [ - "R1", - "winter", - "day", - "S_IMPETH" - ], - [ - "R1", - "spring", - "day", - "T_BLND" - ], - [ - "R1", - "fall", - "night", - "S_IMPOIL" - ], - [ - "R2", - "winter", - "night", - "T_BLND" - ], - [ - "R2", - "winter", - "night", - "S_IMPURN" - ], - [ - "R1", - "fall", - "day", - "S_OILREF" - ], - [ - "R2", - "fall", - "day", - "T_BLND" - ], - [ - "R2", - "winter", - "night", - "S_IMPOIL" - ], - [ - "R1", - "fall", - "night", - "R_EH" - ], - [ - "R1", - "fall", - "night", - "T_EV" - ], - [ - "R1", - "summer", - "night", - "E_SOLPV" - ], - [ - "R2", - "summer", - "day", - "T_GSL" - ], - [ - "R1", - "spring", - "day", - "E_NUCLEAR" - ], - [ - "R2", - "winter", - "day", - "S_OILREF" - ], - [ - "R2", - "summer", - "day", - "S_IMPNG" - ], - [ - "R1", - "summer", - "night", - "T_DSL" - ], - [ - "R2", - "winter", - "night", - "R_EH" - ], - [ - "R1", - "spring", - "day", - "S_IMPETH" - ], - [ - "R2", - "fall", - "day", - "E_NUCLEAR" - ], - [ - "R2", - "summer", - "day", - "E_BATT" - ], - [ - "R2-R1", - "winter", - "day", - "E_TRANS" - ], - [ - "R2", - "winter", - "night", - "S_IMPETH" - ], - [ - "R2", - "spring", - "night", - "S_IMPURN" - ], - [ - "R1", - "winter", - "night", - "T_BLND" - ], - [ - "R2", - "fall", - "day", - "S_IMPETH" - ], - [ - "R2", - "winter", - "day", - "T_DSL" - ], - [ - "R2", - "summer", - "night", - "E_NGCC" - ], - [ - "R1", - "fall", - "day", - "T_BLND" - ], - [ - "R2", - "spring", - "night", - "S_IMPOIL" - ], - [ - "R1", - "summer", - "day", - "T_GSL" - ], - [ - "R2", - "spring", - "day", - "S_OILREF" - ], - [ - "R1", - "summer", - "day", - "S_IMPNG" - ], - [ - "R1", - "winter", - "night", - "E_NUCLEAR" - ], - [ - "R2", - "spring", - "night", - "T_EV" - ], - [ - "R2", - "spring", - "night", - "R_EH" - ], - [ - "R1", - "winter", - "night", - "R_EH" - ], - [ - "R1", - "fall", - "day", - "E_NUCLEAR" - ], - [ - "R1", - "summer", - "day", - "E_BATT" - ], - [ - "R2", - "spring", - "night", - "S_IMPETH" - ], - [ - "R1", - "winter", - "day", - "E_SOLPV" - ], - [ - "R1", - "winter", - "night", - "S_IMPETH" - ], - [ - "R1", - "spring", - "night", - "T_BLND" - ], - [ - "R1", - "spring", - "night", - "S_IMPURN" - ], - [ - "R2", - "spring", - "day", - "T_DSL" - ], - [ - "R1", - "winter", - "day", - "T_DSL" - ], - [ - "R1", - "summer", - "night", - "E_NGCC" - ], - [ - "R2", - "winter", - "day", - "E_NUCLEAR" - ], - [ - "R1", - "fall", - "night", - "S_OILREF" - ], - [ - "R2", - "fall", - "night", - "T_BLND" - ], - [ - "R1", - "spring", - "night", - "S_IMPOIL" - ], - [ - "R1", - "spring", - "day", - "S_OILREF" - ], - [ - "R2-R1", - "fall", - "day", - "E_TRANS" - ], - [ - "R2", - "summer", - "night", - "T_GSL" - ], - [ - "R2", - "winter", - "night", - "S_OILREF" - ], - [ - "R1", - "spring", - "night", - "R_EH" - ], - [ - "R2", - "winter", - "day", - "E_NGCC" - ], - [ - "R2", - "summer", - "night", - "S_IMPNG" - ], - [ - "R1", - "spring", - "day", - "E_SOLPV" - ], - [ - "R1", - "spring", - "night", - "S_IMPETH" - ], - [ - "R2", - "fall", - "night", - "E_NUCLEAR" - ], - [ - "R2", - "summer", - "night", - "E_BATT" - ], - [ - "R2", - "fall", - "night", - "R_EH" - ], - [ - "R2-R1", - "winter", - "night", - "E_TRANS" - ], - [ - "R2", - "summer", - "day", - "R_NGH" - ], - [ - "R1", - "spring", - "day", - "T_DSL" - ], - [ - "R2", - "spring", - "day", - "E_NUCLEAR" - ], - [ - "R2", - "fall", - "day", - "E_SOLPV" - ], - [ - "R2", - "fall", - "night", - "S_IMPETH" - ], - [ - "R2", - "winter", - "night", - "T_DSL" - ], - [ - "R1", - "fall", - "night", - "T_BLND" - ], - [ - "R2", - "fall", - "day", - "T_DSL" - ], - [ - "R1", - "summer", - "night", - "T_GSL" - ], - [ - "R2", - "spring", - "day", - "E_NGCC" - ], - [ - "R1", - "winter", - "night", - "S_OILREF" - ], - [ - "R1", - "summer", - "night", - "S_IMPNG" - ], - [ - "R1", - "winter", - "day", - "E_NGCC" - ] - ], - "LifetimeProcess_rtv": [ - [ - "R2", - "T_BLND", - 2020 - ], - [ - "R2", - "E_BATT", - 2030 - ], - [ - "R2", - "S_IMPURN", - 2020 - ], - [ - "R1", - "E_BATT", - 2020 - ], - [ - "R1", - "T_EV", - 2030 - ], - [ - "R1", - "E_NGCC", - 2020 - ], - [ - "R1", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - "T_DSL", - 2020 - ], - [ - "R1", - "R_NGH", - 2020 - ], - [ - "R1", - "T_GSL", - 2030 - ], - [ - "R2", - "T_EV", - 2030 - ], - [ - "R2", - "E_NGCC", - 2020 - ], - [ - "R1", - "R_EH", - 2030 - ], - [ - "R2", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - "R_NGH", - 2020 - ], - [ - "R1", - "S_OILREF", - 2020 - ], - [ - "R1", - "E_SOLPV", - 2025 - ], - [ - "R1", - "T_DSL", - 2025 - ], - [ - "R1", - "S_IMPOIL", - 2020 - ], - [ - "R2", - "E_BATT", - 2020 - ], - [ - "R2", - "T_GSL", - 2030 - ], - [ - "R2", - "E_SOLPV", - 2025 - ], - [ - "R1", - "T_EV", - 2020 - ], - [ - "R2", - "R_EH", - 2030 - ], - [ - "R2", - "R_NGH", - 2030 - ], - [ - "R2", - "S_OILREF", - 2020 - ], - [ - "R2", - "S_IMPOIL", - 2020 - ], - [ - "R1", - "E_BATT", - 2025 - ], - [ - "R2", - "T_EV", - 2020 - ], - [ - "R1", - "S_IMPNG", - 2020 - ], - [ - "R1", - "E_NGCC", - 2025 - ], - [ - "R1", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - "T_GSL", - 2020 - ], - [ - "R1", - "S_IMPETH", - 2020 - ], - [ - "R1", - "R_EH", - 2020 - ], - [ - "R1", - "R_NGH", - 2025 - ], - [ - "R2", - "T_GSL", - 2020 - ], - [ - "R2", - "T_DSL", - 2025 - ], - [ - "R2", - "S_IMPNG", - 2020 - ], - [ - "R2", - "E_NGCC", - 2025 - ], - [ - "R2", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - "R_EH", - 2020 - ], - [ - "R2", - "S_IMPETH", - 2020 - ], - [ - "R2", - "R_NGH", - 2025 - ], - [ - "R1", - "E_SOLPV", - 2030 - ], - [ - "R1", - "T_DSL", - 2030 - ], - [ - "R2", - "E_BATT", - 2025 - ], - [ - "R2", - "E_SOLPV", - 2030 - ], - [ - "R1", - "T_EV", - 2025 - ], - [ - "R1", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - "E_BATT", - 2030 - ], - [ - "R1", - "T_GSL", - 2025 - ], - [ - "R2", - "T_EV", - 2025 - ], - [ - "R1", - "E_NGCC", - 2030 - ], - [ - "R1", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - "E_NUCLEAR", - 2015 - ], - [ - "R2-R1", - "E_TRANS", - 2015 - ], - [ - "R2", - "T_DSL", - 2030 - ], - [ - "R1", - "R_EH", - 2025 - ], - [ - "R1", - "R_NGH", - 2030 - ], - [ - "R1", - "E_SOLPV", - 2020 - ], - [ - "R1", - "T_DSL", - 2020 - ], - [ - "R2", - "T_GSL", - 2025 - ], - [ - "R2", - "E_NGCC", - 2030 - ], - [ - "R2", - "R_EH", - 2025 - ], - [ - "R2", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - "S_IMPURN", - 2020 - ], - [ - "R1-R2", - "E_TRANS", - 2015 - ], - [ - "R1", - "T_BLND", - 2020 - ], - [ - "R2", - "E_SOLPV", - 2020 - ] - ], - "LoanLifetimeProcess_rtv": [ - [ - "R2", - "T_BLND", - 2020 - ], - [ - "R2", - "E_BATT", - 2030 - ], - [ - "R2", - "S_IMPURN", - 2020 - ], - [ - "R1", - "E_BATT", - 2020 - ], - [ - "R1", - "T_EV", - 2030 - ], - [ - "R1", - "E_NGCC", - 2020 - ], - [ - "R1", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - "T_DSL", - 2020 - ], - [ - "R1", - "R_NGH", - 2020 - ], - [ - "R1", - "T_GSL", - 2030 - ], - [ - "R2", - "T_EV", - 2030 - ], - [ - "R2", - "E_NGCC", - 2020 - ], - [ - "R1", - "R_EH", - 2030 - ], - [ - "R2", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - "R_NGH", - 2020 - ], - [ - "R1", - "S_OILREF", - 2020 - ], - [ - "R1", - "E_SOLPV", - 2025 - ], - [ - "R1", - "T_DSL", - 2025 - ], - [ - "R1", - "S_IMPOIL", - 2020 - ], - [ - "R2", - "E_BATT", - 2020 - ], - [ - "R2", - "T_GSL", - 2030 - ], - [ - "R2", - "E_SOLPV", - 2025 - ], - [ - "R1", - "T_EV", - 2020 - ], - [ - "R2", - "R_EH", - 2030 - ], - [ - "R2", - "R_NGH", - 2030 - ], - [ - "R2", - "S_OILREF", - 2020 - ], - [ - "R2", - "S_IMPOIL", - 2020 - ], - [ - "R1", - "E_BATT", - 2025 - ], - [ - "R2", - "T_EV", - 2020 - ], - [ - "R1", - "S_IMPNG", - 2020 - ], - [ - "R1", - "E_NGCC", - 2025 - ], - [ - "R1", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - "T_GSL", - 2020 - ], - [ - "R1", - "S_IMPETH", - 2020 - ], - [ - "R1", - "R_EH", - 2020 - ], - [ - "R1", - "R_NGH", - 2025 - ], - [ - "R2", - "T_GSL", - 2020 - ], - [ - "R2", - "T_DSL", - 2025 - ], - [ - "R2", - "S_IMPNG", - 2020 - ], - [ - "R2", - "E_NGCC", - 2025 - ], - [ - "R2", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - "R_EH", - 2020 - ], - [ - "R2", - "S_IMPETH", - 2020 - ], - [ - "R2", - "R_NGH", - 2025 - ], - [ - "R1", - "E_SOLPV", - 2030 - ], - [ - "R1", - "T_DSL", - 2030 - ], - [ - "R2", - "E_BATT", - 2025 - ], - [ - "R2", - "E_SOLPV", - 2030 - ], - [ - "R1", - "T_EV", - 2025 - ], - [ - "R1", - "E_BATT", - 2030 - ], - [ - "R1", - "T_GSL", - 2025 - ], - [ - "R2", - "T_EV", - 2025 - ], - [ - "R1", - "E_NGCC", - 2030 - ], - [ - "R1", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - "T_DSL", - 2030 - ], - [ - "R1", - "R_EH", - 2025 - ], - [ - "R1", - "R_NGH", - 2030 - ], - [ - "R1", - "E_SOLPV", - 2020 - ], - [ - "R1", - "T_DSL", - 2020 - ], - [ - "R2", - "T_GSL", - 2025 - ], - [ - "R2", - "E_NGCC", - 2030 - ], - [ - "R2", - "R_EH", - 2025 - ], - [ - "R2", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - "S_IMPURN", - 2020 - ], - [ - "R1", - "T_BLND", - 2020 - ], - [ - "R2", - "E_SOLPV", - 2020 - ] - ], - "RenewablePortfolioStandardConstraint_rpg": [], - "CostFixed_rptv": [ - [ - "R1", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2025, - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "T_EV", - 2020 - ], - [ - "R1-R2", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2020, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2020 - ], - [ - "R1", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2020, - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2-R1", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2030, - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R2-R1", - 2020, - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2030 - ], - [ - "R1-R2", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "T_GSL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2025 - ], - [ - "R2", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "R_NGH", - 2020 - ], - [ - "R1-R2", - 2025, - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2025, - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2020, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2030 - ], - [ - "R2-R1", - 2025, - "E_TRANS", - 2015 - ], - [ - "R2", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "R_EH", - 2025 - ] - ], - "CostInvest_rtv": [ - [ - "R1", - "E_NGCC", - 2020 - ], - [ - "R1", - "E_NGCC", - 2025 - ], - [ - "R1", - "E_NGCC", - 2030 - ], - [ - "R1", - "E_SOLPV", - 2020 - ], - [ - "R1", - "E_SOLPV", - 2025 - ], - [ - "R1", - "E_SOLPV", - 2030 - ], - [ - "R1", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - "E_BATT", - 2020 - ], - [ - "R1", - "E_BATT", - 2025 - ], - [ - "R1", - "E_BATT", - 2030 - ], - [ - "R1", - "T_GSL", - 2020 - ], - [ - "R1", - "T_GSL", - 2025 - ], - [ - "R1", - "T_GSL", - 2030 - ], - [ - "R1", - "T_DSL", - 2020 - ], - [ - "R1", - "T_DSL", - 2025 - ], - [ - "R1", - "T_DSL", - 2030 - ], - [ - "R1", - "T_EV", - 2020 - ], - [ - "R1", - "T_EV", - 2025 - ], - [ - "R1", - "T_EV", - 2030 - ], - [ - "R1", - "R_EH", - 2020 - ], - [ - "R1", - "R_EH", - 2025 - ], - [ - "R1", - "R_EH", - 2030 - ], - [ - "R1", - "R_NGH", - 2020 - ], - [ - "R1", - "R_NGH", - 2025 - ], - [ - "R1", - "R_NGH", - 2030 - ], - [ - "R2", - "E_NGCC", - 2020 - ], - [ - "R2", - "E_NGCC", - 2025 - ], - [ - "R2", - "E_NGCC", - 2030 - ], - [ - "R2", - "E_SOLPV", - 2020 - ], - [ - "R2", - "E_SOLPV", - 2025 - ], - [ - "R2", - "E_SOLPV", - 2030 - ], - [ - "R2", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - "E_BATT", - 2020 - ], - [ - "R2", - "E_BATT", - 2025 - ], - [ - "R2", - "E_BATT", - 2030 - ], - [ - "R2", - "T_GSL", - 2020 - ], - [ - "R2", - "T_GSL", - 2025 - ], - [ - "R2", - "T_GSL", - 2030 - ], - [ - "R2", - "T_DSL", - 2020 - ], - [ - "R2", - "T_DSL", - 2025 - ], - [ - "R2", - "T_DSL", - 2030 - ], - [ - "R2", - "T_EV", - 2020 - ], - [ - "R2", - "T_EV", - 2025 - ], - [ - "R2", - "T_EV", - 2030 - ], - [ - "R2", - "R_EH", - 2020 - ], - [ - "R2", - "R_EH", - 2025 - ], - [ - "R2", - "R_EH", - 2030 - ], - [ - "R2", - "R_NGH", - 2020 - ], - [ - "R2", - "R_NGH", - 2025 - ], - [ - "R2", - "R_NGH", - 2030 - ] - ], - "CostVariable_rptv": [ - [ - "R1", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2025, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "T_EV", - 2020 - ], - [ - "R1-R2", - 2030, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2020, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2025, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2020, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "S_IMPNG", - 2020 - ], - [ - "R1", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "T_DSL", - 2020 - ], - [ - "R1", - 2020, - "S_IMPURN", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2025 - ], - [ - "R1", - 2025, - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2020, - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2020, - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2-R1", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2025, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2020, - "S_IMPOIL", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "S_IMPURN", - 2020 - ], - [ - "R2-R1", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2030 - ], - [ - "R1-R2", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "T_GSL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "S_IMPETH", - 2020 - ], - [ - "R2", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2025 - ], - [ - "R2", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2030, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "S_IMPETH", - 2020 - ], - [ - "R1-R2", - 2025, - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2025, - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2025, - "S_IMPNG", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2020, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2030 - ], - [ - "R2", - 2025, - "S_IMPETH", - 2020 - ], - [ - "R2-R1", - 2025, - "E_TRANS", - 2015 - ], - [ - "R2", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2020, - "S_IMPURN", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2025, - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2025 - ], - [ - "R2", - 2025, - "R_EH", - 2025 - ] - ], - "CostEmission_rpe": [], - "ModelProcessLife_rptv": [ - [ - "R1", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2025, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "T_EV", - 2020 - ], - [ - "R1-R2", - 2030, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2020, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2025, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2020, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "S_IMPNG", - 2020 - ], - [ - "R1", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "T_DSL", - 2020 - ], - [ - "R1", - 2020, - "S_IMPURN", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2025 - ], - [ - "R1", - 2025, - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2020, - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2020, - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2-R1", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2025, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2020, - "S_IMPOIL", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "S_IMPURN", - 2020 - ], - [ - "R2-R1", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2030 - ], - [ - "R1-R2", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "T_GSL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "S_IMPETH", - 2020 - ], - [ - "R2", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2025 - ], - [ - "R2", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2030, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "S_IMPETH", - 2020 - ], - [ - "R1-R2", - 2025, - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2025, - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2025, - "S_IMPNG", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2020, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2030 - ], - [ - "R2", - 2025, - "S_IMPETH", - 2020 - ], - [ - "R2-R1", - 2025, - "E_TRANS", - 2015 - ], - [ - "R2", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2020, - "S_IMPURN", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2025, - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2025 - ], - [ - "R2", - 2025, - "R_EH", - 2025 - ] - ], - "ProcessLifeFrac_rptv": [ - [ - "R1", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2025, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "T_EV", - 2020 - ], - [ - "R1-R2", - 2030, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2020, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2025, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2020, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "S_IMPNG", - 2020 - ], - [ - "R1", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "T_DSL", - 2020 - ], - [ - "R1", - 2020, - "S_IMPURN", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2025 - ], - [ - "R1", - 2025, - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2020, - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2020, - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2-R1", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2025, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2020, - "S_IMPOIL", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "S_IMPURN", - 2020 - ], - [ - "R2-R1", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2030 - ], - [ - "R1-R2", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "T_GSL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "S_IMPETH", - 2020 - ], - [ - "R2", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2025 - ], - [ - "R2", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2030, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "S_IMPNG", - 2020 - ], - [ - "R2", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "S_IMPETH", - 2020 - ], - [ - "R1-R2", - 2025, - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2025, - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2025, - "S_IMPNG", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2020, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2030 - ], - [ - "R2", - 2025, - "S_IMPETH", - 2020 - ], - [ - "R2-R1", - 2025, - "E_TRANS", - 2015 - ], - [ - "R2", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "S_IMPOIL", - 2020 - ], - [ - "R2", - 2020, - "S_IMPURN", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "S_IMPETH", - 2020 - ], - [ - "R1", - 2025, - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "S_IMPURN", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2025 - ], - [ - "R2", - 2025, - "R_EH", - 2025 - ] - ], - "MinCapacityConstraint_rpt": [], - "MaxCapacityConstraint_rpt": [], - "MinNewCapacityConstraint_rpt": [], - "MaxNewCapacityConstraint_rpt": [], - "MaxResourceConstraint_rt": [], - "MaxActivityConstraint_rpt": [], - "MinActivityConstraint_rpt": [ - [ - "R1", - 2020, - "T_GSL" - ], - [ - "R1", - 2025, - "T_GSL" - ], - [ - "R1", - 2030, - "T_GSL" - ], - [ - "R2", - 2020, - "T_GSL" - ], - [ - "R2", - 2025, - "T_GSL" - ], - [ - "R2", - 2030, - "T_GSL" - ] - ], - "MinAnnualCapacityFactorConstraint_rpto": [], - "MaxAnnualCapacityFactorConstraint_rpto": [], - "EmissionLimitConstraint_rpe": [ - [ - "R1", - 2020, - "CO2" - ], - [ - "R1", - 2025, - "CO2" - ], - [ - "R1", - 2030, - "CO2" - ], - [ - "global", - 2020, - "CO2" - ], - [ - "global", - 2025, - "CO2" - ], - [ - "global", - 2030, - "CO2" - ] - ], - "EmissionActivity_reitvo": [ - [ - "R1", - "CO2", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - "CO2", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - "CO2", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - "CO2", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R2", - "CO2", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - "CO2", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - "CO2", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - "CO2", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - "CO2", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - "CO2", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - "CO2", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - "CO2", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - "CO2", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - "CO2", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - "CO2", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - "CO2", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - "CO2", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - "CO2", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - "CO2", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - "CO2", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R1", - "CO2", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - "CO2", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - "CO2", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - "CO2", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - "CO2", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - "CO2", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - "CO2", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - "CO2", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - "CO2", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - "CO2", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - "CO2", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - "CO2", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - "CO2", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - "CO2", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - "CO2", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - "CO2", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - "CO2", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - "CO2", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R2", - "CO2", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - "CO2", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - "CO2", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - "CO2", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - "CO2", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - "CO2", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - "CO2", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R1", - "CO2", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - "CO2", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R2", - "CO2", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - "CO2", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - "CO2", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - "CO2", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - "CO2", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - "CO2", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - "CO2", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - "CO2", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - "CO2", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - "CO2", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - "CO2", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - "CO2", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - "CO2", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - "CO2", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - "CO2", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - "CO2", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - "CO2", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R1", - "CO2", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - "CO2", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - "CO2", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - "CO2", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - "CO2", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - "CO2", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - "CO2", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - "CO2", - "NG", - "E_NGCC", - 2025, - "ELC" - ] - ], - "MinActivityGroup_rpg": [], - "MaxActivityGroup_rpg": [], - "MinCapacityGroupConstraint_rpg": [], - "MaxCapacityGroupConstraint_rpg": [], - "MinNewCapacityGroupConstraint_rpg": [], - "MaxNewCapacityGroupConstraint_rpg": [], - "GroupShareIndices": [], - "MinCapacityShareConstraint_rptg": [], - "MaxCapacityShareConstraint_rptg": [], - "MinActivityShareConstraint_rptg": [], - "MaxActivityShareConstraint_rptg": [], - "MinNewCapacityShareConstraint_rptg": [], - "MaxNewCapacityShareConstraint_rptg": [], - "StorageInit_rtv": [ - [ - "R2", - "E_BATT", - 2030 - ], - [ - "R2", - "E_BATT", - 2020 - ], - [ - "R1", - "E_BATT", - 2030 - ], - [ - "R1", - "E_BATT", - 2020 - ], - [ - "R2", - "E_BATT", - 2025 - ], - [ - "R1", - "E_BATT", - 2025 - ] - ], - "FlowVar_rpsditvo": [ - [ - "R1", - 2025, - "summer", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2025, - "spring", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "summer", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "summer", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "winter", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "summer", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "winter", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "fall", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "fall", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "fall", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "winter", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "fall", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "fall", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "fall", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "spring", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "spring", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2020, - "winter", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1-R2", - 2020, - "winter", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "summer", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "fall", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "spring", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "summer", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2020, - "winter", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "spring", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2020, - "spring", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "summer", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "summer", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "spring", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "summer", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "summer", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "fall", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "fall", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "spring", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "summer", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "fall", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2-R1", - 2030, - "winter", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "spring", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2020, - "fall", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "spring", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "winter", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1-R2", - 2020, - "winter", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "winter", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "winter", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "winter", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2-R1", - 2020, - "winter", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "spring", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "spring", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "winter", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2020, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1-R2", - 2020, - "spring", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1-R2", - 2025, - "spring", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "fall", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2020, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "winter", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1-R2", - 2030, - "spring", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1-R2", - 2025, - "winter", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "winter", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "fall", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "winter", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2025, - "winter", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "fall", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "summer", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "summer", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "spring", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "winter", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "spring", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2020, - "winter", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "spring", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "winter", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "fall", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "summer", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "winter", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "spring", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2-R1", - 2025, - "fall", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "summer", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "winter", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2-R1", - 2025, - "winter", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2025, - "fall", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "winter", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2-R1", - 2020, - "fall", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "spring", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "winter", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "spring", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "fall", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "spring", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2020, - "winter", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "fall", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1-R2", - 2030, - "winter", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "summer", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1-R2", - 2025, - "summer", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "winter", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "summer", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "spring", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "winter", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "winter", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "winter", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2025, - "summer", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "winter", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2025, - "summer", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "fall", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1-R2", - 2020, - "fall", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2-R1", - 2020, - "spring", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1-R2", - 2025, - "fall", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "spring", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "fall", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "spring", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "summer", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "summer", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "summer", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2-R1", - 2020, - "summer", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2-R1", - 2030, - "fall", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "spring", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "winter", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "fall", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "fall", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "fall", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "spring", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "summer", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2020, - "spring", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "fall", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1-R2", - 2025, - "fall", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "spring", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2020, - "summer", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "spring", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2020, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "summer", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "summer", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2025, - "fall", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2025, - "summer", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "summer", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "fall", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "fall", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "summer", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2020, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2020, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "winter", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "winter", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1-R2", - 2020, - "spring", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "summer", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2-R1", - 2030, - "summer", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2020, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1-R2", - 2030, - "winter", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "summer", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "summer", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R1-R2", - 2030, - "summer", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "spring", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "summer", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "spring", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "summer", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "summer", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "summer", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "spring", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "fall", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "spring", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "summer", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "winter", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2020, - "summer", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "spring", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "spring", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "spring", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "summer", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "spring", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "summer", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "spring", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "fall", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R1", - 2030, - "winter", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "summer", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "fall", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "summer", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "spring", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2020, - "winter", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "spring", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "spring", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "winter", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2-R1", - 2020, - "summer", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2020, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "fall", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "summer", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "fall", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2025, - "spring", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "fall", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "winter", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "summer", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2-R1", - 2025, - "winter", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "spring", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2020, - "summer", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "winter", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "fall", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2-R1", - 2030, - "summer", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2-R1", - 2025, - "summer", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "fall", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2025, - "fall", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "winter", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "fall", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "winter", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2025, - "spring", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1-R2", - 2030, - "summer", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "summer", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "spring", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "winter", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "winter", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "winter", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "fall", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "winter", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2020, - "spring", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "winter", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2020, - "winter", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "winter", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "fall", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R1", - 2030, - "winter", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2025, - "winter", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "spring", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1-R2", - 2030, - "spring", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "winter", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1-R2", - 2020, - "summer", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1-R2", - 2020, - "summer", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "summer", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "fall", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2030, - "spring", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2-R1", - 2030, - "winter", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "winter", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2-R1", - 2020, - "winter", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "fall", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "spring", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "spring", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "winter", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1-R2", - 2030, - "fall", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R2", - 2020, - "spring", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "spring", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2020, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "winter", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "summer", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "winter", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "spring", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2020, - "winter", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "fall", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2-R1", - 2030, - "fall", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "fall", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "spring", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "summer", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "winter", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1-R2", - 2025, - "winter", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2020, - "spring", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "winter", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2025, - "spring", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2025, - "summer", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2-R1", - 2025, - "spring", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "fall", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "winter", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R1", - 2030, - "winter", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2020, - "spring", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "winter", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "winter", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "winter", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "spring", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2030, - "winter", - "day", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2-R1", - 2025, - "fall", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "spring", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2030, - "spring", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "winter", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2020, - "fall", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "winter", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "spring", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R1", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2-R1", - 2030, - "spring", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "winter", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "spring", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "fall", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2025, - "spring", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "fall", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "fall", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2020, - "fall", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "summer", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1-R2", - 2020, - "fall", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "fall", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2025, - "winter", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "spring", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "spring", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "fall", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "summer", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "fall", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "summer", - "day", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "summer", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "winter", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "fall", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "fall", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "winter", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2-R1", - 2020, - "fall", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "winter", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2020, - "winter", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2020, - "spring", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2020, - "fall", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2020, - "spring", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "summer", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R2", - 2020, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "spring", - "day", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "summer", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "spring", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "spring", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "summer", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2025, - "summer", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2030, - "winter", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2020, - "winter", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1-R2", - 2025, - "summer", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R2-R1", - 2030, - "spring", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R2", - 2025, - "spring", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2025, - "summer", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2-R1", - 2020, - "spring", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2025, - "fall", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2020, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "summer", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "winter", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "ethos", - "S_IMPNG", - 2020, - "NG" - ], - [ - "R1", - 2025, - "fall", - "night", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2025, - "spring", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "winter", - "night", - "SOL", - "E_SOLPV", - 2030, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2025, - "winter", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "summer", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R1-R2", - 2025, - "spring", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R2-R1", - 2025, - "summer", - "night", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "summer", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2", - 2030, - "winter", - "day", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "spring", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2020, - "spring", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "day", - "DSL", - "T_DSL", - 2025, - "VMT" - ], - [ - "R1", - 2020, - "winter", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R1", - 2025, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R1", - 2030, - "spring", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "winter", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "R_EH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "fall", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2025, - "summer", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "summer", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2020, - "winter", - "day", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2020, - "fall", - "day", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2030, - "spring", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2020, - "summer", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "winter", - "day", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "NG", - "E_NGCC", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "T_EV", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R2", - 2025, - "winter", - "day", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "spring", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "URN", - "E_NUCLEAR", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "T_EV", - 2025, - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "GSL", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "spring", - "night", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2020, - "summer", - "day", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "winter", - "night", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "NG", - "E_NGCC", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R2", - 2030, - "winter", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2025, - "spring", - "day", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2025, - "winter", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2025, - "summer", - "night", - "ethos", - "S_IMPOIL", - 2020, - "OIL" - ], - [ - "R2", - 2030, - "spring", - "day", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "R_EH", - 2020, - "RH" - ], - [ - "R2", - 2030, - "fall", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R2", - 2020, - "winter", - "night", - "E10", - "T_GSL", - 2020, - "VMT" - ], - [ - "R1", - 2025, - "spring", - "day", - "E10", - "T_GSL", - 2025, - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "NG", - "R_NGH", - 2020, - "RH" - ], - [ - "R1", - 2030, - "summer", - "night", - "E10", - "T_GSL", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "summer", - "day", - "DSL", - "T_DSL", - 2030, - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "SOL", - "E_SOLPV", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2020, - "winter", - "night", - "ETH", - "T_BLND", - 2020, - "E10" - ], - [ - "R1", - 2030, - "summer", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "SOL", - "E_SOLPV", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2020, - "fall", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1-R2", - 2030, - "fall", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2030, - "winter", - "day", - "URN", - "E_NUCLEAR", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R1", - 2020, - "fall", - "night", - "ELC", - "T_EV", - 2020, - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "R_EH", - 2030, - "RH" - ], - [ - "R2", - 2030, - "summer", - "night", - "DSL", - "T_DSL", - 2020, - "VMT" - ], - [ - "R1", - 2030, - "winter", - "day", - "NG", - "E_NGCC", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "NG", - "R_NGH", - 2025, - "RH" - ], - [ - "R1", - 2025, - "spring", - "day", - "ethos", - "S_IMPETH", - 2020, - "ETH" - ], - [ - "R1", - 2025, - "summer", - "day", - "URN", - "E_NUCLEAR", - 2015, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "URN", - "E_NUCLEAR", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "NG", - "R_NGH", - 2030, - "RH" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "ethos", - "S_IMPURN", - 2020, - "URN" - ], - [ - "R2-R1", - 2025, - "spring", - "day", - "ELC", - "E_TRANS", - 2015, - "ELC" - ] - ], - "FlowVarAnnual_rpitvo": [], - "FlexVar_rpsditvo": [], - "FlexVarAnnual_rpitvo": [], - "CurtailmentVar_rpsditvo": [ - [ - "R1", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "spring", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "winter", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "summer", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "fall", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "spring", - "night", - "OIL", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "winter", - "day", - "OIL", - "S_OILREF", - 2020, - "GSL" - ] - ], - "FlowInStorage_rpsditvo": [ - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2020, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2020, - "winter", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC", - "E_BATT", - 2025, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2020, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC", - "E_BATT", - 2020, - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC", - "E_BATT", - 2030, - "ELC" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC", - "E_BATT", - 2025, - "ELC" - ] - ], - "StorageLevel_rpsdtv": [ - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2025 - ] - ], - "CapacityVar_rptv": [ - [ - "R1", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2025, - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "T_EV", - 2020 - ], - [ - "R1-R2", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2020, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2020 - ], - [ - "R1", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2030 - ], - [ - "R1", - 2020, - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "E_NGCC", - 2030 - ], - [ - "R2-R1", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2030, - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "E_SOLPV", - 2020 - ], - [ - "R2-R1", - 2020, - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2030 - ], - [ - "R1-R2", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "T_GSL", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "T_GSL", - 2025 - ], - [ - "R2", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "S_OILREF", - 2020 - ], - [ - "R2", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "R_NGH", - 2020 - ], - [ - "R1-R2", - 2025, - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "R_NGH", - 2030 - ], - [ - "R1", - 2025, - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "T_EV", - 2030 - ], - [ - "R1", - 2020, - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "T_GSL", - 2030 - ], - [ - "R2-R1", - 2025, - "E_TRANS", - 2015 - ], - [ - "R2", - 2020, - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "R_EH", - 2025 - ] - ], - "NewCapacityVar_rtv": [ - [ - "R2", - "T_BLND", - 2020 - ], - [ - "R2", - "E_BATT", - 2030 - ], - [ - "R1", - "E_BATT", - 2020 - ], - [ - "R1", - "T_EV", - 2030 - ], - [ - "R1", - "E_NGCC", - 2020 - ], - [ - "R1", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - "T_DSL", - 2020 - ], - [ - "R1", - "R_NGH", - 2020 - ], - [ - "R1", - "T_GSL", - 2030 - ], - [ - "R2", - "T_EV", - 2030 - ], - [ - "R2", - "E_NGCC", - 2020 - ], - [ - "R1", - "R_EH", - 2030 - ], - [ - "R2", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - "R_NGH", - 2020 - ], - [ - "R1", - "S_OILREF", - 2020 - ], - [ - "R1", - "E_SOLPV", - 2025 - ], - [ - "R1", - "T_DSL", - 2025 - ], - [ - "R2", - "E_BATT", - 2020 - ], - [ - "R2", - "T_GSL", - 2030 - ], - [ - "R2", - "R_EH", - 2030 - ], - [ - "R2", - "E_SOLPV", - 2025 - ], - [ - "R1", - "T_EV", - 2020 - ], - [ - "R2", - "R_NGH", - 2030 - ], - [ - "R2", - "S_OILREF", - 2020 - ], - [ - "R1", - "E_BATT", - 2025 - ], - [ - "R2", - "T_EV", - 2020 - ], - [ - "R1", - "E_NGCC", - 2025 - ], - [ - "R1", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - "T_GSL", - 2020 - ], - [ - "R2", - "T_DSL", - 2025 - ], - [ - "R1", - "R_EH", - 2020 - ], - [ - "R1", - "R_NGH", - 2025 - ], - [ - "R2", - "T_GSL", - 2020 - ], - [ - "R2", - "E_NGCC", - 2025 - ], - [ - "R2", - "R_EH", - 2020 - ], - [ - "R2", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - "R_NGH", - 2025 - ], - [ - "R1", - "E_SOLPV", - 2030 - ], - [ - "R1", - "T_DSL", - 2030 - ], - [ - "R2", - "E_BATT", - 2025 - ], - [ - "R2", - "E_SOLPV", - 2030 - ], - [ - "R1", - "T_EV", - 2025 - ], - [ - "R1", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - "E_BATT", - 2030 - ], - [ - "R1", - "T_GSL", - 2025 - ], - [ - "R2", - "T_EV", - 2025 - ], - [ - "R1", - "E_NGCC", - 2030 - ], - [ - "R1", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - "E_NUCLEAR", - 2015 - ], - [ - "R2-R1", - "E_TRANS", - 2015 - ], - [ - "R2", - "T_DSL", - 2030 - ], - [ - "R1", - "R_EH", - 2025 - ], - [ - "R1", - "R_NGH", - 2030 - ], - [ - "R1", - "E_SOLPV", - 2020 - ], - [ - "R1", - "T_DSL", - 2020 - ], - [ - "R2", - "T_GSL", - 2025 - ], - [ - "R2", - "E_NGCC", - 2030 - ], - [ - "R2", - "R_EH", - 2025 - ], - [ - "R2", - "E_NUCLEAR", - 2030 - ], - [ - "R1-R2", - "E_TRANS", - 2015 - ], - [ - "R1", - "T_BLND", - 2020 - ], - [ - "R2", - "E_SOLPV", - 2020 - ] - ], - "RetiredCapacityVar_rptv": [], - "CapacityAvailableVar_rpt": [ - [ - "R1-R2", - 2025, - "E_TRANS" - ], - [ - "R2", - 2025, - "E_SOLPV" - ], - [ - "R1", - 2030, - "E_NGCC" - ], - [ - "R2", - 2030, - "R_EH" - ], - [ - "R2", - 2025, - "E_BATT" - ], - [ - "R2", - 2030, - "T_EV" - ], - [ - "R1", - 2020, - "S_OILREF" - ], - [ - "R1", - 2025, - "T_EV" - ], - [ - "R2", - 2030, - "E_NUCLEAR" - ], - [ - "R2", - 2025, - "T_DSL" - ], - [ - "R1", - 2025, - "E_NUCLEAR" - ], - [ - "R2-R1", - 2025, - "E_TRANS" - ], - [ - "R1", - 2025, - "R_EH" - ], - [ - "R1", - 2020, - "T_GSL" - ], - [ - "R2", - 2025, - "T_BLND" - ], - [ - "R2", - 2020, - "S_OILREF" - ], - [ - "R1", - 2030, - "R_NGH" - ], - [ - "R2", - 2020, - "T_GSL" - ], - [ - "R1", - 2020, - "E_BATT" - ], - [ - "R1", - 2020, - "E_SOLPV" - ], - [ - "R1", - 2020, - "T_DSL" - ], - [ - "R1-R2", - 2020, - "E_TRANS" - ], - [ - "R2", - 2030, - "E_NGCC" - ], - [ - "R2", - 2020, - "E_SOLPV" - ], - [ - "R1", - 2025, - "E_NGCC" - ], - [ - "R1", - 2020, - "T_BLND" - ], - [ - "R2", - 2020, - "E_BATT" - ], - [ - "R2", - 2025, - "T_EV" - ], - [ - "R2-R1", - 2020, - "E_TRANS" - ], - [ - "R2", - 2025, - "E_NUCLEAR" - ], - [ - "R2", - 2020, - "T_DSL" - ], - [ - "R2", - 2025, - "R_EH" - ], - [ - "R1", - 2030, - "S_OILREF" - ], - [ - "R2", - 2020, - "T_BLND" - ], - [ - "R2", - 2030, - "R_NGH" - ], - [ - "R1", - 2030, - "T_GSL" - ], - [ - "R1", - 2025, - "R_NGH" - ], - [ - "R1", - 2020, - "T_EV" - ], - [ - "R1", - 2030, - "E_SOLPV" - ], - [ - "R1", - 2020, - "R_EH" - ], - [ - "R2", - 2025, - "E_NGCC" - ], - [ - "R1", - 2020, - "E_NUCLEAR" - ], - [ - "R1", - 2030, - "E_BATT" - ], - [ - "R2", - 2020, - "R_EH" - ], - [ - "R2", - 2020, - "T_EV" - ], - [ - "R1", - 2030, - "T_DSL" - ], - [ - "R2", - 2020, - "E_NUCLEAR" - ], - [ - "R2", - 2030, - "S_OILREF" - ], - [ - "R1", - 2030, - "T_BLND" - ], - [ - "R1", - 2025, - "S_OILREF" - ], - [ - "R2", - 2030, - "T_GSL" - ], - [ - "R1", - 2020, - "E_NGCC" - ], - [ - "R1", - 2025, - "T_GSL" - ], - [ - "R2", - 2025, - "R_NGH" - ], - [ - "R1-R2", - 2030, - "E_TRANS" - ], - [ - "R2", - 2030, - "E_SOLPV" - ], - [ - "R1", - 2025, - "E_SOLPV" - ], - [ - "R2", - 2030, - "E_BATT" - ], - [ - "R2", - 2020, - "E_NGCC" - ], - [ - "R2-R1", - 2030, - "E_TRANS" - ], - [ - "R1", - 2025, - "E_BATT" - ], - [ - "R1", - 2030, - "T_EV" - ], - [ - "R2", - 2030, - "T_DSL" - ], - [ - "R1", - 2030, - "E_NUCLEAR" - ], - [ - "R1", - 2025, - "T_DSL" - ], - [ - "R1", - 2030, - "R_EH" - ], - [ - "R2", - 2030, - "T_BLND" - ], - [ - "R1", - 2020, - "R_NGH" - ], - [ - "R1", - 2025, - "T_BLND" - ], - [ - "R2", - 2025, - "S_OILREF" - ], - [ - "R2", - 2025, - "T_GSL" - ], - [ - "R2", - 2020, - "R_NGH" - ] - ], - "CapacityConstraint_rpsdtv": [ - [ - "R1", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2020, - "spring", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_GSL", - 2030 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "summer", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "R_NGH", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "winter", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "R_NGH", - 2030 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_GSL", - 2030 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_GSL", - 2030 - ], - [ - "R1", - 2025, - "summer", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2-R1", - 2020, - "summer", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_EV", - 2030 - ], - [ - "R1", - 2030, - "winter", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "T_DSL", - 2020 - ], - [ - "R2-R1", - 2025, - "fall", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "R_NGH", - 2025 - ], - [ - "R1", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2-R1", - 2020, - "summer", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_EV", - 2030 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "spring", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2020, - "spring", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_EV", - 2030 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_EV", - 2030 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "spring", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "R_NGH", - 2025 - ], - [ - "R1", - 2020, - "fall", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_DSL", - 2030 - ], - [ - "R1", - 2020, - "spring", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_EV", - 2025 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_DSL", - 2030 - ], - [ - "R1", - 2030, - "summer", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_EV", - 2030 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_DSL", - 2030 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "spring", - "day", - "T_DSL", - 2020 - ], - [ - "R2-R1", - 2025, - "summer", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "fall", - "day", - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_EV", - 2025 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "summer", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_EV", - 2030 - ], - [ - "R1", - 2020, - "spring", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_DSL", - 2030 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NGCC", - 2030 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_SOLPV", - 2030 - ], - [ - "R1", - 2020, - "summer", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_DSL", - 2025 - ], - [ - "R2", - 2020, - "summer", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "summer", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_EV", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2020, - "fall", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_DSL", - 2030 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1-R2", - 2025, - "spring", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "summer", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2020, - "summer", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_DSL", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2020, - "summer", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NGCC", - 2030 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1-R2", - 2025, - "winter", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "summer", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "summer", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "T_EV", - 2025 - ], - [ - "R2", - 2025, - "winter", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "spring", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1-R2", - 2030, - "summer", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2020, - "spring", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "spring", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_GSL", - 2030 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2025, - "winter", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NGCC", - 2030 - ], - [ - "R2", - 2025, - "summer", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "summer", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_GSL", - 2030 - ], - [ - "R1", - 2025, - "fall", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_BLND", - 2020 - ], - [ - "R1-R2", - 2025, - "summer", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NGCC", - 2030 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_GSL", - 2030 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1-R2", - 2025, - "fall", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2025, - "winter", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "summer", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "R_EH", - 2030 - ], - [ - "R1", - 2025, - "spring", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "summer", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "spring", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_GSL", - 2025 - ], - [ - "R2", - 2020, - "spring", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2025, - "spring", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2020, - "spring", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "fall", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "R_NGH", - 2030 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1-R2", - 2025, - "summer", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2020, - "spring", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "R_EH", - 2025 - ], - [ - "R2-R1", - 2020, - "fall", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "night", - "T_EV", - 2025 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_GSL", - 2020 - ], - [ - "R2-R1", - 2030, - "winter", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_GSL", - 2030 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_GSL", - 2030 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "R_EH", - 2030 - ], - [ - "R2-R1", - 2030, - "spring", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "R_NGH", - 2025 - ], - [ - "R2", - 2020, - "fall", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "spring", - "night", - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_GSL", - 2030 - ], - [ - "R2", - 2020, - "summer", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_GSL", - 2030 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_GSL", - 2030 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "summer", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "R_EH", - 2030 - ], - [ - "R1", - 2020, - "fall", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_GSL", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R2-R1", - 2025, - "fall", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "fall", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "T_EV", - 2025 - ], - [ - "R2-R1", - 2020, - "fall", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "summer", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "winter", - "night", - "R_EH", - 2030 - ], - [ - "R1", - 2020, - "fall", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "R_NGH", - 2020 - ], - [ - "R2-R1", - 2030, - "winter", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "spring", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "spring", - "day", - "R_NGH", - 2030 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "fall", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2020, - "fall", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_GSL", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "winter", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_DSL", - 2030 - ], - [ - "R2", - 2030, - "winter", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "R_NGH", - 2030 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_DSL", - 2030 - ], - [ - "R1-R2", - 2030, - "fall", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "winter", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "T_EV", - 2025 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "R_NGH", - 2030 - ], - [ - "R1-R2", - 2030, - "fall", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "R_NGH", - 2025 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NGCC", - 2030 - ], - [ - "R2", - 2025, - "fall", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "R_NGH", - 2025 - ], - [ - "R2-R1", - 2020, - "winter", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_EV", - 2030 - ], - [ - "R1", - 2030, - "spring", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2020, - "fall", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_SOLPV", - 2030 - ], - [ - "R1", - 2025, - "spring", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2020, - "winter", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2020, - "summer", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_DSL", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_EV", - 2020 - ], - [ - "R2-R1", - 2030, - "summer", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "summer", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "R_NGH", - 2025 - ], - [ - "R2-R1", - 2020, - "spring", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_EV", - 2030 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_DSL", - 2030 - ], - [ - "R2", - 2030, - "spring", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_EV", - 2025 - ], - [ - "R2", - 2020, - "winter", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "spring", - "day", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2-R1", - 2025, - "winter", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "winter", - "day", - "T_DSL", - 2025 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_DSL", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2-R1", - 2025, - "winter", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_DSL", - 2030 - ], - [ - "R2-R1", - 2020, - "winter", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "summer", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "summer", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "summer", - "night", - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "S_OILREF", - 2020 - ], - [ - "R1-R2", - 2030, - "spring", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_DSL", - 2030 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2-R1", - 2025, - "spring", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "winter", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "fall", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R2-R1", - 2030, - "fall", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "spring", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2030 - ], - [ - "R1-R2", - 2020, - "fall", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "summer", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2-R1", - 2025, - "spring", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "R_EH", - 2030 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NGCC", - 2020 - ], - [ - "R1-R2", - 2030, - "winter", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_DSL", - 2030 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "fall", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_DSL", - 2025 - ], - [ - "R1-R2", - 2030, - "summer", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_EV", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NGCC", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2020, - "fall", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NGCC", - 2030 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "summer", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1-R2", - 2020, - "summer", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "summer", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2025, - "winter", - "night", - "S_OILREF", - 2020 - ], - [ - "R1-R2", - 2020, - "summer", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NGCC", - 2030 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2025, - "spring", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1-R2", - 2025, - "fall", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NGCC", - 2025 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_SOLPV", - 2030 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "summer", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2020, - "fall", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NGCC", - 2025 - ], - [ - "R1", - 2020, - "winter", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "spring", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "R_EH", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_EV", - 2030 - ], - [ - "R2", - 2030, - "fall", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_GSL", - 2030 - ], - [ - "R1", - 2030, - "winter", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_EV", - 2030 - ], - [ - "R1", - 2025, - "winter", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "R_NGH", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_GSL", - 2030 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "T_GSL", - 2025 - ], - [ - "R2", - 2025, - "summer", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "summer", - "day", - "T_DSL", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2-R1", - 2030, - "spring", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_DSL", - 2030 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_EV", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_GSL", - 2030 - ], - [ - "R1", - 2025, - "winter", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2020, - "spring", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "spring", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "T_EV", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "R_NGH", - 2030 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NGCC", - 2030 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2020, - "summer", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "R_EH", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "spring", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_EV", - 2030 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_EV", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_EV", - 2030 - ], - [ - "R2-R1", - 2025, - "summer", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "fall", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "winter", - "day", - "T_DSL", - 2020 - ], - [ - "R2-R1", - 2020, - "spring", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_DSL", - 2030 - ], - [ - "R1-R2", - 2025, - "winter", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "winter", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2020, - "fall", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_EV", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_DSL", - 2030 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "spring", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1-R2", - 2025, - "spring", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_DSL", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "T_GSL", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2020, - "spring", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NGCC", - 2030 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_DSL", - 2025 - ], - [ - "R1-R2", - 2030, - "spring", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NGCC", - 2030 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NGCC", - 2030 - ], - [ - "R2-R1", - 2030, - "summer", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2020, - "spring", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1-R2", - 2020, - "fall", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "spring", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "R_EH", - 2030 - ], - [ - "R1", - 2025, - "fall", - "night", - "T_EV", - 2025 - ], - [ - "R1-R2", - 2030, - "winter", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "winter", - "day", - "R_EH", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "winter", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_DSL", - 2025 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NGCC", - 2030 - ], - [ - "R2", - 2020, - "winter", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "R_EH", - 2030 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NGCC", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NGCC", - 2030 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2020, - "winter", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "T_EV", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "R_NGH", - 2030 - ], - [ - "R1", - 2025, - "fall", - "night", - "T_DSL", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "winter", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "R_NGH", - 2030 - ], - [ - "R2-R1", - 2030, - "fall", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2025, - "winter", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_GSL", - 2030 - ], - [ - "R2", - 2030, - "spring", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NGCC", - 2025 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "R_EH", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "R_EH", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "R_NGH", - 2030 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NGCC", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "spring", - "night", - "R_NGH", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2020, - "winter", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NGCC", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "summer", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "R_EH", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "T_GSL", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "R_NGH", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "R_NGH", - 2025 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_SOLPV", - 2030 - ], - [ - "R1", - 2020, - "fall", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "winter", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_GSL", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "winter", - "night", - "T_DSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "spring", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "R_EH", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "R_NGH", - 2025 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1-R2", - 2020, - "winter", - "day", - "E_TRANS", - 2015 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_SOLPV", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NGCC", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "spring", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "spring", - "day", - "T_EV", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "winter", - "night", - "R_EH", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "T_DSL", - 2025 - ], - [ - "R1-R2", - 2020, - "winter", - "night", - "E_TRANS", - 2015 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "T_GSL", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_SOLPV", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "winter", - "night", - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "fall", - "night", - "R_NGH", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_NGCC", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "S_OILREF", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "spring", - "day", - "R_NGH", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "T_GSL", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2020, - "winter", - "day", - "T_EV", - 2020 - ], - [ - "R1-R2", - 2020, - "spring", - "day", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "summer", - "day", - "R_EH", - 2030 - ], - [ - "R2", - 2025, - "winter", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_SOLPV", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "T_EV", - 2030 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_SOLPV", - 2025 - ], - [ - "R2", - 2020, - "spring", - "day", - "T_DSL", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "spring", - "day", - "R_NGH", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "T_EV", - 2020 - ], - [ - "R1-R2", - 2020, - "spring", - "night", - "E_TRANS", - 2015 - ], - [ - "R2", - 2030, - "summer", - "night", - "R_EH", - 2030 - ], - [ - "R1", - 2030, - "spring", - "day", - "R_EH", - 2030 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2020, - "spring", - "night", - "R_NGH", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "T_DSL", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "T_DSL", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_DSL", - 2030 - ], - [ - "R1", - 2030, - "spring", - "night", - "R_EH", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2020, - "summer", - "day", - "R_EH", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "R_NGH", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2025, - "winter", - "day", - "T_DSL", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "S_OILREF", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "T_EV", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "R_NGH", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_SOLPV", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "T_EV", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "R_EH", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "R_NGH", - 2030 - ] - ], - "CapacityAnnualConstraint_rptv": [], - "DemandConstraint_rpsdc": [ - [ - "R2", - 2020, - "summer", - "night", - "RH" - ], - [ - "R2", - 2030, - "fall", - "day", - "RH" - ], - [ - "R1", - 2025, - "fall", - "day", - "RH" - ], - [ - "R1", - 2020, - "fall", - "day", - "RH" - ], - [ - "R2", - 2020, - "fall", - "night", - "RH" - ], - [ - "R1", - 2025, - "spring", - "night", - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "RH" - ], - [ - "R1", - 2020, - "spring", - "night", - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "RH" - ], - [ - "R1", - 2030, - "winter", - "night", - "VMT" - ], - [ - "R1", - 2030, - "summer", - "day", - "VMT" - ], - [ - "R2", - 2030, - "fall", - "day", - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "RH" - ], - [ - "R1", - 2020, - "summer", - "night", - "VMT" - ], - [ - "R1", - 2025, - "fall", - "day", - "VMT" - ], - [ - "R2", - 2025, - "summer", - "night", - "RH" - ], - [ - "R2", - 2020, - "spring", - "day", - "VMT" - ], - [ - "R1", - 2030, - "fall", - "day", - "RH" - ], - [ - "R1", - 2025, - "winter", - "day", - "VMT" - ], - [ - "R2", - 2030, - "fall", - "night", - "RH" - ], - [ - "R2", - 2030, - "winter", - "day", - "RH" - ], - [ - "R1", - 2025, - "fall", - "night", - "RH" - ], - [ - "R1", - 2020, - "fall", - "night", - "RH" - ], - [ - "R1", - 2030, - "spring", - "night", - "RH" - ], - [ - "R1", - 2030, - "summer", - "night", - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "RH" - ], - [ - "R2", - 2030, - "spring", - "day", - "VMT" - ], - [ - "R2", - 2020, - "spring", - "night", - "VMT" - ], - [ - "R2", - 2025, - "spring", - "day", - "RH" - ], - [ - "R1", - 2030, - "fall", - "night", - "RH" - ], - [ - "R2", - 2025, - "summer", - "day", - "VMT" - ], - [ - "R1", - 2025, - "winter", - "night", - "VMT" - ], - [ - "R2", - 2020, - "winter", - "day", - "RH" - ], - [ - "R2", - 2030, - "summer", - "day", - "RH" - ], - [ - "R2", - 2020, - "summer", - "night", - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "RH" - ], - [ - "R1", - 2020, - "fall", - "day", - "VMT" - ], - [ - "R2", - 2025, - "fall", - "day", - "RH" - ], - [ - "R2", - 2020, - "fall", - "night", - "VMT" - ], - [ - "R2", - 2020, - "winter", - "day", - "VMT" - ], - [ - "R1", - 2030, - "spring", - "day", - "VMT" - ], - [ - "R2", - 2030, - "spring", - "night", - "VMT" - ], - [ - "R2", - 2025, - "winter", - "day", - "VMT" - ], - [ - "R1", - 2020, - "winter", - "night", - "RH" - ], - [ - "R2", - 2025, - "spring", - "night", - "RH" - ], - [ - "R2", - 2025, - "summer", - "night", - "VMT" - ], - [ - "R2", - 2020, - "winter", - "night", - "RH" - ], - [ - "R1", - 2030, - "fall", - "day", - "VMT" - ], - [ - "R2", - 2030, - "fall", - "night", - "VMT" - ], - [ - "R1", - 2020, - "winter", - "day", - "RH" - ], - [ - "R2", - 2030, - "winter", - "day", - "VMT" - ], - [ - "R1", - 2025, - "fall", - "night", - "VMT" - ], - [ - "R1", - 2020, - "fall", - "night", - "VMT" - ], - [ - "R2", - 2025, - "fall", - "night", - "RH" - ], - [ - "R1", - 2030, - "spring", - "night", - "VMT" - ], - [ - "R1", - 2025, - "spring", - "day", - "RH" - ], - [ - "R1", - 2020, - "spring", - "day", - "RH" - ], - [ - "R1", - 2030, - "winter", - "day", - "RH" - ], - [ - "R2", - 2030, - "winter", - "night", - "RH" - ], - [ - "R2", - 2025, - "winter", - "night", - "VMT" - ], - [ - "R1", - 2020, - "summer", - "day", - "RH" - ], - [ - "R2", - 2025, - "spring", - "day", - "VMT" - ], - [ - "R1", - 2030, - "fall", - "night", - "VMT" - ], - [ - "R2", - 2030, - "winter", - "night", - "VMT" - ], - [ - "R2", - 2030, - "summer", - "day", - "VMT" - ], - [ - "R1", - 2025, - "summer", - "day", - "VMT" - ], - [ - "R1", - 2025, - "spring", - "night", - "RH" - ], - [ - "R1", - 2020, - "spring", - "night", - "RH" - ], - [ - "R1", - 2030, - "winter", - "night", - "RH" - ], - [ - "R1", - 2030, - "summer", - "day", - "RH" - ], - [ - "R2", - 2025, - "fall", - "day", - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "RH" - ], - [ - "R1", - 2025, - "summer", - "night", - "RH" - ], - [ - "R1", - 2020, - "summer", - "night", - "RH" - ], - [ - "R1", - 2020, - "winter", - "night", - "VMT" - ], - [ - "R2", - 2025, - "spring", - "night", - "VMT" - ], - [ - "R2", - 2020, - "spring", - "day", - "RH" - ], - [ - "R2", - 2020, - "winter", - "night", - "VMT" - ], - [ - "R2", - 2030, - "summer", - "night", - "VMT" - ], - [ - "R1", - 2025, - "winter", - "day", - "RH" - ], - [ - "R1", - 2020, - "winter", - "day", - "VMT" - ], - [ - "R1", - 2025, - "summer", - "night", - "VMT" - ], - [ - "R2", - 2020, - "summer", - "day", - "RH" - ], - [ - "R1", - 2030, - "summer", - "night", - "RH" - ], - [ - "R2", - 2025, - "fall", - "night", - "VMT" - ], - [ - "R2", - 2020, - "fall", - "day", - "RH" - ], - [ - "R1", - 2025, - "spring", - "day", - "VMT" - ], - [ - "R1", - 2020, - "spring", - "day", - "VMT" - ], - [ - "R2", - 2030, - "spring", - "day", - "RH" - ], - [ - "R1", - 2030, - "winter", - "day", - "VMT" - ], - [ - "R2", - 2020, - "summer", - "day", - "VMT" - ], - [ - "R2", - 2020, - "spring", - "night", - "RH" - ], - [ - "R1", - 2020, - "summer", - "day", - "VMT" - ], - [ - "R2", - 2025, - "summer", - "day", - "RH" - ], - [ - "R2", - 2020, - "fall", - "day", - "VMT" - ] - ], - "DemandActivityConstraint_rpsdtv_dem_s0d0": [ - [ - "R1", - 2020, - "spring", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "spring", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "spring", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R1", - 2020, - "spring", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "spring", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "summer", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "fall", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2020, - "winter", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2025, - "winter", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "spring", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "day", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "summer", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "day", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "fall", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "day", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R1", - 2030, - "winter", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "spring", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_GSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_GSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_GSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "spring", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_DSL", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_DSL", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_DSL", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "spring", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_EV", - 2020, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_EV", - 2025, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "T_EV", - 2030, - "VMT", - "spring", - "day" - ], - [ - "R2", - 2020, - "spring", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "R_EH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "R_EH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "R_EH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "spring", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "fall", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2020, - "winter", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "R_NGH", - 2020, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "spring", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "fall", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2025, - "winter", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "R_NGH", - 2025, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "spring", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "day", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "summer", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "day", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "day", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ], - [ - "R2", - 2030, - "winter", - "night", - "R_NGH", - 2030, - "RH", - "spring", - "day" - ] - ], - "CommodityBalanceConstraint_rpsdc": [ - [ - "R2", - 2025, - "winter", - "night", - "E10" - ], - [ - "R1", - 2020, - "fall", - "day", - "ELC" - ], - [ - "R2", - 2020, - "fall", - "night", - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "NG" - ], - [ - "R2", - 2020, - "fall", - "night", - "NG" - ], - [ - "R2", - 2020, - "summer", - "day", - "OIL" - ], - [ - "R2", - 2025, - "fall", - "day", - "GSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "ETH" - ], - [ - "R2", - 2030, - "spring", - "day", - "OIL" - ], - [ - "R2", - 2030, - "winter", - "day", - "GSL" - ], - [ - "R1", - 2030, - "fall", - "day", - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "OIL" - ], - [ - "R1", - 2030, - "fall", - "day", - "NG" - ], - [ - "R2", - 2020, - "winter", - "night", - "GSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "ETH" - ], - [ - "R1", - 2025, - "fall", - "night", - "ELC" - ], - [ - "R1", - 2020, - "fall", - "day", - "DSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "DSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "NG" - ], - [ - "R2", - 2020, - "spring", - "night", - "NG" - ], - [ - "R1", - 2025, - "winter", - "night", - "ELC" - ], - [ - "R1", - 2020, - "winter", - "night", - "E10" - ], - [ - "R2", - 2025, - "fall", - "day", - "URN" - ], - [ - "R1", - 2025, - "winter", - "night", - "NG" - ], - [ - "R2", - 2025, - "spring", - "day", - "GSL" - ], - [ - "R2", - 2030, - "summer", - "night", - "E10" - ], - [ - "R1", - 2030, - "spring", - "day", - "OIL" - ], - [ - "R1", - 2020, - "spring", - "day", - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "GSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "ELC" - ], - [ - "R1", - 2030, - "fall", - "day", - "DSL" - ], - [ - "R2", - 2025, - "winter", - "day", - "OIL" - ], - [ - "R2", - 2030, - "winter", - "day", - "DSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "URN" - ], - [ - "R1", - 2025, - "summer", - "day", - "GSL" - ], - [ - "R1", - 2030, - "spring", - "day", - "NG" - ], - [ - "R1", - 2025, - "fall", - "night", - "DSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "DSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "E10" - ], - [ - "R2", - 2030, - "fall", - "night", - "OIL" - ], - [ - "R2", - 2025, - "winter", - "day", - "NG" - ], - [ - "R1", - 2020, - "fall", - "night", - "OIL" - ], - [ - "R1", - 2020, - "spring", - "day", - "URN" - ], - [ - "R1", - 2025, - "spring", - "night", - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "URN" - ], - [ - "R2", - 2020, - "summer", - "day", - "URN" - ], - [ - "R2", - 2025, - "spring", - "day", - "DSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "DSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "ETH" - ], - [ - "R1", - 2020, - "fall", - "night", - "NG" - ], - [ - "R1", - 2025, - "summer", - "day", - "DSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "OIL" - ], - [ - "R2", - 2030, - "fall", - "day", - "E10" - ], - [ - "R2", - 2020, - "winter", - "night", - "ETH" - ], - [ - "R2", - 2025, - "fall", - "night", - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "URN" - ], - [ - "R1", - 2030, - "fall", - "night", - "NG" - ], - [ - "R2", - 2030, - "winter", - "night", - "ELC" - ], - [ - "R2", - 2030, - "winter", - "night", - "NG" - ], - [ - "R2", - 2030, - "summer", - "day", - "NG" - ], - [ - "R1", - 2020, - "summer", - "day", - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "NG" - ], - [ - "R1", - 2020, - "spring", - "day", - "ETH" - ], - [ - "R1", - 2030, - "winter", - "day", - "ETH" - ], - [ - "R2", - 2025, - "summer", - "day", - "GSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "OIL" - ], - [ - "R1", - 2020, - "spring", - "night", - "GSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "DSL" - ], - [ - "R1", - 2025, - "summer", - "day", - "ETH" - ], - [ - "R2", - 2030, - "spring", - "day", - "E10" - ], - [ - "R2", - 2020, - "summer", - "night", - "GSL" - ], - [ - "R2", - 2025, - "spring", - "night", - "ELC" - ], - [ - "R1", - 2020, - "winter", - "day", - "OIL" - ], - [ - "R1", - 2020, - "spring", - "night", - "ELC" - ], - [ - "R2", - 2025, - "spring", - "night", - "NG" - ], - [ - "R1", - 2030, - "winter", - "night", - "ELC" - ], - [ - "R1", - 2030, - "summer", - "day", - "ELC" - ], - [ - "R1", - 2030, - "winter", - "night", - "NG" - ], - [ - "R1", - 2030, - "summer", - "day", - "NG" - ], - [ - "R2", - 2020, - "spring", - "night", - "E10" - ], - [ - "R1", - 2025, - "winter", - "night", - "E10" - ], - [ - "R2", - 2030, - "spring", - "night", - "GSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "ELC" - ], - [ - "R1", - 2020, - "winter", - "day", - "NG" - ], - [ - "R1", - 2025, - "summer", - "night", - "NG" - ], - [ - "R1", - 2025, - "spring", - "night", - "ETH" - ], - [ - "R2", - 2025, - "summer", - "day", - "URN" - ], - [ - "R1", - 2030, - "spring", - "day", - "E10" - ], - [ - "R2", - 2020, - "summer", - "night", - "URN" - ], - [ - "R1", - 2020, - "spring", - "night", - "DSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "DSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "DSL" - ], - [ - "R2", - 2025, - "winter", - "day", - "E10" - ], - [ - "R2", - 2020, - "fall", - "day", - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "URN" - ], - [ - "R2", - 2020, - "fall", - "day", - "NG" - ], - [ - "R1", - 2020, - "winter", - "night", - "OIL" - ], - [ - "R2", - 2030, - "summer", - "night", - "OIL" - ], - [ - "R2", - 2030, - "fall", - "night", - "E10" - ], - [ - "R1", - 2020, - "summer", - "night", - "OIL" - ], - [ - "R1", - 2020, - "fall", - "night", - "E10" - ], - [ - "R2", - 2020, - "spring", - "day", - "OIL" - ], - [ - "R2", - 2020, - "winter", - "day", - "GSL" - ], - [ - "R2", - 2025, - "summer", - "day", - "ETH" - ], - [ - "R1", - 2025, - "fall", - "day", - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "NG" - ], - [ - "R1", - 2030, - "spring", - "night", - "URN" - ], - [ - "R2", - 2020, - "spring", - "day", - "NG" - ], - [ - "R2", - 2020, - "summer", - "night", - "ETH" - ], - [ - "R1", - 2025, - "winter", - "day", - "ELC" - ], - [ - "R1", - 2030, - "summer", - "night", - "OIL" - ], - [ - "R1", - 2025, - "winter", - "day", - "NG" - ], - [ - "R2", - 2025, - "winter", - "night", - "URN" - ], - [ - "R1", - 2030, - "fall", - "night", - "E10" - ], - [ - "R2", - 2030, - "winter", - "night", - "E10" - ], - [ - "R2", - 2030, - "summer", - "day", - "E10" - ], - [ - "R2", - 2025, - "summer", - "night", - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "ETH" - ], - [ - "R2", - 2020, - "winter", - "day", - "DSL" - ], - [ - "R2", - 2030, - "fall", - "day", - "OIL" - ], - [ - "R2", - 2025, - "spring", - "night", - "E10" - ], - [ - "R1", - 2020, - "fall", - "day", - "OIL" - ], - [ - "R2", - 2020, - "fall", - "night", - "OIL" - ], - [ - "R1", - 2025, - "spring", - "day", - "GSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "DSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "ETH" - ], - [ - "R1", - 2020, - "winter", - "night", - "URN" - ], - [ - "R1", - 2020, - "winter", - "day", - "E10" - ], - [ - "R2", - 2030, - "summer", - "night", - "URN" - ], - [ - "R2", - 2025, - "winter", - "night", - "ETH" - ], - [ - "R1", - 2030, - "fall", - "day", - "OIL" - ], - [ - "R2", - 2020, - "winter", - "day", - "ETH" - ], - [ - "R2", - 2025, - "fall", - "day", - "ELC" - ], - [ - "R1", - 2025, - "fall", - "night", - "OIL" - ], - [ - "R1", - 2025, - "spring", - "day", - "URN" - ], - [ - "R2", - 2030, - "winter", - "day", - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "OIL" - ], - [ - "R2", - 2030, - "winter", - "day", - "NG" - ], - [ - "R2", - 2020, - "winter", - "night", - "ELC" - ], - [ - "R2", - 2020, - "winter", - "night", - "NG" - ], - [ - "R2", - 2025, - "spring", - "day", - "OIL" - ], - [ - "R1", - 2020, - "winter", - "night", - "ETH" - ], - [ - "R2", - 2025, - "fall", - "day", - "DSL" - ], - [ - "R2", - 2020, - "summer", - "day", - "GSL" - ], - [ - "R1", - 2020, - "spring", - "day", - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "NG" - ], - [ - "R1", - 2030, - "winter", - "day", - "ELC" - ], - [ - "R1", - 2020, - "summer", - "night", - "E10" - ], - [ - "R1", - 2030, - "winter", - "day", - "NG" - ], - [ - "R2", - 2030, - "fall", - "day", - "URN" - ], - [ - "R2", - 2020, - "spring", - "day", - "E10" - ], - [ - "R1", - 2025, - "winter", - "day", - "E10" - ], - [ - "R2", - 2030, - "spring", - "day", - "GSL" - ], - [ - "R1", - 2025, - "summer", - "day", - "ELC" - ], - [ - "R1", - 2025, - "summer", - "day", - "NG" - ], - [ - "R1", - 2025, - "spring", - "day", - "ETH" - ], - [ - "R2", - 2020, - "spring", - "night", - "GSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "E10" - ], - [ - "R1", - 2025, - "spring", - "night", - "ELC" - ], - [ - "R1", - 2020, - "spring", - "day", - "DSL" - ], - [ - "R2", - 2020, - "summer", - "day", - "DSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "OIL" - ], - [ - "R2", - 2030, - "spring", - "day", - "URN" - ], - [ - "R1", - 2030, - "spring", - "day", - "GSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "OIL" - ], - [ - "R2", - 2030, - "summer", - "day", - "OIL" - ], - [ - "R2", - 2020, - "spring", - "night", - "URN" - ], - [ - "R2", - 2025, - "winter", - "day", - "GSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "NG" - ], - [ - "R1", - 2020, - "summer", - "day", - "OIL" - ], - [ - "R1", - 2020, - "fall", - "day", - "E10" - ], - [ - "R2", - 2020, - "fall", - "night", - "E10" - ], - [ - "R2", - 2030, - "fall", - "night", - "GSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "DSL" - ], - [ - "R1", - 2030, - "spring", - "day", - "URN" - ], - [ - "R1", - 2020, - "fall", - "night", - "GSL" - ], - [ - "R2", - 2025, - "spring", - "night", - "OIL" - ], - [ - "R2", - 2020, - "summer", - "day", - "ETH" - ], - [ - "R1", - 2020, - "spring", - "night", - "OIL" - ], - [ - "R1", - 2030, - "winter", - "night", - "OIL" - ], - [ - "R1", - 2030, - "summer", - "day", - "OIL" - ], - [ - "R2", - 2025, - "winter", - "day", - "URN" - ], - [ - "R1", - 2030, - "fall", - "day", - "E10" - ], - [ - "R2", - 2030, - "winter", - "day", - "E10" - ], - [ - "R2", - 2025, - "summer", - "day", - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "OIL" - ], - [ - "R2", - 2025, - "summer", - "day", - "NG" - ], - [ - "R2", - 2030, - "spring", - "day", - "ETH" - ], - [ - "R1", - 2025, - "fall", - "night", - "E10" - ], - [ - "R1", - 2020, - "spring", - "night", - "NG" - ], - [ - "R2", - 2030, - "fall", - "night", - "URN" - ], - [ - "R2", - 2020, - "summer", - "night", - "ELC" - ], - [ - "R1", - 2030, - "fall", - "night", - "GSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "NG" - ], - [ - "R2", - 2020, - "spring", - "night", - "ETH" - ], - [ - "R2", - 2030, - "fall", - "night", - "DSL" - ], - [ - "R1", - 2020, - "fall", - "night", - "URN" - ], - [ - "R2", - 2030, - "spring", - "night", - "ELC" - ], - [ - "R2", - 2025, - "spring", - "day", - "E10" - ], - [ - "R2", - 2020, - "fall", - "day", - "OIL" - ], - [ - "R2", - 2025, - "summer", - "day", - "DSL" - ], - [ - "R1", - 2030, - "spring", - "day", - "ETH" - ], - [ - "R1", - 2025, - "summer", - "day", - "E10" - ], - [ - "R1", - 2030, - "fall", - "night", - "URN" - ], - [ - "R2", - 2030, - "summer", - "day", - "URN" - ], - [ - "R2", - 2025, - "winter", - "day", - "ETH" - ], - [ - "R1", - 2030, - "spring", - "night", - "GSL" - ], - [ - "R1", - 2020, - "winter", - "day", - "GSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "ELC" - ], - [ - "R2", - 2030, - "spring", - "night", - "DSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "GSL" - ], - [ - "R2", - 2030, - "fall", - "night", - "ETH" - ], - [ - "R2", - 2025, - "winter", - "night", - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "OIL" - ], - [ - "R1", - 2020, - "fall", - "night", - "ETH" - ], - [ - "R1", - 2025, - "winter", - "day", - "OIL" - ], - [ - "R2", - 2025, - "spring", - "night", - "URN" - ], - [ - "R2", - 2020, - "winter", - "day", - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "OIL" - ], - [ - "R2", - 2020, - "winter", - "day", - "NG" - ], - [ - "R2", - 2025, - "fall", - "night", - "E10" - ], - [ - "R1", - 2020, - "winter", - "day", - "URN" - ], - [ - "R1", - 2030, - "spring", - "night", - "DSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "ETH" - ], - [ - "R2", - 2030, - "summer", - "day", - "ETH" - ], - [ - "R2", - 2025, - "winter", - "night", - "DSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "NG" - ], - [ - "R1", - 2020, - "winter", - "night", - "GSL" - ], - [ - "R1", - 2020, - "summer", - "day", - "E10" - ], - [ - "R2", - 2030, - "summer", - "night", - "GSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "GSL" - ], - [ - "R2", - 2020, - "spring", - "day", - "GSL" - ], - [ - "R1", - 2020, - "spring", - "night", - "E10" - ], - [ - "R2", - 2025, - "spring", - "night", - "ETH" - ], - [ - "R1", - 2030, - "winter", - "night", - "E10" - ], - [ - "R1", - 2030, - "summer", - "day", - "E10" - ], - [ - "R1", - 2025, - "spring", - "day", - "ELC" - ], - [ - "R1", - 2025, - "summer", - "night", - "E10" - ], - [ - "R1", - 2025, - "spring", - "day", - "NG" - ], - [ - "R1", - 2020, - "winter", - "day", - "ETH" - ], - [ - "R1", - 2030, - "summer", - "night", - "GSL" - ], - [ - "R1", - 2020, - "winter", - "night", - "DSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "OIL" - ], - [ - "R2", - 2030, - "summer", - "night", - "DSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "URN" - ], - [ - "R2", - 2030, - "winter", - "day", - "OIL" - ], - [ - "R2", - 2020, - "spring", - "day", - "URN" - ], - [ - "R1", - 2025, - "winter", - "day", - "URN" - ], - [ - "R2", - 2025, - "fall", - "day", - "NG" - ], - [ - "R2", - 2020, - "winter", - "night", - "OIL" - ], - [ - "R2", - 2020, - "fall", - "day", - "E10" - ], - [ - "R2", - 2030, - "fall", - "day", - "GSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "DSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "URN" - ], - [ - "R1", - 2020, - "fall", - "day", - "GSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "GSL" - ], - [ - "R1", - 2020, - "spring", - "day", - "OIL" - ], - [ - "R1", - 2030, - "winter", - "day", - "OIL" - ], - [ - "R2", - 2030, - "summer", - "night", - "ETH" - ], - [ - "R1", - 2025, - "summer", - "day", - "OIL" - ], - [ - "R1", - 2025, - "fall", - "day", - "E10" - ], - [ - "R1", - 2020, - "spring", - "day", - "NG" - ], - [ - "R2", - 2020, - "winter", - "day", - "E10" - ], - [ - "R1", - 2020, - "summer", - "night", - "ETH" - ], - [ - "R1", - 2030, - "fall", - "day", - "GSL" - ], - [ - "R2", - 2020, - "summer", - "day", - "ELC" - ], - [ - "R2", - 2020, - "summer", - "day", - "NG" - ], - [ - "R2", - 2020, - "spring", - "day", - "ETH" - ], - [ - "R1", - 2025, - "winter", - "day", - "ETH" - ], - [ - "R2", - 2030, - "fall", - "day", - "DSL" - ], - [ - "R1", - 2020, - "fall", - "day", - "URN" - ], - [ - "R1", - 2025, - "fall", - "night", - "GSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "URN" - ], - [ - "R2", - 2030, - "spring", - "day", - "ELC" - ], - [ - "R2", - 2025, - "summer", - "night", - "E10" - ], - [ - "R1", - 2025, - "spring", - "night", - "OIL" - ], - [ - "R2", - 2030, - "spring", - "day", - "NG" - ], - [ - "R1", - 2025, - "winter", - "night", - "GSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "ETH" - ], - [ - "R2", - 2020, - "spring", - "night", - "ELC" - ], - [ - "R1", - 2025, - "spring", - "night", - "NG" - ], - [ - "R1", - 2030, - "fall", - "day", - "URN" - ], - [ - "R2", - 2030, - "winter", - "day", - "URN" - ], - [ - "R1", - 2025, - "fall", - "night", - "URN" - ], - [ - "R1", - 2030, - "spring", - "day", - "ELC" - ], - [ - "R2", - 2030, - "spring", - "day", - "DSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "URN" - ], - [ - "R2", - 2030, - "fall", - "day", - "ETH" - ], - [ - "R2", - 2025, - "winter", - "day", - "ELC" - ], - [ - "R2", - 2020, - "spring", - "night", - "DSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "DSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "ETH" - ], - [ - "R1", - 2020, - "fall", - "day", - "ETH" - ], - [ - "R2", - 2025, - "spring", - "day", - "URN" - ], - [ - "R2", - 2030, - "fall", - "night", - "ELC" - ], - [ - "R2", - 2030, - "fall", - "night", - "NG" - ], - [ - "R2", - 2025, - "summer", - "day", - "OIL" - ], - [ - "R2", - 2025, - "fall", - "day", - "E10" - ], - [ - "R1", - 2025, - "summer", - "day", - "URN" - ], - [ - "R1", - 2020, - "fall", - "night", - "ELC" - ], - [ - "R1", - 2030, - "spring", - "day", - "DSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "OIL" - ], - [ - "R1", - 2030, - "fall", - "day", - "ETH" - ], - [ - "R2", - 2030, - "winter", - "day", - "ETH" - ], - [ - "R2", - 2025, - "winter", - "day", - "DSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "GSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "ETH" - ], - [ - "R2", - 2020, - "winter", - "night", - "E10" - ], - [ - "R2", - 2030, - "spring", - "night", - "OIL" - ], - [ - "R2", - 2030, - "winter", - "night", - "GSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "GSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "ELC" - ], - [ - "R1", - 2025, - "winter", - "night", - "ETH" - ], - [ - "R2", - 2030, - "summer", - "day", - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "GSL" - ], - [ - "R2", - 2030, - "spring", - "night", - "NG" - ], - [ - "R1", - 2020, - "fall", - "night", - "DSL" - ], - [ - "R1", - 2020, - "spring", - "day", - "E10" - ], - [ - "R2", - 2025, - "spring", - "day", - "ETH" - ], - [ - "R1", - 2030, - "winter", - "day", - "E10" - ], - [ - "R2", - 2020, - "summer", - "day", - "E10" - ], - [ - "R2", - 2025, - "fall", - "night", - "URN" - ], - [ - "R2", - 2025, - "spring", - "night", - "GSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "URN" - ], - [ - "R1", - 2030, - "summer", - "day", - "GSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "GSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "DSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "OIL" - ], - [ - "R2", - 2030, - "winter", - "night", - "DSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "DSL" - ], - [ - "R1", - 2020, - "summer", - "day", - "URN" - ], - [ - "R1", - 2025, - "summer", - "night", - "GSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "NG" - ], - [ - "R1", - 2020, - "winter", - "day", - "ELC" - ], - [ - "R1", - 2020, - "summer", - "day", - "DSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "E10" - ], - [ - "R2", - 2025, - "winter", - "night", - "NG" - ], - [ - "R2", - 2020, - "winter", - "day", - "OIL" - ], - [ - "R1", - 2020, - "spring", - "night", - "URN" - ], - [ - "R1", - 2030, - "winter", - "night", - "URN" - ], - [ - "R1", - 2030, - "summer", - "day", - "URN" - ], - [ - "R2", - 2025, - "spring", - "night", - "DSL" - ], - [ - "R2", - 2020, - "fall", - "day", - "GSL" - ], - [ - "R1", - 2030, - "summer", - "day", - "DSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "URN" - ], - [ - "R2", - 2025, - "fall", - "night", - "ETH" - ], - [ - "R1", - 2020, - "winter", - "day", - "DSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "DSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "ETH" - ], - [ - "R1", - 2020, - "summer", - "day", - "ETH" - ], - [ - "R1", - 2020, - "winter", - "night", - "ELC" - ], - [ - "R1", - 2020, - "winter", - "night", - "NG" - ], - [ - "R2", - 2030, - "summer", - "night", - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "GSL" - ], - [ - "R2", - 2020, - "fall", - "day", - "URN" - ], - [ - "R2", - 2030, - "summer", - "night", - "NG" - ], - [ - "R2", - 2025, - "summer", - "day", - "E10" - ], - [ - "R1", - 2025, - "spring", - "day", - "OIL" - ], - [ - "R1", - 2020, - "summer", - "night", - "ELC" - ], - [ - "R2", - 2020, - "fall", - "day", - "DSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "GSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "NG" - ], - [ - "R1", - 2020, - "spring", - "night", - "ETH" - ], - [ - "R1", - 2030, - "winter", - "night", - "ETH" - ], - [ - "R1", - 2030, - "summer", - "day", - "ETH" - ], - [ - "R2", - 2020, - "spring", - "day", - "ELC" - ], - [ - "R2", - 2020, - "summer", - "night", - "E10" - ], - [ - "R2", - 2025, - "summer", - "night", - "GSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "ETH" - ], - [ - "R2", - 2030, - "spring", - "night", - "E10" - ], - [ - "R1", - 2030, - "summer", - "night", - "ELC" - ], - [ - "R1", - 2025, - "fall", - "day", - "URN" - ], - [ - "R1", - 2030, - "summer", - "night", - "NG" - ], - [ - "R2", - 2020, - "winter", - "day", - "URN" - ], - [ - "R1", - 2020, - "summer", - "night", - "DSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "DSL" - ], - [ - "R2", - 2020, - "spring", - "day", - "DSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "DSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "URN" - ], - [ - "R2", - 2020, - "fall", - "day", - "ETH" - ], - [ - "R1", - 2030, - "spring", - "night", - "E10" - ], - [ - "R2", - 2030, - "fall", - "day", - "ELC" - ], - [ - "R2", - 2030, - "fall", - "day", - "NG" - ], - [ - "R1", - 2030, - "summer", - "night", - "DSL" - ] - ], - "CommodityBalanceAnnualConstraint_rpc": [], - "BaseloadDiurnalConstraint_rpsdtv": [ - [ - "R1", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2015 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_NUCLEAR", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_NUCLEAR", - 2030 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_NUCLEAR", - 2015 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_NUCLEAR", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_NUCLEAR", - 2030 - ] - ], - "RegionalExchangeCapacityConstraint_rrptv": [ - [ - "R1", - "R2", - 2030, - "E_TRANS", - 2015 - ], - [ - "R2", - "R1", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - "R1", - 2030, - "E_TRANS", - 2015 - ], - [ - "R1", - "R2", - 2025, - "E_TRANS", - 2015 - ], - [ - "R1", - "R2", - 2020, - "E_TRANS", - 2015 - ], - [ - "R2", - "R1", - 2025, - "E_TRANS", - 2015 - ] - ], - "StorageConstraints_rpsdtv": [ - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2020, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2020, - "fall", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2030 - ], - [ - "R1", - 2030, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2030 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2025, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2025 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2030, - "spring", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "fall", - "day", - "E_BATT", - 2025 - ], - [ - "R1", - 2025, - "winter", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2020, - "fall", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "E_BATT", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "E_BATT", - 2030 - ], - [ - "R2", - 2025, - "fall", - "night", - "E_BATT", - 2025 - ], - [ - "R1", - 2030, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "E_BATT", - 2030 - ], - [ - "R2", - 2020, - "spring", - "night", - "E_BATT", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "E_BATT", - 2025 - ] - ], - "StorageInitConstraint_rtv": [], - "RampConstraintDay_rpsdtv": [], - "RampConstraintPeriod_rptv": [], - "ReserveMargin_rpsd": [ - [ - "R2", - 2025, - "winter", - "night" - ], - [ - "R1", - 2020, - "spring", - "night" - ], - [ - "R2", - 2020, - "winter", - "day" - ], - [ - "R1", - 2030, - "spring", - "day" - ], - [ - "R1", - 2025, - "summer", - "night" - ], - [ - "R1", - 2030, - "summer", - "day" - ], - [ - "R2", - 2030, - "spring", - "day" - ], - [ - "R1", - 2025, - "fall", - "night" - ], - [ - "R1", - 2030, - "fall", - "day" - ], - [ - "R2", - 2030, - "summer", - "day" - ], - [ - "R2", - 2030, - "fall", - "day" - ], - [ - "R1", - 2020, - "winter", - "day" - ], - [ - "R1", - 2030, - "winter", - "night" - ], - [ - "R1", - 2020, - "summer", - "night" - ], - [ - "R1", - 2025, - "winter", - "day" - ], - [ - "R1", - 2020, - "fall", - "night" - ], - [ - "R2", - 2030, - "winter", - "night" - ], - [ - "R2", - 2025, - "spring", - "night" - ], - [ - "R2", - 2020, - "spring", - "day" - ], - [ - "R2", - 2025, - "summer", - "night" - ], - [ - "R2", - 2025, - "fall", - "night" - ], - [ - "R2", - 2020, - "fall", - "day" - ], - [ - "R2", - 2025, - "summer", - "day" - ], - [ - "R2", - 2020, - "winter", - "night" - ], - [ - "R1", - 2030, - "spring", - "night" - ], - [ - "R1", - 2025, - "spring", - "day" - ], - [ - "R2", - 2020, - "summer", - "day" - ], - [ - "R1", - 2030, - "summer", - "night" - ], - [ - "R2", - 2030, - "spring", - "night" - ], - [ - "R2", - 2020, - "summer", - "night" - ], - [ - "R1", - 2030, - "fall", - "night" - ], - [ - "R1", - 2020, - "winter", - "night" - ], - [ - "R2", - 2030, - "summer", - "night" - ], - [ - "R2", - 2025, - "winter", - "day" - ], - [ - "R1", - 2020, - "spring", - "day" - ], - [ - "R2", - 2030, - "fall", - "night" - ], - [ - "R1", - 2025, - "winter", - "night" - ], - [ - "R1", - 2025, - "summer", - "day" - ], - [ - "R1", - 2025, - "fall", - "day" - ], - [ - "R2", - 2020, - "spring", - "night" - ], - [ - "R1", - 2030, - "winter", - "day" - ], - [ - "R1", - 2020, - "summer", - "day" - ], - [ - "R2", - 2020, - "fall", - "night" - ], - [ - "R1", - 2020, - "fall", - "day" - ], - [ - "R2", - 2030, - "winter", - "day" - ], - [ - "R2", - 2025, - "spring", - "day" - ], - [ - "R1", - 2025, - "spring", - "night" - ], - [ - "R2", - 2025, - "fall", - "day" - ] - ], - "GrowthRateMaxConstraint_rtv": [], - "TechInputSplitConstraint_rpsditv": [ - [ - "R2", - 2030, - "fall", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "spring", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "spring", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "fall", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "summer", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "spring", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "fall", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "summer", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "fall", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "summer", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "fall", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "winter", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "summer", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "winter", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "summer", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "spring", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "summer", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "spring", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "spring", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "summer", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "spring", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "summer", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "fall", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "fall", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "spring", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "spring", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "fall", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "winter", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "winter", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "summer", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "winter", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "spring", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "fall", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "summer", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "spring", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "fall", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "fall", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "fall", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "summer", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "fall", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "spring", - "night", - "GSL", - "T_BLND", - 2020 - ], - [ - "R1", - 2030, - "winter", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "winter", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "winter", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "fall", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2020, - "winter", - "day", - "ETH", - "T_BLND", - 2020 - ], - [ - "R2", - 2030, - "summer", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2025, - "summer", - "night", - "ETH", - "T_BLND", - 2020 - ], - [ - "R1", - 2025, - "winter", - "day", - "GSL", - "T_BLND", - 2020 - ], - [ - "R2", - 2020, - "spring", - "day", - "GSL", - "T_BLND", - 2020 - ] - ], - "TechInputSplitAnnualConstraint_rpitv": [], - "TechInputSplitAverageConstraint_rpitv": [], - "TechOutputSplitConstraint_rpsdtvo": [ - [ - "R1", - 2030, - "summer", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "summer", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "fall", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "spring", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "fall", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "fall", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "fall", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "fall", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "spring", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "winter", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "summer", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "spring", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "spring", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "winter", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "fall", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "winter", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "fall", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "spring", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "spring", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "summer", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "fall", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "summer", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "summer", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "winter", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "spring", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "summer", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "spring", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "fall", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "winter", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "summer", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "winter", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "spring", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "fall", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "spring", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "winter", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "winter", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "winter", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "fall", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "fall", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "winter", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "summer", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "winter", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "summer", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2025, - "summer", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "summer", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "spring", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "fall", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "winter", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "spring", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "winter", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "winter", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2030, - "fall", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "spring", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "summer", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2030, - "spring", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "spring", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "summer", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "summer", - "night", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2020, - "summer", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2020, - "spring", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2030, - "fall", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R2", - 2025, - "spring", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "fall", - "day", - "S_OILREF", - 2020, - "GSL" - ], - [ - "R1", - 2020, - "spring", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2020, - "summer", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2025, - "summer", - "night", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R2", - 2030, - "spring", - "day", - "S_OILREF", - 2020, - "DSL" - ], - [ - "R1", - 2025, - "fall", - "night", - "S_OILREF", - 2020, - "GSL" - ] - ], - "TechOutputSplitAnnualConstraint_rptvo": [], - "LinkedEmissionsTechConstraint_rpsdtve": [] -} \ No newline at end of file + "annual_commodity_balance_constraint_rpc": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "annual_retirement_var_rptv": "ee162bcd5c52c6e713033f89c7ffd0d244e4a34721217cfaf699959b131adcda", + "baseload_diurnal_constraint_rpsdtv": "20070e9c3c7443e237d3779a8ef33a25ae3a29284f5e1158a257f1a1d80c1421", + "capacity_annual_constraint_rptv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "capacity_available_var_rpt": "8f305de45d0d42708ef17b16602d770ef731810252b0d0b3a89dc396b54b8e8a", + "capacity_constraint_rpsdtv": "cd98cf3db577dec19c292199f5b7b8af2dfe336174af315ce2186bbcbd8e81cf", + "capacity_factor_rsdt": "53e46681758c146b82380d26b0fc8eb4f6e95a91c3c2766221e33f895863e1aa", + "capacity_var_rptv": "d839477ae7f5d8fc897e2df4c3c519d742f946b2ce74e1d92fff91e50285296f", + "commodity_all": "01fbdcd2e272aa7c36fbf1400e575188f4501e1957bba1dc2b29cd11ec6a070c", + "commodity_annual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "commodity_balance_constraint_rpsdc": "4a46a0de6ec903639858a35c63fc46223f82ab1e057b20f4a1624eacb6652229", + "commodity_carrier": "3508a787012b20265e9c97acebe2c39d6648b972e7bb4a6d42083e7e167b5c92", + "commodity_demand": "6b657255c2baf8b6d4fc96f21cb21263bae7378eb11a9b9cf25a0d8aa83c6aca", + "commodity_emissions": "6aa406c61418fb1a0cc0d9f505fafcbf94e65a66cb5c5f1dc0afa8117b762979", + "commodity_flex": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "commodity_physical": "555367a723a957f166d9ec6c580ea46e958bd3a3f684aceee050e26ba2d423c6", + "commodity_sink": "6b657255c2baf8b6d4fc96f21cb21263bae7378eb11a9b9cf25a0d8aa83c6aca", + "commodity_source": "44567fa34febd7556a3797bba4949e8d57aa1416fc240258417955809226d723", + "commodity_waste": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "cost_emission_rpe": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "cost_fixed_rptv": "d839477ae7f5d8fc897e2df4c3c519d742f946b2ce74e1d92fff91e50285296f", + "cost_invest_rtv": "46d311691b8fc8d384d75ae29216ecbbd4ecb16253e4d1836e868c5b2f8a6bc5", + "cost_variable_rptv": "5501652d0145dbf7c2e8c006aabd3dbb85ca64ed5caf1d8f45a1957274af81ca", + "curtailment_var_rpsditvo": "6bdb84e67318e7f5bbaeb28a90ab905caea9f826814f330a3e075c21befbbce7", + "demand_activity_constraint_rpsdtv_dem": "5e98cba1dc8ee89799becaa6a4bb7824d41badf80940f18ddb11724ec883e93d", + "demand_constraint_rpc": "eae7c4920f895c8e036bc95461418eafb9dc9eb1f836755ad16c78b945019623", + "emission_activity_reitvo": "a161b5d9619833e599c9201d4da6e0f45df261cfb93b185b5840daee36880443", + "flex_var_annual_rpitvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "flex_var_rpsditvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "flow_in_storage_rpsditvo": "8c97fbeacbca1a772ba04f63d0a82f7703500f83c0dc602114b17c64ad4d2e28", + "flow_var_annual_rpitvo": "f98f5f41c5cba8ce2a894734abbc5e90310b695bee3c24c52acd8b4800c5ab85", + "flow_var_rpsditvo": "784d98e451a8dcf0122f475c2e229162d99e403bdea85f7c608a3d86e850db44", + "lifetime_process_rtv": "5033502364848a3a3f295f1b3d051531ecd5c1e5f8bbaecd61afcd18181225ab", + "limit_activity_constraint_rpt": "2561103e7e6dd3dee3ba5832290ab5e933f4af08661dff4f2e661b6f4ffefc86", + "limit_activity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_annual_capacity_factor_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_annual_capacity_factor_constraint_rtvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_capacity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_emission_constraint_rpe": "6f11e4ee041970584903d5afba7ee1147824b233260422564fdf0c701e9fabde", + "limit_growth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_growth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_growth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_new_capacity_constraint_rtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_new_capacity_share_constraint_rggv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_resource_constraint_rt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_seasonal_capacity_factor_constraint_rpst": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_seasonal_capacity_factor_constraint_rst": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_storage_fraction_constraint_rpsdtv": "13bd0438fa93073d0820deb25c91729fc14bde24f8053cbc42f9412d10f25cd3", + "limit_storage_fraction_param_rsdt": "426fc3cc85aeb1d61ea1e862f82915520e380ee9dbdf81c17423dc881a29791b", + "limit_tech_input_split_annual_constraint_rpitv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_average_constraint_rpitv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_constraint_rpsditv": "ccadc066c091a1c0326154aff6b60229c6f1885a8ace32b7957aff39af910682", + "limit_tech_output_split_annual_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_output_split_average_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_output_split_constraint_rpsdtvo": "115dc2061e98232f0b3b4884fedd68d5bb347dc04db7debf615655ba1e511d35", + "linked_emissions_tech_constraint_rpsdtve": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "loan_lifetime_process_rtv": "5033502364848a3a3f295f1b3d051531ecd5c1e5f8bbaecd61afcd18181225ab", + "new_capacity_var_rtv": "bc0ec9e7f812410cb2af924cbc99a31c6e99cd95fc2de26193daad38f33cc132", + "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", + "ordered_season_sequential": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "process_life_frac_rptv": "5501652d0145dbf7c2e8c006aabd3dbb85ca64ed5caf1d8f45a1957274af81ca", + "ramp_down_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "ramp_down_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "ramp_up_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "ramp_up_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "regional_exchange_capacity_constraint_rrptv": "ae0671daa1263140faff22e3d2610c1fbab942d4e814892a50471adfa1bcc740", + "regional_global_indices": "92fa6c5d5745d765d6e16ad1bca7e1fc72f4377273be7cfbfde626ca1967d81b", + "regional_indices": "f74187f92c4fdb3c12d5610304c7ac9696001433150bdaa9ff20793fb6365b32", + "regions": "0ddd05d695b255ac719dfa85de1e900a3036d547ffc7261c9c9ca2c81bfda029", + "renewable_portfolio_standard_constraint_rpg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "reserve_margin_method": "7869283c0d14273f720716309207a8f0c24606d03c679d6b68e656ed8d86241d", + "reserve_margin_rpsd": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "retired_capacity_var_rptv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "seasonal_storage_constraints_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "seasonal_storage_level_rpstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "storage_constraints_rpsdtv": "a724916a2cae70bf0de3b0af98d53d017c515cec75692363b541580ead74dc7c", + "storage_init_rpstv": "60214760f620f09278b3aa4b6b510411c1e2c177df5e80838ab97917ca84445d", + "storage_level_rpsdtv": "a724916a2cae70bf0de3b0af98d53d017c515cec75692363b541580ead74dc7c", + "tech_all": "85a3645929dbeaf6b7eb17e8085c8923ef86949eaa3fb4fd81724dcdcf38fd30", + "tech_annual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_baseload": "050aff703818154bf3439ed7d4a2cfef892be61da0c477526001c8c38b41a935", + "tech_curtailment": "81683c0099a4eb22c8cca4b8eaf65a5bbf77c24b559cd1219823e6d6eb6cf915", + "tech_demand": "cf8d1075bb0667935fc3834c5266e775d1c746692179ef253809eaaa7abab6f8", + "tech_downramping": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_exchange": "6610eb4a0360be009669b5d50a2c3e1447bcf39eb820a0e63fd3718c27fae768", + "tech_exist": "eefd900ce35331470ba9f3312de6f03883efb5f402d321ff714cfe22d9ecdf94", + "tech_flex": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_group_members": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_group_names": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_or_group": "85a3645929dbeaf6b7eb17e8085c8923ef86949eaa3fb4fd81724dcdcf38fd30", + "tech_production": "85a3645929dbeaf6b7eb17e8085c8923ef86949eaa3fb4fd81724dcdcf38fd30", + "tech_reserve": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_retirement": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_seasonal_storage": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_storage": "7109b89425e6707adc8a5e571bf70fc64475d2d76471e5afe93110fd86bbcec8", + "tech_uncap": "50b9da0bce0fcc3b318930929a2191c6c6c5e535e9ee986ca2ee740c7ff789bc", + "tech_upramping": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_with_capacity": "bf3db0293ae9b3ec605ba8260a111eb71cbf3a786077a8172142a4e90ca1141b", + "time_exist": "5206b4814992da4c7a4f643fdf04124735accc989ec0047874eb80e8541766c9", + "time_future": "2739be4c64d75aa5fb1552392c1f9d5587f15dba7a728f1768e415c0792b044e", + "time_manual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "time_of_day": "9f58a9fbc74271e641f4b7624b22daca061195a3d85b58d270fb8590937007b1", + "time_optimize": "376da8132a26c5ebebc6d27395c2c883261928ac8d91004c167843c1e21d6141", + "time_season": "6d8e2fd49929c1f5216c0313307108c4993e58b00207210d3b6390d3f64e2896", + "time_season_sequential": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "time_sequencing": "91f69c8abab9959c1f8c90f5aaa56db29bccc67e37d12673ab41c54e4179d7ca", + "vintage_all": "a19045d7d5b67af65f00af8d62f398968fea2c2d418f182b5d3ad0a56415eddf", + "vintage_exist": "5206b4814992da4c7a4f643fdf04124735accc989ec0047874eb80e8541766c9", + "vintage_optimize": "376da8132a26c5ebebc6d27395c2c883261928ac8d91004c167843c1e21d6141" +} diff --git a/tests/testing_data/utopia.sql b/tests/testing_data/utopia.sql deleted file mode 100644 index 2d49c629f..000000000 --- a/tests/testing_data/utopia.sql +++ /dev/null @@ -1,1343 +0,0 @@ -PRAGMA foreign_keys=OFF; -BEGIN TRANSACTION; -CREATE TABLE MetaData -( - element TEXT, - value INT, - notes TEXT, - PRIMARY KEY (element) -); -INSERT INTO MetaData VALUES('myopic_base_year',1990,'Base Year for Myopic Analysis'); -INSERT INTO MetaData VALUES('DB_MAJOR',3,'DB major version number'); -INSERT INTO MetaData VALUES('DB_MINOR',0,'DB minor version number'); -CREATE TABLE MetaDataReal -( - element TEXT, - value REAL, - notes TEXT, - - PRIMARY KEY (element) -); -INSERT INTO MetaDataReal VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in LoanRate table'); -INSERT INTO MetaDataReal VALUES('global_discount_rate',0.05,''); -CREATE TABLE OutputDualVariable -( - scenario TEXT, - constraint_name TEXT, - dual REAL, - PRIMARY KEY (constraint_name, scenario) -); -CREATE TABLE OutputObjective -( - scenario TEXT, - objective_name TEXT, - total_system_cost REAL -); -CREATE TABLE SectorLabel -( - sector TEXT, - PRIMARY KEY (sector) -); -INSERT INTO SectorLabel VALUES('supply'); -INSERT INTO SectorLabel VALUES('electric'); -INSERT INTO SectorLabel VALUES('transport'); -INSERT INTO SectorLabel VALUES('commercial'); -INSERT INTO SectorLabel VALUES('residential'); -INSERT INTO SectorLabel VALUES('industrial'); -CREATE TABLE CapacityCredit -( - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - credit REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage), - CHECK (credit >= 0 AND credit <= 1) -); -CREATE TABLE CapacityFactorProcess -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER, - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech, vintage), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','day','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','night','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','day','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','night','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','day','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','night','E31',2000,0.2752999999999999892,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','day','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','inter','night','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','day','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','winter','night','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','day','E31',2010,0.2756000000000000116,''); -INSERT INTO CapacityFactorProcess VALUES('utopia','summer','night','E31',2010,0.2756000000000000116,''); -CREATE TABLE CapacityFactorTech -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - tech TEXT - REFERENCES Technology (tech), - factor REAL, - notes TEXT, - PRIMARY KEY (region, season, tod, tech), - CHECK (factor >= 0 AND factor <= 1) -); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E01',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E21',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E31',0.2750000000000000222,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E51',0.1700000000000000122,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','day','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','inter','night','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','day','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','winter','night','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','day','E70',0.8000000000000000444,''); -INSERT INTO CapacityFactorTech VALUES('utopia','summer','night','E70',0.8000000000000000444,''); -CREATE TABLE CapacityToActivity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - c2a REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO CapacityToActivity VALUES('utopia','E01',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E21',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E31',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E51',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','E70',31.53999999999999915,''); -INSERT INTO CapacityToActivity VALUES('utopia','RHE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','RHO',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','RL1',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','SRE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXD',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXE',1.0,''); -INSERT INTO CapacityToActivity VALUES('utopia','TXG',1.0,''); -CREATE TABLE Commodity -( - name TEXT - PRIMARY KEY, - flag TEXT - REFERENCES CommodityType (label), - description TEXT -); -INSERT INTO Commodity VALUES('ethos','s','# dummy commodity to supply inputs (makes graph easier to read)'); -INSERT INTO Commodity VALUES('DSL','p','# diesel'); -INSERT INTO Commodity VALUES('ELC','p','# electricity'); -INSERT INTO Commodity VALUES('FEQ','p','# fossil equivalent'); -INSERT INTO Commodity VALUES('GSL','p','# gasoline'); -INSERT INTO Commodity VALUES('HCO','p','# coal'); -INSERT INTO Commodity VALUES('HYD','p','# water'); -INSERT INTO Commodity VALUES('OIL','p','# crude oil'); -INSERT INTO Commodity VALUES('URN','p','# uranium'); -INSERT INTO Commodity VALUES('co2','e','#CO2 emissions'); -INSERT INTO Commodity VALUES('nox','e','#NOX emissions'); -INSERT INTO Commodity VALUES('RH','d','# residential heating'); -INSERT INTO Commodity VALUES('RL','d','# residential lighting'); -INSERT INTO Commodity VALUES('TX','d','# transportation'); -CREATE TABLE CommodityType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO CommodityType VALUES('s','source commodity'); -INSERT INTO CommodityType VALUES('p','physical commodity'); -INSERT INTO CommodityType VALUES('e','emissions commodity'); -INSERT INTO CommodityType VALUES('d','demand commodity'); -CREATE TABLE CostEmission -( - region TEXT - REFERENCES Region (region), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT NOT NULL - REFERENCES Commodity (name), - cost REAL NOT NULL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE CostFixed -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1960,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1970,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1980,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E01',1990,40.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1970,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1980,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',1990,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E01',2000,70.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',1980,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',1990,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',2000,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E01',2010,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',1990,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E21',2000,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',2000,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E21',2010,500.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E31',2000,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',1980,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',1990,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',2000,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E31',2010,75.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E51',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E51',2010,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1960,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1970,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1970,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'E70',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',1980,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',1990,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',2000,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'E70',2010,30.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1970,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1980,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1980,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RHO',2000,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',1990,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2000,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RHO',2010,1.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1980,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'RL1',1990,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'RL1',2000,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'RL1',2010,9.46000000000000086,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1970,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1980,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXD',1990,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1980,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',1990,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXD',2000,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2000,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXD',2010,52.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXE',1990,100.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXE',1990,90.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXE',2000,90.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2000,80.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXE',2010,80.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1970,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1980,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',1990,'TXG',1990,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1980,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',1990,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2000,'TXG',2000,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2000,48.0,'',''); -INSERT INTO CostFixed VALUES('utopia',2010,'TXG',2010,48.0,'',''); -CREATE TABLE CostInvest -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO CostInvest VALUES('utopia','E01',1990,2000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E01',2000,1300.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E01',2010,1200.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E21',1990,5000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E21',2000,5000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E21',2010,5000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E31',1990,3000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E31',2000,3000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E31',2010,3000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E51',1990,900.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E51',2000,900.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E51',2010,900.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E70',1990,1000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E70',2000,1000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','E70',2010,1000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHE',1990,90.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHE',2000,90.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHE',2010,90.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHO',1990,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHO',2000,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','RHO',2010,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','SRE',1990,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','SRE',2000,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','SRE',2010,100.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXD',1990,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXD',2000,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXD',2010,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXE',1990,2000.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXE',2000,1750.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXE',2010,1500.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXG',1990,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXG',2000,1044.0,'',''); -INSERT INTO CostInvest VALUES('utopia','TXG',2010,1044.0,'',''); -CREATE TABLE CostVariable -( - region TEXT NOT NULL, - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech TEXT NOT NULL - REFERENCES Technology (tech), - vintage INTEGER NOT NULL - REFERENCES TimePeriod (period), - cost REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech, vintage) -); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPDSL1',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPDSL1',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPDSL1',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPGSL1',1990,15.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPGSL1',1990,15.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPGSL1',1990,15.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPHCO1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPHCO1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPHCO1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPOIL1',1990,8.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPOIL1',1990,8.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPOIL1',1990,8.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'IMPURN1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'IMPURN1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'IMPURN1',1990,2.0,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1960,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1970,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1980,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E01',1990,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1970,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1980,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',1990,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E01',2000,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',1980,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',1990,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',2000,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E01',2010,0.2999999999999999889,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E21',1990,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E21',1990,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',1990,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E21',2000,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',2000,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E21',2010,1.5,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1960,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1970,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1980,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'E70',1990,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1970,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1980,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',1990,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'E70',2000,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',1980,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',1990,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',2000,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'E70',2010,0.4000000000000000222,'',''); -INSERT INTO CostVariable VALUES('utopia',1990,'SRE',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'SRE',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2000,'SRE',2000,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',1990,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2000,10.0,'',''); -INSERT INTO CostVariable VALUES('utopia',2010,'SRE',2010,10.0,'',''); -CREATE TABLE Demand -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - commodity TEXT - REFERENCES Commodity (name), - demand REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, commodity) -); -INSERT INTO Demand VALUES('utopia',1990,'RH',25.19999999999999929,'',''); -INSERT INTO Demand VALUES('utopia',2000,'RH',37.79999999999999715,'',''); -INSERT INTO Demand VALUES('utopia',2010,'RH',56.70000000000000284,'',''); -INSERT INTO Demand VALUES('utopia',1990,'RL',5.599999999999999645,'',''); -INSERT INTO Demand VALUES('utopia',2000,'RL',8.400000000000000355,'',''); -INSERT INTO Demand VALUES('utopia',2010,'RL',12.59999999999999965,'',''); -INSERT INTO Demand VALUES('utopia',1990,'TX',5.200000000000000177,'',''); -INSERT INTO Demand VALUES('utopia',2000,'TX',7.799999999999999823,'',''); -INSERT INTO Demand VALUES('utopia',2010,'TX',11.68999999999999951,'',''); -CREATE TABLE DemandSpecificDistribution -( - region TEXT, - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - demand_name TEXT - REFERENCES Commodity (name), - dds REAL, - dds_notes TEXT, - PRIMARY KEY (region, season, tod, demand_name), - CHECK (dds >= 0 AND dds <= 1) -); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','day','RH',0.1199999999999999956,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','night','RH',0.05999999999999999778,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','day','RH',0.5466999999999999638,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','night','RH',0.2732999999999999874,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','day','RL',0.1499999999999999945,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','inter','night','RL',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','summer','day','RL',0.1499999999999999945,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','summer','night','RL',0.05000000000000000277,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','day','RL',0.5,''); -INSERT INTO DemandSpecificDistribution VALUES('utopia','winter','night','RL',0.1000000000000000055,''); -CREATE TABLE LoanRate -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -CREATE TABLE Efficiency -( - region TEXT, - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - efficiency REAL, - notes TEXT, - PRIMARY KEY (region, input_comm, tech, vintage, output_comm), - CHECK (efficiency > 0) -); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,''); -INSERT INTO Efficiency VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,''); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1960,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1970,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1980,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',1990,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',2000,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HCO','E01',2010,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',1990,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2000,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','FEQ','E21',2010,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','URN','E21',1990,'ELC',0.4000000000000000222,'# 1/2.5'); -INSERT INTO Efficiency VALUES('utopia','URN','E21',2000,'ELC',0.4000000000000000222,'# 1/2.5'); -INSERT INTO Efficiency VALUES('utopia','URN','E21',2010,'ELC',0.4000000000000000222,'# 1/2.5'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',1980,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',1990,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',2000,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','HYD','E31',2010,'ELC',0.3200000000000000066,'# 1/3.125'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1960,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1970,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1980,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',1990,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',2000,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','DSL','E70',2010,'ELC',0.2939999999999999836,'# 1/3.4'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',1980,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',1990,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',2000,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','E51',2010,'ELC',0.7199999999999999734,'# 1/1.3889'); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',1990,'RH',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2000,'RH',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RHE',2010,'RH',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1970,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1980,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',1990,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2000,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','RHO',2010,'RH',0.6999999999999999556,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1980,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',1990,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2000,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','RL1',2010,'RL',1.0,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'# direct translation from PRC_INP2, PRC_OUT'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1970,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1980,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',1990,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2000,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','DSL','TXD',2010,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',1990,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2000,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','ELC','TXE',2010,'TX',0.8269999999999999574,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1970,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1980,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',1990,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2000,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -INSERT INTO Efficiency VALUES('utopia','GSL','TXG',2010,'TX',0.2310000000000000108,'# direct translation from DMD_EFF'); -CREATE TABLE EmissionActivity -( - region TEXT, - emis_comm TEXT - REFERENCES Commodity (name), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - activity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, emis_comm, input_comm, tech, vintage, output_comm) -); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.07499999999999999723,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.07499999999999999723,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',0.08899999999999999579,'',''); -INSERT INTO EmissionActivity VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.07499999999999999723,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'',''); -INSERT INTO EmissionActivity VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'',''); -CREATE TABLE ExistingCapacity -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1960,0.1749999999999999889,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1970,0.1749999999999999889,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E01',1980,0.1499999999999999945,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E31',1980,0.1000000000000000055,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E51',1980,0.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1960,0.05000000000000000277,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1970,0.05000000000000000277,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','E70',1980,0.2000000000000000111,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RHO',1970,12.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RHO',1980,12.5,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','RL1',1980,5.599999999999999645,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXD',1970,0.4000000000000000222,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXD',1980,0.2000000000000000111,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXG',1970,3.100000000000000088,'',''); -INSERT INTO ExistingCapacity VALUES('utopia','TXG',1980,1.5,'',''); -CREATE TABLE TechGroup -( - group_name TEXT - PRIMARY KEY, - notes TEXT -); -CREATE TABLE GrowthRateMax -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE GrowthRateSeed -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - seed REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE LoanLifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LoanLifetimeTech VALUES('utopia','E01',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E21',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E31',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E51',100.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','E70',40.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RHE',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RHO',30.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','RL1',10.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','SRE',50.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXD',15.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXE',15.0,''); -INSERT INTO LoanLifetimeTech VALUES('utopia','TXG',15.0,''); -CREATE TABLE LifetimeProcess -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech, vintage) -); -INSERT INTO LifetimeProcess VALUES('utopia','RL1',1980,20.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXD',1970,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXD',1980,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXG',1970,30.0,'#forexistingcap'); -INSERT INTO LifetimeProcess VALUES('utopia','TXG',1980,30.0,'#forexistingcap'); -CREATE TABLE LifetimeTech -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - lifetime REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -INSERT INTO LifetimeTech VALUES('utopia','E01',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E21',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E31',100.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E51',100.0,''); -INSERT INTO LifetimeTech VALUES('utopia','E70',40.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RHE',30.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RHO',30.0,''); -INSERT INTO LifetimeTech VALUES('utopia','RL1',10.0,''); -INSERT INTO LifetimeTech VALUES('utopia','SRE',50.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXD',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXE',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','TXG',15.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPDSL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPGSL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPHCO1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPOIL1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPURN1',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPHYD',1000.0,''); -INSERT INTO LifetimeTech VALUES('utopia','IMPFEQ',1000.0,''); -CREATE TABLE LinkedTech -( - primary_region TEXT, - primary_tech TEXT - REFERENCES Technology (tech), - emis_comm TEXT - REFERENCES Commodity (name), - driven_tech TEXT - REFERENCES Technology (tech), - notes TEXT, - PRIMARY KEY (primary_region, primary_tech, emis_comm) -); -CREATE TABLE MaxActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MaxCapacity VALUES('utopia',1990,'E31',0.1300000000000000044,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2000,'E31',0.1700000000000000122,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2010,'E31',0.2099999999999999923,'',''); -INSERT INTO MaxCapacity VALUES('utopia',1990,'RHE',0.0,'',''); -INSERT INTO MaxCapacity VALUES('utopia',1990,'TXD',0.5999999999999999778,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2000,'TXD',1.760000000000000008,'',''); -INSERT INTO MaxCapacity VALUES('utopia',2010,'TXD',4.759999999999999787,'',''); -CREATE TABLE MaxResource -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - max_res REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE MinActivity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -INSERT INTO MinCapacity VALUES('utopia',1990,'E31',0.1300000000000000044,'',''); -INSERT INTO MinCapacity VALUES('utopia',2000,'E31',0.1300000000000000044,'',''); -INSERT INTO MinCapacity VALUES('utopia',2010,'E31',0.1300000000000000044,'',''); -INSERT INTO MinCapacity VALUES('utopia',1990,'SRE',0.1000000000000000055,'',''); -CREATE TABLE MinCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE OutputCurtailment -( - scenario TEXT, - region TEXT, - sector TEXT, - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - curtailment REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputNetCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputBuiltCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, tech, vintage) -); -CREATE TABLE OutputRetiredCapacity -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - capacity REAL, - PRIMARY KEY (region, scenario, period, tech, vintage) -); -CREATE TABLE OutputFlowIn -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE OutputFlowOut -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - season TEXT - REFERENCES TimePeriod (period), - tod TEXT - REFERENCES TimeOfDay (tod), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - output_comm TEXT - REFERENCES Commodity (name), - flow REAL, - PRIMARY KEY (region, scenario, period, season, tod, input_comm, tech, vintage, output_comm) -); -CREATE TABLE PlanningReserveMargin -( - region TEXT - PRIMARY KEY - REFERENCES Region (region), - margin REAL -); -CREATE TABLE RampDown -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE RampUp -( - region TEXT, - tech TEXT - REFERENCES Technology (tech), - rate REAL, - PRIMARY KEY (region, tech) -); -CREATE TABLE Region -( - region TEXT - PRIMARY KEY, - notes TEXT -); -INSERT INTO Region VALUES('utopia',NULL); -CREATE TABLE TimeSegmentFraction -( - season TEXT - REFERENCES TimeSeason (season), - tod TEXT - REFERENCES TimeOfDay (tod), - segfrac REAL, - notes TEXT, - PRIMARY KEY (season, tod), - CHECK (segfrac >= 0 AND segfrac <= 1) -); -INSERT INTO TimeSegmentFraction VALUES('inter','day',0.166699999999999987,'# I-D'); -INSERT INTO TimeSegmentFraction VALUES('inter','night',0.08329999999999999905,'# I-N'); -INSERT INTO TimeSegmentFraction VALUES('summer','day',0.166699999999999987,'# S-D'); -INSERT INTO TimeSegmentFraction VALUES('summer','night',0.08329999999999999905,'# S-N'); -INSERT INTO TimeSegmentFraction VALUES('winter','day',0.3332999999999999852,'# W-D'); -INSERT INTO TimeSegmentFraction VALUES('winter','night',0.166699999999999987,'# W-N'); -CREATE TABLE StorageDuration -( - region TEXT, - tech TEXT, - duration REAL, - notes TEXT, - PRIMARY KEY (region, tech) -); -CREATE TABLE StorageInit -( - tech TEXT - PRIMARY KEY, - value REAL, - notes TEXT -); -CREATE TABLE TechnologyType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TechnologyType VALUES('r','resource technology'); -INSERT INTO TechnologyType VALUES('p','production technology'); -INSERT INTO TechnologyType VALUES('pb','baseload production technology'); -INSERT INTO TechnologyType VALUES('ps','storage production technology'); -CREATE TABLE TechInputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechInputSplitAverage -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - input_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, input_comm, tech) -); -CREATE TABLE TechOutputSplit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, output_comm) -); -INSERT INTO TechOutputSplit VALUES('utopia',1990,'SRE','DSL',0.6999999999999999556,''); -INSERT INTO TechOutputSplit VALUES('utopia',2000,'SRE','DSL',0.6999999999999999556,''); -INSERT INTO TechOutputSplit VALUES('utopia',2010,'SRE','DSL',0.6999999999999999556,''); -INSERT INTO TechOutputSplit VALUES('utopia',1990,'SRE','GSL',0.2999999999999999889,''); -INSERT INTO TechOutputSplit VALUES('utopia',2000,'SRE','GSL',0.2999999999999999889,''); -INSERT INTO TechOutputSplit VALUES('utopia',2010,'SRE','GSL',0.2999999999999999889,''); -CREATE TABLE TimeOfDay -( - sequence INTEGER UNIQUE, - tod TEXT - PRIMARY KEY -); -INSERT INTO TimeOfDay VALUES(1,'day'); -INSERT INTO TimeOfDay VALUES(2,'night'); -CREATE TABLE TimePeriod -( - sequence INTEGER UNIQUE, - period INTEGER - PRIMARY KEY, - flag TEXT - REFERENCES TimePeriodType (label) -); -INSERT INTO TimePeriod VALUES(1,1960,'e'); -INSERT INTO TimePeriod VALUES(2,1970,'e'); -INSERT INTO TimePeriod VALUES(3,1980,'e'); -INSERT INTO TimePeriod VALUES(4,1990,'f'); -INSERT INTO TimePeriod VALUES(5,2000,'f'); -INSERT INTO TimePeriod VALUES(6,2010,'f'); -INSERT INTO TimePeriod VALUES(7,2020,'f'); -CREATE TABLE TimeSeason -( - sequence INTEGER UNIQUE, - season TEXT - PRIMARY KEY -); -INSERT INTO TimeSeason VALUES(1,'inter'); -INSERT INTO TimeSeason VALUES(2,'summer'); -INSERT INTO TimeSeason VALUES(3,'winter'); -CREATE TABLE TimePeriodType -( - label TEXT - PRIMARY KEY, - description TEXT -); -INSERT INTO TimePeriodType VALUES('e','existing vintages'); -INSERT INTO TimePeriodType VALUES('f','future'); -CREATE TABLE MaxActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MaxAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MaxNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - max_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MaxNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MaxNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinActivityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinAnnualCapacityFactor -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - output_comm TEXT - REFERENCES Commodity (name), - factor REAL, - source TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech), - CHECK (factor >= 0 AND factor <= 1) -); -CREATE TABLE MinCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - min_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE MinNewCapacity -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - min_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, tech) -); -CREATE TABLE MinNewCapacityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_new_cap REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE MinNewCapacityShare -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - tech TEXT - REFERENCES Technology (tech), - group_name TEXT - REFERENCES TechGroup (group_name), - max_proportion REAL, - notes TEXT, - PRIMARY KEY (region, period, tech, group_name) -); -CREATE TABLE OutputEmission -( - scenario TEXT, - region TEXT, - sector TEXT - REFERENCES SectorLabel (sector), - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - tech TEXT - REFERENCES Technology (tech), - vintage INTEGER - REFERENCES TimePeriod (period), - emission REAL, - PRIMARY KEY (region, scenario, period, emis_comm, tech, vintage) -); -CREATE TABLE MinActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - min_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE EmissionLimit -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - emis_comm TEXT - REFERENCES Commodity (name), - value REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, emis_comm) -); -CREATE TABLE MaxActivityGroup -( - region TEXT, - period INTEGER - REFERENCES TimePeriod (period), - group_name TEXT - REFERENCES TechGroup (group_name), - max_act REAL, - units TEXT, - notes TEXT, - PRIMARY KEY (region, period, group_name) -); -CREATE TABLE RPSRequirement -( - region TEXT NOT NULL - REFERENCES Region (region), - period INTEGER NOT NULL - REFERENCES TimePeriod (period), - tech_group TEXT NOT NULL - REFERENCES TechGroup (group_name), - requirement REAL NOT NULL, - notes TEXT -); -CREATE TABLE TechGroupMember -( - group_name TEXT - REFERENCES TechGroup (group_name), - tech TEXT - REFERENCES Technology (tech), - PRIMARY KEY (group_name, tech) -); -CREATE TABLE Technology -( - tech TEXT NOT NULL PRIMARY KEY, - flag TEXT NOT NULL, - sector TEXT, - category TEXT, - sub_category TEXT, - unlim_cap INTEGER NOT NULL DEFAULT 0, - annual INTEGER NOT NULL DEFAULT 0, - reserve INTEGER NOT NULL DEFAULT 0, - curtail INTEGER NOT NULL DEFAULT 0, - retire INTEGER NOT NULL DEFAULT 0, - flex INTEGER NOT NULL DEFAULT 0, - variable INTEGER NOT NULL DEFAULT 0, - exchange INTEGER NOT NULL DEFAULT 0, - description TEXT, - FOREIGN KEY (flag) REFERENCES TechnologyType (label) -); -INSERT INTO Technology VALUES('IMPDSL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); -INSERT INTO Technology VALUES('IMPGSL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); -INSERT INTO Technology VALUES('IMPHCO1','r','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); -INSERT INTO Technology VALUES('IMPOIL1','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); -INSERT INTO Technology VALUES('IMPURN1','r','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); -INSERT INTO Technology VALUES('IMPFEQ','r','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); -INSERT INTO Technology VALUES('IMPHYD','r','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); -INSERT INTO Technology VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); -INSERT INTO Technology VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); -INSERT INTO Technology VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); -INSERT INTO Technology VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); -INSERT INTO Technology VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); -INSERT INTO Technology VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); -INSERT INTO Technology VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); -INSERT INTO Technology VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); -INSERT INTO Technology VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); -INSERT INTO Technology VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); -INSERT INTO Technology VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); -INSERT INTO Technology VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); -CREATE TABLE OutputCost -( - scenario TEXT, - region TEXT, - period INTEGER, - tech TEXT, - vintage INTEGER, - d_invest REAL, - d_fixed REAL, - d_var REAL, - d_emiss REAL, - invest REAL, - fixed REAL, - var REAL, - emiss REAL, - PRIMARY KEY (scenario, region, period, tech, vintage), - FOREIGN KEY (vintage) REFERENCES TimePeriod (period), - FOREIGN KEY (tech) REFERENCES Technology (tech) -); -COMMIT; diff --git a/tests/testing_data/utopia_data.sql b/tests/testing_data/utopia_data.sql new file mode 100644 index 000000000..d0f131cb3 --- /dev/null +++ b/tests/testing_data/utopia_data.sql @@ -0,0 +1,465 @@ +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2000,0.2753,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','day','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','inter','night','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','day','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','winter','night','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','day','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_process" VALUES('utopia','summer','night','E31',2010,0.2756,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E01',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E21',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E31',0.275,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E51',0.17,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','day','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','inter','night','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','day','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','winter','night','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','day','E70',0.8,''); +REPLACE INTO "capacity_factor_tech" VALUES('utopia','summer','night','E70',0.8,''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E01',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E21',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E31',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E51',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','E70',31.54,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','RHE',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','RHO',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','RL1',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','SRE',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','TXD',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','TXE',1.0,'PJ / (GW * year)',''); +REPLACE INTO "capacity_to_activity" VALUES('utopia','TXG',1.0,'PJ / (GW * year)',''); +REPLACE INTO "commodity" VALUES('ethos','s','# dummy commodity to supply inputs','PJ'); +REPLACE INTO "commodity" VALUES('DSL','p','# diesel','PJ'); +REPLACE INTO "commodity" VALUES('ELC','p','# electricity','PJ'); +REPLACE INTO "commodity" VALUES('FEQ','p','# fossil equivalent','PJ'); +REPLACE INTO "commodity" VALUES('GSL','p','# gasoline','PJ'); +REPLACE INTO "commodity" VALUES('HCO','p','# coal','PJ'); +REPLACE INTO "commodity" VALUES('HYD','p','# water','PJ'); +REPLACE INTO "commodity" VALUES('OIL','p','# crude oil','PJ'); +REPLACE INTO "commodity" VALUES('URN','p','# uranium','PJ'); +REPLACE INTO "commodity" VALUES('co2','e','#CO2 emissions','Mt'); +REPLACE INTO "commodity" VALUES('nox','e','#NOX emissions','Mt'); +REPLACE INTO "commodity" VALUES('RH','d','# residential heating','PJ'); +REPLACE INTO "commodity" VALUES('RL','d','# residential lighting','PJ'); +REPLACE INTO "commodity" VALUES('TX','d','# transportation','PJ'); +REPLACE INTO "commodity_type" VALUES('w','waste commodity'); +REPLACE INTO "commodity_type" VALUES('wa','waste annual commodity'); +REPLACE INTO "commodity_type" VALUES('wp','waste physical commodity'); +REPLACE INTO "commodity_type" VALUES('a','annual commodity'); +REPLACE INTO "commodity_type" VALUES('s','source commodity'); +REPLACE INTO "commodity_type" VALUES('p','physical commodity'); +REPLACE INTO "commodity_type" VALUES('e','emissions commodity'); +REPLACE INTO "commodity_type" VALUES('d','demand commodity'); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1960,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1970,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1980,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E01',1990,40.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',1970,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',1980,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',1990,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E01',2000,70.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',1980,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',2000,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E01',2010,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E21',1990,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E21',2000,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E21',2010,500.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',1980,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',1990,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',2000,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E31',2010,75.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E51',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1960,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',1970,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',1980,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',1990,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',2000,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'E70',2010,30.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RHO',1970,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RHO',1980,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RHO',1990,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RHO',2000,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RHO',2010,1.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RL1',1980,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'RL1',1990,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'RL1',2000,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'RL1',2010,9.46,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXD',1970,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXD',1980,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXD',1990,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXD',2000,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXD',2010,52.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXE',1990,100.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXE',1990,90.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXE',2000,90.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXE',2000,80.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXE',2010,80.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXG',1970,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',1990,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXG',1980,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXG',1990,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2000,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXG',2000,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_fixed" VALUES('utopia',2010,'TXG',2010,48.0,'Mdollar / (PJ^2 / GW / year)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E01',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E01',2000,1300.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E01',2010,1200.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E21',1990,5000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E21',2000,5000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E21',2010,5000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E31',1990,3000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E31',2000,3000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E31',2010,3000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E51',1990,900.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E51',2000,900.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E51',2010,900.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E70',1990,1000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E70',2000,1000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','E70',2010,1000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHE',1990,90.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHE',2000,90.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHE',2010,90.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHO',1990,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHO',2000,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','RHO',2010,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','SRE',1990,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','SRE',2000,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','SRE',2010,100.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXD',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXD',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXD',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXE',1990,2000.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXE',2000,1750.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXE',2010,1500.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXG',1990,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXG',2000,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_invest" VALUES('utopia','TXG',2010,1044.0,'Mdollar / (PJ^2 / GW)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPDSL1',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPGSL1',1990,15.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPHCO1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPOIL1',1990,8.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'IMPURN1',1990,2.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1960,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1970,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1980,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E01',1990,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',1970,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',1980,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',1990,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E01',2000,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',1980,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',1990,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',2000,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E01',2010,0.3,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E21',1990,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E21',1990,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E21',1990,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E21',2000,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E21',2000,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E21',2010,1.5,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1960,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1970,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1980,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'E70',1990,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',1970,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',1980,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',1990,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'E70',2000,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',1980,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',1990,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',2000,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'E70',2010,0.4,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',1990,'SRE',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'SRE',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2000,'SRE',2000,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'SRE',1990,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'SRE',2000,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "cost_variable" VALUES('utopia',2010,'SRE',2010,10.0,'Mdollar / (PJ)',''); +REPLACE INTO "demand" VALUES('utopia',1990,'RH',25.2,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2000,'RH',37.8,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2010,'RH',56.7,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',1990,'RL',5.6,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2000,'RL',8.4,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2010,'RL',12.6,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',1990,'TX',5.2,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2000,'TX',7.8,'PJ',''); +REPLACE INTO "demand" VALUES('utopia',2010,'TX',11.69,'PJ',''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RH',0.12,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RH',0.06,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RH',0.5467,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RH',0.2733,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'inter','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'summer','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','day','RL',0.5,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',1990,'winter','night','RL',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RH',0.12,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RH',0.06,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RH',0.5467,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RH',0.2733,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'inter','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'summer','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','day','RL',0.5,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2000,'winter','night','RL',0.1,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RH',0.12,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RH',0.06,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RH',0.5467,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RH',0.2733,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'inter','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','day','RL',0.15,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'summer','night','RL',0.05,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','day','RL',0.5,''); +REPLACE INTO "demand_specific_distribution" VALUES('utopia',2010,'winter','night','RL',0.1,''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPDSL1',1990,'DSL',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPGSL1',1990,'GSL',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPHCO1',1990,'HCO',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPOIL1',1990,'OIL',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPURN1',1990,'URN',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPFEQ',1990,'FEQ',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','ethos','IMPHYD',1990,'HYD',1.0,'PJ / (PJ)',''); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1960,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1970,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HCO','E01',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','FEQ','E21',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','FEQ','E21',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','FEQ','E21',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','URN','E21',1990,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +REPLACE INTO "efficiency" VALUES('utopia','URN','E21',2000,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +REPLACE INTO "efficiency" VALUES('utopia','URN','E21',2010,'ELC',0.4,'PJ / (PJ)','# 1/2.5'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',1980,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',1990,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',2000,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','HYD','E31',2010,'ELC',0.32,'PJ / (PJ)','# 1/3.125'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1960,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1970,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1980,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',1990,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',2000,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','E70',2010,'ELC',0.294,'PJ / (PJ)','# 1/3.4'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',1980,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',1990,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',2000,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','E51',2010,'ELC',0.72,'PJ / (PJ)','# 1/1.3889'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RHE',1990,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RHE',2000,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RHE',2010,'RH',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',1970,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',1980,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',1990,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',2000,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','RHO',2010,'RH',0.7,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',1980,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',1990,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',2000,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','RL1',2010,'RL',1.0,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'DSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',1990,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2000,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','OIL','SRE',2010,'GSL',1.0,'PJ / (PJ)','# direct translation from PRC_INP2, PRC_OUT'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','DSL','TXD',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','TXE',1990,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','TXE',2000,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','ELC','TXE',2010,'TX',0.827,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',1970,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',1980,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',1990,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',2000,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "efficiency" VALUES('utopia','GSL','TXG',2010,'TX',0.231,'PJ / (PJ)','# direct translation from DMD_EFF'); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPDSL1',1990,'DSL',0.075,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPGSL1',1990,'GSL',0.075,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPHCO1',1990,'HCO',8.9e-02,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','co2','ethos','IMPOIL1',1990,'OIL',0.075,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1970,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1980,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',1990,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2000,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','DSL','TXD',2010,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1970,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1980,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',1990,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2000,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "emission_activity" VALUES('utopia','nox','GSL','TXG',2010,'TX',1.0,'Mt / (PJ)',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E01',1960,0.175,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E01',1970,0.175,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E01',1980,0.15,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E31',1980,0.1,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E51',1980,0.5,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E70',1960,0.05,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E70',1970,0.05,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','E70',1980,0.2,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','RHO',1970,12.5,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','RHO',1980,12.5,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','RL1',1980,5.6,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXD',1970,0.4,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXD',1980,0.2,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXG',1970,3.1,'GW',''); +REPLACE INTO "existing_capacity" VALUES('utopia','TXG',1980,1.5,'GW',''); +REPLACE INTO "lifetime_process" VALUES('utopia','RL1',1980,20.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXD',1970,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXD',1980,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXG',1970,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_process" VALUES('utopia','TXG',1980,30.0,'year','#forexistingcap'); +REPLACE INTO "lifetime_tech" VALUES('utopia','E01',40.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E21',40.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E31',100.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E51',100.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','E70',40.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','RHE',30.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','RHO',30.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','RL1',10.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','SRE',50.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','TXD',15.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','TXE',15.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','TXG',15.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPDSL1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPGSL1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPHCO1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPOIL1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPURN1',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPHYD',1000.0,'year',''); +REPLACE INTO "lifetime_tech" VALUES('utopia','IMPFEQ',1000.0,'year',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'E31','ge',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2000,'E31','ge',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2010,'E31','ge',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'SRE','ge',0.1,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'E31','le',0.13,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2000,'E31','le',0.17,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2010,'E31','le',2.1e-01,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'RHE','le',0.0,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',1990,'TXD','le',0.6,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2000,'TXD','le',1.76,'GW',''); +REPLACE INTO "limit_capacity" VALUES('utopia',2010,'TXD','le',4.76,'GW',''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','DSL','ge',0.7,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','DSL','ge',0.7,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','DSL','ge',0.7,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',1990,'SRE','GSL','ge',0.3,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2000,'SRE','GSL','ge',0.3,''); +REPLACE INTO "limit_tech_output_split" VALUES('utopia',2010,'SRE','GSL','ge',0.3,''); +REPLACE INTO "metadata" VALUES('DB_MAJOR',4,''); +REPLACE INTO "metadata" VALUES('DB_MINOR',0,''); +REPLACE INTO "metadata_real" VALUES('default_loan_rate',0.05,'Default Loan Rate if not specified in loan_rate table'); +REPLACE INTO "metadata_real" VALUES('global_discount_rate',0.05,''); +REPLACE INTO "operator" VALUES('e','equal to'); +REPLACE INTO "operator" VALUES('le','less than or equal to'); +REPLACE INTO "operator" VALUES('ge','greater than or equal to'); +REPLACE INTO "region" VALUES('utopia',NULL); +REPLACE INTO "sector_label" VALUES('supply',NULL); +REPLACE INTO "sector_label" VALUES('electric',NULL); +REPLACE INTO "sector_label" VALUES('transport',NULL); +REPLACE INTO "sector_label" VALUES('commercial',NULL); +REPLACE INTO "sector_label" VALUES('residential',NULL); +REPLACE INTO "sector_label" VALUES('industrial',NULL); +REPLACE INTO "technology" VALUES('IMPDSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported diesel'); +REPLACE INTO "technology" VALUES('IMPGSL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported gasoline'); +REPLACE INTO "technology" VALUES('IMPHCO1','p','supply','coal','',1,0,0,0,0,0,0,0,' imported coal'); +REPLACE INTO "technology" VALUES('IMPOIL1','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported crude oil'); +REPLACE INTO "technology" VALUES('IMPURN1','p','supply','nuclear','',1,0,0,0,0,0,0,0,' imported uranium'); +REPLACE INTO "technology" VALUES('IMPFEQ','p','supply','petroleum','',1,0,0,0,0,0,0,0,' imported fossil equivalent'); +REPLACE INTO "technology" VALUES('IMPHYD','p','supply','hydro','',1,0,0,0,0,0,0,0,' imported water -- doesnt exist in Utopia'); +REPLACE INTO "technology" VALUES('E01','pb','electric','coal','',0,0,0,0,0,0,0,0,' coal power plant'); +REPLACE INTO "technology" VALUES('E21','pb','electric','nuclear','',0,0,0,0,0,0,0,0,' nuclear power plant'); +REPLACE INTO "technology" VALUES('E31','pb','electric','hydro','',0,0,0,0,0,0,0,0,' hydro power'); +REPLACE INTO "technology" VALUES('E51','ps','electric','electric','',0,0,0,0,0,0,0,0,' electric storage'); +REPLACE INTO "technology" VALUES('E70','p','electric','petroleum','',0,0,0,0,0,0,0,0,' diesel power plant'); +REPLACE INTO "technology" VALUES('RHE','p','residential','electric','',0,0,0,0,0,0,0,0,' electric residential heating'); +REPLACE INTO "technology" VALUES('RHO','p','residential','petroleum','',0,0,0,0,0,0,0,0,' diesel residential heating'); +REPLACE INTO "technology" VALUES('RL1','p','residential','electric','',0,0,0,0,0,0,0,0,' residential lighting'); +REPLACE INTO "technology" VALUES('SRE','p','supply','petroleum','',0,0,0,0,0,0,0,0,' crude oil processor'); +REPLACE INTO "technology" VALUES('TXD','p','transport','petroleum','',0,0,0,0,0,0,0,0,' diesel powered vehicles'); +REPLACE INTO "technology" VALUES('TXE','p','transport','electric','',0,0,0,0,0,0,0,0,' electric powered vehicles'); +REPLACE INTO "technology" VALUES('TXG','p','transport','petroleum','',0,0,0,0,0,0,0,0,' gasoline powered vehicles'); +REPLACE INTO "technology_type" VALUES('p','production technology'); +REPLACE INTO "technology_type" VALUES('pb','baseload production technology'); +REPLACE INTO "technology_type" VALUES('ps','storage production technology'); +REPLACE INTO "time_of_day" VALUES(1,'day',16,NULL); +REPLACE INTO "time_of_day" VALUES(2,'night',8,NULL); +REPLACE INTO "time_period" VALUES(1,1960,'e'); +REPLACE INTO "time_period" VALUES(2,1970,'e'); +REPLACE INTO "time_period" VALUES(3,1980,'e'); +REPLACE INTO "time_period" VALUES(4,1990,'f'); +REPLACE INTO "time_period" VALUES(5,2000,'f'); +REPLACE INTO "time_period" VALUES(6,2010,'f'); +REPLACE INTO "time_period" VALUES(7,2020,'f'); +REPLACE INTO "time_period_type" VALUES('e','existing vintages'); +REPLACE INTO "time_period_type" VALUES('f','future'); +REPLACE INTO "time_season" VALUES(1,'inter',0.25,NULL); +REPLACE INTO "time_season" VALUES(2,'summer',0.25,NULL); +REPLACE INTO "time_season" VALUES(3,'winter',0.5,NULL); diff --git a/tests/testing_data/utopia_sets.json b/tests/testing_data/utopia_sets.json index fa218f837..628c177da 100644 --- a/tests/testing_data/utopia_sets.json +++ b/tests/testing_data/utopia_sets.json @@ -1,25330 +1,115 @@ { - "time_exist": [ - 1960, - 1970, - 1980 - ], - "time_future": [ - 1990, - 2000, - 2010, - 2020 - ], - "time_optimize": [ - 1990, - 2000, - 2010 - ], - "vintage_exist": [ - 1960, - 1970, - 1980 - ], - "vintage_optimize": [ - 1990, - 2000, - 2010 - ], - "vintage_all": [ - 1960, - 1970, - 1980, - 1990, - 2000, - 2010 - ], - "time_season": [ - "inter", - "summer", - "winter" - ], - "time_of_day": [ - "day", - "night" - ], - "regions": [ - "utopia" - ], - "RegionalIndices": [ - "utopia" - ], - "RegionalGlobalIndices": [], - "tech_resource": [ - "IMPDSL1", - "IMPGSL1", - "IMPHCO1", - "IMPOIL1", - "IMPURN1", - "IMPFEQ", - "IMPHYD" - ], - "tech_production": [ - "E01", - "E21", - "E31", - "E51", - "E70", - "RHE", - "RHO", - "RL1", - "SRE", - "TXD", - "TXE", - "TXG" - ], - "tech_all": [ - "IMPDSL1", - "IMPGSL1", - "IMPHCO1", - "IMPOIL1", - "IMPURN1", - "IMPFEQ", - "IMPHYD", - "E01", - "E21", - "E31", - "E51", - "E70", - "RHE", - "RHO", - "RL1", - "SRE", - "TXD", - "TXE", - "TXG" - ], - "tech_baseload": [ - "E01", - "E21", - "E31" - ], - "tech_annual": [], - "tech_storage": [ - "E51" - ], - "tech_reserve": [], - "tech_ramping": [], - "tech_curtailment": [], - "tech_flex": [], - "tech_exchange": [], - "tech_group_names": [], - "tech_group_members": [], - "tech_uncap": [ - "IMPDSL1", - "IMPGSL1", - "IMPHCO1", - "IMPOIL1", - "IMPURN1", - "IMPFEQ", - "IMPHYD" - ], - "tech_with_capacity": [ - "E01", - "E21", - "E31", - "E51", - "E70", - "RHE", - "RHO", - "RL1", - "SRE", - "TXD", - "TXE", - "TXG" - ], - "tech_variable": [], - "tech_retirement": [], - "commodity_demand": [ - "RH", - "RL", - "TX" - ], - "commodity_emissions": [ - "co2", - "nox" - ], - "commodity_physical": [ - "ethos", - "DSL", - "ELC", - "FEQ", - "GSL", - "HCO", - "HYD", - "OIL", - "URN" - ], - "commodity_source": [ - "ethos" - ], - "commodity_carrier": [ - "ethos", - "DSL", - "ELC", - "FEQ", - "GSL", - "HCO", - "HYD", - "OIL", - "URN", - "RH", - "RL", - "TX" - ], - "commodity_all": [ - "ethos", - "DSL", - "ELC", - "FEQ", - "GSL", - "HCO", - "HYD", - "OIL", - "URN", - "RH", - "RL", - "TX", - "co2", - "nox" - ], - "tech_mga": [], - "tech_electric": [], - "tech_transport": [], - "tech_industrial": [], - "tech_commercial": [], - "tech_residential": [], - "tech_PowerPlants": [], - "ResourceConstraint_rpr": [], - "CapacityFactor_rsdt": [ - [ - "utopia", - "inter", - "night", - "E21" - ], - [ - "utopia", - "inter", - "day", - "IMPFEQ" - ], - [ - "utopia", - "inter", - "night", - "IMPGSL1" - ], - [ - "utopia", - "summer", - "day", - "E51" - ], - [ - "utopia", - "inter", - "night", - "RL1" - ], - [ - "utopia", - "winter", - "night", - "E31" - ], - [ - "utopia", - "summer", - "day", - "E31" - ], - [ - "utopia", - "winter", - "night", - "TXD" - ], - [ - "utopia", - "summer", - "day", - "TXD" - ], - [ - "utopia", - "summer", - "night", - "IMPHCO1" - ], - [ - "utopia", - "inter", - "day", - "E01" - ], - [ - "utopia", - "summer", - "night", - "SRE" - ], - [ - "utopia", - "inter", - "night", - "E31" - ], - [ - "utopia", - "inter", - "night", - "TXD" - ], - [ - "utopia", - "summer", - "night", - "E21" - ], - [ - "utopia", - "winter", - "night", - "IMPFEQ" - ], - [ - "utopia", - "summer", - "day", - "IMPFEQ" - ], - [ - "utopia", - "winter", - "day", - "E51" - ], - [ - "utopia", - "winter", - "day", - "IMPDSL1" - ], - [ - "utopia", - "winter", - "day", - "IMPURN1" - ], - [ - "utopia", - "winter", - "day", - "RHO" - ], - [ - "utopia", - "inter", - "night", - "IMPFEQ" - ], - [ - "utopia", - "inter", - "day", - "E51" - ], - [ - "utopia", - "inter", - "day", - "IMPURN1" - ], - [ - "utopia", - "winter", - "night", - "E01" - ], - [ - "utopia", - "winter", - "day", - "E70" - ], - [ - "utopia", - "winter", - "day", - "TXE" - ], - [ - "utopia", - "summer", - "night", - "E31" - ], - [ - "utopia", - "summer", - "night", - "TXD" - ], - [ - "utopia", - "inter", - "night", - "E01" - ], - [ - "utopia", - "inter", - "night", - "SRE" - ], - [ - "utopia", - "inter", - "day", - "E70" - ], - [ - "utopia", - "winter", - "day", - "IMPOIL1" - ], - [ - "utopia", - "summer", - "night", - "IMPFEQ" - ], - [ - "utopia", - "winter", - "night", - "E51" - ], - [ - "utopia", - "summer", - "day", - "IMPDSL1" - ], - [ - "utopia", - "summer", - "day", - "IMPURN1" - ], - [ - "utopia", - "winter", - "night", - "IMPURN1" - ], - [ - "utopia", - "summer", - "day", - "RHO" - ], - [ - "utopia", - "inter", - "night", - "E51" - ], - [ - "utopia", - "summer", - "night", - "E01" - ], - [ - "utopia", - "winter", - "day", - "IMPHYD" - ], - [ - "utopia", - "winter", - "night", - "E70" - ], - [ - "utopia", - "summer", - "day", - "E70" - ], - [ - "utopia", - "summer", - "day", - "TXE" - ], - [ - "utopia", - "inter", - "day", - "IMPHYD" - ], - [ - "utopia", - "winter", - "night", - "IMPOIL1" - ], - [ - "utopia", - "summer", - "day", - "IMPOIL1" - ], - [ - "utopia", - "summer", - "night", - "E51" - ], - [ - "utopia", - "inter", - "day", - "IMPDSL1" - ], - [ - "utopia", - "summer", - "night", - "IMPDSL1" - ], - [ - "utopia", - "summer", - "night", - "IMPURN1" - ], - [ - "utopia", - "inter", - "day", - "RHO" - ], - [ - "utopia", - "inter", - "day", - "TXE" - ], - [ - "utopia", - "winter", - "night", - "IMPHYD" - ], - [ - "utopia", - "summer", - "day", - "IMPHYD" - ], - [ - "utopia", - "summer", - "night", - "E70" - ], - [ - "utopia", - "winter", - "day", - "TXG" - ], - [ - "utopia", - "inter", - "night", - "IMPHYD" - ], - [ - "utopia", - "inter", - "day", - "TXG" - ], - [ - "utopia", - "winter", - "day", - "IMPGSL1" - ], - [ - "utopia", - "inter", - "day", - "IMPOIL1" - ], - [ - "utopia", - "winter", - "night", - "IMPDSL1" - ], - [ - "utopia", - "summer", - "night", - "IMPOIL1" - ], - [ - "utopia", - "winter", - "night", - "RHO" - ], - [ - "utopia", - "winter", - "day", - "RL1" - ], - [ - "utopia", - "inter", - "day", - "IMPGSL1" - ], - [ - "utopia", - "inter", - "night", - "IMPDSL1" - ], - [ - "utopia", - "inter", - "night", - "IMPURN1" - ], - [ - "utopia", - "inter", - "night", - "RHO" - ], - [ - "utopia", - "winter", - "night", - "TXE" - ], - [ - "utopia", - "inter", - "night", - "E70" - ], - [ - "utopia", - "inter", - "night", - "TXE" - ], - [ - "utopia", - "winter", - "day", - "RHE" - ], - [ - "utopia", - "summer", - "night", - "IMPHYD" - ], - [ - "utopia", - "winter", - "night", - "TXG" - ], - [ - "utopia", - "summer", - "day", - "TXG" - ], - [ - "utopia", - "inter", - "day", - "RHE" - ], - [ - "utopia", - "winter", - "night", - "IMPGSL1" - ], - [ - "utopia", - "summer", - "day", - "IMPGSL1" - ], - [ - "utopia", - "inter", - "night", - "IMPOIL1" - ], - [ - "utopia", - "summer", - "night", - "RHO" - ], - [ - "utopia", - "winter", - "night", - "RL1" - ], - [ - "utopia", - "summer", - "day", - "RL1" - ], - [ - "utopia", - "winter", - "day", - "IMPHCO1" - ], - [ - "utopia", - "summer", - "night", - "TXE" - ], - [ - "utopia", - "winter", - "day", - "E01" - ], - [ - "utopia", - "winter", - "day", - "SRE" - ], - [ - "utopia", - "inter", - "day", - "IMPHCO1" - ], - [ - "utopia", - "winter", - "night", - "RHE" - ], - [ - "utopia", - "summer", - "day", - "RHE" - ], - [ - "utopia", - "winter", - "day", - "E21" - ], - [ - "utopia", - "inter", - "day", - "SRE" - ], - [ - "utopia", - "summer", - "night", - "TXG" - ], - [ - "utopia", - "inter", - "night", - "RHE" - ], - [ - "utopia", - "inter", - "day", - "E21" - ], - [ - "utopia", - "summer", - "night", - "IMPGSL1" - ], - [ - "utopia", - "inter", - "day", - "RL1" - ], - [ - "utopia", - "summer", - "night", - "RL1" - ], - [ - "utopia", - "winter", - "night", - "IMPHCO1" - ], - [ - "utopia", - "winter", - "day", - "E31" - ], - [ - "utopia", - "summer", - "day", - "IMPHCO1" - ], - [ - "utopia", - "winter", - "day", - "TXD" - ], - [ - "utopia", - "summer", - "day", - "E01" - ], - [ - "utopia", - "winter", - "night", - "SRE" - ], - [ - "utopia", - "summer", - "day", - "SRE" - ], - [ - "utopia", - "inter", - "night", - "IMPHCO1" - ], - [ - "utopia", - "inter", - "day", - "E31" - ], - [ - "utopia", - "inter", - "day", - "TXD" - ], - [ - "utopia", - "summer", - "night", - "RHE" - ], - [ - "utopia", - "inter", - "night", - "TXG" - ], - [ - "utopia", - "winter", - "night", - "E21" - ], - [ - "utopia", - "summer", - "day", - "E21" - ], - [ - "utopia", - "winter", - "day", - "IMPFEQ" - ] - ], - "LifetimeProcess_rtv": [ - [ - "utopia", - "E70", - 1970 - ], - [ - "utopia", - "E01", - 1990 - ], - [ - "utopia", - "E21", - 1990 - ], - [ - "utopia", - "RHO", - 2010 - ], - [ - "utopia", - "TXD", - 2000 - ], - [ - "utopia", - "TXE", - 2010 - ], - [ - "utopia", - "TXG", - 2000 - ], - [ - "utopia", - "E31", - 1980 - ], - [ - "utopia", - "RHE", - 1990 - ], - [ - "utopia", - "E51", - 1980 - ], - [ - "utopia", - "RHO", - 1970 - ], - [ - "utopia", - "SRE", - 2000 - ], - [ - "utopia", - "RL1", - 1980 - ], - [ - "utopia", - "TXD", - 1990 - ], - [ - "utopia", - "E70", - 2000 - ], - [ - "utopia", - "TXE", - 2000 - ], - [ - "utopia", - "IMPHCO1", - 1990 - ], - [ - "utopia", - "IMPDSL1", - 1990 - ], - [ - "utopia", - "E70", - 1960 - ], - [ - "utopia", - "E01", - 1980 - ], - [ - "utopia", - "E31", - 2010 - ], - [ - "utopia", - "E51", - 2010 - ], - [ - "utopia", - "RHO", - 2000 - ], - [ - "utopia", - "RL1", - 2010 - ], - [ - "utopia", - "TXG", - 1990 - ], - [ - "utopia", - "IMPGSL1", - 1990 - ], - [ - "utopia", - "SRE", - 1990 - ], - [ - "utopia", - "E01", - 2010 - ], - [ - "utopia", - "E70", - 1990 - ], - [ - "utopia", - "E21", - 2010 - ], - [ - "utopia", - "TXE", - 1990 - ], - [ - "utopia", - "TXD", - 1980 - ], - [ - "utopia", - "E01", - 1970 - ], - [ - "utopia", - "E31", - 2000 - ], - [ - "utopia", - "RHE", - 2010 - ], - [ - "utopia", - "RHO", - 1990 - ], - [ - "utopia", - "E51", - 2000 - ], - [ - "utopia", - "RL1", - 2000 - ], - [ - "utopia", - "TXD", - 2010 - ], - [ - "utopia", - "TXG", - 1980 - ], - [ - "utopia", - "E70", - 1980 - ], - [ - "utopia", - "E01", - 2000 - ], - [ - "utopia", - "E21", - 2000 - ], - [ - "utopia", - "IMPHYD", - 1990 - ], - [ - "utopia", - "TXD", - 1970 - ], - [ - "utopia", - "E31", - 1990 - ], - [ - "utopia", - "TXG", - 2010 - ], - [ - "utopia", - "E01", - 1960 - ], - [ - "utopia", - "RHE", - 2000 - ], - [ - "utopia", - "E51", - 1990 - ], - [ - "utopia", - "E70", - 2010 - ], - [ - "utopia", - "RHO", - 1980 - ], - [ - "utopia", - "RL1", - 1990 - ], - [ - "utopia", - "SRE", - 2010 - ], - [ - "utopia", - "IMPURN1", - 1990 - ], - [ - "utopia", - "IMPFEQ", - 1990 - ], - [ - "utopia", - "TXG", - 1970 - ], - [ - "utopia", - "IMPOIL1", - 1990 - ] - ], - "LoanLifetimeProcess_rtv": [ - [ - "utopia", - "E01", - 1990 - ], - [ - "utopia", - "E21", - 1990 - ], - [ - "utopia", - "RHO", - 2010 - ], - [ - "utopia", - "TXD", - 2000 - ], - [ - "utopia", - "TXE", - 2010 - ], - [ - "utopia", - "TXG", - 2000 - ], - [ - "utopia", - "RHE", - 1990 - ], - [ - "utopia", - "SRE", - 2000 - ], - [ - "utopia", - "TXD", - 1990 - ], - [ - "utopia", - "E70", - 2000 - ], - [ - "utopia", - "TXE", - 2000 - ], - [ - "utopia", - "IMPHCO1", - 1990 - ], - [ - "utopia", - "IMPDSL1", - 1990 - ], - [ - "utopia", - "E31", - 2010 - ], - [ - "utopia", - "E51", - 2010 - ], - [ - "utopia", - "RHO", - 2000 - ], - [ - "utopia", - "RL1", - 2010 - ], - [ - "utopia", - "TXG", - 1990 - ], - [ - "utopia", - "IMPGSL1", - 1990 - ], - [ - "utopia", - "SRE", - 1990 - ], - [ - "utopia", - "E01", - 2010 - ], - [ - "utopia", - "E70", - 1990 - ], - [ - "utopia", - "E21", - 2010 - ], - [ - "utopia", - "TXE", - 1990 - ], - [ - "utopia", - "E31", - 2000 - ], - [ - "utopia", - "RHE", - 2010 - ], - [ - "utopia", - "RHO", - 1990 - ], - [ - "utopia", - "E51", - 2000 - ], - [ - "utopia", - "RL1", - 2000 - ], - [ - "utopia", - "TXD", - 2010 - ], - [ - "utopia", - "E01", - 2000 - ], - [ - "utopia", - "E21", - 2000 - ], - [ - "utopia", - "IMPHYD", - 1990 - ], - [ - "utopia", - "E31", - 1990 - ], - [ - "utopia", - "TXG", - 2010 - ], - [ - "utopia", - "RHE", - 2000 - ], - [ - "utopia", - "RL1", - 1990 - ], - [ - "utopia", - "E51", - 1990 - ], - [ - "utopia", - "E70", - 2010 - ], - [ - "utopia", - "SRE", - 2010 - ], - [ - "utopia", - "IMPURN1", - 1990 - ], - [ - "utopia", - "IMPFEQ", - 1990 - ], - [ - "utopia", - "IMPOIL1", - 1990 - ] - ], - "RenewablePortfolioStandardConstraint_rpg": [], - "CostFixed_rptv": [ - [ - "utopia", - 2010, - "E51", - 2010 - ], - [ - "utopia", - 2000, - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2010 - ], - [ - "utopia", - 2010, - "E70", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1990 - ], - [ - "utopia", - 1990, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "RL1", - 2000 - ], - [ - "utopia", - 2000, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1990 - ], - [ - "utopia", - 2010, - "RHE", - 2000 - ], - [ - "utopia", - 2000, - "RHE", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "E70", - 2010 - ], - [ - "utopia", - 2010, - "E31", - 1980 - ], - [ - "utopia", - 2010, - "E51", - 1980 - ], - [ - "utopia", - 2000, - "E51", - 1980 - ], - [ - "utopia", - 1990, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "E31", - 2010 - ], - [ - "utopia", - 2010, - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 2000 - ], - [ - "utopia", - 2000, - "E51", - 2000 - ], - [ - "utopia", - 2010, - "E21", - 2010 - ], - [ - "utopia", - 2010, - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "TXG", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1960 - ], - [ - "utopia", - 1990, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 1980 - ], - [ - "utopia", - 2000, - "E70", - 1980 - ], - [ - "utopia", - 1990, - "E01", - 1970 - ], - [ - "utopia", - 1990, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "TXE", - 2010 - ], - [ - "utopia", - 2000, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1980 - ], - [ - "utopia", - 2010, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 2000 - ], - [ - "utopia", - 1990, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "E31", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1970 - ], - [ - "utopia", - 2000, - "SRE", - 1990 - ], - [ - "utopia", - 2000, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E21", - 2000 - ], - [ - "utopia", - 2000, - "E21", - 2000 - ], - [ - "utopia", - 2010, - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "SRE", - 2010 - ], - [ - "utopia", - 2000, - "E70", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1960 - ], - [ - "utopia", - 2010, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1990 - ], - [ - "utopia", - 1990, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1970 - ], - [ - "utopia", - 2010, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "RL1", - 1980 - ], - [ - "utopia", - 2000, - "E31", - 2000 - ], - [ - "utopia", - 2010, - "RHE", - 2010 - ], - [ - "utopia", - 1990, - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "TXD", - 1970 - ], - [ - "utopia", - 2010, - "E01", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 1990 - ], - [ - "utopia", - 2000, - "E01", - 1990 - ], - [ - "utopia", - 2000, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "SRE", - 2000 - ] - ], - "CostInvest_rtv": [ - [ - "utopia", - "E01", - 1990 - ], - [ - "utopia", - "E01", - 2000 - ], - [ - "utopia", - "E01", - 2010 - ], - [ - "utopia", - "E21", - 1990 - ], - [ - "utopia", - "E21", - 2000 - ], - [ - "utopia", - "E21", - 2010 - ], - [ - "utopia", - "E31", - 1990 - ], - [ - "utopia", - "E31", - 2000 - ], - [ - "utopia", - "E31", - 2010 - ], - [ - "utopia", - "E51", - 1990 - ], - [ - "utopia", - "E51", - 2000 - ], - [ - "utopia", - "E51", - 2010 - ], - [ - "utopia", - "E70", - 1990 - ], - [ - "utopia", - "E70", - 2000 - ], - [ - "utopia", - "E70", - 2010 - ], - [ - "utopia", - "RHE", - 1990 - ], - [ - "utopia", - "RHE", - 2000 - ], - [ - "utopia", - "RHE", - 2010 - ], - [ - "utopia", - "RHO", - 1990 - ], - [ - "utopia", - "RHO", - 2000 - ], - [ - "utopia", - "RHO", - 2010 - ], - [ - "utopia", - "SRE", - 1990 - ], - [ - "utopia", - "SRE", - 2000 - ], - [ - "utopia", - "SRE", - 2010 - ], - [ - "utopia", - "TXD", - 1990 - ], - [ - "utopia", - "TXD", - 2000 - ], - [ - "utopia", - "TXD", - 2010 - ], - [ - "utopia", - "TXE", - 1990 - ], - [ - "utopia", - "TXE", - 2000 - ], - [ - "utopia", - "TXE", - 2010 - ], - [ - "utopia", - "TXG", - 1990 - ], - [ - "utopia", - "TXG", - 2000 - ], - [ - "utopia", - "TXG", - 2010 - ] - ], - "CostVariable_rptv": [ - [ - "utopia", - 2010, - "E51", - 2010 - ], - [ - "utopia", - 1990, - "IMPURN1", - 1990 - ], - [ - "utopia", - 1990, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2000, - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2010 - ], - [ - "utopia", - 1990, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 2010, - "E70", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1990 - ], - [ - "utopia", - 1990, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "RL1", - 2000 - ], - [ - "utopia", - 2000, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2000, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1990 - ], - [ - "utopia", - 2010, - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2010 - ], - [ - "utopia", - 2000, - "RHE", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "E31", - 1980 - ], - [ - "utopia", - 2010, - "E51", - 1980 - ], - [ - "utopia", - 2000, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "IMPURN1", - 1990 - ], - [ - "utopia", - 2000, - "IMPURN1", - 1990 - ], - [ - "utopia", - 2010, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2000, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 2010 - ], - [ - "utopia", - 2010, - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1990 - ], - [ - "utopia", - 2000, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 2000 - ], - [ - "utopia", - 2000, - "E51", - 2000 - ], - [ - "utopia", - 2010, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 1990, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 2010 - ], - [ - "utopia", - 2010, - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1960 - ], - [ - "utopia", - 1990, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 1980 - ], - [ - "utopia", - 2000, - "E70", - 1980 - ], - [ - "utopia", - 1990, - "E01", - 1970 - ], - [ - "utopia", - 1990, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "TXE", - 2010 - ], - [ - "utopia", - 1990, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1980 - ], - [ - "utopia", - 2010, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 1990, - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 2000 - ], - [ - "utopia", - 1990, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "E31", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1970 - ], - [ - "utopia", - 2000, - "SRE", - 1990 - ], - [ - "utopia", - 2000, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E21", - 2000 - ], - [ - "utopia", - 2000, - "E21", - 2000 - ], - [ - "utopia", - 2010, - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "SRE", - 2010 - ], - [ - "utopia", - 2010, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1960 - ], - [ - "utopia", - 2010, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1990 - ], - [ - "utopia", - 1990, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1970 - ], - [ - "utopia", - 2010, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "RL1", - 1980 - ], - [ - "utopia", - 2000, - "E31", - 2000 - ], - [ - "utopia", - 2010, - "RHE", - 2010 - ], - [ - "utopia", - 1990, - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 1990 - ], - [ - "utopia", - 2000, - "E01", - 1990 - ], - [ - "utopia", - 2000, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1970 - ], - [ - "utopia", - 2010, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "SRE", - 2000 - ] - ], - "CostEmission_rpe": [], - "ModelProcessLife_rptv": [ - [ - "utopia", - 2010, - "E51", - 2010 - ], - [ - "utopia", - 1990, - "IMPURN1", - 1990 - ], - [ - "utopia", - 1990, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2000, - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2010 - ], - [ - "utopia", - 1990, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 2010, - "E70", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1990 - ], - [ - "utopia", - 1990, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "RL1", - 2000 - ], - [ - "utopia", - 2000, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2000, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1990 - ], - [ - "utopia", - 2010, - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2010 - ], - [ - "utopia", - 2000, - "RHE", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "E31", - 1980 - ], - [ - "utopia", - 2010, - "E51", - 1980 - ], - [ - "utopia", - 2000, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "IMPURN1", - 1990 - ], - [ - "utopia", - 2000, - "IMPURN1", - 1990 - ], - [ - "utopia", - 2010, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2000, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 2010 - ], - [ - "utopia", - 2010, - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1990 - ], - [ - "utopia", - 2000, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 2000 - ], - [ - "utopia", - 2000, - "E51", - 2000 - ], - [ - "utopia", - 2010, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 1990, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 2010 - ], - [ - "utopia", - 2010, - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1960 - ], - [ - "utopia", - 1990, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 1980 - ], - [ - "utopia", - 2000, - "E70", - 1980 - ], - [ - "utopia", - 1990, - "E01", - 1970 - ], - [ - "utopia", - 1990, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "TXE", - 2010 - ], - [ - "utopia", - 1990, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1980 - ], - [ - "utopia", - 2010, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 1990, - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 2000 - ], - [ - "utopia", - 1990, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "E31", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1970 - ], - [ - "utopia", - 2000, - "SRE", - 1990 - ], - [ - "utopia", - 2000, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E21", - 2000 - ], - [ - "utopia", - 2000, - "E21", - 2000 - ], - [ - "utopia", - 2010, - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "SRE", - 2010 - ], - [ - "utopia", - 2010, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1960 - ], - [ - "utopia", - 2010, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1990 - ], - [ - "utopia", - 1990, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1970 - ], - [ - "utopia", - 2010, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "RL1", - 1980 - ], - [ - "utopia", - 2000, - "E31", - 2000 - ], - [ - "utopia", - 2010, - "RHE", - 2010 - ], - [ - "utopia", - 1990, - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 1990 - ], - [ - "utopia", - 2000, - "E01", - 1990 - ], - [ - "utopia", - 2000, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1970 - ], - [ - "utopia", - 2010, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "SRE", - 2000 - ] - ], - "ProcessLifeFrac_rptv": [ - [ - "utopia", - 2010, - "E51", - 2010 - ], - [ - "utopia", - 1990, - "IMPURN1", - 1990 - ], - [ - "utopia", - 1990, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2000, - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2010 - ], - [ - "utopia", - 1990, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 2010, - "E70", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1990 - ], - [ - "utopia", - 1990, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "RL1", - 2000 - ], - [ - "utopia", - 2000, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2000, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1990 - ], - [ - "utopia", - 2010, - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2010 - ], - [ - "utopia", - 2000, - "RHE", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "E31", - 1980 - ], - [ - "utopia", - 2010, - "E51", - 1980 - ], - [ - "utopia", - 2000, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "IMPURN1", - 1990 - ], - [ - "utopia", - 2000, - "IMPURN1", - 1990 - ], - [ - "utopia", - 2010, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2000, - "IMPOIL1", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 2010 - ], - [ - "utopia", - 2010, - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1990 - ], - [ - "utopia", - 2000, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 2000 - ], - [ - "utopia", - 2000, - "E51", - 2000 - ], - [ - "utopia", - 2010, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 1990, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 2010 - ], - [ - "utopia", - 2010, - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1960 - ], - [ - "utopia", - 1990, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 1980 - ], - [ - "utopia", - 2000, - "E70", - 1980 - ], - [ - "utopia", - 1990, - "E01", - 1970 - ], - [ - "utopia", - 1990, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "TXE", - 2010 - ], - [ - "utopia", - 1990, - "IMPGSL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1980 - ], - [ - "utopia", - 2010, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "IMPHCO1", - 1990 - ], - [ - "utopia", - 1990, - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 2000 - ], - [ - "utopia", - 1990, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "E31", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1970 - ], - [ - "utopia", - 2000, - "SRE", - 1990 - ], - [ - "utopia", - 2000, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E21", - 2000 - ], - [ - "utopia", - 2000, - "E21", - 2000 - ], - [ - "utopia", - 2010, - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "SRE", - 2010 - ], - [ - "utopia", - 2010, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "IMPFEQ", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1960 - ], - [ - "utopia", - 2010, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1990 - ], - [ - "utopia", - 1990, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1970 - ], - [ - "utopia", - 2010, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "RL1", - 1980 - ], - [ - "utopia", - 2000, - "E31", - 2000 - ], - [ - "utopia", - 2010, - "RHE", - 2010 - ], - [ - "utopia", - 1990, - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "IMPHYD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 1990 - ], - [ - "utopia", - 2000, - "E01", - 1990 - ], - [ - "utopia", - 2000, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1970 - ], - [ - "utopia", - 2010, - "IMPDSL1", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "SRE", - 2000 - ] - ], - "MinCapacityConstraint_rpt": [ - [ - "utopia", - 1990, - "E31" - ], - [ - "utopia", - 2000, - "E31" - ], - [ - "utopia", - 2010, - "E31" - ], - [ - "utopia", - 1990, - "SRE" - ] - ], - "MaxCapacityConstraint_rpt": [ - [ - "utopia", - 1990, - "E31" - ], - [ - "utopia", - 2000, - "E31" - ], - [ - "utopia", - 2010, - "E31" - ], - [ - "utopia", - 1990, - "RHE" - ], - [ - "utopia", - 1990, - "TXD" - ], - [ - "utopia", - 2000, - "TXD" - ], - [ - "utopia", - 2010, - "TXD" - ] - ], - "MinNewCapacityConstraint_rpt": [], - "MaxNewCapacityConstraint_rpt": [], - "MaxResourceConstraint_rt": [], - "MaxActivityConstraint_rpt": [], - "MinActivityConstraint_rpt": [], - "MinAnnualCapacityFactorConstraint_rpto": [], - "MaxAnnualCapacityFactorConstraint_rpto": [], - "EmissionLimitConstraint_rpe": [], - "EmissionActivity_reitvo": [ - [ - "utopia", - "nox", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - "co2", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - "co2", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - "co2", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - "co2", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - "co2", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - "co2", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - "co2", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - "co2", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - "co2", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - "co2", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - "nox", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - "co2", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - "nox", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - "co2", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - "nox", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - "nox", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - "co2", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - "nox", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - "nox", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - "co2", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - "nox", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - "co2", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - "nox", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - "nox", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - "co2", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - "nox", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - "nox", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - "co2", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - "nox", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - "nox", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - "co2", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - "co2", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - "co2", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - "nox", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - "nox", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - "co2", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - "co2", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - "co2", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - "nox", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - "nox", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - "nox", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - "nox", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - "co2", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - "co2", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - "nox", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - "nox", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - "nox", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - "nox", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - "nox", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - "nox", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - "co2", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - "co2", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - "co2", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - "co2", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - "co2", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - "co2", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - "nox", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - "co2", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - "nox", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - "co2", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - "co2", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - "co2", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - "nox", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - "co2", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - "nox", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - "co2", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - "co2", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - "co2", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - "co2", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - "co2", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - "co2", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - "nox", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - "nox", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - "co2", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - "nox", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - "co2", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - "nox", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - "nox", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - "co2", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - "co2", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - "nox", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - "co2", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - "nox", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - "co2", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - "nox", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - "co2", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - "nox", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - "nox", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - "co2", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - "co2", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - "co2", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - "nox", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - "co2", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - "co2", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - "nox", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - "co2", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - "nox", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - "co2", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - "nox", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - "co2", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - "nox", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - "co2", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - "co2", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - "co2", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - "nox", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - "nox", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - "co2", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - "nox", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - "co2", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - "nox", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - "co2", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - "nox", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - "nox", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - "co2", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - "nox", - "HYD", - "E31", - 1990, - "ELC" - ] - ], - "MinActivityGroup_rpg": [], - "MaxActivityGroup_rpg": [], - "MinCapacityGroupConstraint_rpg": [], - "MaxCapacityGroupConstraint_rpg": [], - "MinNewCapacityGroupConstraint_rpg": [], - "MaxNewCapacityGroupConstraint_rpg": [], - "GroupShareIndices": [], - "MinCapacityShareConstraint_rptg": [], - "MaxCapacityShareConstraint_rptg": [], - "MinActivityShareConstraint_rptg": [], - "MaxActivityShareConstraint_rptg": [], - "MinNewCapacityShareConstraint_rptg": [], - "MaxNewCapacityShareConstraint_rptg": [], - "StorageInit_rtv": [ - [ - "utopia", - "E51", - 1990 - ], - [ - "utopia", - "E51", - 1980 - ], - [ - "utopia", - "E51", - 2010 - ], - [ - "utopia", - "E51", - 2000 - ] - ], - "FlowVar_rpsditvo": [ - [ - "utopia", - 2010, - "summer", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "night", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2010, - "summer", - "day", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2000, - "summer", - "night", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "night", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "summer", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2000, - "inter", - "day", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "night", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "day", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "summer", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "summer", - "day", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2000, - "summer", - "day", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "summer", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "night", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "day", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2000, - "winter", - "night", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "night", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2000, - "summer", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "OIL", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "night", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "day", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "day", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2010, - "summer", - "night", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HCO", - "E01", - 1960, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 1990, - "summer", - "night", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "TXE", - 2010, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "RL1", - 2000, - "RL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "summer", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "winter", - "night", - "OIL", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "TXD", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "inter", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "E70", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 1990, - "inter", - "day", - "GSL", - "TXG", - 1980, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "GSL", - "TXG", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "RHE", - 2000, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HYD", - "E31", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2010, - "winter", - "day", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "RHO", - 1980, - "RH" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "GSL", - "TXG", - 2010, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HCO", - "E01", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "FEQ", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "OIL", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "RL1", - 2010, - "RL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HCO", - "E01", - 1970, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ethos", - "IMPOIL1", - 1990, - "OIL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "summer", - "night", - "GSL", - "TXG", - 1970, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "RL1", - 1980, - "RL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "day", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "TXE", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "TXD", - 1990, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "E70", - 1960, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ethos", - "IMPGSL1", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "OIL", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL", - "TXD", - 2000, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "GSL", - "TXG", - 1990, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "RHO", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HCO", - "E01", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "E70", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "URN", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 1990, - "summer", - "day", - "FEQ", - "E21", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "URN", - "E21", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "RHO", - 1970, - "RH" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL", - "E70", - 1970, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL", - "TXD", - 2010, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ethos", - "IMPHCO1", - 1990, - "HCO" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL", - "TXD", - 1980, - "TX" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HCO", - "E01", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ethos", - "IMPDSL1", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HCO", - "E01", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ethos", - "IMPURN1", - 1990, - "URN" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL", - "E70", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ethos", - "IMPFEQ", - 1990, - "FEQ" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "RHE", - 2010, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "RHO", - 2010, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL", - "E70", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "URN", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2010, - "summer", - "night", - "FEQ", - "E21", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "RHE", - 1990, - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "summer", - "day", - "OIL", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HYD", - "E31", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL", - "RHO", - 2000, - "RH" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HYD", - "E31", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "RL1", - 1990, - "RL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ethos", - "IMPHYD", - 1990, - "HYD" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HYD", - "E31", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "TXE", - 2000, - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "OIL", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "RHE", - 2000, - "RH" - ] - ], - "FlowVarAnnual_rpitvo": [], - "FlexVar_rpsditvo": [], - "FlexVarAnnual_rpitvo": [], - "CurtailmentVar_rpsditvo": [], - "FlowInStorage_rpsditvo": [ - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC", - "E51", - 2010, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC", - "E51", - 1990, - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC", - "E51", - 2000, - "ELC" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC", - "E51", - 1980, - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC", - "E51", - 2000, - "ELC" - ] - ], - "StorageLevel_rpsdtv": [ - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 2000 - ] - ], - "CapacityVar_rptv": [ - [ - "utopia", - 2010, - "E51", - 2010 - ], - [ - "utopia", - 2000, - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2010 - ], - [ - "utopia", - 2010, - "E70", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 1990 - ], - [ - "utopia", - 1990, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "RL1", - 2000 - ], - [ - "utopia", - 2000, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1990 - ], - [ - "utopia", - 2010, - "RHE", - 2000 - ], - [ - "utopia", - 2000, - "RHE", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "E70", - 2010 - ], - [ - "utopia", - 2010, - "E31", - 1980 - ], - [ - "utopia", - 2010, - "E51", - 1980 - ], - [ - "utopia", - 2000, - "E51", - 1980 - ], - [ - "utopia", - 1990, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "E31", - 2010 - ], - [ - "utopia", - 2010, - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1980 - ], - [ - "utopia", - 2000, - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1980 - ], - [ - "utopia", - 2010, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 2000 - ], - [ - "utopia", - 2000, - "E51", - 2000 - ], - [ - "utopia", - 2010, - "E21", - 2010 - ], - [ - "utopia", - 2010, - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "TXG", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1960 - ], - [ - "utopia", - 1990, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 1980 - ], - [ - "utopia", - 2000, - "E70", - 1980 - ], - [ - "utopia", - 1990, - "E01", - 1970 - ], - [ - "utopia", - 1990, - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "TXE", - 2010 - ], - [ - "utopia", - 2000, - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "E70", - 1980 - ], - [ - "utopia", - 2010, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "RHO", - 2000 - ], - [ - "utopia", - 1990, - "E31", - 1980 - ], - [ - "utopia", - 1990, - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "E31", - 2000 - ], - [ - "utopia", - 2000, - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "E01", - 1970 - ], - [ - "utopia", - 2000, - "SRE", - 1990 - ], - [ - "utopia", - 2000, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "E01", - 2000 - ], - [ - "utopia", - 2010, - "E21", - 2000 - ], - [ - "utopia", - 2000, - "E21", - 2000 - ], - [ - "utopia", - 2010, - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "SRE", - 2010 - ], - [ - "utopia", - 2000, - "E70", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1960 - ], - [ - "utopia", - 2010, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "E70", - 2000 - ], - [ - "utopia", - 1990, - "TXG", - 1970 - ], - [ - "utopia", - 1990, - "E01", - 1990 - ], - [ - "utopia", - 1990, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E70", - 1970 - ], - [ - "utopia", - 2010, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "RL1", - 1980 - ], - [ - "utopia", - 2000, - "E31", - 2000 - ], - [ - "utopia", - 2010, - "RHE", - 2010 - ], - [ - "utopia", - 1990, - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "E31", - 1990 - ], - [ - "utopia", - 2010, - "E51", - 1990 - ], - [ - "utopia", - 1990, - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "TXD", - 1970 - ], - [ - "utopia", - 2010, - "E01", - 1990 - ], - [ - "utopia", - 2010, - "E21", - 1990 - ], - [ - "utopia", - 2000, - "E01", - 1990 - ], - [ - "utopia", - 2000, - "E21", - 1990 - ], - [ - "utopia", - 1990, - "E31", - 1990 - ], - [ - "utopia", - 1990, - "E51", - 1990 - ], - [ - "utopia", - 2010, - "SRE", - 2000 - ] - ], - "NewCapacityVar_rtv": [ - [ - "utopia", - "E70", - 1970 - ], - [ - "utopia", - "E01", - 1990 - ], - [ - "utopia", - "E21", - 1990 - ], - [ - "utopia", - "RHO", - 2010 - ], - [ - "utopia", - "TXG", - 2000 - ], - [ - "utopia", - "E31", - 1980 - ], - [ - "utopia", - "RHE", - 1990 - ], - [ - "utopia", - "E51", - 1980 - ], - [ - "utopia", - "RHO", - 1970 - ], - [ - "utopia", - "SRE", - 2000 - ], - [ - "utopia", - "RL1", - 1980 - ], - [ - "utopia", - "TXD", - 1990 - ], - [ - "utopia", - "E70", - 2000 - ], - [ - "utopia", - "TXE", - 2000 - ], - [ - "utopia", - "E70", - 1960 - ], - [ - "utopia", - "E01", - 1980 - ], - [ - "utopia", - "E31", - 2010 - ], - [ - "utopia", - "E51", - 2010 - ], - [ - "utopia", - "RHO", - 2000 - ], - [ - "utopia", - "RL1", - 2010 - ], - [ - "utopia", - "TXG", - 1990 - ], - [ - "utopia", - "SRE", - 1990 - ], - [ - "utopia", - "E01", - 2010 - ], - [ - "utopia", - "E70", - 1990 - ], - [ - "utopia", - "E21", - 2010 - ], - [ - "utopia", - "TXE", - 1990 - ], - [ - "utopia", - "TXD", - 1980 - ], - [ - "utopia", - "E01", - 1970 - ], - [ - "utopia", - "E31", - 2000 - ], - [ - "utopia", - "RHE", - 2010 - ], - [ - "utopia", - "RHO", - 1990 - ], - [ - "utopia", - "E51", - 2000 - ], - [ - "utopia", - "RL1", - 2000 - ], - [ - "utopia", - "TXD", - 2010 - ], - [ - "utopia", - "TXG", - 1980 - ], - [ - "utopia", - "E70", - 1980 - ], - [ - "utopia", - "E01", - 2000 - ], - [ - "utopia", - "E21", - 2000 - ], - [ - "utopia", - "TXD", - 1970 - ], - [ - "utopia", - "E31", - 1990 - ], - [ - "utopia", - "TXG", - 2010 - ], - [ - "utopia", - "E01", - 1960 - ], - [ - "utopia", - "RHE", - 2000 - ], - [ - "utopia", - "E51", - 1990 - ], - [ - "utopia", - "E70", - 2010 - ], - [ - "utopia", - "RHO", - 1980 - ], - [ - "utopia", - "RL1", - 1990 - ], - [ - "utopia", - "SRE", - 2010 - ], - [ - "utopia", - "TXD", - 2000 - ], - [ - "utopia", - "TXE", - 2010 - ], - [ - "utopia", - "TXG", - 1970 - ] - ], - "RetiredCapacityVar_rptv": [], - "CapacityAvailableVar_rpt": [ - [ - "utopia", - 2010, - "E51" - ], - [ - "utopia", - 1990, - "E51" - ], - [ - "utopia", - 2000, - "TXG" - ], - [ - "utopia", - 2010, - "RHE" - ], - [ - "utopia", - 2010, - "E31" - ], - [ - "utopia", - 2000, - "SRE" - ], - [ - "utopia", - 1990, - "RHE" - ], - [ - "utopia", - 2010, - "TXD" - ], - [ - "utopia", - 1990, - "E31" - ], - [ - "utopia", - 2000, - "E01" - ], - [ - "utopia", - 1990, - "TXD" - ], - [ - "utopia", - 2010, - "RHO" - ], - [ - "utopia", - 1990, - "RHO" - ], - [ - "utopia", - 2000, - "E21" - ], - [ - "utopia", - 2010, - "E70" - ], - [ - "utopia", - 1990, - "E70" - ], - [ - "utopia", - 2000, - "RL1" - ], - [ - "utopia", - 2010, - "TXE" - ], - [ - "utopia", - 1990, - "TXE" - ], - [ - "utopia", - 2000, - "E51" - ], - [ - "utopia", - 2010, - "TXG" - ], - [ - "utopia", - 1990, - "TXG" - ], - [ - "utopia", - 2000, - "RHE" - ], - [ - "utopia", - 2010, - "SRE" - ], - [ - "utopia", - 2000, - "E31" - ], - [ - "utopia", - 2010, - "E01" - ], - [ - "utopia", - 1990, - "SRE" - ], - [ - "utopia", - 2000, - "TXD" - ], - [ - "utopia", - 1990, - "E01" - ], - [ - "utopia", - 2000, - "RHO" - ], - [ - "utopia", - 2010, - "E21" - ], - [ - "utopia", - 1990, - "E21" - ], - [ - "utopia", - 2000, - "E70" - ], - [ - "utopia", - 2010, - "RL1" - ], - [ - "utopia", - 1990, - "RL1" - ], - [ - "utopia", - 2000, - "TXE" - ] - ], - "CapacityConstraint_rpsdtv": [ - [ - "utopia", - 2010, - "summer", - "day", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXG", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E70", - 1960 - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHE", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E70", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXG", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E70", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "RHE", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E70", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E70", - 1960 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHE", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E70", - 1960 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXG", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E70", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E70", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E70", - 1970 - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXG", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "night", - "RL1", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHE", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E70", - 1970 - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "RL1", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "day", - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXG", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E70", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "day", - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXG", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXE", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E21", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXE", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "RL1", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXD", - 1970 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "RL1", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXG", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "RL1", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E70", - 1970 - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXG", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXG", - 1970 - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXE", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E21", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "RL1", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E31", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHE", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E70", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 2010 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHO", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXD", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXD", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXD", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E70", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXG", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E70", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHO", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHO", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E70", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "night", - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHO", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXD", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E70", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXD", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXE", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E70", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E70", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 2010 - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXD", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHO", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E21", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E70", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXG", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXD", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHO", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E70", - 1970 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E70", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXG", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "SRE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1960 - ], - [ - "utopia", - 1990, - "inter", - "day", - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E21", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E70", - 1970 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHO", - 1970 - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXD", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E70", - 1970 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E70", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXD", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXG", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E70", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E70", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1960 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E21", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E70", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E70", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXD", - 1970 - ], - [ - "utopia", - 1990, - "inter", - "night", - "RL1", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E70", - 1970 - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXG", - 1970 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E70", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXG", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E70", - 2010 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXG", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E70", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1960 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXG", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHO", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "day", - "TXG", - 1970 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E70", - 1960 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "TXG", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E70", - 1960 - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "RL1", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E70", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHE", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXG", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E70", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXG", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "SRE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E70", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHE", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E70", - 1960 - ], - [ - "utopia", - 2010, - "inter", - "day", - "RHE", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E70", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXG", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "RL1", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHE", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHE", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "RL1", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXG", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E70", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHE", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "RL1", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXE", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "TXD", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "RHE", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "RL1", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXD", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "day", - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXG", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "RL1", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHO", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "RHO", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXD", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "RHO", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXE", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "TXD", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHO", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXD", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "day", - "RL1", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXD", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXE", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXG", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXD", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXE", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "day", - "TXD", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXD", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXE", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "day", - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHE", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "TXE", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXD", - 1970 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXD", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "RHO", - 1970 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "RL1", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXD", - 1970 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXE", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1960 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E70", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXG", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXE", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "RHO", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXD", - 1970 - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "SRE", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXD", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXE", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "TXD", - 1970 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHO", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1960 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHO", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E70", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E70", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E21", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXD", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1960 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXD", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHO", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXG", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E70", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXD", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXD", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E70", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXE", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHE", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXD", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E70", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "TXE", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXG", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "SRE", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E21", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E70", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHO", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E70", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E70", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXE", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHO", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "SRE", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXG", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E70", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E70", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E70", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXE", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXD", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHO", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E70", - 2000 - ] - ], - "CapacityAnnualConstraint_rptv": [], - "DemandConstraint_rpsdc": [ - [ - "utopia", - 1990, - "winter", - "night", - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "RL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "RL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RH" - ], - [ - "utopia", - 2000, - "summer", - "day", - "RH" - ], - [ - "utopia", - 1990, - "summer", - "day", - "RL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "RL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "RL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RH" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "RH" - ], - [ - "utopia", - 2010, - "inter", - "day", - "TX" - ], - [ - "utopia", - 1990, - "inter", - "night", - "RL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "RH" - ], - [ - "utopia", - 2000, - "inter", - "night", - "RH" - ], - [ - "utopia", - 1990, - "summer", - "night", - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "TX" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TX" - ], - [ - "utopia", - 2000, - "inter", - "day", - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "RH" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TX" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TX" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TX" - ], - [ - "utopia", - 2010, - "winter", - "night", - "TX" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TX" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "RL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "RH" - ], - [ - "utopia", - 1990, - "inter", - "day", - "RL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "RH" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RH" - ], - [ - "utopia", - 2000, - "winter", - "day", - "RH" - ], - [ - "utopia", - 2000, - "inter", - "day", - "RH" - ], - [ - "utopia", - 2000, - "winter", - "night", - "RL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "RL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RH" - ], - [ - "utopia", - 2000, - "summer", - "night", - "RH" - ], - [ - "utopia", - 2010, - "summer", - "day", - "TX" - ], - [ - "utopia", - 1990, - "summer", - "night", - "RL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "RH" - ], - [ - "utopia", - 2010, - "winter", - "day", - "TX" - ], - [ - "utopia", - 1990, - "winter", - "night", - "RL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TX" - ], - [ - "utopia", - 1990, - "inter", - "night", - "RH" - ], - [ - "utopia", - 1990, - "inter", - "day", - "TX" - ], - [ - "utopia", - 2010, - "inter", - "night", - "TX" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TX" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TX" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TX" - ], - [ - "utopia", - 2010, - "inter", - "day", - "RL" - ] - ], - "DemandActivityConstraint_rpsdtv_dem_s0d0": [ - [ - "utopia", - 1990, - "inter", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHE", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHE", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHE", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHE", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHE", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHE", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHE", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "RHO", - 1970, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHO", - 1970, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHO", - 1970, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHO", - 1970, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHO", - 1970, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHO", - 1980, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHO", - 1990, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHO", - 2000, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "RHO", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "RHO", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "RHO", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "RHO", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "RHO", - 2010, - "RH", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXD", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXD", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXD", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXD", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXD", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXD", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXD", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXD", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXD", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXD", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXD", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXD", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXD", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXE", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXE", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXE", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXE", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXE", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXE", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXE", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXG", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXG", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXG", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXG", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXG", - 1970, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXG", - 1980, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "night", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 1990, - "winter", - "night", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXG", - 1990, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "inter", - "night", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "day", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2000, - "winter", - "night", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXG", - 2000, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night", - "TXG", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "day", - "TXG", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "summer", - "night", - "TXG", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "day", - "TXG", - 2010, - "TX", - "inter", - "day" - ], - [ - "utopia", - 2010, - "winter", - "night", - "TXG", - 2010, - "TX", - "inter", - "day" - ] - ], - "CommodityBalanceConstraint_rpsdc": [ - [ - "utopia", - 2010, - "winter", - "day", - "FEQ" - ], - [ - "utopia", - 2000, - "winter", - "day", - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "URN" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HYD" - ], - [ - "utopia", - 1990, - "summer", - "day", - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "FEQ" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HYD" - ], - [ - "utopia", - 2010, - "winter", - "night", - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HCO" - ], - [ - "utopia", - 2010, - "winter", - "night", - "OIL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "URN" - ], - [ - "utopia", - 2000, - "winter", - "day", - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "day", - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "URN" - ], - [ - "utopia", - 2000, - "summer", - "day", - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "FEQ" - ], - [ - "utopia", - 2000, - "inter", - "day", - "FEQ" - ], - [ - "utopia", - 1990, - "summer", - "day", - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HYD" - ], - [ - "utopia", - 1990, - "summer", - "day", - "FEQ" - ], - [ - "utopia", - 2010, - "winter", - "night", - "FEQ" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HYD" - ], - [ - "utopia", - 2000, - "winter", - "night", - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "FEQ" - ], - [ - "utopia", - 2000, - "summer", - "day", - "OIL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "URN" - ], - [ - "utopia", - 1990, - "inter", - "day", - "OIL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "URN" - ], - [ - "utopia", - 1990, - "winter", - "night", - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "FEQ" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HCO" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HYD" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HYD" - ], - [ - "utopia", - 2000, - "inter", - "day", - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HYD" - ], - [ - "utopia", - 2000, - "winter", - "night", - "FEQ" - ], - [ - "utopia", - 2000, - "inter", - "night", - "FEQ" - ], - [ - "utopia", - 1990, - "summer", - "night", - "ELC" - ], - [ - "utopia", - 1990, - "inter", - "day", - "HCO" - ], - [ - "utopia", - 1990, - "summer", - "night", - "FEQ" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HYD" - ], - [ - "utopia", - 2010, - "summer", - "day", - "URN" - ], - [ - "utopia", - 1990, - "inter", - "night", - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "OIL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "OIL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "OIL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "URN" - ], - [ - "utopia", - 2010, - "inter", - "day", - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "OIL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "HCO" - ], - [ - "utopia", - 2000, - "inter", - "day", - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "night", - "URN" - ], - [ - "utopia", - 1990, - "winter", - "day", - "ELC" - ], - [ - "utopia", - 2000, - "summer", - "night", - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "day", - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "day", - "FEQ" - ], - [ - "utopia", - 2000, - "summer", - "night", - "FEQ" - ], - [ - "utopia", - 2010, - "winter", - "night", - "HCO" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HYD" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HYD" - ], - [ - "utopia", - 2000, - "inter", - "night", - "DSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HYD" - ], - [ - "utopia", - 2010, - "winter", - "day", - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "HCO" - ], - [ - "utopia", - 2010, - "summer", - "night", - "URN" - ], - [ - "utopia", - 2010, - "inter", - "night", - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "FEQ" - ], - [ - "utopia", - 2000, - "winter", - "night", - "OIL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "OIL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HYD" - ], - [ - "utopia", - 2010, - "inter", - "day", - "DSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "OIL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "ELC" - ], - [ - "utopia", - 2000, - "inter", - "night", - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HYD" - ], - [ - "utopia", - 2000, - "winter", - "night", - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "day", - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "night", - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "night", - "FEQ" - ], - [ - "utopia", - 2010, - "inter", - "day", - "OIL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "URN" - ], - [ - "utopia", - 1990, - "winter", - "day", - "OIL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "OIL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HYD" - ], - [ - "utopia", - 2010, - "summer", - "night", - "ELC" - ], - [ - "utopia", - 2010, - "inter", - "day", - "FEQ" - ], - [ - "utopia", - 2010, - "summer", - "night", - "FEQ" - ], - [ - "utopia", - 2010, - "inter", - "night", - "HYD" - ], - [ - "utopia", - 2010, - "inter", - "night", - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "HCO" - ], - [ - "utopia", - 2000, - "summer", - "night", - "HCO" - ], - [ - "utopia", - 2010, - "summer", - "day", - "OIL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HYD" - ], - [ - "utopia", - 1990, - "winter", - "night", - "DSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "URN" - ], - [ - "utopia", - 1990, - "inter", - "day", - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "URN" - ], - [ - "utopia", - 2010, - "winter", - "day", - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "day", - "HCO" - ], - [ - "utopia", - 1990, - "winter", - "night", - "OIL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HYD" - ], - [ - "utopia", - 2010, - "summer", - "night", - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "FEQ" - ], - [ - "utopia", - 1990, - "winter", - "night", - "HCO" - ], - [ - "utopia", - 2000, - "winter", - "day", - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "URN" - ], - [ - "utopia", - 2000, - "inter", - "day", - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "OIL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "URN" - ], - [ - "utopia", - 1990, - "summer", - "night", - "URN" - ], - [ - "utopia", - 1990, - "inter", - "night", - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "HYD" - ], - [ - "utopia", - 2010, - "winter", - "day", - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "HCO" - ], - [ - "utopia", - 2010, - "winter", - "night", - "ELC" - ], - [ - "utopia", - 2010, - "summer", - "night", - "HCO" - ], - [ - "utopia", - 2010, - "winter", - "day", - "OIL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "URN" - ], - [ - "utopia", - 2000, - "inter", - "day", - "URN" - ], - [ - "utopia", - 2010, - "inter", - "night", - "OIL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "URN" - ], - [ - "utopia", - 2000, - "summer", - "day", - "ELC" - ], - [ - "utopia", - 1990, - "summer", - "night", - "GSL" - ] - ], - "CommodityBalanceAnnualConstraint_rpc": [], - "BaseloadDiurnalConstraint_rpsdtv": [ - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E21", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1960 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E21", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E31", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1960 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E21", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1960 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1960 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E21", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1960 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 2010 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E21", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E31", - 2010 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E31", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E21", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E31", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E01", - 1970 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E01", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E31", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E21", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E21", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E01", - 1970 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E01", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E21", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E31", - 2000 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E01", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E01", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E31", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E01", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E21", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E31", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E31", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E01", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E01", - 1960 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E21", - 2010 - ] - ], - "RegionalExchangeCapacityConstraint_rrptv": [], - "StorageConstraints_rpsdtv": [ - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 1990, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 2010 - ], - [ - "utopia", - 1990, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E51", - 1990 - ], - [ - "utopia", - 2010, - "summer", - "day", - "E51", - 2000 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "summer", - "night", - "E51", - 2010 - ], - [ - "utopia", - 1990, - "winter", - "night", - "E51", - 1990 - ], - [ - "utopia", - 2000, - "winter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "winter", - "night", - "E51", - 2000 - ], - [ - "utopia", - 2000, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 1990, - "winter", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2010, - "inter", - "day", - "E51", - 2010 - ], - [ - "utopia", - 2010, - "inter", - "night", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "day", - "E51", - 2000 - ], - [ - "utopia", - 1990, - "summer", - "day", - "E51", - 1980 - ], - [ - "utopia", - 2000, - "summer", - "night", - "E51", - 2000 - ] - ], - "StorageInitConstraint_rtv": [], - "RampConstraintDay_rpsdtv": [], - "RampConstraintPeriod_rptv": [], - "ReserveMargin_rpsd": [ - [ - "utopia", - 2000, - "inter", - "night" - ], - [ - "utopia", - 2000, - "winter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "night" - ], - [ - "utopia", - 1990, - "winter", - "night" - ], - [ - "utopia", - 1990, - "inter", - "day" - ], - [ - "utopia", - 1990, - "summer", - "day" - ], - [ - "utopia", - 2000, - "inter", - "day" - ], - [ - "utopia", - 2000, - "summer", - "day" - ], - [ - "utopia", - 1990, - "winter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "day" - ], - [ - "utopia", - 2010, - "inter", - "night" - ], - [ - "utopia", - 2010, - "summer", - "night" - ], - [ - "utopia", - 2010, - "winter", - "night" - ], - [ - "utopia", - 2000, - "winter", - "night" - ], - [ - "utopia", - 2010, - "summer", - "day" - ], - [ - "utopia", - 1990, - "inter", - "night" - ], - [ - "utopia", - 1990, - "summer", - "night" - ], - [ - "utopia", - 2010, - "winter", - "day" - ] - ], - "GrowthRateMaxConstraint_rtv": [], - "TechInputSplitConstraint_rpsditv": [], - "TechInputSplitAnnualConstraint_rpitv": [], - "TechInputSplitAverageConstraint_rpitv": [], - "TechOutputSplitConstraint_rpsdtvo": [ - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "inter", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 1990, - "winter", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "inter", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 2010, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 1990, - "inter", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 1990, - "winter", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "inter", - "night", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2000, - "summer", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "night", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "day", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2010, - "winter", - "night", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "summer", - "night", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "summer", - "day", - "SRE", - 2000, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 2010, - "DSL" - ], - [ - "utopia", - 2010, - "inter", - "day", - "SRE", - 1990, - "DSL" - ], - [ - "utopia", - 2000, - "winter", - "day", - "SRE", - 2000, - "GSL" - ], - [ - "utopia", - 1990, - "summer", - "day", - "SRE", - 1990, - "GSL" - ], - [ - "utopia", - 2010, - "summer", - "day", - "SRE", - 1990, - "GSL" - ] - ], - "TechOutputSplitAnnualConstraint_rptvo": [], - "LinkedEmissionsTechConstraint_rpsdtve": [] -} \ No newline at end of file + "annual_commodity_balance_constraint_rpc": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "annual_retirement_var_rptv": "e2487c314e1404726bdfbc8ba061d507f18882c658e81ea27c4369f0d4a892fd", + "baseload_diurnal_constraint_rpsdtv": "da0dafcd9aef41d231282c85362d767636d8ba3815ea809e0b7eeb326233fbf9", + "capacity_annual_constraint_rptv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "capacity_available_var_rpt": "96e94bf0459e843802e1ea858c807be22b09354409fbd93a6b1a91d4438a7727", + "capacity_constraint_rpsdtv": "67324b45087c277b42b4664f23c93bd75820eb6a0ed7298afe6b185165b97a25", + "capacity_factor_rsdt": "1e5fae1e3999dbea84cc6d39e3ad91e95a2064c96f3569ea2dfef041d6011c5a", + "capacity_var_rptv": "972a1a82e59135297f51ef6ca2ce5760a8e0fcb67e064b270b14cbf5e4661831", + "commodity_all": "25fb43b483a85d6e4b4d8251a272a4c78fb9d02a65af63a9c044d64211661563", + "commodity_annual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "commodity_balance_constraint_rpsdc": "79a5589e5d9b834ff2132fd4ce354a16e3a17529415245da9b9d8b13814ad382", + "commodity_carrier": "23deb826164f35ff8adf73b9c64a4edb3efeeb51b683e14ea40f0771eba42eb8", + "commodity_demand": "8d35b293b5cef0a6d0c6552029082eaec587db0bc5813783f24ab1aafe7491c8", + "commodity_emissions": "cc2dd294fd87cfc0324fcd1d62eb647ff83bd89d7b5abd5dd5374debea96d91b", + "commodity_flex": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "commodity_physical": "3aab3aa4622d1545fe0f3cb8bd1a01fb9918632cff7186cfd49fa8575b73b2f3", + "commodity_sink": "8d35b293b5cef0a6d0c6552029082eaec587db0bc5813783f24ab1aafe7491c8", + "commodity_source": "9679c6d5ef1c2949d7c4b6685ba2fd4195282539b3f24650247f40be62eecf3a", + "commodity_waste": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "cost_emission_rpe": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "cost_fixed_rptv": "972a1a82e59135297f51ef6ca2ce5760a8e0fcb67e064b270b14cbf5e4661831", + "cost_invest_rtv": "651a10edbb8297f51b380318385c9f1ae824a73216318e1c9aea78fc9f371a14", + "cost_variable_rptv": "37ad263f78986f4d38c63278a0d1a5a85b3eb8b833e8bfabbbea254eaf3a3efb", + "curtailment_var_rpsditvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "demand_activity_constraint_rpsdtv_dem": "264109f8bf88d194fdfe03d2e18654ffe0ba11bdf8430c50b39c03bdc43a53e1", + "demand_constraint_rpc": "c1654f787e15af2031bcea2dcebde3830df52ea0f798e854ba784e6f1223b301", + "emission_activity_reitvo": "8b4c8f2cf9b46db814898d140f48dc75e50c34bcf71a55adee31afa67cea1eba", + "flex_var_annual_rpitvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "flex_var_rpsditvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "flow_in_storage_rpsditvo": "99eb864a26adcb6633c95462649bca3ef7096f67682702915d7769b54b5de386", + "flow_var_annual_rpitvo": "4053866a304af0a6b9a1d293ddaf358dfee5b170dcb01d41e6bc52437908a98a", + "flow_var_rpsditvo": "350739a59b14969da094cf1c95524134b9c9acd0989710456a8dcc6596d0a401", + "lifetime_process_rtv": "2cfc288b15f25957dfc70f6396d97ad655ecbed91c5a11582329749f1fb3dbd7", + "limit_activity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_activity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_annual_capacity_factor_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_annual_capacity_factor_constraint_rtvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_capacity_constraint_rpt": "5a43de033187da68c612c5bcef6a76edb6ab9806464b6d865e46d289ccbbd815", + "limit_capacity_share_constraint_rpgg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_degrowth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_emission_constraint_rpe": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_growth_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_growth_new_capacity_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_growth_new_capacity_delta_constraint_rpt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_new_capacity_constraint_rtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_new_capacity_share_constraint_rggv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_resource_constraint_rt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_seasonal_capacity_factor_constraint_rpst": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_seasonal_capacity_factor_constraint_rst": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_storage_fraction_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_storage_fraction_param_rsdt": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_annual_constraint_rpitv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_average_constraint_rpitv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_input_split_constraint_rpsditv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_output_split_annual_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_output_split_average_constraint_rptvo": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "limit_tech_output_split_constraint_rpsdtvo": "f8e7d420020c7bc28dc29fb313fae2027f40143e43bc8c39347060d4e63a087d", + "linked_emissions_tech_constraint_rpsdtve": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "loan_lifetime_process_rtv": "2cfc288b15f25957dfc70f6396d97ad655ecbed91c5a11582329749f1fb3dbd7", + "new_capacity_var_rtv": "d4f1cc8b432075001befddab648d54d1f82a29099236948845393a193b6add5b", + "operator": "74d830836f1399fb336a0432dde7d7bd36cffa3ff76b1c42d7945350cfb9bf91", + "ordered_season_sequential": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "process_life_frac_rptv": "37ad263f78986f4d38c63278a0d1a5a85b3eb8b833e8bfabbbea254eaf3a3efb", + "ramp_down_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "ramp_down_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "ramp_up_day_constraint_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "ramp_up_season_constraint_rpsstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "regional_exchange_capacity_constraint_rrptv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "regional_global_indices": "ce00905893c23bc59c15dcf61d7e261c2e97ab1fc298ed0e0ccb1344f1cace37", + "regional_indices": "ce00905893c23bc59c15dcf61d7e261c2e97ab1fc298ed0e0ccb1344f1cace37", + "regions": "ce00905893c23bc59c15dcf61d7e261c2e97ab1fc298ed0e0ccb1344f1cace37", + "renewable_portfolio_standard_constraint_rpg": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "reserve_margin_method": "7869283c0d14273f720716309207a8f0c24606d03c679d6b68e656ed8d86241d", + "reserve_margin_rpsd": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "retired_capacity_var_rptv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "seasonal_storage_constraints_rpsdtv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "seasonal_storage_level_rpstv": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "storage_constraints_rpsdtv": "fe4c04240f67fbe31572a29c9c1d3bef9dfb10c1090d311797530deeae8393e9", + "storage_init_rpstv": "86f4e3cf182540836f499c369cbac966e81c807d55a4f70d9b7a1473276c07b4", + "storage_level_rpsdtv": "fe4c04240f67fbe31572a29c9c1d3bef9dfb10c1090d311797530deeae8393e9", + "tech_all": "5c321f60e5d16e60c5063b83d59ac9e184ab78e7fc469e41b21fd8aea83f600c", + "tech_annual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_baseload": "e7c3d5843adeb51ccb0774118e8e5085f4a01c787a32b5ed34cba220efd9121a", + "tech_curtailment": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_demand": "481b9363bcd61f44255efcc7bb7cc4112bef3c969435024dd5e2659e3c8dbfdb", + "tech_downramping": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_exchange": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_exist": "b7d598371119e425be7beedbdde0525c88b7aa0018887e81d2cccc6bb0385ffa", + "tech_flex": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_group_members": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_group_names": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_or_group": "5c321f60e5d16e60c5063b83d59ac9e184ab78e7fc469e41b21fd8aea83f600c", + "tech_production": "5c321f60e5d16e60c5063b83d59ac9e184ab78e7fc469e41b21fd8aea83f600c", + "tech_reserve": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_retirement": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_seasonal_storage": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_storage": "245737c06f3e838e63da08a47a7d2a8605340ea5cad86397e1bb66461a574358", + "tech_uncap": "b2c8c3f94a51bb7ab55ad7b534cb7aba64a506678c109abb4141f4d2e84bf431", + "tech_upramping": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "tech_with_capacity": "459b04924b80d93e0061564b6c1f1b8d39543d697b81150413d7bc86c3003e33", + "time_exist": "c9529ee6b8874a867c196738619704c725ba6fbf61a03b9739090422b8904de2", + "time_future": "8c9cb7410e2199a84d1b5607c46429f0c6bc67a2b9e322a48434d83a2e81e007", + "time_manual": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "time_of_day": "9f58a9fbc74271e641f4b7624b22daca061195a3d85b58d270fb8590937007b1", + "time_optimize": "b36c1cbd59292ec15b8ff1504ec03c5657665e2b3350e224dfd31364cb393ffa", + "time_season": "df37d485468915bce6cd7fa627702773c5d6c3dee47cffdc6360d31dd1dd28eb", + "time_season_sequential": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945", + "time_sequencing": "91f69c8abab9959c1f8c90f5aaa56db29bccc67e37d12673ab41c54e4179d7ca", + "vintage_all": "b96a0eef5579c688203bc71c6700b5a74d6d9306ee443980e67beef17ac4e8f0", + "vintage_exist": "c9529ee6b8874a867c196738619704c725ba6fbf61a03b9739090422b8904de2", + "vintage_optimize": "b36c1cbd59292ec15b8ff1504ec03c5657665e2b3350e224dfd31364cb393ffa" +} diff --git a/tests/testing_data/utopia_stochastic_config.toml b/tests/testing_data/utopia_stochastic_config.toml new file mode 100644 index 000000000..a3b05f554 --- /dev/null +++ b/tests/testing_data/utopia_stochastic_config.toml @@ -0,0 +1,19 @@ +[scenarios] +low_coal = { probability = 0.5 } +high_coal = { probability = 0.5 } + +[[perturbations]] +scenario = "low_coal" +table = "cost_variable" +column = "cost" +filter = { tech = "IMPHCO1" } +action = "multiply" +value = 0.5 + +[[perturbations]] +scenario = "high_coal" +table = "cost_variable" +column = "cost" +filter = { tech = "IMPHCO1" } +action = "multiply" +value = 1.5 diff --git a/tests/testing_outputs/.gitignore b/tests/testing_outputs/.gitignore index 39fbe462d..4cfa99f77 100644 --- a/tests/testing_outputs/.gitignore +++ b/tests/testing_outputs/.gitignore @@ -1,5 +1,4 @@ -# ignore the databases here. They are just used to catch output +/* -*.sqlite -*.dat -*.xlsx \ No newline at end of file + +!README.txt diff --git a/tests/utilities/capture_set_sizes_for_cache.py b/tests/utilities/capture_set_sizes_for_cache.py index 9394ec758..817e7648f 100644 --- a/tests/utilities/capture_set_sizes_for_cache.py +++ b/tests/utilities/capture_set_sizes_for_cache.py @@ -1,29 +1,5 @@ """ Utility to capture the set sizes for inspection/comparison - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 11/14/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ import json @@ -33,27 +9,32 @@ import pyomo.environ as pyo -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_sequencer import TemoaSequencer +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig logger = logging.getLogger(__name__) print( - 'WARNING: Continuing to execute this file will update the cached values for the set sizes for US_9R model in ' - 'the testing_data folder from the sqlite databases in the same folder. This should only need to be done if the ' - 'schema or model have changed and that database has been updated.' + 'WARNING: Continuing to execute this file will update the cached values for the set sizes ' + 'for US_9R model in the testing_data folder from the sqlite databases in the same folder. ' + 'This should only need to be done if the schema or model have changed and that database has ' + 'been updated.' ) t = input('Type "Y" to continue, any other key to exit now.') if t not in {'y', 'Y'}: sys.exit(0) -output_file = Path(PROJECT_ROOT, 'tests', 'testing_data', 'US_9R_8D_set_sizes.json') -config_file = Path(PROJECT_ROOT, 'tests', 'utilities', 'config_US_9R_8D.toml') -options = {'silent': True, 'debug': True} -sequencer = TemoaSequencer( - config_file=config_file, output_path=Path(PROJECT_ROOT, 'tests', 'testing_log'), **options +output_file = Path(__file__).parent.parent / 'testing_data' / 'US_9R_8D_set_sizes.json' +config_file_path = Path(__file__).parent / 'config_US_9R_8D.toml' +output_path = Path(__file__).parent.parent / 'testing_log' +output_path.mkdir(parents=True, exist_ok=True) + +options = {'silent': True} +config = TemoaConfig.build_config( + config_file=config_file_path, output_path=output_path, silent=options['silent'] ) -instance = sequencer.start() +sequencer = TemoaSequencer(config=config) +instance = sequencer.build_model() # catch the built model model_sets = instance.component_map(ctype=pyo.Set) sets_dict = {k: len(v) for k, v in model_sets.items() if '_index' not in k} diff --git a/tests/utilities/capture_set_values_for_cache.py b/tests/utilities/capture_set_values_for_cache.py index bef53dcb6..f0ca2d218 100644 --- a/tests/utilities/capture_set_values_for_cache.py +++ b/tests/utilities/capture_set_values_for_cache.py @@ -2,83 +2,52 @@ Quick utility to capture set values from a pyomo model to enable later comparison. This file should not need to be run again unless model schema changes - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 8/26/23 - -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . """ import json -import sys from pathlib import Path import pyomo.environ as pyo -from definitions import PROJECT_ROOT -from temoa.temoa_model.temoa_sequencer import TemoaSequencer +from temoa._internal.temoa_sequencer import TemoaSequencer +from temoa.core.config import TemoaConfig +from temoa.core.modes import TemoaMode from tests.conftest import refresh_databases +from tests.utilities.hash_utils import hash_set -print( - 'WARNING: Continuing to execute this file will ' - 'update the cached values in the testing_data folder' - 'from the sqlite databases in the same folder. ' - 'This should only need to be done if the schema or' - 'model have changed and that database has been updated.' - '\nRunning this basically resets the expected value sets' - 'for Utopia, TestSystem, and Mediumville' -) - -t = input('Type "Y" to continue, any other key to exit now.') -if t not in {'y', 'Y'}: - sys.exit(0) - -output_path = Path(PROJECT_ROOT, 'tests', 'testing_log') # capture the log here +output_path = Path(__file__).parent.parent / 'testing_log' # capture the log here +output_path.mkdir(parents=True, exist_ok=True) scenarios = [ { - 'output_file': Path(PROJECT_ROOT, 'tests', 'testing_data', 'utopia_sets.json'), - 'config_file': Path(PROJECT_ROOT, 'tests', 'utilities', 'config_utopia.toml'), + 'output_file': Path(__file__).parent.parent / 'testing_data' / 'utopia_sets.json', + 'config_file': Path(__file__).parent.parent / 'testing_configs' / 'config_utopia.toml', }, { - 'output_file': Path(PROJECT_ROOT, 'tests', 'testing_data', 'test_system_sets.json'), - 'config_file': Path(PROJECT_ROOT, 'tests', 'utilities', 'config_test_system.toml'), + 'output_file': Path(__file__).parent.parent / 'testing_data' / 'test_system_sets.json', + 'config_file': Path(__file__).parent.parent / 'testing_configs' / 'config_test_system.toml', }, { - 'output_file': Path(PROJECT_ROOT, 'tests', 'testing_data', 'mediumville_sets.json'), - 'config_file': Path(PROJECT_ROOT, 'tests', 'utilities', 'config_mediumville.toml'), + 'output_file': Path(__file__).parent.parent / 'testing_data' / 'mediumville_sets.json', + 'config_file': Path(__file__).parent.parent / 'testing_configs' / 'config_mediumville.toml', }, ] # make new copies of the DB's from source... refresh_databases() for scenario in scenarios: - ts = TemoaSequencer(config_file=scenario['config_file'], output_path=output_path) + config = TemoaConfig.build_config( + config_file=scenario['config_file'], output_path=output_path, silent=True + ) + ts = TemoaSequencer(config=config, mode_override=TemoaMode.BUILD_ONLY) - built_instance = ts.start() # catch the built model + built_instance = ts.build_model() # catch the built model model_sets = built_instance.component_map(ctype=pyo.Set) - sets_dict = {k: list(v) for k, v in model_sets.items()} + sets_dict = { + k: hash_set(v) for k, v in model_sets.items() if '_index' not in k and '_domain' not in k + } # stash the result in a json file... with open(scenario['output_file'], 'w') as f_out: - json.dump(sets_dict, f_out, indent=2) + json.dump(sets_dict, f_out, indent=2, sort_keys=True) diff --git a/tests/utilities/config_US_9R_8D.toml b/tests/utilities/config_US_9R_8D.toml index 586ae1e5d..93c87051a 100644 --- a/tests/utilities/config_US_9R_8D.toml +++ b/tests/utilities/config_US_9R_8D.toml @@ -7,7 +7,7 @@ output_database = "../testing_outputs/US_9R_8D.sqlite" neos = false # solver -solver_name = "cbc" +solver_name = "appsi_highs" # generate an excel file in the output_files folder save_excel = false @@ -30,7 +30,3 @@ weight = "integer" # currently supported: [integer, normalized] [myopic] myopic_view = 2 # number of periods seen at one iteration - - - - diff --git a/tests/utilities/config_mediumville.toml b/tests/utilities/config_mediumville.toml index accac040a..5a0c0582f 100644 --- a/tests/utilities/config_mediumville.toml +++ b/tests/utilities/config_mediumville.toml @@ -36,7 +36,7 @@ output_database = "../testing_outputs/mediumville.sqlite" neos = false # solver -solver_name = "cbc" +solver_name = "appsi_highs" # ------------------------------------ # OUTPUTS @@ -64,7 +64,3 @@ weight = "integer" # currently supported: [integer, normalized] [myopic] myopic_view = 2 # number of periods seen at one iteration - - - - diff --git a/tests/utilities/config_test_system.toml b/tests/utilities/config_test_system.toml index bab80ff0a..50ff7737a 100644 --- a/tests/utilities/config_test_system.toml +++ b/tests/utilities/config_test_system.toml @@ -7,7 +7,7 @@ output_database = "../testing_outputs/test_system.sqlite" neos = false # solver -solver_name = "cbc" +solver_name = "appsi_highs" # generate an excel file in the output_files folder save_excel = false @@ -30,7 +30,3 @@ weight = "integer" # currently supported: [integer, normalized] [myopic] myopic_view = 2 # number of periods seen at one iteration - - - - diff --git a/tests/utilities/config_utopia.toml b/tests/utilities/config_utopia.toml index c5bcf4b90..c4db1213e 100644 --- a/tests/utilities/config_utopia.toml +++ b/tests/utilities/config_utopia.toml @@ -7,7 +7,7 @@ output_database = "../testing_outputs/utopia.sqlite" neos = false # solver -solver_name = "cbc" +solver_name = "appsi_highs" # generate an excel file in the output_files folder save_excel = false @@ -30,7 +30,3 @@ weight = "integer" # currently supported: [integer, normalized] [myopic] myopic_view = 2 # number of periods seen at one iteration - - - - diff --git a/tests/utilities/hash_utils.py b/tests/utilities/hash_utils.py new file mode 100644 index 000000000..008aa7378 --- /dev/null +++ b/tests/utilities/hash_utils.py @@ -0,0 +1,16 @@ +import hashlib +import json +from typing import Any + + +def hash_set(s: Any) -> str: + """ + Produce a stable SHA256 hash for a pyomo set or any iterable. + """ + try: + sorted_elements = sorted(s) + except TypeError: + # elements are of mixed types, fallback to canonicalized typed pairs + sorted_elements = sorted([(type(e).__name__, str(e)) for e in s]) + s_bytes = json.dumps(sorted_elements, ensure_ascii=False).encode('utf-8') + return hashlib.sha256(s_bytes).hexdigest() diff --git a/tests/utilities/namespace_mock.py b/tests/utilities/namespace_mock.py index 5ba0c69d7..062afc5da 100644 --- a/tests/utilities/namespace_mock.py +++ b/tests/utilities/namespace_mock.py @@ -1,32 +1,3 @@ -""" -Tools for Energy Model Optimization and Analysis (Temoa): -An open source framework for energy systems optimization modeling - -Copyright (C) 2015, NC State University - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A complete copy of the GNU General Public License v2 (GPLv2) is available -in LICENSE.txt. Users uncompressing this from an archive may not have -received this license file. If not, see . - - -Written by: J. F. Hyink -jeff@westernspark.us -https://westernspark.us -Created on: 4/1/24 - -""" - - class Namespace: - def __init__(self, **kwargs): + def __init__(self, **kwargs: object) -> None: self.__dict__.update(kwargs) diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..c2339a724 --- /dev/null +++ b/uv.lock @@ -0,0 +1,2047 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "filelock" +version = "3.25.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, +] + +[[package]] +name = "flexcache" +version = "0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/b0/8a21e330561c65653d010ef112bf38f60890051d244ede197ddaa08e50c1/flexcache-0.3.tar.gz", hash = "sha256:18743bd5a0621bfe2cf8d519e4c3bfdf57a269c15d1ced3fb4b64e0ff4600656", size = 15816, upload-time = "2024-03-09T03:21:07.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/cd/c883e1a7c447479d6e13985565080e3fea88ab5a107c21684c813dba1875/flexcache-0.3-py3-none-any.whl", hash = "sha256:d43c9fea82336af6e0115e308d9d33a185390b8346a017564611f1466dcd2e32", size = 13263, upload-time = "2024-03-09T03:21:05.635Z" }, +] + +[[package]] +name = "flexparser" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/99/b4de7e39e8eaf8207ba1a8fa2241dd98b2ba72ae6e16960d8351736d8702/flexparser-0.4.tar.gz", hash = "sha256:266d98905595be2ccc5da964fe0a2c3526fbbffdc45b65b3146d75db992ef6b2", size = 31799, upload-time = "2024-11-07T02:00:56.249Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/5e/3be305568fe5f34448807976dc82fc151d76c3e0e03958f34770286278c1/flexparser-0.4-py3-none-any.whl", hash = "sha256:3738b456192dcb3e15620f324c447721023c0293f6af9955b481e91d00179846", size = 27625, upload-time = "2024-11-07T02:00:54.523Z" }, +] + +[[package]] +name = "fonttools" +version = "4.62.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "gurobipy" +version = "13.0.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/5f/7cc9a23fac538e200ec0985cce6abce5f5dcc1187e63e21167b5d5bbefca/gurobipy-13.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:850f553795a5f11439dd2844e6afcbab380db191d7dbb5bb6f4e6b19e1fde637", size = 15963915, upload-time = "2026-01-21T10:36:36.074Z" }, + { url = "https://files.pythonhosted.org/packages/85/9b/9363877895a78258f24a883b137fae83e5cc5e33ed3768618f8ae2aa8da3/gurobipy-13.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8848329014960a640c57136bf2adc6a75dc73716f50729ca6c86d68b9f0b6b2", size = 14843835, upload-time = "2026-01-21T10:36:49.236Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/2fc774d1df58f5e9f798d90e39ca15cf6f0f94635669e6a83c97901e0d1f/gurobipy-13.0.1-cp312-cp312-manylinux_2_26_aarch64.whl", hash = "sha256:c0a4232009a133e4a69375f3ce547c66dc31269afc86f6d5d794137d2331b84f", size = 87192977, upload-time = "2026-01-21T10:38:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c7/c106367b5209ab5500df8feed64d596276d7e78fe7ab3b918ac938ce580b/gurobipy-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a77a8fa0937b274382dd3b436ab1aada3730d4270fb819b22614725dac927d9c", size = 11222551, upload-time = "2026-01-21T10:38:23.295Z" }, + { url = "https://files.pythonhosted.org/packages/b3/32/75c9df1755b20422155674b8d189ca62cf6d069b7c9de1f8e5348e3bdd29/gurobipy-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8fc13ccec3ebd66e2aee9d62e22854bb33d27336d12ae6465a9d02fe8371d0b7", size = 15953428, upload-time = "2026-01-21T10:38:39.935Z" }, + { url = "https://files.pythonhosted.org/packages/10/b2/b829bf5ad5f0f241bbdcbeca2fccc0655452ed7c7e55787f446d6e773142/gurobipy-13.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c159bac8c7eb2f4acc81579ba3f5860326fb2ca3a9d77f60f14d0686009b4687", size = 14844652, upload-time = "2026-01-21T10:38:52.84Z" }, + { url = "https://files.pythonhosted.org/packages/2e/8d/cbf532a8c373ee47d46f300a18498a478de69929cb93e1e3dd40ceb45e15/gurobipy-13.0.1-cp313-cp313-manylinux_2_26_aarch64.whl", hash = "sha256:42f1dfb2c6e72b0a0199caaa05b38fdb02dc949ef1a25eb4e789fc5ea52d938e", size = 87197330, upload-time = "2026-01-21T10:40:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/de/98/ae44df62a7bc1f4b78770713f00014557e520a268544640e90fa63cc50f2/gurobipy-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:532794c3204163315225f7b84776df4aa1b9b68e612596221d928066bf36b1a7", size = 11216528, upload-time = "2026-01-21T10:40:20.473Z" }, + { url = "https://files.pythonhosted.org/packages/18/4d/3ce4f83b5631bbda7f59321453341e8b7ecf927dffb569084178248a8c0a/gurobipy-13.0.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:402cad299b4f4b37460ca8342e315b252ff2e7086a6bc99ca2d35f79c6173d5c", size = 15821573, upload-time = "2026-01-21T10:40:37.459Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5d/f8bc51c76f80f133cb57be3c6fa8f664cb2308a498a0f0d0b6081c554c56/gurobipy-13.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5944820a3278b964f0c48b1ab083a2cc4999f47b99068595574177c0f8973826", size = 14743643, upload-time = "2026-01-21T10:40:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/26/53/515e60ee42248d22242c4d0774ae504e775cef26fea9ffd8a174459bb2e6/gurobipy-13.0.1-cp314-cp314-manylinux_2_26_aarch64.whl", hash = "sha256:a8700e549c2667aa235034a6149af16a2138ba7c1f9ecd15b55754704ab6ceaf", size = 87125439, upload-time = "2026-01-21T10:42:07.665Z" }, + { url = "https://files.pythonhosted.org/packages/55/cc/96058a65f18ba427ed5d148065b1e3c44fdffaf4125e81f1b5d2cd1fd192/gurobipy-13.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:d7374de3d602364480de330847c0c642ccf05d13edde431fda7061b092add1e4", size = 11437728, upload-time = "2026-01-21T10:43:56.314Z" }, + { url = "https://files.pythonhosted.org/packages/eb/73/dfed3c9c9727f825457f15bc2fc606204a96df81c7a6182b23b298de2ae5/gurobipy-13.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e9de59083600c7a4b52e3ea48d126ece9c491326138e16e8523c9d81e8519656", size = 16136707, upload-time = "2026-01-21T10:42:21.097Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6b/56939031627076c2df54685e652a5a7a41b56112c0aa67fb2bcc8f8d5190/gurobipy-13.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f7e556a4e7dd077a53fa6a683805e409ce66bae33b9b2af2122e81432d32b02a", size = 14738383, upload-time = "2026-01-21T10:42:34.24Z" }, + { url = "https://files.pythonhosted.org/packages/d9/67/c8e65fef49bbbfd0f526d90713b49e61c7df8f4e302df4868efe5544e98d/gurobipy-13.0.1-cp314-cp314t-manylinux_2_26_aarch64.whl", hash = "sha256:81405565946d7e212890884c7569e0af4183178d67dfcadaec861b16172012eb", size = 87122127, upload-time = "2026-01-21T10:43:36.176Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6b/b5a10633dc2bf62c8e15c0f114e064b68a7cc0b147f800d630d02830b09d/gurobipy-13.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:70348777693ed8b7348fe43db4444151d49ab7dec7d46b959fb98c2c1e557028", size = 11879457, upload-time = "2026-01-21T10:43:48.059Z" }, +] + +[[package]] +name = "highspy" +version = "1.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/17/74553587944f514142618c6d1b76551acf09fb0dd48abe9f4c37fb7d2bb8/highspy-1.13.1.tar.gz", hash = "sha256:7888873501c6ca3e0fa19fee960c8b3cb1c64132c5a9b514903cc7e259b5b0c7", size = 1597930, upload-time = "2026-02-11T16:39:55.185Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/9f/98a103b443e42755aa4e63800d6a517a928e32f28c40ff2ecce7d9c4e20a/highspy-1.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f9f7001b9a6afd1cb06e5f3ad24848aa0df78a32b004538f2630535a881e7d", size = 2240384, upload-time = "2026-02-11T16:38:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/8e/98/920115e7e451e20a2a84e3fec4f5ade6760561d96a001ee8c87886331b9a/highspy-1.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce77b3771c20715e4552b890b9a2f4eee33faf1bd86ab08c6a7b00424ebe12b3", size = 2051929, upload-time = "2026-02-11T16:38:26.235Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6b/9efc679003ff5d10cbf88e02deb47c7095b2e23d700eea895a4faecf1dcc/highspy-1.13.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f469f0a340870e1f16324a096dead3775857470b7e9d552b08a878a945f34917", size = 2321820, upload-time = "2026-02-11T16:38:27.75Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ad/be6847577282389a5b8ab4ef6bee71c5ccb2383c84a123561b4e538449b7/highspy-1.13.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f00b426b09e28306b279645beec2f892dd652ad41ec1597b742865bb844dd38d", size = 2525364, upload-time = "2026-02-11T16:38:29.152Z" }, + { url = "https://files.pythonhosted.org/packages/70/83/6c72c558c3bed2a06b3d850943621376116f374949bc77f2b353995cfba9/highspy-1.13.1-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:dce26ba2926612847be6d3e01ee98df2af8472a99de8ad9dc562060f6c8e5d0a", size = 2696833, upload-time = "2026-02-11T16:38:30.65Z" }, + { url = "https://files.pythonhosted.org/packages/1e/94/376940d949547d5bfdd68a86b0631d8c2fd86a7fd771c93a23c683be1293/highspy-1.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:506038db5c4732a4b65755aa63f580cef91e1a99a5d1399e311dc31d3300dadd", size = 3382720, upload-time = "2026-02-11T16:38:32.135Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8d/6ecee935e5f57df9fbe9fedfcac14e4ac76186335595bf201f86302a7379/highspy-1.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eda07ca3ec5506d279679d775dedc447fbcc3711814c5f2e22d2d063598586af", size = 3943880, upload-time = "2026-02-11T16:38:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7b/524d977387e3ff3eba165feb81614ead36a7974c0f8d9948f8d9347f74ff/highspy-1.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce9481fb96771c3b74592e1a94cc8dc2748d18eafb0bad6a03ebca5f4cfaa8fd", size = 3610040, upload-time = "2026-02-11T16:38:35.582Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/3133cc87d13399f66250f9e2ff0592c72a42edae945d27ec1dfadf009ebd/highspy-1.13.1-cp312-cp312-win32.whl", hash = "sha256:0a10b8d7bc6a2a50226bc6821f21ed5f456479950271e742aff8bc97412c4591", size = 1888436, upload-time = "2026-02-11T16:38:37.327Z" }, + { url = "https://files.pythonhosted.org/packages/2a/50/0c6986b87cc373dc9e9eb6254508f0f77bb4de897ae1d2a8c7e6ae70ef6c/highspy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:26f023093ed2fa2407f12a4a7dc9c1de253cd14228874e58e6719d78a74e2a9c", size = 2231427, upload-time = "2026-02-11T16:38:38.805Z" }, + { url = "https://files.pythonhosted.org/packages/86/4b/77e7f6b936dc130eb4dc78e64bb75d15940232027f519a7bb6564c91153b/highspy-1.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a255204f4ec79f5e447765d745e41bf3115a6c196ecfab5ed636d1ff53c0401", size = 2240587, upload-time = "2026-02-11T16:38:40.249Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5c/8a57a0f2d18bd9fb88f64c87c11882defe90832361ecd4f612b36d1c5f05/highspy-1.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb7509b5606a1ac7bc1fff794cebeb668f43531678f898803c12cbb694bea61", size = 2052178, upload-time = "2026-02-11T16:38:41.628Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d8/fec866c1a48e98897f6348a303fa565dc00fd29e5fb90e6befe1e1ead12b/highspy-1.13.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2e572c9ec5eb0fd5d178690981116413f277cb59d150bf271d927161335df23", size = 2321460, upload-time = "2026-02-11T16:38:43.04Z" }, + { url = "https://files.pythonhosted.org/packages/24/6f/3bfec9e2a0ff3adc20d3ae071572ae473f810c35e04a7455abf278623932/highspy-1.13.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e577ff0fdeccc7e207be8577dd8b223816d024c40c1b7af4afb6e96818f4544b", size = 2525308, upload-time = "2026-02-11T16:38:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/33/7b/b21a2dffca27742a50c48e88b3c850b1a212506543ec7b38a5f843a689e0/highspy-1.13.1-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:dc8677bb6da2216fd72110d95d904022bf3150832441eb08562bdb8d2c5aeb6c", size = 2696881, upload-time = "2026-02-11T16:38:47.498Z" }, + { url = "https://files.pythonhosted.org/packages/ea/cc/793b6819c1a8d360b8a536f62526d3bb54a3ba8deaf3b0ba7d4076e56da3/highspy-1.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3ec5adcef1028b40ae6d76d03c24b512c5f33849c5e09b70c15734b0e48c68ca", size = 3382687, upload-time = "2026-02-11T16:38:49.18Z" }, + { url = "https://files.pythonhosted.org/packages/18/13/20bbbf7a20779d799565ab4ce9c9f80ad39eeeb069b2a91e1fd5dece3109/highspy-1.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1a435dd11fcefab2e9e0ad90d75a459a656b177ef3d1173b31db889808580e92", size = 3943115, upload-time = "2026-02-11T16:38:50.761Z" }, + { url = "https://files.pythonhosted.org/packages/02/df/1c37558d33e52376822136c3288b65d74cb0482da841904dc5ec89540dc4/highspy-1.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0f473e94b19c186fda0abed1e31d3682ac4a9109930e77a1901c63f1b3d7d772", size = 3610136, upload-time = "2026-02-11T16:38:52.724Z" }, + { url = "https://files.pythonhosted.org/packages/27/e3/094bae0233275f5d293c40598a97a7ec8b0dee61572d5c2086aec8e35056/highspy-1.13.1-cp313-cp313-win32.whl", hash = "sha256:1464ed94e467de3cc20fdf3779609dd13aa91db8d11305b1d8f6a029ae129f33", size = 1888446, upload-time = "2026-02-11T16:38:54.341Z" }, + { url = "https://files.pythonhosted.org/packages/e2/15/d9be1b1f22ecafedddbf504b3c63b7e4a17a710e3b45f615be1869160938/highspy-1.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:620fad11a92517d525300ad70f27f84f41e8c0af10ece5ae5537f2be13ae0970", size = 2231418, upload-time = "2026-02-11T16:38:56.499Z" }, + { url = "https://files.pythonhosted.org/packages/92/70/22dc5ae52b9a4a13658a3a9c3b9909ae9aecaee4f579c1996096f8f19a75/highspy-1.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0868538ce474be93219013fa242fe3ddf0859562e3b354b50786da03c96b941a", size = 2240675, upload-time = "2026-02-11T16:38:57.971Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1a/426dddf9a838a54d09d6658b97bd843e41e1db4e21c5d6839b17578d672f/highspy-1.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d3f38a68783d690a4fe14521fbad18cbeae6121e1785f1dbdf5c045bcb35a926", size = 2050718, upload-time = "2026-02-11T16:38:59.479Z" }, + { url = "https://files.pythonhosted.org/packages/50/4b/a33a1bc2a7d40247bd0921fabdafa06b8af196c2ab01418eabf305d179c6/highspy-1.13.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:edf109c83d1488e0305f8fce88b9a3fc4fa7655b0b72c6cdb4437bad2a4a7fc0", size = 2322273, upload-time = "2026-02-11T16:39:01.534Z" }, + { url = "https://files.pythonhosted.org/packages/75/68/8d197789a73714ae81fc0923f0bdc415eae9dff1fa2d65a999a329a5de72/highspy-1.13.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56cb459c01696c83d2d65057e03df2cb809fb9cc9357f91d30b1af192765d809", size = 2529809, upload-time = "2026-02-11T16:39:03.359Z" }, + { url = "https://files.pythonhosted.org/packages/07/77/ed99d26ec450676b764eae89247e1ef5b194ecf5f0979ca6101f04573d0b/highspy-1.13.1-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:aada6027a92401967476b93ce10f13c0d7c45aedfb322c8049b5d2672fbce5a8", size = 2694699, upload-time = "2026-02-11T16:39:05.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/b51d716f2fea045f2de6cfae29b9e3bdf7d095668e565e2be3fccdf0d281/highspy-1.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2072f3a303b9ac988e9e37b5d7942cbba8c9fce7a323357122429c1a85c344b", size = 3383641, upload-time = "2026-02-11T16:39:07.107Z" }, + { url = "https://files.pythonhosted.org/packages/ab/22/3dc1fc494c190ad5e6e0dad9758c915cdf4df0bdfdd19d7d12ac975e9833/highspy-1.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:755d924e7650703e217debd69f566da078cdf7f9bd957ccd88c3be134c63c8f5", size = 3943007, upload-time = "2026-02-11T16:39:09.509Z" }, + { url = "https://files.pythonhosted.org/packages/79/20/e084a47336416ed02efd64e6a68b41c3ad3867a38c460dd9a1d14837f366/highspy-1.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:516c5b5a7d01d7c073206f787316c60f4800994cf3a947031f336e737177f6cc", size = 3613402, upload-time = "2026-02-11T16:39:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/ea/66/e72cc05b0fc8b31b7d2992fbf386e54cd217bc2a6c202fec97a37d2fb97f/highspy-1.13.1-cp314-cp314-win32.whl", hash = "sha256:4cfff7f50615a588a760b33cd2dd37761679b400196213015230db1cd6c46232", size = 1930483, upload-time = "2026-02-11T16:39:13.7Z" }, + { url = "https://files.pythonhosted.org/packages/30/6c/7abf63f02302d64934d9decd04939334db1938917f552059570ea27b41b3/highspy-1.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:242d00f46b09c9d6f077881739d7487030a9ed2c56bcd28f3e8d5942da407df4", size = 2313528, upload-time = "2026-02-11T16:39:15.765Z" }, +] + +[[package]] +name = "identify" +version = "2.6.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, +] + +[[package]] +name = "latexcodec" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/dd/4270b2c5e2ee49316c3859e62293bd2ea8e382339d63ab7bbe9f39c0ec3b/latexcodec-3.0.1.tar.gz", hash = "sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357", size = 31222, upload-time = "2025-06-17T18:47:34.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/40/23569737873cc9637fd488606347e9dd92b9fa37ba4fcda1f98ee5219a97/latexcodec-3.0.1-py3-none-any.whl", hash = "sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e", size = 18532, upload-time = "2025-06-17T18:47:30.726Z" }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, + { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, + { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, + { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mpi-sppy" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pyomo" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/d2/1df67d240fb6f9b07d550fc6d8477a74dd220e17f24fce6ccac9d5316ad2/mpi-sppy-0.12.1.tar.gz", hash = "sha256:c405b88f3ab69c67dd564789cf156c0cfb704a9eb74d0645f29a1058c19bed27", size = 246281, upload-time = "2024-01-11T16:41:08.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/29/5d104b54a67d088f0df34801dcc891c702da6dcbb0a1764708a0488f800d/mpi_sppy-0.12.1-py3-none-any.whl", hash = "sha256:0fdf71087f4c5cd9ae22f93ff9ec1feb724ae6bc8e12f9a194154b97dddc750e", size = 310166, upload-time = "2024-01-11T16:41:06.816Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414, upload-time = "2026-01-19T06:47:35.915Z" }, + { url = "https://files.pythonhosted.org/packages/a0/61/af9115673a5870fd885247e2f1b68c4f1197737da315b520a91c757a861a/multiprocess-0.70.19-py314-none-any.whl", hash = "sha256:e8cc7fbdff15c0613f0a1f1f8744bef961b0a164c0ca29bdff53e9d2d93c5e5f", size = 160318, upload-time = "2026-01-19T06:47:37.497Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "myst-parser" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, + { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, + { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, + { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, + { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, + { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, + { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, + { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, + { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, + { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" }, + { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" }, + { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" }, + { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" }, + { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" }, + { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" }, + { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" }, + { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" }, + { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" }, + { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465, upload-time = "2026-02-17T22:19:36.803Z" }, + { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632, upload-time = "2026-02-17T22:19:39.001Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535, upload-time = "2026-02-17T22:19:41.157Z" }, + { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940, upload-time = "2026-02-17T22:19:43.493Z" }, + { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711, upload-time = "2026-02-17T22:19:46.074Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918, upload-time = "2026-02-17T22:19:48.838Z" }, + { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099, upload-time = "2026-02-17T22:19:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333, upload-time = "2026-02-17T22:19:53.266Z" }, + { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664, upload-time = "2026-02-17T22:19:55.806Z" }, + { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286, upload-time = "2026-02-17T22:19:58.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050, upload-time = "2026-02-17T22:20:01.376Z" }, + { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055, upload-time = "2026-02-17T22:20:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632, upload-time = "2026-02-17T22:20:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974, upload-time = "2026-02-17T22:20:08.946Z" }, + { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622, upload-time = "2026-02-17T22:20:11.711Z" }, + { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, +] + +[[package]] +name = "pandas-stubs" +version = "3.0.0.260204" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/1d/297ff2c7ea50a768a2247621d6451abb2a07c0e9be7ca6d36ebe371658e5/pandas_stubs-3.0.0.260204.tar.gz", hash = "sha256:bf9294b76352effcffa9cb85edf0bed1339a7ec0c30b8e1ac3d66b4228f1fbc3", size = 109383, upload-time = "2026-02-04T15:17:17.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/2f/f91e4eee21585ff548e83358332d5632ee49f6b2dcd96cb5dca4e0468951/pandas_stubs-3.0.0.260204-py3-none-any.whl", hash = "sha256:5ab9e4d55a6e2752e9720828564af40d48c4f709e6a2c69b743014a6fcb6c241", size = 168540, upload-time = "2026-02-04T15:17:15.615Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, +] + +[[package]] +name = "pint" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flexcache" }, + { name = "flexparser" }, + { name = "platformdirs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/74/bc3f671997158aef171194c3c4041e549946f4784b8690baa0626a0a164b/pint-0.25.2.tar.gz", hash = "sha256:85a45d1da8fe9c9f7477fed8aef59ad2b939af3d6611507e1a9cbdacdcd3450a", size = 254467, upload-time = "2025-11-06T22:08:09.184Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/88/550d41e81e6d43335603a960cd9c75c1d88f9cf01bc9d4ee8e86290aba7d/pint-0.25.2-py3-none-any.whl", hash = "sha256:ca35ab1d8eeeb6f7d9942b3cb5f34ca42b61cdd5fb3eae79531553dcca04dda7", size = 306762, upload-time = "2025-11-06T22:08:07.745Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + +[[package]] +name = "pybtex" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "latexcodec" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/bc/c2be05ca72f8c103670e983df8be26d1e288bc6556f487fa8cccaa27779f/pybtex-0.25.1.tar.gz", hash = "sha256:9eaf90267c7e83e225af89fea65c370afbf65f458220d3946a9e3049e1eca491", size = 406157, upload-time = "2025-06-26T13:27:41.903Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/68/ceb5d6679baa326261f5d3e5113d9cfed6efef2810afd9f18bffb8ed312b/pybtex-0.25.1-py2.py3-none-any.whl", hash = "sha256:9053b0d619409a0a83f38abad5d9921de5f7b3ede00742beafcd9f10ad0d8c5c", size = 127437, upload-time = "2025-06-26T13:27:43.585Z" }, +] + +[[package]] +name = "pybtex-docutils" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pybtex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/84/796ea94d26188a853660f81bded39f8de4cfe595130aef0dea1088705a11/pybtex-docutils-1.0.3.tar.gz", hash = "sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b", size = 18348, upload-time = "2023-08-22T18:47:54.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385, upload-time = "2023-08-22T06:43:20.513Z" }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyomo" +version = "6.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/be/6aa8022dec69bf837885a094b9bc8d452debcb455ab69b66ee27cc6563e3/pyomo-6.10.0.tar.gz", hash = "sha256:672fac375e57e121ca935adcc16a1cd118be8afa1a3e5608161fb86220c3a577", size = 3171562, upload-time = "2026-02-20T21:42:31.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/8c/bbc4cfab889eaa689e85b2ec0574bd9a584d9de89d36e22ad11bd050acf5/pyomo-6.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a564996b6e3c0b425d5961186ee5d726ba59d1fb29490eb63cd32d8183859c46", size = 4430199, upload-time = "2026-02-20T21:49:11.58Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/b5ff9b8b786741507eb899c865871c7bdac02f05eef6faa7cf625dae99b3/pyomo-6.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e686b529e81a650ac9d1458476c664ee1cf2b45a90f57acce318524f50255ad", size = 4415951, upload-time = "2026-02-20T21:49:17.426Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6b/7371e09047b6b0c3d475545aa819415a84d579797bfe9a9b822771dc8ea2/pyomo-6.10.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c7d8802b399541e611f289172c3ec721e75a029a7d3af4cc56a0e8ff6735d", size = 4484110, upload-time = "2026-02-27T17:09:34.503Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2b/ae9f35156948b959b7d292e842fea4a08dffbf96e944e88fdcf10a3c3391/pyomo-6.10.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5ca37402b686816704a95f72d7d6c7aa5245f38f10010828ce9d0937694f4cb", size = 4512622, upload-time = "2026-02-20T21:49:23.022Z" }, + { url = "https://files.pythonhosted.org/packages/1c/66/b4fe4f6f9dcdb01c8590d507a4090c7ffdb72a2832115ce07fb83b23e718/pyomo-6.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:376dc045e62285b42ce41706df370f2ab5ac8babb31e2750aa3bf9eb6ce938ac", size = 4430253, upload-time = "2026-02-20T21:49:28.949Z" }, + { url = "https://files.pythonhosted.org/packages/75/01/a8c1539654b29903a98310fb34fd4caeebf67061289f552b1ea826462156/pyomo-6.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bc7b77278da187e6bcd603317cb60dd6555aeff8159c811eb9755fc873c4928", size = 4416089, upload-time = "2026-02-20T21:49:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/bf19632527bba7149d5a1d18ad5a923ee4956cd2a7c6dcc595a43321ca2e/pyomo-6.10.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e13d6ac1c837419d2140618faeee061c7ab8e09c872005a9e75ec46bcb6c85e4", size = 4484042, upload-time = "2026-02-27T17:09:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4e/9d523d75a64dc7ed13fd76bbf94618666456b0b69dcf1f2dc308ada982f9/pyomo-6.10.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dcf06214b71617d45a7cb238793171863bf0a30d9e1740190443813baee200", size = 4513077, upload-time = "2026-02-20T21:49:40.608Z" }, + { url = "https://files.pythonhosted.org/packages/2b/11/ebc9e817490a7992e950cca96b2671c2a490b08f9eb9e4fdc5091c6e77b8/pyomo-6.10.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:581dfe7e747c322177e64236cb3fee7fa015d7e6022f66b59394b927f0f29b0a", size = 4431213, upload-time = "2026-02-20T21:49:47.522Z" }, + { url = "https://files.pythonhosted.org/packages/93/54/f0705ba23eab3f2287e58c3cdd34b51b18e12bbd98dc96bf57defb0eb6cd/pyomo-6.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:70ab5312b1aa8b95618c3766c3e3f7be3e4d730e41ce24eea1b59eb5831d6817", size = 4416909, upload-time = "2026-02-20T21:49:53.383Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d8/57012015ead9c60815976d3aa8948f8a0bcc621ea643c0eb520b84147739/pyomo-6.10.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7519673917e049ba0910fc8b19648ebf711ebec9c685c7c840d66d5471dcc0c", size = 4486590, upload-time = "2026-02-27T17:09:54.395Z" }, + { url = "https://files.pythonhosted.org/packages/52/48/8bdf61ee998f65e2e00d65778e2a4cfe41129953a6b0ba72e7a5f5cefdc6/pyomo-6.10.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:505a1ea36f80125b878d8d0e34f3eb6af2cac3309523011caf1877d88fd16ecd", size = 4514075, upload-time = "2026-02-20T21:49:59.483Z" }, + { url = "https://files.pythonhosted.org/packages/73/17/62983f857eb592d5ede12f0f41ddbe5d1fa17c2796fb8c3db8ac7859e177/pyomo-6.10.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:905a16f21e90b44d9a52e2aaa9884c467abf50a945d08b9ddb8db40d4d707dbc", size = 4452689, upload-time = "2026-02-20T21:50:05.288Z" }, + { url = "https://files.pythonhosted.org/packages/67/e3/502d561a64259274c046f705e2325189e84a77c624f043f34fbd2f14e793/pyomo-6.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4859fb1a0f78ac0f5fad00199244d461d7bc100c8f76a61d696a1e1461d68bb0", size = 4439487, upload-time = "2026-02-20T21:50:11.116Z" }, + { url = "https://files.pythonhosted.org/packages/3b/89/89164dcf7549f1034b1f8646b4b3cf6e1ec09cb0d30dfaa3fce125fd95a4/pyomo-6.10.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07d69ae184c8206a4064fc13fe6b48d4a5aac7f18cbf8f6bed30faf9d3762106", size = 4479850, upload-time = "2026-02-27T17:10:04.269Z" }, + { url = "https://files.pythonhosted.org/packages/65/bd/cb4535b0bb63a7bcc968464ab739f54d2f4d15fe7924547336f5bd299513/pyomo-6.10.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0590505b282468c9fc2555ced544213227a0a466bbc667826a36cc922afec6d7", size = 4506738, upload-time = "2026-02-20T21:50:17.382Z" }, + { url = "https://files.pythonhosted.org/packages/88/1d/b5dad00739979353726059137a647acf031ef3af5557a9d227ca7b32481d/pyomo-6.10.0-py3-none-any.whl", hash = "sha256:3fc34402474f78c9144c735499c8dc6a0570948034f4c668d24727ec1b4671b1", size = 4052690, upload-time = "2026-02-27T17:10:13.653Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/7e/9f3b0dd3a074a6c3e1e79f35e465b1f2ee4b262d619de00cfce523cc9b24/python_discovery-1.1.3.tar.gz", hash = "sha256:7acca36e818cd88e9b2ba03e045ad7e93e1713e29c6bbfba5d90202310b7baa5", size = 56945, upload-time = "2026-03-10T15:08:15.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl", hash = "sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e", size = 31485, upload-time = "2026-03-10T15:08:13.06Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, + { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, + { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, + { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, + { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, + { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, +] + +[[package]] +name = "salib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "multiprocess" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/63/bdde6c303e95e2c819c668076fb3255f4453ee2041103d0edd18a90b6839/salib-1.5.2.tar.gz", hash = "sha256:a8eeedc4e87cf070f7ef450b76cdecd120d1e5c602a9c35855ec7777591f949f", size = 725859, upload-time = "2025-10-12T02:38:50.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/e2/9fbf2d571c0d5360f9cf6656de6c94b273d08ede1b628f23f319fa1a910d/salib-1.5.2-py3-none-any.whl", hash = "sha256:6f4b6bebc1eeed1d081c8f951fa8c2ad7b0cd8a7159d206af48ef137cc806c43", size = 780059, upload-time = "2025-10-12T02:38:48.878Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/f7/154786f3cfb7692cd7acc24b6dfe4dcd1146b66f376b17df9e47125555e9/sphinx_book_theme-1.2.0.tar.gz", hash = "sha256:4a7ebfc7da4395309ac942ddfc38fbec5c5254c3be22195e99ad12586fbda9e3", size = 443962, upload-time = "2026-03-09T23:20:30.442Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl", hash = "sha256:709605d308e1991c5ef0cf19c481dbe9084b62852e317fafab74382a0ee7ccfa", size = 455936, upload-time = "2026-03-09T23:20:28.788Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-bibtex" +version = "2.6.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "pybtex" }, + { name = "pybtex-docutils" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/83/1488c9879f2fa3c2cbd6f666c7a3a42a1fa9e08462bec73281fa6c092cba/sphinxcontrib_bibtex-2.6.5.tar.gz", hash = "sha256:9b3224dd6fece9268ebd8c905dc0a83ff2f6c54148a9235fe70e9d1e9ff149c0", size = 118462, upload-time = "2025-06-27T10:40:14.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/a0/3a612da94f828f26cabb247817393e79472c32b12c49222bf85fb6d7b6c8/sphinxcontrib_bibtex-2.6.5-py3-none-any.whl", hash = "sha256:455ea4509642ea0b28ede3721550273626f85af65af01f161bfd8e19dc1edd7d", size = 40410, upload-time = "2025-06-27T10:40:12.274Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-mermaid" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/ae/999891de292919b66ea34f2c22fc22c9be90ab3536fbc0fca95716277351/sphinxcontrib_mermaid-2.0.1.tar.gz", hash = "sha256:a21a385a059a6cafd192aa3a586b14bf5c42721e229db67b459dc825d7f0a497", size = 19839, upload-time = "2026-03-05T14:10:41.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/46/25d64bcd7821c8d6f1080e1c43d5fcdfc442a18f759a230b5ccdc891093e/sphinxcontrib_mermaid-2.0.1-py3-none-any.whl", hash = "sha256:9dca7fbe827bad5e7e2b97c4047682cfd26e3e07398cfdc96c7a8842ae7f06e7", size = 14064, upload-time = "2026-03-05T14:10:40.533Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + +[[package]] +name = "temoa" +source = { editable = "." } +dependencies = [ + { name = "deprecated" }, + { name = "graphviz" }, + { name = "highspy" }, + { name = "joblib" }, + { name = "matplotlib" }, + { name = "mpi-sppy" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "openpyxl" }, + { name = "pandas" }, + { name = "pint" }, + { name = "pyomo" }, + { name = "pytest" }, + { name = "rich" }, + { name = "salib" }, + { name = "scipy" }, + { name = "seaborn" }, + { name = "tabulate" }, + { name = "tomlkit" }, + { name = "typer" }, + { name = "xlsxwriter" }, +] + +[package.optional-dependencies] +docs = [ + { name = "myst-parser" }, + { name = "sphinx" }, + { name = "sphinx-book-theme" }, + { name = "sphinxcontrib-bibtex" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-mermaid" }, + { name = "sphinxcontrib-serializinghtml" }, +] +plotting = [ + { name = "matplotlib" }, + { name = "seaborn" }, +] +solver = [ + { name = "gurobipy" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mypy" }, + { name = "pandas-stubs" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "types-deprecated" }, + { name = "types-networkx" }, +] + +[package.metadata] +requires-dist = [ + { name = "deprecated", specifier = ">=1.2.14" }, + { name = "graphviz", specifier = ">=0.20.3" }, + { name = "gurobipy", marker = "extra == 'solver'", specifier = ">=12.0.3" }, + { name = "highspy", specifier = ">=1.7.2" }, + { name = "joblib" }, + { name = "matplotlib", specifier = ">=3.9.2" }, + { name = "matplotlib", marker = "extra == 'plotting'", specifier = ">=3.9.2" }, + { name = "mpi-sppy", specifier = ">=0.12.1" }, + { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=2.0.0" }, + { name = "networkx", specifier = ">=3.3" }, + { name = "numpy", specifier = ">=2.1.0" }, + { name = "openpyxl", specifier = ">=3.1.5" }, + { name = "pandas", specifier = ">=2.2.2" }, + { name = "pint", specifier = ">=0.25.2" }, + { name = "pyomo", specifier = ">=6.8.0" }, + { name = "pytest", specifier = ">=8.3.2" }, + { name = "rich", specifier = ">=14.2.0" }, + { name = "salib", specifier = ">=1.5.1" }, + { name = "scipy", specifier = ">=1.14.1" }, + { name = "seaborn", specifier = ">=0.13.2" }, + { name = "seaborn", marker = "extra == 'plotting'", specifier = ">=0.13.2" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=7.4.7" }, + { name = "sphinx-book-theme", marker = "extra == 'docs'", specifier = ">=1.1.3" }, + { name = "sphinxcontrib-bibtex", marker = "extra == 'docs'", specifier = ">=2.6.2" }, + { name = "sphinxcontrib-htmlhelp", marker = "extra == 'docs'", specifier = ">=2.1.0" }, + { name = "sphinxcontrib-mermaid", marker = "extra == 'docs'", specifier = ">=1.2.3" }, + { name = "sphinxcontrib-serializinghtml", marker = "extra == 'docs'", specifier = ">=2.0.0" }, + { name = "tabulate", specifier = ">=0.9.0" }, + { name = "tomlkit", specifier = ">=0.12.0" }, + { name = "typer", specifier = ">=0.20.0" }, + { name = "xlsxwriter", specifier = ">=3.2.0" }, +] +provides-extras = ["docs", "plotting", "solver"] + +[package.metadata.requires-dev] +dev = [ + { name = "mypy", specifier = ">=1.0.0" }, + { name = "pandas-stubs", specifier = ">=2.3.2.250926" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff", specifier = ">=0.2.0" }, + { name = "types-deprecated", specifier = ">=1.2.15.20250304" }, + { name = "types-networkx", specifier = ">=3.5.0.20251001" }, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, +] + +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + +[[package]] +name = "types-deprecated" +version = "1.3.1.20260130" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/97/9924e496f88412788c432891cacd041e542425fe0bffff4143a7c1c89ac4/types_deprecated-1.3.1.20260130.tar.gz", hash = "sha256:726b05e5e66d42359b1d6631835b15de62702588c8a59b877aa4b1e138453450", size = 8455, upload-time = "2026-01-30T03:58:17.401Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/b2/6f920582af7efcd37165cd6321707f3ad5839dd24565a8a982f2bd9c6fd1/types_deprecated-1.3.1.20260130-py3-none-any.whl", hash = "sha256:593934d85c38ca321a9d301f00c42ffe13e4cf830b71b10579185ba0ce172d9a", size = 9077, upload-time = "2026-01-30T03:58:16.633Z" }, +] + +[[package]] +name = "types-networkx" +version = "3.6.1.20260303" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/37/d8f7a68a5291cab793e27967d187abbf0c6db318d49e5b6e9dd32b13c2e5/types_networkx-3.6.1.20260303.tar.gz", hash = "sha256:8248aa6fcadc08bd7992af6e412bfc5cfa043bda5ce7ab407fa591c808ce8557", size = 73790, upload-time = "2026-03-03T04:03:46.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/b7/eedcba86c567832699eb242709e8951c0df2b6658beb5f931e954292bcda/types_networkx-3.6.1.20260303-py3-none-any.whl", hash = "sha256:754c7c7bcaab3c317b0b86441240c0a5bd0d2f419aba80a88e9718248a5c89af", size = 162680, upload-time = "2026-03-03T04:03:44.081Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, +] + +[[package]] +name = "wrapt" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/64/925f213fdcbb9baeb1530449ac71a4d57fc361c053d06bf78d0c5c7cd80c/wrapt-2.1.2.tar.gz", hash = "sha256:3996a67eecc2c68fd47b4e3c564405a5777367adfd9b8abb58387b63ee83b21e", size = 81678, upload-time = "2026-03-06T02:53:25.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/b6/1db817582c49c7fcbb7df6809d0f515af29d7c2fbf57eb44c36e98fb1492/wrapt-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff2aad9c4cda28a8f0653fc2d487596458c2a3f475e56ba02909e950a9efa6a9", size = 61255, upload-time = "2026-03-06T02:52:45.663Z" }, + { url = "https://files.pythonhosted.org/packages/a2/16/9b02a6b99c09227c93cd4b73acc3678114154ec38da53043c0ddc1fba0dc/wrapt-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6433ea84e1cfacf32021d2a4ee909554ade7fd392caa6f7c13f1f4bf7b8e8748", size = 61848, upload-time = "2026-03-06T02:53:48.728Z" }, + { url = "https://files.pythonhosted.org/packages/af/aa/ead46a88f9ec3a432a4832dfedb84092fc35af2d0ba40cd04aea3889f247/wrapt-2.1.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c20b757c268d30d6215916a5fa8461048d023865d888e437fab451139cad6c8e", size = 121433, upload-time = "2026-03-06T02:54:40.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/9f/742c7c7cdf58b59085a1ee4b6c37b013f66ac33673a7ef4aaed5e992bc33/wrapt-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79847b83eb38e70d93dc392c7c5b587efe65b3e7afcc167aa8abd5d60e8761c8", size = 123013, upload-time = "2026-03-06T02:53:26.58Z" }, + { url = "https://files.pythonhosted.org/packages/e8/44/2c3dd45d53236b7ed7c646fcf212251dc19e48e599debd3926b52310fafb/wrapt-2.1.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f8fba1bae256186a83d1875b2b1f4e2d1242e8fac0f58ec0d7e41b26967b965c", size = 117326, upload-time = "2026-03-06T02:53:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/74/e2/b17d66abc26bd96f89dec0ecd0ef03da4a1286e6ff793839ec431b9fae57/wrapt-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e3d3b35eedcf5f7d022291ecd7533321c4775f7b9cd0050a31a68499ba45757c", size = 121444, upload-time = "2026-03-06T02:54:09.5Z" }, + { url = "https://files.pythonhosted.org/packages/3c/62/e2977843fdf9f03daf1586a0ff49060b1b2fc7ff85a7ea82b6217c1ae36e/wrapt-2.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6f2c5390460de57fa9582bc8a1b7a6c86e1a41dfad74c5225fc07044c15cc8d1", size = 116237, upload-time = "2026-03-06T02:54:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/27fc67914e68d740bce512f11734aec08696e6b17641fef8867c00c949fc/wrapt-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7dfa9f2cf65d027b951d05c662cc99ee3bd01f6e4691ed39848a7a5fffc902b2", size = 120563, upload-time = "2026-03-06T02:53:20.412Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9f/b750b3692ed2ef4705cb305bd68858e73010492b80e43d2a4faa5573cbe7/wrapt-2.1.2-cp312-cp312-win32.whl", hash = "sha256:eba8155747eb2cae4a0b913d9ebd12a1db4d860fc4c829d7578c7b989bd3f2f0", size = 58198, upload-time = "2026-03-06T02:53:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b2/feecfe29f28483d888d76a48f03c4c4d8afea944dbee2b0cd3380f9df032/wrapt-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1c51c738d7d9faa0b3601708e7e2eda9bf779e1b601dce6c77411f2a1b324a63", size = 60441, upload-time = "2026-03-06T02:52:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/44/e1/e328f605d6e208547ea9fd120804fcdec68536ac748987a68c47c606eea8/wrapt-2.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:c8e46ae8e4032792eb2f677dbd0d557170a8e5524d22acc55199f43efedd39bf", size = 58836, upload-time = "2026-03-06T02:53:22.053Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7a/d936840735c828b38d26a854e85d5338894cda544cb7a85a9d5b8b9c4df7/wrapt-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787fd6f4d67befa6fe2abdffcbd3de2d82dfc6fb8a6d850407c53332709d030b", size = 61259, upload-time = "2026-03-06T02:53:41.922Z" }, + { url = "https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e", size = 61851, upload-time = "2026-03-06T02:52:48.672Z" }, + { url = "https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb", size = 121446, upload-time = "2026-03-06T02:54:14.013Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca", size = 123056, upload-time = "2026-03-06T02:54:10.829Z" }, + { url = "https://files.pythonhosted.org/packages/93/b9/ff205f391cb708f67f41ea148545f2b53ff543a7ac293b30d178af4d2271/wrapt-2.1.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:162e4e2ba7542da9027821cb6e7c5e068d64f9a10b5f15512ea28e954893a267", size = 117359, upload-time = "2026-03-06T02:53:03.623Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/1ea04d7747825119c3c9a5e0874a40b33594ada92e5649347c457d982805/wrapt-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f29c827a8d9936ac320746747a016c4bc66ef639f5cd0d32df24f5eacbf9c69f", size = 121479, upload-time = "2026-03-06T02:53:45.844Z" }, + { url = "https://files.pythonhosted.org/packages/78/cc/ee3a011920c7a023b25e8df26f306b2484a531ab84ca5c96260a73de76c0/wrapt-2.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a9dd9813825f7ecb018c17fd147a01845eb330254dff86d3b5816f20f4d6aaf8", size = 116271, upload-time = "2026-03-06T02:54:46.356Z" }, + { url = "https://files.pythonhosted.org/packages/98/fd/e5ff7ded41b76d802cf1191288473e850d24ba2e39a6ec540f21ae3b57cb/wrapt-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f8dbdd3719e534860d6a78526aafc220e0241f981367018c2875178cf83a413", size = 120573, upload-time = "2026-03-06T02:52:50.163Z" }, + { url = "https://files.pythonhosted.org/packages/47/c5/242cae3b5b080cd09bacef0591691ba1879739050cc7c801ff35c8886b66/wrapt-2.1.2-cp313-cp313-win32.whl", hash = "sha256:5c35b5d82b16a3bc6e0a04349b606a0582bc29f573786aebe98e0c159bc48db6", size = 58205, upload-time = "2026-03-06T02:53:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/12/69/c358c61e7a50f290958809b3c61ebe8b3838ea3e070d7aac9814f95a0528/wrapt-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f8bc1c264d8d1cf5b3560a87bbdd31131573eb25f9f9447bb6252b8d4c44a3a1", size = 60452, upload-time = "2026-03-06T02:53:30.038Z" }, + { url = "https://files.pythonhosted.org/packages/8e/66/c8a6fcfe321295fd8c0ab1bd685b5a01462a9b3aa2f597254462fc2bc975/wrapt-2.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:3beb22f674550d5634642c645aba4c72a2c66fb185ae1aebe1e955fae5a13baf", size = 58842, upload-time = "2026-03-06T02:52:52.114Z" }, + { url = "https://files.pythonhosted.org/packages/da/55/9c7052c349106e0b3f17ae8db4b23a691a963c334de7f9dbd60f8f74a831/wrapt-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fc04bc8664a8bc4c8e00b37b5355cffca2535209fba1abb09ae2b7c76ddf82b", size = 63075, upload-time = "2026-03-06T02:53:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/ce7b4006f7218248dd71b7b2b732d0710845a0e49213b18faef64811ffef/wrapt-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a9b9d50c9af998875a1482a038eb05755dfd6fe303a313f6a940bb53a83c3f18", size = 63719, upload-time = "2026-03-06T02:54:33.452Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e5/2ca472e80b9e2b7a17f106bb8f9df1db11e62101652ce210f66935c6af67/wrapt-2.1.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d3ff4f0024dd224290c0eabf0240f1bfc1f26363431505fb1b0283d3b08f11d", size = 152643, upload-time = "2026-03-06T02:52:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/36/42/30f0f2cefca9d9cbf6835f544d825064570203c3e70aa873d8ae12e23791/wrapt-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3278c471f4468ad544a691b31bb856374fbdefb7fee1a152153e64019379f015", size = 158805, upload-time = "2026-03-06T02:54:25.441Z" }, + { url = "https://files.pythonhosted.org/packages/bb/67/d08672f801f604889dcf58f1a0b424fe3808860ede9e03affc1876b295af/wrapt-2.1.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8914c754d3134a3032601c6984db1c576e6abaf3fc68094bb8ab1379d75ff92", size = 145990, upload-time = "2026-03-06T02:53:57.456Z" }, + { url = "https://files.pythonhosted.org/packages/68/a7/fd371b02e73babec1de6ade596e8cd9691051058cfdadbfd62a5898f3295/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ff95d4264e55839be37bafe1536db2ab2de19da6b65f9244f01f332b5286cfbf", size = 155670, upload-time = "2026-03-06T02:54:55.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/9fe0095dfdb621009f40117dcebf41d7396c2c22dca6eac779f4c007b86c/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:76405518ca4e1b76fbb1b9f686cff93aebae03920cc55ceeec48ff9f719c5f67", size = 144357, upload-time = "2026-03-06T02:54:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b6/ec7b4a254abbe4cde9fa15c5d2cca4518f6b07d0f1b77d4ee9655e30280e/wrapt-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c0be8b5a74c5824e9359b53e7e58bef71a729bacc82e16587db1c4ebc91f7c5a", size = 150269, upload-time = "2026-03-06T02:53:31.268Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6b/2fabe8ebf148f4ee3c782aae86a795cc68ffe7d432ef550f234025ce0cfa/wrapt-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:f01277d9a5fc1862f26f7626da9cf443bebc0abd2f303f41c5e995b15887dabd", size = 59894, upload-time = "2026-03-06T02:54:15.391Z" }, + { url = "https://files.pythonhosted.org/packages/ca/fb/9ba66fc2dedc936de5f8073c0217b5d4484e966d87723415cc8262c5d9c2/wrapt-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:84ce8f1c2104d2f6daa912b1b5b039f331febfeee74f8042ad4e04992bd95c8f", size = 63197, upload-time = "2026-03-06T02:54:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1c/012d7423c95d0e337117723eb8ecf73c622ce15a97847e84cf3f8f26cd7e/wrapt-2.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:a93cd767e37faeddbe07d8fc4212d5cba660af59bdb0f6372c93faaa13e6e679", size = 60363, upload-time = "2026-03-06T02:54:48.093Z" }, + { url = "https://files.pythonhosted.org/packages/39/25/e7ea0b417db02bb796182a5316398a75792cd9a22528783d868755e1f669/wrapt-2.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1370e516598854e5b4366e09ce81e08bfe94d42b0fd569b88ec46cc56d9164a9", size = 61418, upload-time = "2026-03-06T02:53:55.706Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0f/fa539e2f6a770249907757eaeb9a5ff4deb41c026f8466c1c6d799088a9b/wrapt-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6de1a3851c27e0bd6a04ca993ea6f80fc53e6c742ee1601f486c08e9f9b900a9", size = 61914, upload-time = "2026-03-06T02:52:53.37Z" }, + { url = "https://files.pythonhosted.org/packages/53/37/02af1867f5b1441aaeda9c82deed061b7cd1372572ddcd717f6df90b5e93/wrapt-2.1.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:de9f1a2bbc5ac7f6012ec24525bdd444765a2ff64b5985ac6e0692144838542e", size = 120417, upload-time = "2026-03-06T02:54:30.74Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b7/0138a6238c8ba7476c77cf786a807f871672b37f37a422970342308276e7/wrapt-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:970d57ed83fa040d8b20c52fe74a6ae7e3775ae8cff5efd6a81e06b19078484c", size = 122797, upload-time = "2026-03-06T02:54:51.539Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ad/819ae558036d6a15b7ed290d5b14e209ca795dd4da9c58e50c067d5927b0/wrapt-2.1.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3969c56e4563c375861c8df14fa55146e81ac11c8db49ea6fb7f2ba58bc1ff9a", size = 117350, upload-time = "2026-03-06T02:54:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2d/afc18dc57a4600a6e594f77a9ae09db54f55ba455440a54886694a84c71b/wrapt-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:57d7c0c980abdc5f1d98b11a2aa3bb159790add80258c717fa49a99921456d90", size = 121223, upload-time = "2026-03-06T02:54:35.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5b/5ec189b22205697bc56eb3b62aed87a1e0423e9c8285d0781c7a83170d15/wrapt-2.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:776867878e83130c7a04237010463372e877c1c994d449ca6aaafeab6aab2586", size = 116287, upload-time = "2026-03-06T02:54:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2d/f84939a7c9b5e6cdd8a8d0f6a26cabf36a0f7e468b967720e8b0cd2bdf69/wrapt-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fab036efe5464ec3291411fabb80a7a39e2dd80bae9bcbeeca5087fdfa891e19", size = 119593, upload-time = "2026-03-06T02:54:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/0b/fe/ccd22a1263159c4ac811ab9374c061bcb4a702773f6e06e38de5f81a1bdc/wrapt-2.1.2-cp314-cp314-win32.whl", hash = "sha256:e6ed62c82ddf58d001096ae84ce7f833db97ae2263bff31c9b336ba8cfe3f508", size = 58631, upload-time = "2026-03-06T02:53:06.498Z" }, + { url = "https://files.pythonhosted.org/packages/65/0a/6bd83be7bff2e7efaac7b4ac9748da9d75a34634bbbbc8ad077d527146df/wrapt-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:467e7c76315390331c67073073d00662015bb730c566820c9ca9b54e4d67fd04", size = 60875, upload-time = "2026-03-06T02:53:50.252Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c0/0b3056397fe02ff80e5a5d72d627c11eb885d1ca78e71b1a5c1e8c7d45de/wrapt-2.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:da1f00a557c66225d53b095a97eace0fc5349e3bfda28fa34ffae238978ee575", size = 59164, upload-time = "2026-03-06T02:53:59.128Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/5d89c798741993b2371396eb9d4634f009ff1ad8a6c78d366fe2883ea7a6/wrapt-2.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:62503ffbc2d3a69891cf29beeaccdb4d5e0a126e2b6a851688d4777e01428dbb", size = 63163, upload-time = "2026-03-06T02:52:54.873Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8c/05d277d182bf36b0a13d6bd393ed1dec3468a25b59d01fba2dd70fe4d6ae/wrapt-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7e6cd120ef837d5b6f860a6ea3745f8763805c418bb2f12eeb1fa6e25f22d22", size = 63723, upload-time = "2026-03-06T02:52:56.374Z" }, + { url = "https://files.pythonhosted.org/packages/f4/27/6c51ec1eff4413c57e72d6106bb8dec6f0c7cdba6503d78f0fa98767bcc9/wrapt-2.1.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3769a77df8e756d65fbc050333f423c01ae012b4f6731aaf70cf2bef61b34596", size = 152652, upload-time = "2026-03-06T02:53:23.79Z" }, + { url = "https://files.pythonhosted.org/packages/db/4c/d7dd662d6963fc7335bfe29d512b02b71cdfa23eeca7ab3ac74a67505deb/wrapt-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a76d61a2e851996150ba0f80582dd92a870643fa481f3b3846f229de88caf044", size = 158807, upload-time = "2026-03-06T02:53:35.742Z" }, + { url = "https://files.pythonhosted.org/packages/b4/4d/1e5eea1a78d539d346765727422976676615814029522c76b87a95f6bcdd/wrapt-2.1.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6f97edc9842cf215312b75fe737ee7c8adda75a89979f8e11558dfff6343cc4b", size = 146061, upload-time = "2026-03-06T02:52:57.574Z" }, + { url = "https://files.pythonhosted.org/packages/89/bc/62cabea7695cd12a288023251eeefdcb8465056ddaab6227cb78a2de005b/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4006c351de6d5007aa33a551f600404ba44228a89e833d2fadc5caa5de8edfbf", size = 155667, upload-time = "2026-03-06T02:53:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/e9/99/6f2888cd68588f24df3a76572c69c2de28287acb9e1972bf0c83ce97dbc1/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a9372fc3639a878c8e7d87e1556fa209091b0a66e912c611e3f833e2c4202be2", size = 144392, upload-time = "2026-03-06T02:54:22.41Z" }, + { url = "https://files.pythonhosted.org/packages/40/51/1dfc783a6c57971614c48e361a82ca3b6da9055879952587bc99fe1a7171/wrapt-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3144b027ff30cbd2fca07c0a87e67011adb717eb5f5bd8496325c17e454257a3", size = 150296, upload-time = "2026-03-06T02:54:07.848Z" }, + { url = "https://files.pythonhosted.org/packages/6c/38/cbb8b933a0201076c1f64fc42883b0023002bdc14a4964219154e6ff3350/wrapt-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:3b8d15e52e195813efe5db8cec156eebe339aaf84222f4f4f051a6c01f237ed7", size = 60539, upload-time = "2026-03-06T02:54:00.594Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/e5176e4b241c9f528402cebb238a36785a628179d7d8b71091154b3e4c9e/wrapt-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:08ffa54146a7559f5b8df4b289b46d963a8e74ed16ba3687f99896101a3990c5", size = 63969, upload-time = "2026-03-06T02:54:39Z" }, + { url = "https://files.pythonhosted.org/packages/5c/99/79f17046cf67e4a95b9987ea129632ba8bcec0bc81f3fb3d19bdb0bd60cd/wrapt-2.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:72aaa9d0d8e4ed0e2e98019cea47a21f823c9dd4b43c7b77bba6679ffcca6a00", size = 60554, upload-time = "2026-03-06T02:53:14.132Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c7/8528ac2dfa2c1e6708f647df7ae144ead13f0a31146f43c7264b4942bf12/wrapt-2.1.2-py3-none-any.whl", hash = "sha256:b8fd6fa2b2c4e7621808f8c62e8317f4aae56e59721ad933bac5239d913cf0e8", size = 43993, upload-time = "2026-03-06T02:53:12.905Z" }, +] + +[[package]] +name = "xlsxwriter" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940, upload-time = "2025-09-16T00:16:21.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" }, +]