├── .github
└── workflows
│ ├── python-lint.yml
│ ├── python-test.yml
│ └── release.yml
├── .gitignore
├── .markdownlint.jsonc
├── .prettierrc.yml
├── .readthedocs.yaml
├── README.md
├── docs
├── images
│ ├── logo-180.png
│ ├── nextmv-favicon.svg
│ └── scenario-test.png
├── index.md
├── nextmv-gurobipy
│ ├── index.md
│ ├── reference
│ │ ├── model.md
│ │ ├── options.md
│ │ ├── solution.md
│ │ └── statistics.md
│ └── tutorials
│ │ ├── model.md
│ │ ├── options.md
│ │ ├── solution.md
│ │ └── statistics.md
├── nextmv-scikit-learn
│ ├── index.md
│ ├── reference
│ │ ├── dummy
│ │ │ ├── model.md
│ │ │ ├── options.md
│ │ │ ├── solution.md
│ │ │ └── statistics.md
│ │ ├── ensemble
│ │ │ ├── model.md
│ │ │ ├── options.md
│ │ │ ├── solution.md
│ │ │ └── statistics.md
│ │ ├── linear_model
│ │ │ ├── model.md
│ │ │ ├── options.md
│ │ │ ├── solution.md
│ │ │ └── statistics.md
│ │ ├── ndarray.md
│ │ ├── neural_network
│ │ │ ├── model.md
│ │ │ ├── options.md
│ │ │ ├── solution.md
│ │ │ └── statistics.md
│ │ └── tree
│ │ │ ├── index.md
│ │ │ ├── model.md
│ │ │ ├── options.md
│ │ │ ├── solution.md
│ │ │ └── statistics.md
│ └── tutorials
│ │ ├── model.md
│ │ ├── options.md
│ │ ├── solution.md
│ │ └── statistics.md
├── nextmv
│ ├── index.md
│ ├── reference
│ │ ├── base_model.md
│ │ ├── cloud
│ │ │ ├── acceptance_test.md
│ │ │ ├── account.md
│ │ │ ├── application.md
│ │ │ ├── batch_experiment.md
│ │ │ ├── client.md
│ │ │ ├── input_set.md
│ │ │ ├── instance.md
│ │ │ ├── manifest.md
│ │ │ ├── run.md
│ │ │ ├── scenario.md
│ │ │ ├── secrets.md
│ │ │ ├── status.md
│ │ │ └── version.md
│ │ ├── input.md
│ │ ├── logger.md
│ │ ├── model.md
│ │ ├── options.md
│ │ └── output.md
│ └── tutorials
│ │ ├── applications
│ │ ├── external-runs.md
│ │ ├── manage.md
│ │ ├── push.md
│ │ ├── runs.md
│ │ └── secrets.md
│ │ ├── experiments
│ │ └── scenario-tests.md
│ │ ├── input.md
│ │ ├── logging.md
│ │ ├── model.md
│ │ ├── options.md
│ │ └── output.md
└── requirements.txt
├── mkdocs.yml
├── nextmv-gurobipy
├── LICENSE
├── README.md
├── nextmv_gurobipy
│ ├── __about__.py
│ ├── __init__.py
│ ├── model.py
│ ├── options.py
│ ├── solution.py
│ └── statistics.py
├── pyproject.toml
├── requirements.txt
└── tests
│ ├── __init__.py
│ ├── expected_option_parameters.json
│ ├── test_model.py
│ ├── test_options.py
│ ├── test_statistics.py
│ └── test_version.py
├── nextmv-py.code-workspace
├── nextmv-scikit-learn
├── LICENSE
├── README.md
├── nextmv_sklearn
│ ├── __about__.py
│ ├── __init__.py
│ ├── dummy
│ │ ├── __init__.py
│ │ ├── model.py
│ │ ├── options.py
│ │ ├── solution.py
│ │ └── statistics.py
│ ├── ensemble
│ │ ├── __init__.py
│ │ ├── model.py
│ │ ├── options.py
│ │ ├── solution.py
│ │ └── statistics.py
│ ├── linear_model
│ │ ├── __init__.py
│ │ ├── model.py
│ │ ├── options.py
│ │ ├── solution.py
│ │ └── statistics.py
│ ├── ndarray.py
│ ├── neural_network
│ │ ├── __init__.py
│ │ ├── model.py
│ │ ├── options.py
│ │ ├── solution.py
│ │ └── statistics.py
│ └── tree
│ │ ├── __init__.py
│ │ ├── model.py
│ │ ├── options.py
│ │ ├── solution.py
│ │ └── statistics.py
├── pyproject.toml
├── requirements.txt
└── tests
│ ├── __init__.py
│ ├── expected_decision_tree_option_parameters.json
│ ├── expected_dummy_option_parameters.json
│ ├── expected_gradient_boosting_option_parameters.json
│ ├── expected_linear_regression_option_parameters.json
│ ├── expected_mlp_regressor_option_parameters.json
│ ├── expected_random_forest_option_parameters.json
│ ├── test_model.py
│ ├── test_options.py
│ ├── test_solution.py
│ ├── test_statistics.py
│ └── test_version.py
├── nextmv
├── LICENSE
├── README.md
├── nextmv
│ ├── __about__.py
│ ├── __entrypoint__.py
│ ├── __init__.py
│ ├── base_model.py
│ ├── cloud
│ │ ├── __init__.py
│ │ ├── acceptance_test.py
│ │ ├── account.py
│ │ ├── application.py
│ │ ├── batch_experiment.py
│ │ ├── client.py
│ │ ├── input_set.py
│ │ ├── instance.py
│ │ ├── manifest.py
│ │ ├── package.py
│ │ ├── run.py
│ │ ├── safe.py
│ │ ├── scenario.py
│ │ ├── secrets.py
│ │ ├── status.py
│ │ └── version.py
│ ├── deprecated.py
│ ├── input.py
│ ├── logger.py
│ ├── model.py
│ ├── options.py
│ └── output.py
├── pyproject.toml
├── requirements.txt
└── tests
│ ├── __init__.py
│ ├── cloud
│ ├── __init__.py
│ ├── app.yaml
│ ├── test_application.py
│ ├── test_client.py
│ ├── test_manifest.py
│ ├── test_package.py
│ ├── test_run.py
│ ├── test_safe_name_id.py
│ └── test_scenario.py
│ ├── scripts
│ ├── __init__.py
│ ├── options1.py
│ ├── options2.py
│ ├── options3.py
│ ├── options4.py
│ ├── options5.py
│ ├── options6.py
│ ├── options7.py
│ └── options_deprecated.py
│ ├── test_base_model.py
│ ├── test_entrypoint
│ ├── __init__.py
│ └── test_entrypoint.py
│ ├── test_input.py
│ ├── test_logger.py
│ ├── test_model.py
│ ├── test_options.py
│ ├── test_output.py
│ └── test_version.py
└── ruff.toml
/.github/workflows/python-lint.yml:
--------------------------------------------------------------------------------
1 | name: python lint
2 | on: [push]
3 | jobs:
4 | python-lint:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
10 | package: ["nextmv", "nextmv-gurobipy", "nextmv-scikit-learn"]
11 | steps:
12 | - name: git clone
13 | uses: actions/checkout@v4
14 |
15 | - name: set up Python ${{ matrix.python-version }}
16 | uses: actions/setup-python@v5
17 | with:
18 | python-version: ${{ matrix.python-version }}
19 |
20 | - name: install dependencies
21 | run: |
22 | python -m pip install --upgrade pip
23 | pip install -r requirements.txt
24 | working-directory: ./${{ matrix.package }}
25 |
26 | - name: lint with ruff
27 | run: ruff check --output-format=github .
28 | working-directory: ./${{ matrix.package }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/python-test.yml:
--------------------------------------------------------------------------------
1 | name: python test
2 | on: [push]
3 | jobs:
4 | python-test:
5 | runs-on: ${{ matrix.platform }}
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
10 | platform: [ubuntu-latest, windows-latest, macos-latest, macos-13]
11 | package: ["nextmv", "nextmv-gurobipy", "nextmv-scikit-learn"]
12 | steps:
13 | - name: git clone
14 | uses: actions/checkout@v4
15 |
16 | - name: set up Python ${{ matrix.python-version }}
17 | uses: actions/setup-python@v5
18 | with:
19 | python-version: ${{ matrix.python-version }}
20 |
21 | - name: install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | pip install -r requirements.txt
25 | working-directory: ./${{ matrix.package }}
26 |
27 | - name: unit tests
28 | run: python -m unittest
29 | working-directory: ./${{ matrix.package }}
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv*/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
162 | # VSCode
163 | .vscode/
164 |
165 | # Documentation stuff
166 | site/
167 |
--------------------------------------------------------------------------------
/.markdownlint.jsonc:
--------------------------------------------------------------------------------
1 | // Reference: https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint
2 | // Rules: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#rules
3 | //
4 | // Run markdown linting:
5 | // - Install [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2):
6 | // npm install markdownlint-cli2 --global
7 | // - Lint. Stand at the root of the repo and run:
8 | // markdownlint-cli2 "**/*.md"
9 | // - Ignore a certain rule for some lines:
10 | //
11 | // Your line here
12 | //
13 | {
14 | // Use the default configurations as a starting point
15 | "default": true,
16 |
17 | // Max line length configurations
18 | "MD013": {
19 | "code_blocks": false,
20 | "headings": false,
21 | "line_length": 100,
22 | "stern": false,
23 | "strict": false,
24 | "tables": false
25 | },
26 |
27 | // GitBook compatibility configurations
28 | // Unordered lists use asterisks
29 | "MD004": { "style": "asterisk" },
30 |
31 | // First line in a file should be a top-level heading specifying a
32 | // description, as such:
33 | // ---
34 | // description: You will learn the basics of getting started on cloud.
35 | // ---
36 | "MD041": { "level": 1, "front_matter_title": "^\\s*description\\s*[:=]" },
37 |
38 | // There can only be a single top-level heading, which should be the YAML
39 | // front matter containing the "title" property.
40 | "MD025": { "level": 1, "front_matter_title": "^\\s*title\\s*[:=]" },
41 |
42 | // Don't enforce heading level continuity.
43 | "MD001": false,
44 |
45 | // Use spaces as opposed to hard-tabs for consistency.
46 | "MD010": { "spaces_per_tab": 4 },
47 |
48 | // Material for mkdocs has admonitions which are indented by 4 spaces and not
49 | // fenced.
50 | "MD046": false
51 | }
52 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | tabWidth: 2
2 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # Read the Docs configuration file for MkDocs projects
2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
3 |
4 | # Required
5 | version: 2
6 |
7 | # Set the version of Python and other tools you might need
8 | build:
9 | os: ubuntu-22.04
10 | tools:
11 | python: "3.11"
12 |
13 | mkdocs:
14 | configuration: mkdocs.yml
15 |
16 | # Optionally declare the Python requirements required to build your docs
17 | python:
18 | install:
19 | - requirements: docs/requirements.txt
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nextmv Python SDKs
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Welcome to `nextmv-py`, a repository for all of Nextmv’s Python SDKs. Here you
37 | can find the source for the following packages:
38 |
39 | * [`nextmv`](./nextmv/README.md): The general-purpose Python SDK for working
40 | with decision models and the Nextmv Cloud API.
41 | * [`nextmv-gurobipy`](./nextmv-gurobipy/README.md): A Python SDK providing
42 | convenience functions for working with Gurobi (`gurobipy`) models in the
43 | Nextmv platform.
44 | * [`nextmv-scikit-learn`](./nextmv-scikit-learn/README.md): A Python SDK
45 | providing convenience functions for working with scikit-learn models in the
46 | Nextmv platform.
47 |
48 | 📖 To learn more about the SDKs, visit the [docs][docs].
49 |
50 | > [!IMPORTANT]
51 | > Please review the LICENSE on each package, as they may not all have the same license.
52 |
53 | [docs]: https://nextmv-py.readthedocs.io/en/latest/
54 |
--------------------------------------------------------------------------------
/docs/images/logo-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/docs/images/logo-180.png
--------------------------------------------------------------------------------
/docs/images/nextmv-favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/images/scenario-test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/docs/images/scenario-test.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # The Nextmv Python SDKs
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | Nextmv offers several Python SDKs to help you work with decision models and the
37 | Nextmv Cloud API:
38 |
39 | * [`nextmv`][nextmv]: The general-purpose Python SDK for working with decision
40 | models and the Nextmv Cloud API.
41 | * [`nextmv-gurobipy`][nextmv-gurobipy]: A Python SDK providing convenience
42 | functions for working with Gurobi (`gurobipy`) models in the Nextmv
43 | platform.
44 | * [`nextmv-scikit-learn`][nextmv-scikit-learn]: A Python SDK providing
45 | convenience functions for working with `scikit-learn` models in the
46 | Nextmv platform.
47 |
48 | These packages can be found in the [`nextmv-py`][nextmv-py] repository.
49 |
50 | !!! warning
51 |
52 | Please review the license of each package, as they may not all have the same
53 | license. For example, `nextmv` is licensed under the Apache 2.0 license, while
54 | `nextmv-gurobipy` is licensed as BSL-1.1.
55 |
56 | The best place to start with the Nextmv Python SDKs is to check out the
57 | [community apps][community-apps-get-started]. These are fully-functional apps
58 | that run both locally and on Nextmv Cloud. Please direct your attention to the
59 | `python-*` apps.
60 |
61 | There are two ways in which you can use community apps:
62 |
63 | * Go to the [`community-apps` GitHub repository][community-apps-gh]. You can
64 | clone the repo, or view the apps directly in the GitHub UI.
65 | * Use the [Nextmv CLI][cli] to first list the available apps and clone them
66 | locally. Check out the guide [here][community-apps-get-started].
67 |
68 | [nextmv-py]: https://github.com/nextmv-io/nextmv-py
69 | [nextmv]: ./nextmv/index.md
70 | [nextmv-gurobipy]: ./nextmv-gurobipy/index.md
71 | [nextmv-scikit-learn]: ./nextmv-scikit-learn/index.md
72 | [community-apps-gh]: https://github.com/nextmv-io/community-apps
73 | [cli]: https://docs.nextmv.io/docs/using-nextmv/reference/cli
74 | [community-apps-get-started]: https://docs.nextmv.io/docs/use-cases/community-apps/get-started
75 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/index.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | The [Nextmv & Gurobi Python SDK][nextmv-gurobipy], `nextmv-gurobipy`, is a
23 | package to interact programmatically with Nextmv and the Gurobi solver, from
24 | Python. A great way to get started is to check out the [community
25 | apps][community-apps-get-started]. The following apps are a non-exhaustive list
26 | of concrete examples for using this SDK:
27 |
28 | * [`python-nextmv-gurobipy-knapsack`][python-nextmv-gurobipy-knapsack]
29 |
30 | !!! warning
31 |
32 | Please note that `nextmv-gurobipy` is provided as _source-available_
33 | software (not _open-source_). For further information, please refer to the
34 | [LICENSE](https://github.com/nextmv-io/nextmv-py/blob/develop/nextmv-gurobipy/LICENSE) file.
35 |
36 | ## Installation
37 |
38 | The package is hosted on [PyPI][nextmv-gurobipy-pypi]. Python `>=3.9` is
39 | required.
40 |
41 | Install via `pip`:
42 |
43 | ```bash
44 | pip install nextmv-gurobipy
45 | ```
46 |
47 | !!! tip
48 |
49 | Note that `nextmv-gurobipy` installs the `nextmv_gurobipy` package, which is
50 | the importable name for the SDK.
51 |
52 | [nextmv-gurobipy-pypi]: https://pypi.org/project/nextmv-gurobipy/
53 | [nextmv-gurobipy]: https://github.com/nextmv-io/nextmv-py/tree/develop/nextmv-gurobipy
54 | [community-apps-get-started]: https://docs.nextmv.io/docs/use-cases/community-apps/get-started
55 | [python-nextmv-gurobipy-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-nextmv-gurobipy-knapsack
56 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/reference/model.md:
--------------------------------------------------------------------------------
1 | # Model Module
2 |
3 | This section documents the model components of the Nextmv Gurobi Python SDK.
4 |
5 | ::: nextmv-gurobipy.nextmv_gurobipy.model
6 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/reference/options.md:
--------------------------------------------------------------------------------
1 | # Options Module
2 |
3 | This section documents the options components of the Nextmv Gurobi Python SDK.
4 |
5 | ::: nextmv-gurobipy.nextmv_gurobipy.options
6 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/reference/solution.md:
--------------------------------------------------------------------------------
1 | # Solution Module
2 |
3 | This section documents the solution components of the Nextmv Gurobi Python SDK.
4 |
5 | ::: nextmv-gurobipy.nextmv_gurobipy.solution
6 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/reference/statistics.md:
--------------------------------------------------------------------------------
1 | # Statistics Module
2 |
3 | This section documents the statistics components of the Nextmv Gurobi Python SDK.
4 |
5 | ::: nextmv-gurobipy.nextmv_gurobipy.statistics
6 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/tutorials/model.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | !!! tip "Reference"
4 |
5 | Find the reference for the `model` module [here](../reference/model.md).
6 |
7 | `Model` allows you to create a `gurobipy.Model` object from `nextmv.Options`.
8 | This convenience function allows you to set up a Gurobi model using the
9 | parameters that are customized through options. Notice that the return type is
10 | a `gurobipy.Model`.
11 |
12 | Consider the following script, which solves a simple knapsack problem.
13 |
14 | ```python
15 | import time
16 |
17 | import nextmv_gurobipy as ngp
18 | from gurobipy import GRB
19 |
20 | data = {
21 | "items": [
22 | {"id": "cat", "value": 100, "weight": 20},
23 | {"id": "dog", "value": 20, "weight": 45},
24 | {"id": "water", "value": 40, "weight": 2},
25 | {"id": "phone", "value": 6, "weight": 1},
26 | {"id": "book", "value": 63, "weight": 10},
27 | {"id": "rx", "value": 81, "weight": 1},
28 | {"id": "tablet", "value": 28, "weight": 8},
29 | {"id": "coat", "value": 44, "weight": 9},
30 | {"id": "laptop", "value": 51, "weight": 13},
31 | {"id": "keys", "value": 92, "weight": 1},
32 | {"id": "nuts", "value": 18, "weight": 4},
33 | ],
34 | "weight_capacity": 50,
35 | }
36 |
37 | options = ngp.ModelOptions().to_nextmv()
38 | start_time = time.time()
39 | model = ngp.Model(options)
40 |
41 | # Initializes the linear sums.
42 | weights = 0.0
43 | values = 0.0
44 |
45 | # Creates the decision variables and adds them to the linear sums.
46 | items = []
47 | for item in data["items"]:
48 | item_variable = model.addVar(vtype=GRB.BINARY, name=item["id"])
49 | items.append({"item": item, "variable": item_variable})
50 | weights += item_variable * item["weight"]
51 | values += item_variable * item["value"]
52 |
53 | # This constraint ensures the weight capacity of the knapsack will not be
54 | # exceeded.
55 | model.addConstr(weights <= data["weight_capacity"])
56 |
57 | # Sets the objective function: maximize the value of the chosen items.
58 | model.setObjective(expr=values, sense=GRB.MAXIMIZE)
59 |
60 | # Solves the problem.
61 | model.optimize()
62 | ```
63 |
64 | Run the script with custom options:
65 |
66 | ```bash
67 | python main.py -TimeLimit 1 -MIPGap 0.4
68 | ...
69 | ...
70 |
71 | Non-default parameters:
72 | TimeLimit 1
73 | MIPGap 0.4
74 |
75 | Optimize a model with 1 rows, 11 columns and 11 nonzeros
76 | Model fingerprint: 0x3fd9f770
77 | Variable types: 0 continuous, 11 integer (11 binary)
78 | Coefficient statistics:
79 | Matrix range [1e+00, 4e+01]
80 | Objective range [6e+00, 1e+02]
81 | Bounds range [1e+00, 1e+00]
82 | RHS range [5e+01, 5e+01]
83 | Found heuristic solution: objective 428.0000000
84 |
85 | Explored 0 nodes (0 simplex iterations) in 0.00 seconds (0.00 work units)
86 | Thread count was 1 (of 10 available processors)
87 |
88 | Solution count 1: 428
89 |
90 | Optimal solution found (tolerance 4.00e-01)
91 | Best objective 4.280000000000e+02, best bound 5.430000000000e+02, gap 26.8692%
92 | ```
93 |
94 | Notice how the solver output states which options were customized. Using the
95 | `nextmv_gurobipy.Model` class has other added benefits, besides the convenience
96 | of customizing solver options:
97 |
98 | * It instantiates an empty environment.
99 | * It redirects `stdout` to `stderr` for logging in Nextmv Cloud.
100 | * It loads the license automatically if the `license_path` is given.
101 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/tutorials/solution.md:
--------------------------------------------------------------------------------
1 | # Solution
2 |
3 | !!! tip "Reference"
4 |
5 | Find the reference for the `solution` module [here](../reference/solution.md).
6 |
7 | `ModelSolution` allows access to the solution of a `gurobipy.Model`. It is a
8 | simple convenience function that will collect the decision variables and their
9 | values.
10 |
11 | Consider the same script shown in the [model section][model], which solves a
12 | simple knapsack problem. You may use the `ModelSolution` to obtain the solution
13 | of the model. This convenience functionality is provided out of the box, but we
14 | recommend that you customize how the model is interpreted to parse the solution.
15 |
16 | ```python
17 | import json
18 |
19 | import nextmv_gurobipy as ngp
20 |
21 | # Model code here.
22 |
23 | solution = ngp.ModelSolution(model)
24 | print(json.dumps(solution, indent=2))
25 | ```
26 |
27 | Run the script:
28 |
29 | ```bash
30 | python main.py
31 | {
32 | "cat": 1.0,
33 | "dog": 0.0,
34 | "water": 1.0,
35 | "phone": 1.0,
36 | "book": 1.0,
37 | "rx": 1.0,
38 | "tablet": 0.0,
39 | "coat": 1.0,
40 | "laptop": 0.0,
41 | "keys": 1.0,
42 | "nuts": 1.0
43 | }
44 | ```
45 |
46 | [model]: ./model.md
47 |
--------------------------------------------------------------------------------
/docs/nextmv-gurobipy/tutorials/statistics.md:
--------------------------------------------------------------------------------
1 | # Statistics
2 |
3 | !!! tip "Reference"
4 |
5 | Find the reference for the `statistics` module [here](../reference/statistics.md).
6 |
7 | `ModelStatistics` allows access to the statistics of a `gurobipy.Model`. It is
8 | a convenience function that will collect a simple set of statistics.
9 |
10 | Consider the same script shown in the [model section][model], which solves a
11 | simple knapsack problem. You may use the `ModelStatistics` to obtain the
12 | statistics of the model. This convenience functionality is provided out of the
13 | box, but we recommend that you customize how the model is interpreted to
14 | extract statistics.
15 |
16 | ```python
17 | import json
18 |
19 | import nextmv_gurobipy as ngp
20 |
21 | # Model code here.
22 |
23 | statistics = ngp.ModelStatistics(model, start_time)
24 | print(json.dumps(statistics.to_dict(), indent=2))
25 | ```
26 |
27 | Run the script:
28 |
29 | ```bash
30 | python main.py
31 | {
32 | "run": {
33 | "duration": 0.004244089126586914
34 | },
35 | "result": {
36 | "duration": 0.00031304359436035156,
37 | "value": 444.0,
38 | "custom": {
39 | "status": "OPTIMAL",
40 | "variables": 11,
41 | "constraints": 1
42 | }
43 | },
44 | "series_data": {},
45 | "schema": "v1"
46 | }
47 | ```
48 |
49 | [model]: ./model.md
50 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/index.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | The [Nextmv & scikit-learn Python SDK][nextmv-scikit-learn],
23 | `nextmv-scikit-learn`, is a package to interact programmatically with Nextmv
24 | and the scikit-learn machine library, from Python. A great way to get started
25 | is to check out the [community apps][community-apps-get-started]. The following
26 | apps are a non-exhaustive list of concrete examples for using this SDK:
27 |
28 | * [`python-nextmv-scikit-learn-diabetes`][python-nextmv-scikit-learn-diabetes]
29 |
30 | !!! warning
31 |
32 | Please note that `nextmv-scikit-learn` is provided as _source-available_
33 | software (not _open-source_). For further information, please refer to the
34 | [LICENSE](https://github.com/nextmv-io/nextmv-py/blob/develop/nextmv-scikit-learn/LICENSE) file.
35 |
36 | The `nextmv-scikit-learn` package is a wrapper around the `scikit-learn`
37 | library to make it easy to use with Nextmv. It provides support for these
38 | models:
39 |
40 | * `dummy`:
41 | * [`DummyRegressor`][dummy-regressor]
42 | * `ensemble`:
43 | * [`GradientBoostingRegressor`][gradient-boosting-regressor]
44 | * [`RandomForestRegressor`][random-forest-regressor]
45 | * `linear_model`:
46 | * [`LinearRegression`][linear-regression]
47 | * `neural_network`:
48 | * [`MLPRegressor`][mlp-regressor]
49 | * `tree`:
50 | * [`DecisionTreeRegressor`][decision-tree-regressor]
51 |
52 | ## Installation
53 |
54 | The package is hosted on [PyPI][nextmv-scikit-learn-pypi]. Python `>=3.9` is
55 | required.
56 |
57 | Install via `pip`:
58 |
59 | ```bash
60 | pip install nextmv-scikit-learn
61 | ```
62 |
63 | !!! tip
64 |
65 | Note that `nextmv-scikit-learn` installs the `nextmv_sklearn` package, which
66 | is the importable name for the SDK.
67 |
68 | [nextmv-scikit-learn-pypi]: https://pypi.org/project/nextmv-scikit-learn/
69 | [nextmv-scikit-learn]: https://github.com/nextmv-io/nextmv-py/tree/develop/nextmv-scikit-learn
70 | [community-apps-get-started]: https://docs.nextmv.io/docs/use-cases/community-apps/get-started
71 | [python-nextmv-scikit-learn-diabetes]: https://github.com/nextmv-io/community-apps/blob/develop/python-nextmv-scikit-learn-diabetes
72 | [dummy-regressor]: https://scikit-learn.org/stable/modules/generated/sklearn.dummy.DummyRegressor.html
73 | [linear-regression]: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html
74 | [gradient-boosting-regressor]: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingRegressor.html
75 | [random-forest-regressor]: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestRegressor.html
76 | [mlp-regressor]: https://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPRegressor.html
77 | [decision-tree-regressor]: https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeRegressor.html
78 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/dummy/model.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | This section documents the model components of the dummy module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.dummy.model
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/dummy/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | This section documents the options components of the dummy module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.dummy.options
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/dummy/solution.md:
--------------------------------------------------------------------------------
1 | # Solution
2 |
3 | This section documents the solution components of the dummy module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.dummy.solution
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/dummy/statistics.md:
--------------------------------------------------------------------------------
1 | # Statistics
2 |
3 | This section documents the statistics components of the dummy module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.dummy.statistics
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/ensemble/model.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | This section documents the model components of the ensemble module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.ensemble.model
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/ensemble/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | This section documents the options components of the ensemble module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.ensemble.options
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/ensemble/solution.md:
--------------------------------------------------------------------------------
1 | # Solution
2 |
3 | This section documents the solution components of the ensemble module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.ensemble.solution
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/ensemble/statistics.md:
--------------------------------------------------------------------------------
1 | # Statistics
2 |
3 | This section documents the statistics components of the ensemble module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.ensemble.statistics
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/linear_model/model.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | This section documents the model components of the linear_model module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.linear_model.model
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/linear_model/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | This section documents the options components of the linear_model module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.linear_model.options
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/linear_model/solution.md:
--------------------------------------------------------------------------------
1 | # Solution
2 |
3 | This section documents the solution components of the linear_model module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.linear_model.solution
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/linear_model/statistics.md:
--------------------------------------------------------------------------------
1 | # Statistics
2 |
3 | This section documents the statistics components of the linear_model module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.linear_model.statistics
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/ndarray.md:
--------------------------------------------------------------------------------
1 | # ndarray Module
2 |
3 | This section documents the NumPy ndarray utilities of the nextmv-scikit-learn package.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.ndarray
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/neural_network/model.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | This section documents the model components of the neural_network module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.neural_network.model
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/neural_network/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | This section documents the options components of the neural_network module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.neural_network.options
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/neural_network/solution.md:
--------------------------------------------------------------------------------
1 | # Solution
2 |
3 | This section documents the solution components of the neural_network module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.neural_network.solution
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/neural_network/statistics.md:
--------------------------------------------------------------------------------
1 | # Statistics
2 |
3 | This section documents the statistics components of the neural_network module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.neural_network.statistics
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/tree/index.md:
--------------------------------------------------------------------------------
1 | # Tree Models
2 |
3 | This section documents the decision tree model components of the nextmv-scikit-learn package.
4 |
5 | ## Model
6 |
7 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.model
8 |
9 | ## Options
10 |
11 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.options
12 |
13 | ## Solution
14 |
15 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.solution
16 |
17 | ## Statistics
18 |
19 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.statistics
20 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/tree/model.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | This section documents the model components of the tree module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.model
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/tree/options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | This section documents the options components of the tree module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.options
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/tree/solution.md:
--------------------------------------------------------------------------------
1 | # Solution
2 |
3 | This section documents the solution components of the tree module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.solution
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/reference/tree/statistics.md:
--------------------------------------------------------------------------------
1 | # Statistics
2 |
3 | This section documents the statistics components of the tree module.
4 |
5 | ::: nextmv-scikit-learn.nextmv_sklearn.tree.statistics
6 |
--------------------------------------------------------------------------------
/docs/nextmv-scikit-learn/tutorials/model.md:
--------------------------------------------------------------------------------
1 | # Model
2 |
3 | Each sub-module of `nextmv_sklearn` has a convenience function for creating
4 | native `sklearn` model objects from `nextmv.Options`. This convenience function
5 | allows you to set up a model using the parameters that are customized through
6 | options. Notice that the return type is an `sklearn.`.
7 |
8 | Consider the following examples, which make use of the classic diabetes dataset
9 | to fit a model and then make a prediction.
10 |
11 | ## Dummy
12 |
13 | !!! tip "Reference"
14 |
15 | Find the reference for the `dummy.model` module [here](../reference/dummy/model.md).
16 |
17 | ```python
18 | import time
19 |
20 | from nextmv_sklearn import dummy
21 | from sklearn.datasets import load_diabetes
22 |
23 | X, y = load_diabetes(return_X_y=True)
24 |
25 | start_time = time.time()
26 |
27 | options = dummy.DummyRegressorOptions().to_nextmv()
28 | model = dummy.DummyRegressor(options)
29 |
30 | fit = model.fit(X, y)
31 | print(fit.predict(X[:1]))
32 | ```
33 |
34 | Run the script:
35 |
36 | ```bash
37 | $ python main.py --help
38 | [152.13348416]
39 | ```
40 |
41 | ## Ensemble
42 |
43 | !!! tip "Reference"
44 |
45 | Find the reference for the `ensemble.model` module [here](../reference/ensemble/model.md).
46 |
47 | * `GradientBoostingRegressor`
48 |
49 | ```python
50 | import time
51 |
52 | from nextmv_sklearn import ensemble
53 | from sklearn.datasets import load_diabetes
54 |
55 | X, y = load_diabetes(return_X_y=True)
56 |
57 | start_time = time.time()
58 |
59 | options = ensemble.GradientBoostingRegressorOptions().to_nextmv()
60 | model = ensemble.GradientBoostingRegressor(options)
61 |
62 | fit = model.fit(X, y)
63 | print(fit.predict(X[:1]))
64 | ```
65 |
66 | Run the script:
67 |
68 | ```bash
69 | $ python main.py --help
70 | [200.87337372]
71 | ```
72 |
73 | * `RandomForestRegressor`
74 |
75 | ```python
76 | import time
77 |
78 | from nextmv_sklearn import ensemble
79 | from sklearn.datasets import load_diabetes
80 |
81 | X, y = load_diabetes(return_X_y=True)
82 |
83 | start_time = time.time()
84 |
85 | options = ensemble.RandomForestRegressorOptions().to_nextmv()
86 | model = ensemble.RandomForestRegressor(options)
87 |
88 | fit = model.fit(X, y)
89 | print(fit.predict(X[:1]))
90 | ```
91 |
92 | Run the script:
93 |
94 | ```bash
95 | $ python main.py --help
96 | [180.54]
97 | ```
98 |
99 | ## Linear model
100 |
101 | !!! tip "Reference"
102 |
103 | Find the reference for the `linear_model.model` module [here](../reference/linear_model/model.md).
104 |
105 | ```python
106 | import time
107 |
108 | from nextmv_sklearn import linear_model
109 | from sklearn.datasets import load_diabetes
110 |
111 | X, y = load_diabetes(return_X_y=True)
112 |
113 | start_time = time.time()
114 |
115 | options = linear_model.LinearRegressionOptions().to_nextmv()
116 | model = linear_model.LinearRegression(options)
117 |
118 | fit = model.fit(X, y)
119 | print(fit.predict(X[:1]))
120 | ```
121 |
122 | Run the script:
123 |
124 | ```bash
125 | $ python main.py --help
126 | [206.11667725]
127 | ```
128 |
129 | ## Neural network
130 |
131 | !!! tip "Reference"
132 |
133 | Find the reference for the `neural_network.model` module [here](../reference/neural_network/model.md).
134 |
135 | ```python
136 | import time
137 |
138 | from nextmv_sklearn import neural_network
139 | from sklearn.datasets import load_diabetes
140 |
141 | X, y = load_diabetes(return_X_y=True)
142 |
143 | start_time = time.time()
144 |
145 | options = neural_network.MLPRegressorOptions().to_nextmv()
146 | model = neural_network.MLPRegressor(options)
147 |
148 | fit = model.fit(X, y)
149 | print(fit.predict(X[:1]))
150 | ```
151 |
152 | Run the script:
153 |
154 | ```bash
155 | $ python main.py -max_iter 2500
156 | [195.53288524]
157 | ```
158 |
159 | ## Tree
160 |
161 | !!! tip "Reference"
162 |
163 | Find the reference for the `tree.model` module [here](../reference/tree/model.md).
164 |
165 | ```python
166 | import time
167 |
168 | from nextmv_sklearn import tree
169 | from sklearn.datasets import load_diabetes
170 |
171 | X, y = load_diabetes(return_X_y=True)
172 |
173 | start_time = time.time()
174 |
175 | options = tree.DecisionTreeRegressorOptions().to_nextmv()
176 | model = tree.DecisionTreeRegressor(options)
177 |
178 | fit = model.fit(X, y)
179 | print(fit.predict(X[:1]))
180 | ```
181 |
182 | Run the script:
183 |
184 | ```bash
185 | $ python main.py
186 | [151.]
187 | ```
188 |
--------------------------------------------------------------------------------
/docs/nextmv/index.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | The [Nextmv Python SDK][nextmv], `nextmv`, is a package to interact
23 | programmatically with the Nextmv Platform from Python. A great way to get
24 | started is to check out the [community apps][community-apps-get-started]. The
25 | following apps are a non-exhaustive list of concrete examples for using this
26 | SDK:
27 |
28 | * [`python-ampl-facilitylocation`][python-ampl-facilitylocation],
29 | [`python-ampl-knapsack`][python-ampl-knapsack],
30 | [`python-ampl-priceoptimization`][python-ampl-priceoptimization]
31 | * [`python-gurobi-knapsack`][python-gurobi-knapsack]
32 | * [`python-hello-world`][python-hello-world]
33 | * [`python-hexaly-knapsack`][python-hexaly-knapsack]
34 | * [`python-highs-knapsack`][python-highs-knapsack]
35 | * [`python-nextroute`][python-nextroute]
36 | * [`python-ortools-costflow`][python-ortools-costflow],
37 | [`python-ortools-demandforecasting`][python-ortools-demandforecasting],
38 | [`python-ortools-knapsack-multicsv`][python-ortools-knapsack-multicsv],
39 | [`python-ortools-knapsack`][python-ortools-knapsack],
40 | [`python-ortools-routing`][python-ortools-routing],
41 | [`python-ortools-shiftassignment`][python-ortools-shiftassignment],
42 | [`python-ortools-shiftplanning`][python-ortools-shiftplanning]
43 | * [`python-pyomo-knapsack`][python-pyomo-knapsack],
44 | [`python-pyomo-shiftassignment`][python-pyomo-shiftassignment],
45 | [`python-pyomo-shiftplanning`][python-pyomo-shiftplanning]
46 | * [`python-pyoptinterface-knapsack`][python-pyoptinterface-knapsack]
47 | * [`python-pyvroom-routing`][python-pyvroom-routing]
48 | * [`python-xpress-knapsack`][python-xpress-knapsack]
49 |
50 | ## Installation
51 |
52 | The package is hosted on [PyPI][nextmv-pypi]. Python `>=3.9` is required.
53 |
54 | Install via `pip`:
55 |
56 | ```bash
57 | pip install nextmv
58 | ```
59 |
60 | Install all optional dependencies (recommended):
61 |
62 | ```bash
63 | pip install "nextmv[all]"
64 | ```
65 |
66 | [nextmv-pypi]: https://pypi.org/project/nextmv/
67 | [nextmv]: https://github.com/nextmv-io/nextmv-py/tree/develop/nextmv
68 | [community-apps-get-started]: https://docs.nextmv.io/docs/use-cases/community-apps/get-started
69 |
70 | [python-ampl-facilitylocation]: https://github.com/nextmv-io/community-apps/blob/develop/python-ampl-facilitylocation
71 | [python-ampl-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-ampl-knapsack
72 | [python-ampl-priceoptimization]: https://github.com/nextmv-io/community-apps/blob/develop/python-ampl-priceoptimization
73 | [python-gurobi-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-gurobi-knapsack
74 | [python-hello-world]: https://github.com/nextmv-io/community-apps/blob/develop/python-hello-world
75 | [python-hexaly-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-hexaly-knapsack
76 | [python-highs-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-highs-knapsack
77 | [python-nextroute]: https://github.com/nextmv-io/community-apps/blob/develop/python-nextroute
78 | [python-ortools-costflow]: https://github.com/nextmv-io/community-apps/blob/develop/python-ortools-costflow
79 | [python-ortools-demandforecasting]: https://github.com/nextmv-io/community-apps/blob/develop/python-ortools-demandforecasting
80 | [python-ortools-knapsack-multicsv]: https://github.com/nextmv-io/community-apps/blob/develop/python-ortools-knapsack-multicsv
81 | [python-ortools-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-ortools-knapsack
82 | [python-ortools-routing]: https://github.com/nextmv-io/community-apps/blob/develop/python-ortools-routing
83 | [python-ortools-shiftassignment]: https://github.com/nextmv-io/community-apps/blob/develop/python-ortools-shiftassignment
84 | [python-ortools-shiftplanning]: https://github.com/nextmv-io/community-apps/blob/develop/python-ortools-shiftplanning
85 | [python-pyomo-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-pyomo-knapsack
86 | [python-pyomo-shiftassignment]: https://github.com/nextmv-io/community-apps/blob/develop/python-pyomo-shiftassignment
87 | [python-pyomo-shiftplanning]: https://github.com/nextmv-io/community-apps/blob/develop/python-pyomo-shiftplanning
88 | [python-pyoptinterface-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-pyoptinterface-knapsack
89 | [python-pyvroom-routing]: https://github.com/nextmv-io/community-apps/blob/develop/python-pyvroom-routing
90 | [python-xpress-knapsack]: https://github.com/nextmv-io/community-apps/blob/develop/python-xpress-knapsack
91 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/base_model.md:
--------------------------------------------------------------------------------
1 | # Base Model Module
2 |
3 | This section documents the base model components of the Nextmv Python SDK.
4 |
5 | ::: nextmv.nextmv.base_model
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/acceptance_test.md:
--------------------------------------------------------------------------------
1 | # Acceptance Test Module
2 |
3 | This section documents the acceptance test components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.acceptance_test
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/account.md:
--------------------------------------------------------------------------------
1 | # Account Module
2 |
3 | This section documents the account components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.account
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/application.md:
--------------------------------------------------------------------------------
1 | # Application Module
2 |
3 | This section documents the application components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.application
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/batch_experiment.md:
--------------------------------------------------------------------------------
1 | # Batch Experiment Module
2 |
3 | This section documents the batch experiment components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.batch_experiment
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/client.md:
--------------------------------------------------------------------------------
1 | # Client Module
2 |
3 | This section documents the client components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.client
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/input_set.md:
--------------------------------------------------------------------------------
1 | # Input Set Module
2 |
3 | This section documents the input set components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.input_set
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/instance.md:
--------------------------------------------------------------------------------
1 | # Instance Module
2 |
3 | This section documents the instance components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.instance
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/manifest.md:
--------------------------------------------------------------------------------
1 | # Manifest Module
2 |
3 | This section documents the manifest components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.manifest
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/run.md:
--------------------------------------------------------------------------------
1 | # Run Module
2 |
3 | This section documents the run components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.run
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/scenario.md:
--------------------------------------------------------------------------------
1 | # Scenario Module
2 |
3 | This section documents the scenario components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.scenario
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/secrets.md:
--------------------------------------------------------------------------------
1 | # Secrets Module
2 |
3 | This section documents the secrets components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.secrets
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/status.md:
--------------------------------------------------------------------------------
1 | # Status Module
2 |
3 | This section documents the status components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.status
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/cloud/version.md:
--------------------------------------------------------------------------------
1 | # Version Module
2 |
3 | This section documents the version components of the Nextmv Cloud API.
4 |
5 | ::: nextmv.nextmv.cloud.version
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/input.md:
--------------------------------------------------------------------------------
1 | # Input Module
2 |
3 | This section documents the input components of the Nextmv Python SDK.
4 |
5 | ::: nextmv.nextmv.input
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/logger.md:
--------------------------------------------------------------------------------
1 | # Logger Module
2 |
3 | This section documents the logging components of the Nextmv Python SDK.
4 |
5 | ::: nextmv.nextmv.logger
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/model.md:
--------------------------------------------------------------------------------
1 | # Model Module
2 |
3 | This section documents the model components of the Nextmv Python SDK.
4 |
5 | ::: nextmv.nextmv.model
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/options.md:
--------------------------------------------------------------------------------
1 | # Options Module
2 |
3 | This section documents the options components of the Nextmv Python SDK.
4 |
5 | ::: nextmv.nextmv.options
6 |
--------------------------------------------------------------------------------
/docs/nextmv/reference/output.md:
--------------------------------------------------------------------------------
1 | # Output Module
2 |
3 | This section documents the output components of the Nextmv Python SDK.
4 |
5 | ::: nextmv.nextmv.output
6 |
--------------------------------------------------------------------------------
/docs/nextmv/tutorials/applications/manage.md:
--------------------------------------------------------------------------------
1 | # Managing applications
2 |
3 | One of the main features of `nextmv`, the general Nextmv Python SDK, is the
4 | ability to interact with the Nextmv Platform. This allows you to run, and
5 | experiment with your decision applications in Cloud.
6 |
7 | Before starting:
8 |
9 | 1. [Sign up][signup] for a Nextmv account.
10 | 2. Get your API key. Go to [Team > API Key][api-key].
11 |
12 | Make sure that you have your API key set as an environment variable:
13 |
14 | ```bash
15 | export NEXTMV_API_KEY=""
16 | ```
17 |
18 | Once you have your API key set up, you can create a cloud
19 | [`Client`][reference-client]:
20 |
21 | ```python
22 | import os
23 | from nextmv import cloud
24 |
25 | client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
26 | ```
27 |
28 | ## Application
29 |
30 | An application represents a decision model that you can run in the Nextmv
31 | Cloud. The [`Application`][reference-application] class defines the methods to
32 | interact with an app. You can create an app in multiple ways:
33 |
34 | * Through the Python SDK (see next header).
35 | * Through the [Nextmv CLI][cli].
36 | * Through the Nextmv Cloud Console.
37 |
38 | Once you have an app, and can identify its ID, you can interact with it.
39 |
40 | ### Create an app
41 |
42 | You can create an app by providing the [`Client`][reference-client] instance
43 | and the app ID:
44 |
45 | ```python
46 | import os
47 |
48 | from nextmv import cloud
49 |
50 | client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
51 | application = cloud.Application.new(
52 | client=client,
53 | name="My Application",
54 | id="",
55 | description="My first application",
56 | )
57 | ```
58 |
59 | ### Delete an app
60 |
61 | You can delete an application by using its ID.
62 |
63 | ```python
64 | import os
65 |
66 | from nextmv import cloud
67 |
68 | client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
69 | application = cloud.Application(client=client, id="")
70 | application.delete()
71 | ```
72 |
73 | ### Do more
74 |
75 | !!! tip "Reference"
76 |
77 | Find the reference for the `Application` class [here](../../reference/cloud/application.md).
78 |
79 | Make sure to check out the latest version of `nextmv` to see all the available
80 | features of the [`Application`][reference-application]. Here are some of the
81 | methods you can use that were not covered in this guide:
82 |
83 | * `acceptance_test`: gets an acceptance test.
84 | * `batch_experiment`: gets a batch experiment.
85 | * `cancel_run`: cancels a run.
86 | * `delete_batch_experiment`: deletes a batch experiment.
87 | * `delete_acceptance_test`: deletes an acceptance test.
88 | * `input_set`: gets an input set.
89 | * `instance`: gets an instance.
90 | * `list_acceptance_tests`: lists acceptance tests.
91 | * `list_batch_experiments`: lists batch experiments.
92 | * `list_input_sets`: lists input sets.
93 | * `list_instances`: lists instances.
94 | * `list_versions`: lists versions.
95 | * `new_acceptance_test`: creates a new acceptance test.
96 | * `new_acceptance_test_with_result`: creates a new acceptance test and gets the result.
97 | * `new_batch_experiment`: creates a new batch experiment.
98 | * `new_input_set`: creates a new input set.
99 | * `new_version`: creates a new version.
100 | * `new_instance`: creates a new instance.
101 | * `run_input`: gets the input of a run.
102 | * `run_logs`: gets the logs of a run.
103 | * `run_metadata`: gets the metadata of a run.
104 | * `run_result`: gets the result of a run.
105 | * `run_result_with_polling`: gets the result of a run with polling.
106 | * `update_instance`: updates an instance.
107 | * `upload_large_input`: uploads a large input.
108 | * `upload_url`: gets the URL for uploading.
109 | * `version`: gets a version.
110 |
111 | [signup]: https://cloud.nextmv.io
112 | [api-key]: https://cloud.nextmv.io/team/api-keys
113 | [cli]: https://docs.nextmv.io/docs/using-nextmv/reference/cli
114 | [reference-client]: ../../reference/cloud/client.md
115 | [reference-application]: ../../reference/cloud/application.md
116 |
--------------------------------------------------------------------------------
/docs/nextmv/tutorials/applications/runs.md:
--------------------------------------------------------------------------------
1 | # Run an Application
2 |
3 | !!! tip "Reference"
4 |
5 | Find the reference for the `Application` class [here](../../reference/cloud/application.md).
6 |
7 | To run an `Application`, you can use of the following methods:
8 |
9 | * `new_run`: creates (submits) a new run and returns the ID (`run_id`) of the
10 | run. With the `run_id`, you can perform other operations, such as getting the
11 | run’s metadata, logs, and result.
12 | * `new_run_with_result`: does the same as `new_run`, but it also polls for the
13 | result of the run. This method returns the result of the run, and it is
14 | useful for submitting and getting the result in a single call. Using this
15 | method is recommended because we have a built-in polling mechanism that
16 | handles retries, exponential backoff, jitter, and timeouts.
17 |
18 | The following example shows how to make a new run.
19 |
20 | ```python
21 | import os
22 |
23 | from nextmv import cloud
24 |
25 | client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
26 | app = cloud.Application(client=client, id="")
27 |
28 | run_id = app.new_run(
29 | input={"foo": "bar"},
30 | )
31 |
32 | print(run_id)
33 | ```
34 |
35 | ```bash
36 | $ python main.py
37 | devint-9KzmD41Hg
38 | ```
39 |
40 | As mentioned before, you may use the recommended `new_run_with_result` method
41 | and get the results immediately after submitting the run and they become
42 | available.
43 |
44 | ```python
45 | import json
46 | import os
47 |
48 | from nextmv import cloud
49 |
50 | client = cloud.Client(api_key=os.getenv("NEXTMV_API_KEY"))
51 | app = cloud.Application(client=client, id="")
52 |
53 | result = app.new_run_with_result(
54 | input={...},
55 | )
56 |
57 | print(json.dumps(result.to_dict(), indent=2))
58 |
59 | ```
60 |
61 | ```bash
62 | $ python main.py
63 | {
64 | "description": "",
65 | "id": "devint-srVCH41NR",
66 | "metadata": {
67 | "application_id": "...",
68 | "application_instance_id": "devint",
69 | "application_version_id": "",
70 | "created_at": "2025-04-18T03:56:30Z",
71 | "duration": 14302.0,
72 | "error": "",
73 | "input_size": 502.0,
74 | "output_size": 1075.0,
75 | "status": "succeeded",
76 | "status_v2": "succeeded"
77 | },
78 | "name": "",
79 | "user_email": "sebastian@nextmv.io",
80 | "output": {
81 | "options": {
82 | "input": "",
83 | "output": "",
84 | "duration": 30,
85 | "provider": "SCIP"
86 | },
87 | "solution": {...},
88 | "statistics": {
89 | "run": {
90 | "duration": 0.004137277603149414
91 | },
92 | "result": {
93 | "duration": 0.002,
94 | "value": 444.0,
95 | "custom": {
96 | "status": "optimal",
97 | "variables": 11,
98 | "constraints": 1
99 | }
100 | },
101 | "schema": "v1"
102 | },
103 | "assets": []
104 | }
105 | }
106 | ```
107 |
--------------------------------------------------------------------------------
/docs/nextmv/tutorials/input.md:
--------------------------------------------------------------------------------
1 | # Input
2 |
3 | !!! tip "Reference"
4 |
5 | Find the reference for the `input` module [here](../reference/input.md).
6 |
7 | Capture the input data for the run.
8 |
9 | ## `JSON` input
10 |
11 | Work with `JSON` inputs.
12 |
13 | ```python
14 | import nextmv
15 |
16 | # Read JSON from stdin.
17 | json_input_1 = nextmv.load()
18 | print(json_input_1.data)
19 |
20 | # Can also specify JSON format directly, and read from a file.
21 | json_input_2 = nextmv.load(input_format=nextmv.InputFormat.JSON, path="input.json")
22 | print(json_input_2.data)
23 | ```
24 |
25 | ## `TEXT` input
26 |
27 | Work with plain, `utf-8` encoded, text inputs.
28 |
29 | ```python
30 | import nextmv
31 |
32 | # Read text from stdin.
33 | text_input_1 = nextmv.load(input_format=nextmv.InputFormat.TEXT)
34 | print(text_input_1.data)
35 |
36 | # Can also read from a file.
37 | text_input_2 = nextmv.load(input_format=nextmv.InputFormat.TEXT, path="input.txt")
38 | print(text_input_2.data)
39 | ```
40 |
41 | ## `CSV` input
42 |
43 | Work with one, or multiple, `CSV` files. In the resulting `data` property of
44 | the input, the keys are the filenames and the values are the dataframes,
45 | represented as a list of dictionaries.
46 |
47 | ```python
48 | import nextmv
49 |
50 | # Read multiple CSV files from a dir named "input".
51 | csv_archive_input_1 = nextmv.load(input_format=nextmv.InputFormat.CSV_ARCHIVE)
52 | print(csv_archive_input_1.data)
53 |
54 | # Read multiple CSV files from a custom dir.
55 | csv_archive_input_2 = nextmv.load(input_format=nextmv.InputFormat.CSV_ARCHIVE, path="custom_dir")
56 | print(csv_archive_input_2.data)
57 | ```
58 |
--------------------------------------------------------------------------------
/docs/nextmv/tutorials/logging.md:
--------------------------------------------------------------------------------
1 | # Logging
2 |
3 | !!! tip "Reference"
4 |
5 | Find the reference for the `logger` module [here](../reference/logger.md).
6 |
7 | The Nextmv platform captures logs via `stderr`. Please note that any messages
8 | printed to `stdout` will not be displayed in Nextmv Cloud. Use the provided
9 | functionality to record logs. The `redirect_stdout` function is particularly
10 | useful when you want to redirect `stdout` to `stderr` for logging purposes.
11 | Many solvers and libraries print messages to `stdout`, which can be redirected
12 | to `stderr` for logging.
13 |
14 | ```python
15 | import sys
16 |
17 | import nextmv
18 |
19 | print("0. I do nothing")
20 |
21 | nextmv.redirect_stdout()
22 |
23 | nextmv.log("1. I log a message to stderr")
24 |
25 | print("2. I print a message to stdout")
26 |
27 | nextmv.reset_stdout()
28 |
29 | print("3. I print another message to stdout")
30 |
31 | print("4. I print yet another message to stderr without the logger", file=sys.stderr)
32 |
33 | nextmv.log("5. I log a message to stderr using the nextmv module directly")
34 |
35 | print("6. I print a message to stdout, again")
36 | ```
37 |
38 | After executing it, here are the messages printed to the different streams.
39 |
40 | * `stdout`:
41 |
42 | ```txt
43 | 1. I do nothing
44 | 2. I print another message to stdout
45 | 3. I print a message to stdout, again
46 | ```
47 |
48 | * `stderr`:
49 |
50 | ```txt
51 | 1. I log a message to stderr
52 | 2. I print a message to stdout
53 | 3. I print yet another message to stderr without the logger
54 | 4. I log a message to stderr using the nextmv module directly
55 | ```
56 |
--------------------------------------------------------------------------------
/docs/nextmv/tutorials/output.md:
--------------------------------------------------------------------------------
1 | # Output
2 |
3 | !!! tip "Reference"
4 |
5 | Find the reference for the `output` module [here](../reference/output.md).
6 |
7 | Write the output data after a run is completed.
8 |
9 | ## `JSON` outputs
10 |
11 | Work with `JSON`output.
12 |
13 | ```python
14 | import nextmv
15 |
16 | output = nextmv.Output(
17 | solution={"foo": "bar"},
18 | statistics=nextmv.Statistics(
19 | result=nextmv.ResultStatistics(
20 | duration=1.0,
21 | value=2.0,
22 | custom={"custom": "result_value"},
23 | ),
24 | run=nextmv.RunStatistics(
25 | duration=3.0,
26 | iterations=4,
27 | custom={"custom": "run_value"},
28 | ),
29 | ),
30 | )
31 |
32 | # Write to stdout.
33 | nextmv.write(output)
34 |
35 | # Write to a file.
36 | nextmv.write(output, path="output.json")
37 | ```
38 |
39 | The `solution` property of the output is a dictionary that represents the
40 | output data. The `statistics` property can be a `nextmv.Statistics` object, or
41 | a dictionary.
42 |
43 | ## `CSV` output
44 |
45 | Work with one, or multiple, `CSV` files. In the `solution` property of the
46 | output, the keys are the filenames and the values are the dataframes,
47 | represented as a list of dictionaries.
48 |
49 | ```python
50 | import nextmv
51 |
52 | output = nextmv.Output(
53 | output_format=nextmv.OutputFormat.CSV_ARCHIVE,
54 | solution={
55 | "output": [
56 | {"name": "Alice", "age": 30},
57 | {"name": "Bob", "age": 40},
58 | ],
59 | },
60 | statistics=nextmv.Statistics(
61 | result=nextmv.ResultStatistics(
62 | duration=1.0,
63 | value=2.0,
64 | custom={"custom": "result_value"},
65 | ),
66 | run=nextmv.RunStatistics(
67 | duration=3.0,
68 | iterations=4,
69 | custom={"custom": "run_value"},
70 | ),
71 | ),
72 | )
73 |
74 | # Write multiple CSV fiules to a dir named "output".
75 | nextmv.write(output)
76 |
77 | # Write multiple CSV files to a custom dir.
78 | nextmv.write(output, "custom_dir")
79 | ```
80 |
81 | Similarly to the `JSON` output, the `statistics` property can be a
82 | `nextmv.Statistics` object, or a dictionary.
83 |
84 | ## Assets
85 |
86 | A run in Nextmv Cloud can include [custom assets][custom-assets], such as those
87 | used in [custom visualization][custom-visualization].
88 |
89 | You can use the `assets` property of the output to include these assets.
90 |
91 | ### Example
92 |
93 | You can create a simple plot, which consists of a Plotly bar chart with radius
94 | and distance for a planet. Consider the following `visuals.py` file:
95 |
96 | ```python
97 | import json
98 |
99 | import nextmv
100 | import plotly.graph_objects as go
101 |
102 |
103 | def create_visuals(name: str, radius: float, distance: float) -> list[nextmv.Asset]:
104 | """Create a Plotly bar chart with radius and distance for a planet."""
105 |
106 | fig = go.Figure()
107 | fig.add_trace(
108 | go.Bar(x=[name], y=[radius], name="Radius (km)", marker_color="red", opacity=0.5),
109 | )
110 | fig.add_trace(
111 | go.Bar(x=[name], y=[distance], name="Distance (Millions km)", marker_color="blue", opacity=0.5),
112 | )
113 | fig.update_layout(
114 | title="Radius and Distance by Planet", xaxis_title="Planet", yaxis_title="Values", barmode="group"
115 | )
116 | fig = fig.to_json()
117 |
118 | assets = [
119 | nextmv.Asset(
120 | name="Plotly example",
121 | content_type="json",
122 | visual=nextmv.Visual(
123 | visual_schema=nextmv.VisualSchema.PLOTLY,
124 | visual_type="custom-tab",
125 | label="Charts",
126 | ),
127 | content=[json.loads(fig)],
128 | )
129 | ]
130 |
131 | return assets
132 | ```
133 |
134 | This list of assets can be included in the output.
135 |
136 | ```python
137 | import nextmv
138 | from visuals import create_visuals
139 |
140 | # Read the input from stdin.
141 | input = nextmv.load()
142 | name = input.data["name"]
143 |
144 | options = nextmv.Options(
145 | nextmv.Option("details", bool, True, "Print details to logs. Default true.", False),
146 | )
147 |
148 | ##### Insert model here
149 |
150 | # Print logs that render in the run view in Nextmv Console.
151 | message = f"Hello, {name}"
152 | nextmv.log(message)
153 |
154 | if options.details:
155 | detail = "You are", {input.data["distance"]}, " million km from the sun"
156 | nextmv.log(detail)
157 |
158 | assets = create_visuals(name, input.data["radius"], input.data["distance"])
159 |
160 | # Write output and statistics.
161 | output = nextmv.Output(
162 | options=options,
163 | solution=None,
164 | statistics=nextmv.Statistics(
165 | result=nextmv.ResultStatistics(
166 | value=1.23,
167 | custom={"message": message},
168 | ),
169 | ),
170 | assets=assets,
171 | )
172 | nextmv.write(output)
173 | ```
174 |
175 | The `assets` can be a list of `nextmv.Asset` objects, or a list of dictionaries
176 | that comply with the [custom assets][custom-assets] and [custom
177 | visualization][custom-visualization] schemas, whichever the case may be.
178 |
179 | [custom-assets]: https://docs.nextmv.io/docs/using-nextmv/run/custom-assets
180 | [custom-visualization]: https://docs.nextmv.io/docs/using-nextmv/run/custom-visualization/overview
181 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs>=1.6.1
2 | mkdocstrings-python>=1.16.10
3 | mkdocs-material>=9.6.14
4 | ruff>=0.11.11
5 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/LICENSE:
--------------------------------------------------------------------------------
1 | # LICENSE
2 |
3 | Business Source License 1.1
4 |
5 | Parameters
6 |
7 | Licensor: nextmv.io inc
8 | Licensed Work: nextroute
9 |
10 | Change Date: Four years from the date the Licensed Work is published.
11 | Change License: GPLv3
12 |
13 | For information about alternative licensing arrangements for the Software,
14 | please email info@nextmv.io.
15 |
16 | Notice
17 |
18 | The Business Source License (this document, or the “License”) is not an Open
19 | Source license. However, the Licensed Work will eventually be made available
20 | under an Open Source License, as stated in this License.
21 |
22 | License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
23 | License” is a trademark of MariaDB plc.
24 |
25 | -----------------------------------------------------------------------------
26 |
27 | ## Terms
28 |
29 | The Licensor hereby grants you the right to copy, modify, create derivative
30 | works, redistribute, and make non-production use of the Licensed Work. The
31 | Licensor may make an Additional Use Grant, above, permitting limited production
32 | use.
33 |
34 | Effective on the Change Date, or the fourth anniversary of the first publicly
35 | available distribution of a specific version of the Licensed Work under this
36 | License, whichever comes first, the Licensor hereby grants you rights under the
37 | terms of the Change License, and the rights granted in the paragraph above
38 | terminate.
39 |
40 | If your use of the Licensed Work does not comply with the requirements currently
41 | in effect as described in this License, you must purchase a commercial license
42 | from the Licensor, its affiliated entities, or authorized resellers, or you must
43 | refrain from using the Licensed Work.
44 |
45 | All copies of the original and modified Licensed Work, and derivative works of
46 | the Licensed Work, are subject to this License. This License applies separately
47 | for each version of the Licensed Work and the Change Date may vary for each
48 | version of the Licensed Work released by Licensor.
49 |
50 | You must conspicuously display this License on each original or modified copy of
51 | the Licensed Work. If you receive the Licensed Work in original or modified form
52 | from a third party, the terms and conditions set forth in this License apply to
53 | your use of that work.
54 |
55 | Any use of the Licensed Work in violation of this License will automatically
56 | terminate your rights under this License for the current and all other versions
57 | of the Licensed Work.
58 |
59 | This License does not grant you any right in any trademark or logo of Licensor
60 | or its affiliates (provided that you may use a trademark or logo of Licensor as
61 | expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW,
62 | THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL
63 | WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
64 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
65 | NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this
66 | License’s text to license your works, and to refer to it using the trademark
67 | “Business Source License”, as long as you comply with the Covenants of Licensor
68 | below.
69 |
70 | ## Covenants of Licensor
71 |
72 | In consideration of the right to use this License’s text and the “Business
73 | Source License” name and trademark, Licensor covenants to MariaDB, and to all
74 | other recipients of the licensed work to be provided by Licensor:
75 |
76 | To specify as the Change License the GPL Version 2.0 or any later version, or a
77 | license that is compatible with GPL Version 2.0 or a later version, where
78 | “compatible” means that software provided under the Change License can be
79 | included in a program with software provided under GPL Version 2.0 or a later
80 | version. Licensor may specify additional Change Licenses without limitation. To
81 | either: (a) specify an additional grant of rights to use that does not impose
82 | any additional restriction on the right granted in this License, as the
83 | Additional Use Grant; or (b) insert the text “None” to specify a Change Date.
84 | Not to modify this License in any other way.
85 |
86 | License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
87 | License” is a trademark of MariaDB plc.
88 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/README.md:
--------------------------------------------------------------------------------
1 | # Nextmv & Gurobi Python SDK
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Welcome to `nextmv-gurobipy`, an SDK for integrating Gurobi with the Nextmv
23 | platform.
24 |
25 | > [!IMPORTANT]
26 | > Please note that `nextmv-gurobipy` is provided as _source-available_ software
27 | > (not _open-source_). For further information, please refer to the
28 | > [LICENSE](./LICENSE.md) file.
29 |
30 | 📖 To learn more about the `nextmv-gurobipy`, visit the [docs][docs].
31 |
32 | ## Installation
33 |
34 | Requires Python `>=3.9`. Install using `pip`:
35 |
36 | ```bash
37 | pip install nextmv-gurobipy
38 | ```
39 |
40 | [docs]: https://nextmv-py.readthedocs.io/en/latest/nextmv-gurobipy/
41 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/nextmv_gurobipy/__about__.py:
--------------------------------------------------------------------------------
1 | __version__ = "v0.4.1"
2 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/nextmv_gurobipy/__init__.py:
--------------------------------------------------------------------------------
1 | """Nextmv & Gurobi Python SDK."""
2 |
3 | from .__about__ import __version__
4 | from .model import Model as Model
5 | from .options import OPTION_TYPE_TRANSLATION as OPTION_TYPE_TRANSLATION
6 | from .options import SKIP_PARAMETERS as SKIP_PARAMETERS
7 | from .options import ModelOptions as ModelOptions
8 | from .solution import ModelSolution as ModelSolution
9 | from .statistics import STATUS as STATUS
10 | from .statistics import ModelStatistics as ModelStatistics
11 |
12 | VERSION = __version__
13 | """The version of the Nextmv & Gurobi Python SDK."""
14 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/nextmv_gurobipy/model.py:
--------------------------------------------------------------------------------
1 | """Defines gurobipy model interoperability.
2 |
3 | This module provides functions for integrating Nextmv with Gurobi optimization.
4 |
5 | Functions
6 | ---------
7 | Model
8 | Creates a Gurobi model that can be used to solve optimization problems.
9 | """
10 |
11 | import os
12 | from typing import Optional
13 |
14 | import gurobipy as gp
15 | from gurobipy._paramdetails import param_details
16 |
17 | import nextmv
18 |
19 |
20 | def Model(options: nextmv.Options, license_path: Optional[str] = "") -> gp.Model:
21 | """
22 | Creates a Gurobi model, using Nextmv options.
23 |
24 | You can import the `Model` function directly from `nextmv_gurobipy`:
25 |
26 | ```python
27 | from nextmv_gurobipy import Model
28 | ```
29 |
30 | The returned type is a `gurobipy.Model` class. This means that once the Gurobi
31 | model is created, it can be used as any other Gurobi model. This loader will
32 | look for the `gurobi.lic` file in the provided `license_path`. If the file
33 | is not found, it will not be read. This means that by default, you will be
34 | working with Gurobi's community license.
35 |
36 | Only the parameters that are available in the Gurobi API are set. If a
37 | parameter is not available, it will be skipped.
38 |
39 | This function has some side effects that you should be aware of:
40 | - It redirects the solver chatter to stderr.
41 | - It sets the provider to "gurobi" in the options.
42 |
43 | Parameters
44 | ----------
45 | options : nextmv.Options
46 | The options for the Gurobi model. Any option that matches a Gurobi
47 | parameter name will be set in the model.
48 | license_path : str, optional
49 | Path to the directory containing the Gurobi license file.
50 | Default is "" (empty string).
51 |
52 | Returns
53 | -------
54 | gp.Model
55 | The Gurobi model instance that can be used to define and solve
56 | optimization problems.
57 |
58 | Examples
59 | --------
60 | >>> import nextmv
61 | >>> from nextmv_gurobipy import Model
62 | >>>
63 | >>> # Create options
64 | >>> options = nextmv.Options()
65 | >>> options.threads = 4
66 | >>> options.time_limit = 60
67 | >>>
68 | >>> # Create Gurobi model with Nextmv options
69 | >>> model = Model(options, license_path="/path/to/license/directory")
70 | >>>
71 | >>> # Use model as any other Gurobi model
72 | >>> x = model.addVar(name="x")
73 | >>> y = model.addVar(name="y")
74 | >>> model.addConstr(x + y <= 1)
75 | >>> model.setObjective(x + y, sense=gp.GRB.MAXIMIZE)
76 | >>> model.optimize()
77 | """
78 |
79 | # Solver chatter is logged to stderr.
80 | nextmv.redirect_stdout()
81 |
82 | env = gp.Env(empty=True)
83 |
84 | file_path = os.path.join(license_path, "gurobi.lic")
85 | if os.path.isfile(file_path):
86 | env.readParams(file_path)
87 |
88 | env.start()
89 | model = gp.Model(env=env)
90 |
91 | gp_names = [val["name"] for val in param_details.values()]
92 | for opt in options.options:
93 | name = opt.name
94 | if name not in gp_names:
95 | continue
96 |
97 | model.setParam(name, getattr(options, name))
98 |
99 | options.provider = "gurobi"
100 |
101 | return model
102 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/nextmv_gurobipy/solution.py:
--------------------------------------------------------------------------------
1 | """Defines gurobipy solution interoperability.
2 |
3 | This module provides functionality for interacting with Gurobi solutions.
4 |
5 | Functions
6 | ---------
7 | ModelSolution
8 | Creates a basic solution dictionary from a Gurobi model.
9 | """
10 |
11 | from typing import Any, Optional
12 |
13 | import gurobipy as gp
14 |
15 |
16 | def ModelSolution(model: gp.Model) -> Optional[dict[str, Any]]:
17 | """
18 | Creates a basic solution dictionary from a Gurobi model.
19 |
20 | You can import the `ModelSolution` function directly from `nextmv_gurobipy`:
21 |
22 | ```python
23 | from nextmv_gurobipy import ModelSolution
24 | ```
25 |
26 | The simple solution dictionary contains the variable name and the value of the
27 | variable for each variable in the model. If the model has not been solved,
28 | it will return `None`. Although this method is a good starting point to
29 | visualize the solution of a Gurobi model, we recommend that you implement
30 | your own logic to extract the information you need.
31 |
32 | Parameters
33 | ----------
34 | model : gp.Model
35 | The Gurobi model that has been solved.
36 |
37 | Returns
38 | -------
39 | dict[str, Any] or None
40 | A dictionary with variable names as keys and their optimal values as values.
41 | Returns None if the model has not been solved.
42 |
43 | Examples
44 | --------
45 | >>> import gurobipy as gp
46 | >>> from nextmv_gurobipy import ModelSolution
47 | >>>
48 | >>> # Create and solve a simple model
49 | >>> model = gp.Model("example")
50 | >>> x = model.addVar(name="x")
51 | >>> y = model.addVar(name="y")
52 | >>> model.addConstr(x + y <= 1)
53 | >>> model.setObjective(x + y, gp.GRB.MAXIMIZE)
54 | >>> model.optimize()
55 | >>>
56 | >>> # Get the solution dictionary
57 | >>> solution = ModelSolution(model)
58 | >>> print(solution)
59 | {'x': 0.5, 'y': 0.5}
60 | """
61 |
62 | if model.SolCount < 1:
63 | return None
64 |
65 | return {x.VarName: x.X for x in model.getVars()}
66 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/nextmv_gurobipy/statistics.py:
--------------------------------------------------------------------------------
1 | """Defines gurobipy statistics interoperability.
2 |
3 | This module provides utilities to convert Gurobi optimization results into
4 | Nextmv statistics objects for consistency across the Nextmv platform.
5 |
6 | Functions
7 | ---------
8 | ModelStatistics
9 | Creates a Nextmv statistics object from a Gurobi model.
10 |
11 | Variables
12 | ---------
13 | STATUS
14 | Mapping between Gurobi status codes and their string representations.
15 | """
16 |
17 | import time
18 | from typing import Any, Optional
19 |
20 | import gurobipy as gp
21 | from gurobipy import GRB
22 |
23 | import nextmv
24 |
25 | STATUS = {
26 | GRB.LOADED: "LOADED",
27 | GRB.OPTIMAL: "OPTIMAL",
28 | GRB.INFEASIBLE: "INFEASIBLE",
29 | GRB.INF_OR_UNBD: "INF_OR_UNBD",
30 | GRB.UNBOUNDED: "UNBOUNDED",
31 | GRB.CUTOFF: "CUTOFF",
32 | GRB.ITERATION_LIMIT: "ITERATION_LIMIT",
33 | GRB.NODE_LIMIT: "NODE_LIMIT",
34 | GRB.TIME_LIMIT: "TIME_LIMIT",
35 | GRB.SOLUTION_LIMIT: "SOLUTION_LIMIT",
36 | GRB.INTERRUPTED: "INTERRUPTED",
37 | GRB.NUMERIC: "NUMERIC",
38 | GRB.SUBOPTIMAL: "SUBOPTIMAL",
39 | GRB.INPROGRESS: "INPROGRESS",
40 | GRB.USER_OBJ_LIMIT: "USER_OBJ_LIMIT",
41 | GRB.WORK_LIMIT: "WORK_LIMIT",
42 | GRB.MEM_LIMIT: "MEM_LIMIT",
43 | }
44 | """
45 | dict: Mapping between Gurobi status codes and their string representations.
46 |
47 | You can import the `STATUS` dictionary directly from `nextmv_gurobipy`:
48 |
49 | ```python
50 | from nextmv_gurobipy import STATUS
51 | ```
52 |
53 | This dictionary converts numerical Gurobi status codes to human-readable
54 | string representations for use in statistics reporting. The status codes
55 | indicate the outcome of the optimization process (e.g., whether an optimal
56 | solution was found, or the solver hit a limit).
57 |
58 | Examples
59 | --------
60 | >>> from nextmv_gurobipy import STATUS
61 | >>> from gurobipy import GRB
62 | >>> print(STATUS[GRB.OPTIMAL])
63 | 'OPTIMAL'
64 | >>> print(STATUS[GRB.TIME_LIMIT])
65 | 'TIME_LIMIT'
66 | """
67 |
68 |
69 | def ModelStatistics(model: gp.Model, run_duration_start: Optional[float] = None) -> nextmv.Statistics:
70 | """
71 | Creates a Nextmv statistics object from a Gurobi model, once it has been optimized.
72 |
73 | You can import the `ModelStatistics` function directly from `nextmv_gurobipy`:
74 |
75 | ```python
76 | from nextmv_gurobipy import ModelStatistics
77 | ```
78 |
79 | The statistics returned are quite basic, and should be extended
80 | according to the custom metrics that the user wants to track. The optional
81 | `run_duration_start` parameter can be used to set the start time of the
82 | whole run. This is useful to separate the run time from the solve time.
83 |
84 | Parameters
85 | ----------
86 | model : gp.Model
87 | The Gurobi model after optimization.
88 | run_duration_start : float, optional
89 | The start time of the run in seconds since the epoch, as returned by `time.time()`.
90 | If provided, the total run duration will be calculated.
91 |
92 | Returns
93 | -------
94 | nextmv.Statistics
95 | The Nextmv statistics object containing run information, result statistics,
96 | and series data.
97 |
98 | Examples
99 | --------
100 | >>> import time
101 | >>> from nextmv_gurobipy import Model, Options, ModelStatistics
102 | >>>
103 | >>> start_time = time.time()
104 | >>> options = Options()
105 | >>> model = Model(options, ".")
106 | >>> # Create and configure your model
107 | >>> model.optimize()
108 | >>> stats = ModelStatistics(model, start_time)
109 | >>> # Add additional information to the statistics object if needed
110 | """
111 |
112 | run = nextmv.RunStatistics()
113 | if run_duration_start is not None:
114 | run.duration = time.time() - run_duration_start
115 |
116 | def safe_get(attr_name: str) -> Optional[Any]:
117 | """
118 | Safely get an attribute from the model by returning None if it does not exist.
119 |
120 | Parameters
121 | ----------
122 | attr_name : str
123 | Name of the attribute to retrieve from the model.
124 |
125 | Returns
126 | -------
127 | Any or None
128 | The value of the attribute if it exists, None otherwise.
129 | """
130 | return getattr(model, attr_name, None)
131 |
132 | return nextmv.Statistics(
133 | run=run,
134 | result=nextmv.ResultStatistics(
135 | duration=safe_get("Runtime"),
136 | value=safe_get("ObjVal"),
137 | custom={
138 | "status": STATUS.get(safe_get("Status"), "UNKNOWN"),
139 | "variables": safe_get("NumVars"),
140 | "constraints": safe_get("NumConstrs"),
141 | },
142 | ),
143 | series_data=nextmv.SeriesData(),
144 | )
145 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | build-backend = "hatchling.build"
3 | requires = ["hatchling >= 1.13.0"]
4 |
5 | [project]
6 | authors = [
7 | { email = "tech@nextmv.io", name = "Nextmv" }
8 | ]
9 | classifiers = [
10 | "License :: Other/Proprietary License",
11 | "Operating System :: OS Independent",
12 | "Programming Language :: Python :: 3",
13 | "Programming Language :: Python :: 3.9",
14 | "Programming Language :: Python :: 3.10",
15 | "Programming Language :: Python :: 3.11",
16 | "Programming Language :: Python :: 3.12",
17 | "Programming Language :: Python :: 3.13"
18 | ]
19 | dependencies = [
20 | "gurobipy>=12.0.1",
21 | "nextmv>=0.25.0"
22 | ]
23 | description = "An SDK for integrating Gurobi with the Nextmv platform"
24 | dynamic = [
25 | "version",
26 | ]
27 | keywords = [
28 | "decision engineering",
29 | "decision science",
30 | "decisions",
31 | "nextmv",
32 | "gurobi",
33 | "optimization",
34 | "operations research",
35 | "solver",
36 | ]
37 | license = { file = "LICENSE" }
38 | maintainers = [
39 | { email = "tech@nextmv.io", name = "Nextmv" }
40 | ]
41 | name = "nextmv-gurobipy"
42 | readme = "README.md"
43 | requires-python = ">=3.9"
44 |
45 | [project.urls]
46 | Homepage = "https://www.nextmv.io"
47 | Documentation = "https://www.nextmv.io/docs/python-sdks/nextmv-gurobipy/installation"
48 | Repository = "https://github.com/nextmv-io/nextmv-py"
49 |
50 | [tool.hatch.version]
51 | path = "nextmv_gurobipy/__about__.py"
52 |
53 | [tool.hatch.build.targets.wheel]
54 | packages = ["nextmv_gurobipy"]
55 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/requirements.txt:
--------------------------------------------------------------------------------
1 | build>=1.0.3
2 | ruff>=0.1.7
3 | twine>=4.0.2
4 | gurobipy>=12.0.1
5 | nextmv>=0.25.0
6 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/nextmv-gurobipy/tests/__init__.py
--------------------------------------------------------------------------------
/nextmv-gurobipy/tests/test_model.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import nextmv_gurobipy as ngp
4 |
5 |
6 | class TestModel(unittest.TestCase):
7 | def test_model(self):
8 | # Super simple test to check that instantiating a model works.
9 | opt = ngp.ModelOptions().to_nextmv()
10 | model = ngp.Model(opt)
11 | model.optimize()
12 |
13 | obj = model.ObjVal
14 | self.assertEqual(obj, 0.0)
15 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/tests/test_options.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import unittest
4 |
5 | import nextmv_gurobipy as ngp
6 |
7 |
8 | class TestModelOptions(unittest.TestCase):
9 | def test_to_nextmv(self):
10 | gopt = ngp.ModelOptions()
11 | nopt = gopt.to_nextmv()
12 | got = nopt.parameters_dict()
13 |
14 | path = os.path.join(os.path.dirname(__file__), "expected_option_parameters.json")
15 | with open(path) as f:
16 | expected = json.load(f)
17 |
18 | self.assertListEqual(got, expected)
19 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/tests/test_statistics.py:
--------------------------------------------------------------------------------
1 | import time
2 | import unittest
3 |
4 | import nextmv_gurobipy as ngp
5 |
6 | import nextmv
7 |
8 |
9 | class TestStatistics(unittest.TestCase):
10 | def test_to_nextmv(self):
11 | start_time = time.time()
12 | opt = ngp.ModelOptions().to_nextmv()
13 | model = ngp.Model(opt)
14 | model.optimize()
15 | stats = ngp.ModelStatistics(model, run_duration_start=start_time)
16 |
17 | self.assertIsNotNone(stats)
18 | self.assertIsInstance(stats, nextmv.Statistics)
19 | self.assertIsInstance(stats.run, nextmv.RunStatistics)
20 | self.assertIsInstance(stats.result, nextmv.ResultStatistics)
21 |
22 | self.assertGreaterEqual(stats.run.duration, 0.0)
23 | self.assertEqual(stats.result.value, 0.0)
24 | self.assertGreaterEqual(stats.result.duration, 0.0)
25 | self.assertDictEqual(stats.result.custom, {"status": "OPTIMAL", "variables": 0, "constraints": 0})
26 |
--------------------------------------------------------------------------------
/nextmv-gurobipy/tests/test_version.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import nextmv_gurobipy as ngp
4 |
5 |
6 | class TestLogger(unittest.TestCase):
7 | def test_version(self):
8 | exported_version = ngp.VERSION
9 | expected_version = ngp.__about__.__version__
10 | self.assertEqual(exported_version, expected_version)
11 |
--------------------------------------------------------------------------------
/nextmv-py.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | }
6 | ],
7 | "settings": {
8 | "python.testing.unittestArgs": ["-v", "-s", ".", "-p", "test*.py"],
9 | "python.testing.pytestEnabled": false,
10 | "python.testing.unittestEnabled": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/LICENSE:
--------------------------------------------------------------------------------
1 | # LICENSE
2 |
3 | Business Source License 1.1
4 |
5 | Parameters
6 |
7 | Licensor: nextmv.io inc
8 | Licensed Work: nextroute
9 |
10 | Change Date: Four years from the date the Licensed Work is published.
11 | Change License: GPLv3
12 |
13 | For information about alternative licensing arrangements for the Software,
14 | please email info@nextmv.io.
15 |
16 | Notice
17 |
18 | The Business Source License (this document, or the “License”) is not an Open
19 | Source license. However, the Licensed Work will eventually be made available
20 | under an Open Source License, as stated in this License.
21 |
22 | License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
23 | License” is a trademark of MariaDB plc.
24 |
25 | -----------------------------------------------------------------------------
26 |
27 | ## Terms
28 |
29 | The Licensor hereby grants you the right to copy, modify, create derivative
30 | works, redistribute, and make non-production use of the Licensed Work. The
31 | Licensor may make an Additional Use Grant, above, permitting limited production
32 | use.
33 |
34 | Effective on the Change Date, or the fourth anniversary of the first publicly
35 | available distribution of a specific version of the Licensed Work under this
36 | License, whichever comes first, the Licensor hereby grants you rights under the
37 | terms of the Change License, and the rights granted in the paragraph above
38 | terminate.
39 |
40 | If your use of the Licensed Work does not comply with the requirements currently
41 | in effect as described in this License, you must purchase a commercial license
42 | from the Licensor, its affiliated entities, or authorized resellers, or you must
43 | refrain from using the Licensed Work.
44 |
45 | All copies of the original and modified Licensed Work, and derivative works of
46 | the Licensed Work, are subject to this License. This License applies separately
47 | for each version of the Licensed Work and the Change Date may vary for each
48 | version of the Licensed Work released by Licensor.
49 |
50 | You must conspicuously display this License on each original or modified copy of
51 | the Licensed Work. If you receive the Licensed Work in original or modified form
52 | from a third party, the terms and conditions set forth in this License apply to
53 | your use of that work.
54 |
55 | Any use of the Licensed Work in violation of this License will automatically
56 | terminate your rights under this License for the current and all other versions
57 | of the Licensed Work.
58 |
59 | This License does not grant you any right in any trademark or logo of Licensor
60 | or its affiliates (provided that you may use a trademark or logo of Licensor as
61 | expressly required by this License).TO THE EXTENT PERMITTED BY APPLICABLE LAW,
62 | THE LICENSED WORK IS PROVIDED ON AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL
63 | WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION)
64 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
65 | NON-INFRINGEMENT, AND TITLE. MariaDB hereby grants you permission to use this
66 | License’s text to license your works, and to refer to it using the trademark
67 | “Business Source License”, as long as you comply with the Covenants of Licensor
68 | below.
69 |
70 | ## Covenants of Licensor
71 |
72 | In consideration of the right to use this License’s text and the “Business
73 | Source License” name and trademark, Licensor covenants to MariaDB, and to all
74 | other recipients of the licensed work to be provided by Licensor:
75 |
76 | To specify as the Change License the GPL Version 2.0 or any later version, or a
77 | license that is compatible with GPL Version 2.0 or a later version, where
78 | “compatible” means that software provided under the Change License can be
79 | included in a program with software provided under GPL Version 2.0 or a later
80 | version. Licensor may specify additional Change Licenses without limitation. To
81 | either: (a) specify an additional grant of rights to use that does not impose
82 | any additional restriction on the right granted in this License, as the
83 | Additional Use Grant; or (b) insert the text “None” to specify a Change Date.
84 | Not to modify this License in any other way.
85 |
86 | License text copyright © 2023 MariaDB plc, All Rights Reserved. “Business Source
87 | License” is a trademark of MariaDB plc.
88 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/README.md:
--------------------------------------------------------------------------------
1 | # Nextmv & scikit-learn Python SDK
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Welcome to `nextmv-scikit-learn`, an SDK for integrating scikit-learn with the
23 | Nextmv platform.
24 |
25 | > [!IMPORTANT]
26 | > Please note that `nextmv-scikit-learn` is provided as _source-available_
27 | > software (not _open-source_). For further information, please refer to the
28 | > [LICENSE](./LICENSE.md) file.
29 |
30 | 📖 To learn more about the `nextmv-scikit-learn`, visit the [docs][docs].
31 |
32 | ## Installation
33 |
34 | Requires Python `>=3.9`. Install using `pip`:
35 |
36 | ```bash
37 | pip install nextmv-scikit-learn
38 | ```
39 |
40 | [docs]: https://nextmv-py.readthedocs.io/en/latest/nextmv-scikit-learn/
41 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/__about__.py:
--------------------------------------------------------------------------------
1 | __version__ = "v0.3.0"
2 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/__init__.py:
--------------------------------------------------------------------------------
1 | """Nextmv & scikit-learn Python SDK."""
2 |
3 | from .__about__ import __version__
4 | from .ndarray import ndarray as ndarray
5 |
6 | VERSION = __version__
7 | """The version of the Nextmv & scikit-learn Python SDK."""
8 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/dummy/__init__.py:
--------------------------------------------------------------------------------
1 | """Defines dummy regressor interoperability classes."""
2 |
3 | from .model import DummyRegressor as DummyRegressor
4 | from .options import DUMMY_REGRESSOR_PARAMETERS as DUMMY_REGRESSOR_PARAMETERS
5 | from .options import DummyRegressorOptions as DummyRegressorOptions
6 | from .solution import DummyRegressorSolution as DummyRegressorSolution
7 | from .statistics import DummyRegressorStatistics as DummyRegressorStatistics
8 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/dummy/model.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.dummy models interoperability.
2 |
3 | This module provides integration between nextmv and scikit-learn's dummy models.
4 |
5 | Functions
6 | ---------
7 | DummyRegressor : function
8 | Creates a sklearn.dummy.DummyRegressor from provided options.
9 | """
10 |
11 | from sklearn import dummy
12 |
13 | import nextmv
14 |
15 | from .options import DUMMY_REGRESSOR_PARAMETERS
16 |
17 |
18 | def DummyRegressor(options: nextmv.Options) -> dummy.DummyRegressor:
19 | """
20 | Creates a `sklearn.dummy.DummyRegressor` from the provided options.
21 |
22 | You can import the `DummyRegressor` function directly from `dummy`:
23 |
24 | ```python
25 | from nextmv_sklearn.dummy import DummyRegressor
26 | ```
27 |
28 | The DummyRegressor is a regressor that makes predictions using simple rules,
29 | which can be useful as a baseline for comparison against actual regressors.
30 |
31 | Parameters
32 | ----------
33 | options : nextmv.Options
34 | Options for the DummyRegressor. Should be created using
35 | DummyRegressorOptions.to_nextmv() from the options module.
36 |
37 | Returns
38 | -------
39 | sklearn.dummy.DummyRegressor
40 | A sklearn.dummy.DummyRegressor instance configured with the provided options.
41 |
42 | Examples
43 | --------
44 | >>> from nextmv_sklearn.dummy import DummyRegressor
45 | >>> from nextmv_sklearn.dummy.options import DummyRegressorOptions
46 | >>> options = DummyRegressorOptions()
47 | >>> # Configure options as needed
48 | >>> regressor = DummyRegressor(options.to_nextmv())
49 | >>> regressor.fit(X_train, y_train)
50 | >>> predictions = regressor.predict(X_test)
51 | """
52 |
53 | names = {p.name for p in DUMMY_REGRESSOR_PARAMETERS}
54 | opt_dict = {k: v for k, v in options.to_dict().items() if k in names if v is not None}
55 |
56 | return dummy.DummyRegressor(**opt_dict)
57 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/dummy/options.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.dummy options interoperability.
2 |
3 | This module provides options for scikit-learn's dummy models. It includes classes
4 | and global variables to help configure and use these models with the Nextmv platform.
5 |
6 | Classes
7 | -------
8 | DummyRegressorOptions
9 | Options for the sklearn.dummy.DummyRegressor.
10 |
11 | Global Variables
12 | ---------------
13 | DUMMY_REGRESSOR_PARAMETERS : list
14 | List of parameters for the DummyRegressor class.
15 | """
16 |
17 | import nextmv
18 |
19 | DUMMY_REGRESSOR_PARAMETERS = [
20 | nextmv.Option(
21 | name="strategy",
22 | option_type=str,
23 | choices=["mean", "median", "quantile", "constant"],
24 | description="Strategy to use to generate predictions.",
25 | ),
26 | nextmv.Option(
27 | name="constant",
28 | option_type=float,
29 | description='The explicit constant as predicted by the "constant" strategy.',
30 | ),
31 | nextmv.Option(
32 | name="quantile",
33 | option_type=float,
34 | description='The quantile to predict using the "quantile" strategy.',
35 | ),
36 | ]
37 | """
38 | List of parameters for configuring DummyRegressor.
39 |
40 | You can import DUMMY_REGRESSOR_PARAMETERS directly from dummy:
41 |
42 | ```python
43 | from nextmv_sklearn.dummy import DUMMY_REGRESSOR_PARAMETERS
44 | ```
45 |
46 | This list contains the options that can be passed to the DummyRegressor model in
47 | scikit-learn. The parameters include:
48 | - strategy: Strategy to use to generate predictions.
49 | - constant: The explicit constant as predicted by the "constant" strategy.
50 | - quantile: The quantile to predict using the "quantile" strategy.
51 |
52 | See Also
53 | --------
54 | DummyRegressorOptions : Class that uses these parameters.
55 | sklearn.dummy.DummyRegressor : The scikit-learn class these options configure.
56 | """
57 |
58 |
59 | class DummyRegressorOptions:
60 | """Options for the sklearn.dummy.DummyRegressor.
61 |
62 | You can import the DummyRegressorOptions class directly from dummy:
63 |
64 | ```python
65 | from nextmv_sklearn.dummy import DummyRegressorOptions
66 | ```
67 |
68 | This class provides a wrapper for the options used by scikit-learn's
69 | DummyRegressor. It allows for easier configuration and integration with
70 | the Nextmv platform.
71 |
72 | Attributes
73 | ----------
74 | params : list
75 | List of Nextmv Option objects for DummyRegressor parameters.
76 |
77 | Examples
78 | --------
79 | >>> from nextmv_sklearn.dummy import DummyRegressorOptions
80 | >>> options = DummyRegressorOptions()
81 | >>> nextmv_options = options.to_nextmv()
82 | """
83 |
84 | def __init__(self):
85 | """Initialize the DummyRegressorOptions.
86 |
87 | Creates a new instance with default parameters for the
88 | sklearn.dummy.DummyRegressor.
89 | """
90 | self.params = DUMMY_REGRESSOR_PARAMETERS
91 |
92 | def to_nextmv(self) -> nextmv.Options:
93 | """Converts the options to a Nextmv options object.
94 |
95 | Returns
96 | -------
97 | nextmv.Options
98 | A Nextmv options object containing the parameters for the
99 | sklearn.dummy.DummyRegressor.
100 |
101 | Examples
102 | --------
103 | >>> options = DummyRegressorOptions()
104 | >>> nextmv_options = options.to_nextmv()
105 | >>> # Use nextmv_options in your Nextmv application
106 | """
107 |
108 | return nextmv.Options(*self.params)
109 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/dummy/statistics.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.dummy statistics interoperability.
2 |
3 | This module provides utilities for integrating scikit-learn dummy models with Nextmv statistics.
4 |
5 | Functions
6 | ---------
7 | DummyRegressorStatistics
8 | Creates a Nextmv statistics object from a sklearn.dummy.DummyRegressor model.
9 | """
10 |
11 | import time
12 | from collections.abc import Iterable
13 | from typing import Optional
14 |
15 | from sklearn import dummy
16 |
17 | import nextmv
18 |
19 |
20 | def DummyRegressorStatistics(
21 | model: dummy.DummyRegressor,
22 | X: Iterable,
23 | y: Iterable,
24 | sample_weight: float = None,
25 | run_duration_start: Optional[float] = None,
26 | ) -> nextmv.Statistics:
27 | """
28 | Creates a Nextmv statistics object from a sklearn.dummy.DummyRegressor model.
29 |
30 | You can import the `DummyRegressorStatistics` function directly from `dummy`:
31 |
32 | ```python
33 | from nextmv_sklearn.dummy import DummyRegressorStatistics
34 | ```
35 |
36 | The statistics returned are quite basic, and should be extended
37 | according to the custom metrics that the user wants to track. The optional
38 | `run_duration_start` parameter can be used to set the start time of the
39 | whole run.
40 |
41 | Parameters
42 | ----------
43 | model : dummy.DummyRegressor
44 | The sklearn DummyRegressor model.
45 | X : Iterable
46 | The input samples.
47 | y : Iterable
48 | The target values.
49 | sample_weight : float, optional
50 | The sample weights, by default None.
51 | run_duration_start : float, optional
52 | The start time of the run, by default None.
53 |
54 | Returns
55 | -------
56 | nextmv.Statistics
57 | The Nextmv statistics object.
58 |
59 | Examples
60 | --------
61 | >>> from sklearn.dummy import DummyRegressor
62 | >>> from nextmv_sklearn.dummy import DummyRegressorStatistics
63 | >>> import numpy as np
64 | >>>
65 | >>> # Create a dummy regressor
66 | >>> model = DummyRegressor(strategy='mean')
67 | >>> X = np.array([[1, 2], [3, 4], [5, 6]])
68 | >>> y = np.array([1, 2, 3])
69 | >>> model.fit(X, y)
70 | >>>
71 | >>> # Create statistics object
72 | >>> start_time = time.time()
73 | >>> # ... perform operations
74 | >>> stats = DummyRegressorStatistics(model, X, y, run_duration_start=start_time)
75 | >>> print(stats.result.custom["score"]) # Access the model score
76 | """
77 |
78 | run = nextmv.RunStatistics()
79 | if run_duration_start is not None:
80 | run.duration = time.time() - run_duration_start
81 |
82 | statistics = nextmv.Statistics(
83 | run=run,
84 | result=nextmv.ResultStatistics(
85 | custom={"score": model.score(X, y, sample_weight)},
86 | ),
87 | series_data=nextmv.SeriesData(),
88 | )
89 |
90 | if sample_weight is not None:
91 | statistics.result.custom["sample_weight"] = sample_weight
92 |
93 | return statistics
94 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/ensemble/__init__.py:
--------------------------------------------------------------------------------
1 | """Defines gradient boosting regressor interoperability classes."""
2 |
3 | from .model import GradientBoostingRegressor as GradientBoostingRegressor
4 | from .model import RandomForestRegressor as RandomForestRegressor
5 | from .options import GRADIENT_BOOSTING_REGRESSOR_PARAMETERS as GRADIENT_BOOSTING_REGRESSOR_PARAMETERS
6 | from .options import RANDOM_FOREST_REGRESSOR_PARAMETERS as RANDOM_FOREST_REGRESSOR_PARAMETERS
7 | from .options import GradientBoostingRegressorOptions as GradientBoostingRegressorOptions
8 | from .options import RandomForestRegressorOptions as RandomForestRegressorOptions
9 | from .solution import GradientBoostingRegressorSolution as GradientBoostingRegressorSolution
10 | from .solution import RandomForestRegressorSolution as RandomForestRegressorSolution
11 | from .statistics import GradientBoostingRegressorStatistics as GradientBoostingRegressorStatistics
12 | from .statistics import RandomForestRegressorStatistics as RandomForestRegressorStatistics
13 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/ensemble/model.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.ensemble models interoperability.
2 |
3 | This module provides wrappers around scikit-learn ensemble models, allowing
4 | them to be created using Nextmv option objects.
5 |
6 | Functions
7 | ---------
8 | GradientBoostingRegressor
9 | Creates a sklearn.ensemble.GradientBoostingRegressor from options.
10 | RandomForestRegressor
11 | Creates a sklearn.ensemble.RandomForestRegressor from options.
12 | """
13 |
14 | from sklearn import ensemble
15 |
16 | import nextmv
17 |
18 | from .options import GRADIENT_BOOSTING_REGRESSOR_PARAMETERS, RANDOM_FOREST_REGRESSOR_PARAMETERS
19 |
20 |
21 | def GradientBoostingRegressor(options: nextmv.Options) -> ensemble.GradientBoostingRegressor:
22 | """
23 | Creates a `sklearn.ensemble.GradientBoostingRegressor` from the provided
24 | options.
25 |
26 | You can import the `GradientBoostingRegressor` function directly from `ensemble`:
27 |
28 | ```python
29 | from nextmv_sklearn.ensemble import GradientBoostingRegressor
30 | ```
31 |
32 | This function takes a Nextmv Options object and converts it to the appropriate
33 | parameters for the scikit-learn GradientBoostingRegressor.
34 |
35 | Parameters
36 | ----------
37 | options : nextmv.Options
38 | Options for the GradientBoostingRegressor. Should contain parameters
39 | defined in GRADIENT_BOOSTING_REGRESSOR_PARAMETERS.
40 |
41 | Returns
42 | -------
43 | ensemble.GradientBoostingRegressor
44 | A sklearn.ensemble.GradientBoostingRegressor instance.
45 |
46 | Examples
47 | --------
48 | >>> from nextmv_sklearn.ensemble.options import GradientBoostingRegressorOptions
49 | >>> options = GradientBoostingRegressorOptions().to_nextmv()
50 | >>> options.set("n_estimators", 100)
51 | >>> options.set("learning_rate", 0.1)
52 | >>> gbr = GradientBoostingRegressor(options)
53 | """
54 |
55 | names = {p.name for p in GRADIENT_BOOSTING_REGRESSOR_PARAMETERS}
56 | opt_dict = {k: v for k, v in options.to_dict().items() if k in names if v is not None}
57 |
58 | return ensemble.GradientBoostingRegressor(**opt_dict)
59 |
60 |
61 | def RandomForestRegressor(options: nextmv.Options) -> ensemble.RandomForestRegressor:
62 | """
63 | Creates a `sklearn.ensemble.RandomForestRegressor` from the provided options.
64 |
65 | You can import the `RandomForestRegressor` function directly from `ensemble`:
66 |
67 | ```python
68 | from nextmv_sklearn.ensemble import RandomForestRegressor
69 | ```
70 |
71 | This function takes a Nextmv Options object and converts it to the appropriate
72 | parameters for the scikit-learn RandomForestRegressor.
73 |
74 | Parameters
75 | ----------
76 | options : nextmv.Options
77 | Options for the RandomForestRegressor. Should contain parameters
78 | defined in RANDOM_FOREST_REGRESSOR_PARAMETERS.
79 |
80 | Returns
81 | -------
82 | ensemble.RandomForestRegressor
83 | A sklearn.ensemble.RandomForestRegressor instance.
84 |
85 | Examples
86 | --------
87 | >>> from nextmv_sklearn.ensemble.options import RandomForestRegressorOptions
88 | >>> options = RandomForestRegressorOptions().to_nextmv()
89 | >>> options.set("n_estimators", 100)
90 | >>> options.set("max_depth", 10)
91 | >>> rfr = RandomForestRegressor(options)
92 | """
93 |
94 | names = {p.name for p in RANDOM_FOREST_REGRESSOR_PARAMETERS}
95 | opt_dict = {k: v for k, v in options.to_dict().items() if k in names if v is not None}
96 |
97 | return ensemble.RandomForestRegressor(**opt_dict)
98 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/linear_model/__init__.py:
--------------------------------------------------------------------------------
1 | """Defines linear regression interoperability classes."""
2 |
3 | from .model import LinearRegression as LinearRegression
4 | from .options import LINEAR_REGRESSION_PARAMETERS as LINEAR_REGRESSION_PARAMETERS
5 | from .options import LinearRegressionOptions as LinearRegressionOptions
6 | from .solution import LinearRegressionSolution as LinearRegressionSolution
7 | from .statistics import LinearRegressionStatistics as LinearRegressionStatistics
8 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/linear_model/model.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.linear_model models interoperability.
2 |
3 | This module provides functions to create scikit-learn linear model instances
4 | from Nextmv options.
5 |
6 | Functions
7 | ---------
8 | LinearRegression
9 | Creates a scikit-learn LinearRegression model from Nextmv options.
10 | """
11 |
12 | from sklearn import linear_model
13 |
14 | import nextmv
15 |
16 | from .options import LINEAR_REGRESSION_PARAMETERS
17 |
18 |
19 | def LinearRegression(options: nextmv.Options) -> linear_model.LinearRegression:
20 | """
21 | Creates a `sklearn.linear_model.LinearRegression` from the provided
22 | options.
23 |
24 | You can import the `LinearRegression` function directly from `linear_model`:
25 |
26 | ```python
27 | from nextmv_sklearn.linear_model import LinearRegression
28 | ```
29 |
30 | This function takes a Nextmv options object and configures a scikit-learn
31 | LinearRegression model with the appropriate parameters. It extracts
32 | parameter values from the options object that match the expected parameters
33 | for LinearRegression.
34 |
35 | Parameters
36 | ----------
37 | options : nextmv.Options
38 | Options for the LinearRegression. Can include the following parameters:
39 |
40 | - fit_intercept : bool, default=True
41 | Whether to calculate the intercept for this model.
42 | - copy_X : bool, default=True
43 | If True, X will be copied; else, it may be overwritten.
44 | - n_jobs : int, default=None
45 | The number of jobs to use for the computation.
46 | - positive : bool, default=False
47 | When set to True, forces the coefficients to be positive.
48 |
49 | Returns
50 | -------
51 | sklearn.linear_model.LinearRegression
52 | A scikit-learn LinearRegression instance configured with the
53 | parameters from the options.
54 |
55 | Examples
56 | --------
57 | >>> from nextmv_sklearn.linear_model.options import LinearRegressionOptions
58 | >>> options = LinearRegressionOptions().to_nextmv()
59 | >>> # Modify options if needed
60 | >>> options.fit_intercept = False
61 | >>> model = LinearRegression(options)
62 | """
63 |
64 | names = {p.name for p in LINEAR_REGRESSION_PARAMETERS}
65 | opt_dict = {k: v for k, v in options.to_dict().items() if k in names if v is not None}
66 |
67 | return linear_model.LinearRegression(**opt_dict)
68 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/linear_model/options.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.linear_model options interoperability.
2 |
3 | This module provides options for scikit-learn linear models to be used with Nextmv.
4 |
5 | Classes
6 | -------
7 | LinearRegressionOptions
8 | Options for the sklearn.linear_model.LinearRegression.
9 |
10 | Variables
11 | ---------
12 | LINEAR_REGRESSION_PARAMETERS : list
13 | List of parameters for LinearRegression.
14 | """
15 |
16 | import nextmv
17 |
18 | LINEAR_REGRESSION_PARAMETERS = [
19 | nextmv.Option(
20 | name="fit_intercept",
21 | option_type=bool,
22 | description="Whether to calculate the intercept for this model.",
23 | ),
24 | nextmv.Option(
25 | name="copy_X",
26 | option_type=bool,
27 | description="If True, X will be copied; else, it may be overwritten.",
28 | ),
29 | nextmv.Option(
30 | name="n_jobs",
31 | option_type=int,
32 | description="The number of jobs to use for the computation.",
33 | ),
34 | nextmv.Option(
35 | name="positive",
36 | option_type=bool,
37 | description="When set to True, forces the coefficients to be positive.",
38 | ),
39 | ]
40 | """List of parameters for scikit-learn's LinearRegression model.
41 |
42 | You can import LINEAR_REGRESSION_PARAMETERS directly from linear_model:
43 |
44 | ```python
45 | from nextmv_sklearn.linear_model import LINEAR_REGRESSION_PARAMETERS
46 | ```
47 |
48 | This list contains all the parameters that can be configured for a scikit-learn
49 | LinearRegression model when using it through Nextmv.
50 |
51 | Parameters
52 | ----------
53 | fit_intercept : bool
54 | Whether to calculate the intercept for this model.
55 | copy_X : bool
56 | If True, X will be copied; else, it may be overwritten.
57 | n_jobs : int
58 | The number of jobs to use for the computation.
59 | positive : bool
60 | When set to True, forces the coefficients to be positive.
61 |
62 | See Also
63 | --------
64 | LinearRegressionOptions : Class that provides options for LinearRegression.
65 | """
66 |
67 |
68 | class LinearRegressionOptions:
69 | """Options for the sklearn.linear_model.LinearRegression.
70 |
71 | You can import the LinearRegressionOptions class directly from linear_model:
72 |
73 | ```python
74 | from nextmv_sklearn.linear_model import LinearRegressionOptions
75 | ```
76 |
77 | This class provides an interface for configuring scikit-learn's
78 | LinearRegression model to work with Nextmv.
79 |
80 | Attributes
81 | ----------
82 | params : list
83 | List of LinearRegression parameters.
84 |
85 | Examples
86 | --------
87 | >>> from nextmv_sklearn.linear_model import LinearRegressionOptions
88 | >>> options = LinearRegressionOptions()
89 | >>> nextmv_options = options.to_nextmv()
90 | """
91 |
92 | def __init__(self):
93 | self.params = LINEAR_REGRESSION_PARAMETERS
94 |
95 | def to_nextmv(self) -> nextmv.Options:
96 | """Converts the options to a Nextmv options object.
97 |
98 | Returns
99 | -------
100 | nextmv.Options
101 | A Nextmv options object containing the LinearRegression parameters.
102 |
103 | Examples
104 | --------
105 | >>> options = LinearRegressionOptions()
106 | >>> nextmv_options = options.to_nextmv()
107 | """
108 |
109 | return nextmv.Options(*self.params)
110 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/linear_model/statistics.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.linear_model statistics interoperability.
2 |
3 | This module provides functions to create Nextmv statistics objects from sklearn
4 | linear models.
5 |
6 | Functions
7 | ---------
8 | LinearRegressionStatistics
9 | Creates a Nextmv statistics object from a sklearn.linear_model.LinearRegression model.
10 | """
11 |
12 | import time
13 | from collections.abc import Iterable
14 | from typing import Optional
15 |
16 | from sklearn import linear_model
17 |
18 | import nextmv
19 |
20 |
21 | def LinearRegressionStatistics(
22 | model: linear_model.LinearRegression,
23 | X: Iterable,
24 | y: Iterable,
25 | sample_weight: float = None,
26 | run_duration_start: Optional[float] = None,
27 | ) -> nextmv.Statistics:
28 | """
29 | Creates a Nextmv statistics object from a sklearn.linear_model.LinearRegression model.
30 |
31 | You can import the `LinearRegressionStatistics` function directly from `linear_model`:
32 |
33 | ```python
34 | from nextmv_sklearn.linear_model import LinearRegressionStatistics
35 | ```
36 |
37 | The statistics returned are quite basic, and should be extended according to the custom
38 | metrics that the user wants to track. The optional `run_duration_start` parameter
39 | can be used to set the start time of the whole run.
40 |
41 | Parameters
42 | ----------
43 | model : linear_model.LinearRegression
44 | The sklearn LinearRegression model.
45 | X : Iterable
46 | The input samples.
47 | y : Iterable
48 | The target values.
49 | sample_weight : float, optional
50 | The sample weights, by default None.
51 | run_duration_start : float, optional
52 | The start time of the run, by default None.
53 |
54 | Returns
55 | -------
56 | nextmv.Statistics
57 | The Nextmv statistics object with basic model metrics.
58 |
59 | Examples
60 | --------
61 | >>> from sklearn.linear_model import LinearRegression
62 | >>> from nextmv_sklearn.linear_model import LinearRegressionStatistics
63 | >>> import numpy as np
64 | >>> X = np.array([[1, 1], [1, 2], [2, 2], [2, 3]])
65 | >>> y = np.dot(X, np.array([1, 2])) + 3
66 | >>> model = LinearRegression()
67 | >>> model.fit(X, y)
68 | >>> stats = LinearRegressionStatistics(model, X, y)
69 | >>> print(stats.result.custom['score']) # R^2 score
70 | 1.0
71 | """
72 |
73 | run = nextmv.RunStatistics()
74 | if run_duration_start is not None:
75 | run.duration = time.time() - run_duration_start
76 |
77 | statistics = nextmv.Statistics(
78 | run=run,
79 | result=nextmv.ResultStatistics(
80 | custom={
81 | "score": model.score(X, y, sample_weight),
82 | },
83 | ),
84 | series_data=nextmv.SeriesData(),
85 | )
86 |
87 | if sample_weight is not None:
88 | statistics.result.custom["sample_weight"] = sample_weight
89 |
90 | return statistics
91 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/ndarray.py:
--------------------------------------------------------------------------------
1 | """NumPy ndarray wrapper for Pydantic model integration.
2 |
3 | This module provides utilities to handle NumPy arrays in Pydantic models.
4 |
5 | Variables
6 | ---------
7 | ndarray : Annotated
8 | A type annotation for NumPy arrays in Pydantic models.
9 | """
10 |
11 | from typing import Annotated
12 |
13 | import numpy as np
14 | from pydantic import BeforeValidator, PlainSerializer
15 |
16 | ndarray = Annotated[
17 | np.ndarray,
18 | BeforeValidator(lambda x: x),
19 | PlainSerializer(lambda x: x.tolist()),
20 | ]
21 | """
22 | ndarray: An annotated type that represents a NumPy array.
23 |
24 | You can import the `ndarray` type directly from `nextmv_sklearn`:
25 |
26 | ```python
27 | from nextmv_sklearn import ndarray
28 | ```
29 |
30 | This type is designed for use with Pydantic models to properly handle
31 | NumPy arrays for validation and serialization.
32 |
33 | The BeforeValidator ensures the input is preserved as is, while
34 | PlainSerializer converts the ndarray to a Python list when serializing.
35 |
36 | Examples
37 | --------
38 | >>> from pydantic import BaseModel
39 | >>> from nextmv_sklearn import ndarray
40 | >>> import numpy as np
41 | >>>
42 | >>> class MyModel(BaseModel):
43 | ... data: ndarray
44 | ...
45 | >>> model = MyModel(data=np.array([1, 2, 3]))
46 | >>> model.model_dump_json()
47 | '{"data":[1,2,3]}'
48 | """
49 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/neural_network/__init__.py:
--------------------------------------------------------------------------------
1 | """Defines Multi-layer Perceptron regressor interoperability classes."""
2 |
3 | from .model import MLPRegressor as MLPRegressor
4 | from .options import MLP_REGRESSOR_PARAMETERS as MLP_REGRESSOR_PARAMETERS
5 | from .options import MLPRegressorOptions as MLPRegressorOptions
6 | from .solution import MLPRegressorSolution as MLPRegressorSolution
7 | from .statistics import MLPRegressorStatistics as MLPRegressorStatistics
8 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/neural_network/model.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.neural_network models interoperability.
2 |
3 | This module provides functions for creating and configuring scikit-learn neural network models
4 | using Nextmv options.
5 |
6 | Functions
7 | ---------
8 | MLPRegressor
9 | Create a Multi-layer Perceptron regressor with Nextmv options.
10 | """
11 |
12 | from sklearn import neural_network
13 |
14 | import nextmv
15 |
16 | from .options import MLP_REGRESSOR_PARAMETERS
17 |
18 |
19 | def MLPRegressor(options: nextmv.Options) -> neural_network.MLPRegressor:
20 | """
21 | Creates a `sklearn.neural_network.MLPRegressor` from the provided options.
22 |
23 | You can import the `MLPRegressor` function directly from `neural_network`:
24 |
25 | ```python
26 | from nextmv_sklearn.neural_network import MLPRegressor
27 | ```
28 |
29 | This function takes Nextmv options and creates a scikit-learn MLPRegressor model with
30 | the specified parameters. The options must be compatible with the MLPRegressor parameters
31 | as defined in the options module.
32 |
33 | Parameters
34 | ----------
35 | options : nextmv.Options
36 | Options for the MLPRegressor. These can include:
37 | - hidden_layer_sizes : str
38 | The ith element represents the number of neurons in the ith hidden layer. (e.g. "1,2,3")
39 | - activation : {'identity', 'logistic', 'tanh', 'relu'}
40 | Activation function for the hidden layer.
41 | - solver : {'lbfgs', 'sgd', 'adam'}
42 | The solver for weight optimization.
43 | - alpha : float
44 | Strength of the L2 regularization term.
45 | - And other parameters as defined in MLP_REGRESSOR_PARAMETERS.
46 |
47 | Returns
48 | -------
49 | neural_network.MLPRegressor
50 | A sklearn.neural_network.MLPRegressor instance configured with the provided options.
51 |
52 | Examples
53 | --------
54 | >>> import nextmv
55 | >>> from nextmv_sklearn.neural_network import MLPRegressor
56 | >>> from nextmv_sklearn.neural_network.options import MLPRegressorOptions
57 | >>>
58 | >>> # Create options
59 | >>> options = MLPRegressorOptions().to_nextmv()
60 | >>> options.set("hidden_layer_sizes", "100,50")
61 | >>> options.set("activation", "relu")
62 | >>>
63 | >>> # Create regressor
64 | >>> regressor = MLPRegressor(options)
65 | >>> regressor.fit(X_train, y_train)
66 | >>> predictions = regressor.predict(X_test)
67 | """
68 |
69 | names = {p.name for p in MLP_REGRESSOR_PARAMETERS}
70 | opt_dict = {k: v for k, v in options.to_dict().items() if k in names if v is not None}
71 |
72 | return neural_network.MLPRegressor(**opt_dict)
73 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/neural_network/statistics.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.neural_network statistics interoperability.
2 |
3 | This module provides functions to convert scikit-learn neural network models
4 | into Nextmv statistics objects.
5 |
6 | Functions
7 | ---------
8 | MLPRegressorStatistics
9 | Creates Nextmv statistics from a sklearn.neural_network.MLPRegressor model.
10 | """
11 |
12 | import time
13 | from collections.abc import Iterable
14 | from typing import Optional
15 |
16 | from sklearn import neural_network
17 |
18 | import nextmv
19 |
20 |
21 | def MLPRegressorStatistics(
22 | model: neural_network.MLPRegressor,
23 | X: Iterable,
24 | y: Iterable,
25 | sample_weight: float = None,
26 | run_duration_start: Optional[float] = None,
27 | ) -> nextmv.Statistics:
28 | """
29 | Creates a Nextmv statistics object from a sklearn.neural_network.MLPRegressor model.
30 |
31 | You can import the `MLPRegressorStatistics` function directly from `neural_network`:
32 |
33 | ```python
34 | from nextmv_sklearn.neural_network import MLPRegressorStatistics
35 | ```
36 |
37 | The statistics returned are quite basic, and should be extended according
38 | to the custom metrics that the user wants to track. The optional
39 | `run_duration_start` parameter can be used to set the start time of the whole run.
40 |
41 | Parameters
42 | ----------
43 | model : neural_network.MLPRegressor
44 | The sklearn MLPRegressor model.
45 | X : Iterable
46 | The input samples. Acceptable formats are:
47 | - Dense numpy arrays of shape (n_samples, n_features)
48 | - Sparse scipy matrices of shape (n_samples, n_features)
49 | - Pandas DataFrames with shape (n_samples, n_features)
50 | y : Iterable
51 | The target values (real numbers for regression).
52 | sample_weight : float, optional
53 | Individual weights for each sample. If None, all samples have equal weight.
54 | run_duration_start : float, optional
55 | The start time of the run in seconds since the epoch. If provided,
56 | the duration of the run will be calculated.
57 |
58 | Returns
59 | -------
60 | nextmv.Statistics
61 | The Nextmv statistics object containing:
62 | - Run statistics including duration if run_duration_start was provided
63 | - Result statistics with the model's score and optionally sample_weight
64 | - Empty series data
65 |
66 | Examples
67 | --------
68 | >>> from sklearn.neural_network import MLPRegressor
69 | >>> from nextmv_sklearn.neural_network import MLPRegressorStatistics
70 | >>> import numpy as np
71 | >>>
72 | >>> # Create and train the model
73 | >>> X = np.array([[0, 0], [1, 1]])
74 | >>> y = np.array([0, 1])
75 | >>> start_time = time.time()
76 | >>> model = MLPRegressor(hidden_layer_sizes=(5,), max_iter=1000)
77 | >>> model.fit(X, y)
78 | >>>
79 | >>> # Create statistics object
80 | >>> stats = MLPRegressorStatistics(model, X, y, run_duration_start=start_time)
81 | >>> print(f"Model score: {stats.result.custom['score']}")
82 | >>> print(f"Run duration: {stats.run.duration} seconds")
83 | """
84 |
85 | run = nextmv.RunStatistics()
86 | if run_duration_start is not None:
87 | run.duration = time.time() - run_duration_start
88 |
89 | statistics = nextmv.Statistics(
90 | run=run,
91 | result=nextmv.ResultStatistics(
92 | custom={
93 | "score": model.score(X, y, sample_weight),
94 | },
95 | ),
96 | series_data=nextmv.SeriesData(),
97 | )
98 |
99 | if sample_weight is not None:
100 | statistics.result.custom["sample_weight"] = sample_weight
101 |
102 | return statistics
103 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/tree/__init__.py:
--------------------------------------------------------------------------------
1 | """Defines decision tree regressor interoperability classes."""
2 |
3 | from .model import DecisionTreeRegressor as DecisionTreeRegressor
4 | from .options import DECISION_TREE_REGRESSOR_PARAMETERS as DECISION_TREE_REGRESSOR_PARAMETERS
5 | from .options import DecisionTreeRegressorOptions as DecisionTreeRegressorOptions
6 | from .solution import DecisionTreeRegressorSolution as DecisionTreeRegressorSolution
7 | from .statistics import DecisionTreeRegressorStatistics as DecisionTreeRegressorStatistics
8 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/tree/model.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.tree models interoperability.
2 |
3 | Functions
4 | ---------
5 | DecisionTreeRegressor
6 | Creates a scikit-learn DecisionTreeRegressor from provided options
7 | """
8 |
9 | from sklearn import tree
10 |
11 | import nextmv
12 |
13 | from .options import DECISION_TREE_REGRESSOR_PARAMETERS
14 |
15 |
16 | def DecisionTreeRegressor(options: nextmv.Options) -> tree.DecisionTreeRegressor:
17 | """
18 | Creates a `sklearn.tree.DecisionTreeRegressor` from the provided options.
19 |
20 | You can import the `DecisionTreeRegressor` function directly from `tree`:
21 |
22 | ```python
23 | from nextmv_sklearn.tree import DecisionTreeRegressor
24 | ```
25 |
26 | This function uses the options to create a scikit-learn DecisionTreeRegressor
27 | model with the specified parameters. It extracts parameter values from the
28 | Nextmv options object and passes them to the scikit-learn constructor.
29 |
30 | Parameters
31 | ----------
32 | options : nextmv.Options
33 | Options for the DecisionTreeRegressor. Can contain the following parameters:
34 | - criterion : str, default='squared_error'
35 | The function to measure the quality of a split.
36 | - splitter : str, default='best'
37 | The strategy used to choose the split at each node.
38 | - max_depth : int, optional
39 | The maximum depth of the tree.
40 | - min_samples_split : int, optional
41 | The minimum number of samples required to split an internal node.
42 | - min_samples_leaf : int, optional
43 | The minimum number of samples required to be at a leaf node.
44 | - min_weight_fraction_leaf : float, optional
45 | The minimum weighted fraction of the sum total of weights required
46 | to be at a leaf node.
47 | - max_features : int, optional
48 | The number of features to consider when looking for the best split.
49 | - random_state : int, optional
50 | Controls the randomness of the estimator.
51 | - max_leaf_nodes : int, optional
52 | Grow a tree with max_leaf_nodes in best-first fashion.
53 | - min_impurity_decrease : float, optional
54 | A node will be split if this split induces a decrease of the impurity.
55 | - ccp_alpha : float, optional
56 | Complexity parameter used for Minimal Cost-Complexity Pruning.
57 |
58 | Returns
59 | -------
60 | DecisionTreeRegressor
61 | A sklearn.tree.DecisionTreeRegressor instance.
62 |
63 | Examples
64 | --------
65 | >>> from nextmv_sklearn.tree import DecisionTreeRegressorOptions
66 | >>> from nextmv_sklearn.tree import DecisionTreeRegressor
67 | >>>
68 | >>> # Create options for the regressor
69 | >>> options = DecisionTreeRegressorOptions().to_nextmv()
70 | >>>
71 | >>> # Set specific parameters if needed
72 | >>> options.set("max_depth", 5)
73 | >>> options.set("min_samples_split", 2)
74 | >>>
75 | >>> # Create the regressor model
76 | >>> regressor = DecisionTreeRegressor(options)
77 | >>>
78 | >>> # Use the regressor with scikit-learn API
79 | >>> X = [[0, 0], [1, 1], [2, 2], [3, 3]]
80 | >>> y = [0, 1, 2, 3]
81 | >>> regressor.fit(X, y)
82 | >>> regressor.predict([[4, 4]])
83 | """
84 |
85 | names = {p.name for p in DECISION_TREE_REGRESSOR_PARAMETERS}
86 | opt_dict = {k: v for k, v in options.to_dict().items() if k in names if v is not None}
87 |
88 | return tree.DecisionTreeRegressor(**opt_dict)
89 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/tree/options.py:
--------------------------------------------------------------------------------
1 | """Defines sklearn.tree options interoperability.
2 |
3 | This module provides functionality for interfacing with scikit-learn's tree-based
4 | algorithms within the Nextmv framework. It includes classes for configuring
5 | decision tree regressors.
6 |
7 | Classes
8 | -------
9 | DecisionTreeRegressorOptions
10 | Options wrapper for scikit-learn's DecisionTreeRegressor.
11 | """
12 |
13 | import nextmv
14 |
15 | DECISION_TREE_REGRESSOR_PARAMETERS = [
16 | nextmv.Option(
17 | name="criterion",
18 | option_type=str,
19 | choices=["squared_error", "friedman_mse", "absolute_error", "poisson"],
20 | description="The function to measure the quality of a split.",
21 | default="squared_error",
22 | ),
23 | nextmv.Option(
24 | name="splitter",
25 | option_type=str,
26 | choices=["best", "random"],
27 | description="The strategy used to choose the split at each node.",
28 | default="best",
29 | ),
30 | nextmv.Option(
31 | name="max_depth",
32 | option_type=int,
33 | description="The maximum depth of the tree.",
34 | ),
35 | nextmv.Option(
36 | name="min_samples_split",
37 | option_type=int,
38 | description="The minimum number of samples required to split an internal node.",
39 | ),
40 | nextmv.Option(
41 | name="min_samples_leaf",
42 | option_type=int,
43 | description="The minimum number of samples required to be at a leaf node.",
44 | ),
45 | nextmv.Option(
46 | name="min_weight_fraction_leaf",
47 | option_type=float,
48 | description="The minimum weighted fraction of the sum total of weights required to be at a leaf node.",
49 | ),
50 | nextmv.Option(
51 | name="max_features",
52 | option_type=int,
53 | description="The number of features to consider when looking for the best split.",
54 | ),
55 | nextmv.Option(
56 | name="random_state",
57 | option_type=int,
58 | description="Controls the randomness of the estimator.",
59 | ),
60 | nextmv.Option(
61 | name="max_leaf_nodes",
62 | option_type=int,
63 | description="Grow a tree with max_leaf_nodes in best-first fashion.",
64 | ),
65 | nextmv.Option(
66 | name="min_impurity_decrease",
67 | option_type=float,
68 | description="A node will be split if this split induces a decrease of the impurity #.",
69 | ),
70 | nextmv.Option(
71 | name="ccp_alpha",
72 | option_type=float,
73 | description="Complexity parameter used for Minimal Cost-Complexity Pruning.",
74 | ),
75 | ]
76 | """
77 | List of Nextmv Option objects for configuring a DecisionTreeRegressor.
78 |
79 | Each option corresponds to a hyperparameter of the scikit-learn DecisionTreeRegressor,
80 | providing a consistent interface for setting up decision tree regression models
81 | within the Nextmv ecosystem.
82 |
83 | You can import the `DECISION_TREE_REGRESSOR_PARAMETERS` directly from `tree`:
84 |
85 | ```python
86 | from nextmv_sklearn.tree import DECISION_TREE_REGRESSOR_PARAMETERS
87 | ```
88 | """
89 |
90 |
91 | class DecisionTreeRegressorOptions:
92 | """Options for the sklearn.tree.DecisionTreeRegressor.
93 |
94 | You can import the `DecisionTreeRegressorOptions` class directly from `tree`:
95 |
96 | ```python
97 | from nextmv_sklearn.tree import DecisionTreeRegressorOptions
98 | ```
99 |
100 | A wrapper class for scikit-learn's DecisionTreeRegressor hyperparameters,
101 | providing a consistent interface for configuring decision tree regression
102 | models within the Nextmv ecosystem.
103 |
104 | Attributes
105 | ----------
106 | params : list
107 | List of Nextmv Option objects corresponding to DecisionTreeRegressor parameters.
108 |
109 | Examples
110 | --------
111 | >>> from nextmv_sklearn.tree import DecisionTreeRegressorOptions
112 | >>> options = DecisionTreeRegressorOptions()
113 | >>> nextmv_options = options.to_nextmv()
114 | """
115 |
116 | def __init__(self):
117 | """Initialize a DecisionTreeRegressorOptions instance.
118 |
119 | Configures the default parameters for a decision tree regressor.
120 | """
121 | self.params = DECISION_TREE_REGRESSOR_PARAMETERS
122 |
123 | def to_nextmv(self) -> nextmv.Options:
124 | """Converts the options to a Nextmv options object.
125 |
126 | Creates a Nextmv Options instance from the configured decision tree
127 | regressor parameters.
128 |
129 | Returns
130 | -------
131 | nextmv.Options
132 | A Nextmv options object containing all decision tree regressor parameters.
133 |
134 | Examples
135 | --------
136 | >>> options = DecisionTreeRegressorOptions()
137 | >>> nextmv_options = options.to_nextmv()
138 | >>> # Access options as CLI arguments
139 | >>> # python script.py --criterion squared_error --max_depth 5
140 | """
141 |
142 | return nextmv.Options(*self.params)
143 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/nextmv_sklearn/tree/statistics.py:
--------------------------------------------------------------------------------
1 | """Scikit-learn tree module statistics interoperability for Nextmv.
2 |
3 | This module provides functionality to integrate scikit-learn tree-based models
4 | with Nextmv statistics tracking.
5 |
6 | Functions
7 | --------
8 | DecisionTreeRegressorStatistics
9 | Convert a DecisionTreeRegressor model to Nextmv statistics format.
10 | """
11 |
12 | import time
13 | from collections.abc import Iterable
14 | from typing import Optional
15 |
16 | from sklearn import tree
17 |
18 | import nextmv
19 |
20 |
21 | def DecisionTreeRegressorStatistics(
22 | model: tree.DecisionTreeRegressor,
23 | X: Iterable,
24 | y: Iterable,
25 | sample_weight: float = None,
26 | run_duration_start: Optional[float] = None,
27 | ) -> nextmv.Statistics:
28 | """Create a Nextmv statistics object from a scikit-learn DecisionTreeRegressor model.
29 |
30 | You can import the `DecisionTreeRegressorStatistics` function directly from `tree`:
31 |
32 | ```python
33 | from nextmv_sklearn.tree import DecisionTreeRegressorStatistics
34 | ```
35 |
36 | Converts a trained scikit-learn DecisionTreeRegressor model into Nextmv statistics
37 | format. The statistics include model depth, feature importances, number of leaves,
38 | and model score. Additional custom metrics can be added by the user after this
39 | function returns. The optional `run_duration_start` parameter can be used to track
40 | the total runtime of the modeling process.
41 |
42 | Parameters
43 | ----------
44 | model : tree.DecisionTreeRegressor
45 | The trained scikit-learn DecisionTreeRegressor model.
46 | X : Iterable
47 | The input features used for scoring the model.
48 | y : Iterable
49 | The target values used for scoring the model.
50 | sample_weight : float, optional
51 | The sample weights used for scoring, by default None.
52 | run_duration_start : float, optional
53 | The timestamp when the model run started, typically from time.time(),
54 | by default None.
55 |
56 | Returns
57 | -------
58 | nextmv.Statistics
59 | A Nextmv statistics object containing model performance metrics.
60 |
61 | Examples
62 | --------
63 | >>> from sklearn.tree import DecisionTreeRegressor
64 | >>> from nextmv_sklearn.tree import DecisionTreeRegressorStatistics
65 | >>> import time
66 | >>>
67 | >>> # Record start time
68 | >>> start_time = time.time()
69 | >>>
70 | >>> # Train model
71 | >>> model = DecisionTreeRegressor(max_depth=5)
72 | >>> model.fit(X_train, y_train)
73 | >>>
74 | >>> # Create statistics
75 | >>> stats = DecisionTreeRegressorStatistics(
76 | ... model, X_test, y_test, run_duration_start=start_time
77 | ... )
78 | >>>
79 | >>> # Add additional metrics
80 | >>> stats.result.custom["my_custom_metric"] = custom_value
81 | """
82 |
83 | run = nextmv.RunStatistics()
84 | if run_duration_start is not None:
85 | run.duration = time.time() - run_duration_start
86 |
87 | statistics = nextmv.Statistics(
88 | run=run,
89 | result=nextmv.ResultStatistics(
90 | custom={
91 | "depth": model.get_depth(),
92 | "feature_importances_": model.feature_importances_.tolist(),
93 | "n_leaves": int(model.get_n_leaves()),
94 | "score": model.score(X, y, sample_weight),
95 | },
96 | ),
97 | series_data=nextmv.SeriesData(),
98 | )
99 |
100 | if sample_weight is not None:
101 | statistics.result.custom["sample_weight"] = sample_weight
102 |
103 | return statistics
104 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | build-backend = "hatchling.build"
3 | requires = ["hatchling >= 1.13.0"]
4 |
5 | [project]
6 | authors = [
7 | { email = "tech@nextmv.io", name = "Nextmv" }
8 | ]
9 | classifiers = [
10 | "License :: Other/Proprietary License",
11 | "Operating System :: OS Independent",
12 | "Programming Language :: Python :: 3",
13 | "Programming Language :: Python :: 3.9",
14 | "Programming Language :: Python :: 3.10",
15 | "Programming Language :: Python :: 3.11",
16 | "Programming Language :: Python :: 3.12",
17 | "Programming Language :: Python :: 3.13"
18 | ]
19 | dependencies = [
20 | "scikit-learn>=1.6.1",
21 | "nextmv>=0.25.0"
22 | ]
23 | description = "An SDK for integrating scikit-learn with the Nextmv platform"
24 | dynamic = [
25 | "version",
26 | ]
27 | keywords = [
28 | "decision engineering",
29 | "decision science",
30 | "decisions",
31 | "nextmv",
32 | "gurobi",
33 | "optimization",
34 | "operations research",
35 | "solver",
36 | ]
37 | license = { file = "LICENSE" }
38 | maintainers = [
39 | { email = "tech@nextmv.io", name = "Nextmv" }
40 | ]
41 | name = "nextmv-scikit-learn"
42 | readme = "README.md"
43 | requires-python = ">=3.9"
44 |
45 | [project.urls]
46 | Homepage = "https://www.nextmv.io"
47 | Documentation = "https://www.nextmv.io/docs/python-sdks/nextmv-scikit-learn/installation"
48 | Repository = "https://github.com/nextmv-io/nextmv-py"
49 |
50 | [tool.hatch.version]
51 | path = "nextmv_sklearn/__about__.py"
52 |
53 | [tool.hatch.build.targets.wheel]
54 | packages = ["nextmv_sklearn"]
55 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/requirements.txt:
--------------------------------------------------------------------------------
1 | build>=1.0.3
2 | ruff>=0.1.7
3 | twine>=4.0.2
4 | scikit-learn>=1.6.1
5 | nextmv>=0.25.0
6 | pydantic>=2.5.2
7 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/nextmv-scikit-learn/tests/__init__.py
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/expected_decision_tree_option_parameters.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "criterion",
4 | "option_type": "",
5 | "default": "squared_error",
6 | "description": "The function to measure the quality of a split.",
7 | "required": false,
8 | "choices": ["squared_error", "friedman_mse", "absolute_error", "poisson"],
9 | "additional_attributes": null
10 | },
11 | {
12 | "name": "splitter",
13 | "option_type": "",
14 | "default": "best",
15 | "description": "The strategy used to choose the split at each node.",
16 | "required": false,
17 | "choices": ["best", "random"],
18 | "additional_attributes": null
19 | },
20 | {
21 | "name": "max_depth",
22 | "option_type": "",
23 | "default": null,
24 | "description": "The maximum depth of the tree.",
25 | "required": false,
26 | "choices": null,
27 | "additional_attributes": null
28 | },
29 | {
30 | "name": "min_samples_split",
31 | "option_type": "",
32 | "default": null,
33 | "description": "The minimum number of samples required to split an internal node.",
34 | "required": false,
35 | "choices": null,
36 | "additional_attributes": null
37 | },
38 | {
39 | "name": "min_samples_leaf",
40 | "option_type": "",
41 | "default": null,
42 | "description": "The minimum number of samples required to be at a leaf node.",
43 | "required": false,
44 | "choices": null,
45 | "additional_attributes": null
46 | },
47 | {
48 | "name": "min_weight_fraction_leaf",
49 | "option_type": "",
50 | "default": null,
51 | "description": "The minimum weighted fraction of the sum total of weights required to be at a leaf node.",
52 | "required": false,
53 | "choices": null,
54 | "additional_attributes": null
55 | },
56 | {
57 | "name": "max_features",
58 | "option_type": "",
59 | "default": null,
60 | "description": "The number of features to consider when looking for the best split.",
61 | "required": false,
62 | "choices": null,
63 | "additional_attributes": null
64 | },
65 | {
66 | "name": "random_state",
67 | "option_type": "",
68 | "default": null,
69 | "description": "Controls the randomness of the estimator.",
70 | "required": false,
71 | "choices": null,
72 | "additional_attributes": null
73 | },
74 | {
75 | "name": "max_leaf_nodes",
76 | "option_type": "",
77 | "default": null,
78 | "description": "Grow a tree with max_leaf_nodes in best-first fashion.",
79 | "required": false,
80 | "choices": null,
81 | "additional_attributes": null
82 | },
83 | {
84 | "name": "min_impurity_decrease",
85 | "option_type": "",
86 | "default": null,
87 | "description": "A node will be split if this split induces a decrease of the impurity #.",
88 | "required": false,
89 | "choices": null,
90 | "additional_attributes": null
91 | },
92 | {
93 | "name": "ccp_alpha",
94 | "option_type": "",
95 | "default": null,
96 | "description": "Complexity parameter used for Minimal Cost-Complexity Pruning.",
97 | "required": false,
98 | "choices": null,
99 | "additional_attributes": null
100 | }
101 | ]
102 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/expected_dummy_option_parameters.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "strategy",
4 | "option_type": "",
5 | "default": null,
6 | "description": "Strategy to use to generate predictions.",
7 | "required": false,
8 | "choices": ["mean", "median", "quantile", "constant"],
9 | "additional_attributes": null
10 | },
11 | {
12 | "name": "constant",
13 | "option_type": "",
14 | "default": null,
15 | "description": "The explicit constant as predicted by the \"constant\" strategy.",
16 | "required": false,
17 | "choices": null,
18 | "additional_attributes": null
19 | },
20 | {
21 | "name": "quantile",
22 | "option_type": "",
23 | "default": null,
24 | "description": "The quantile to predict using the \"quantile\" strategy.",
25 | "required": false,
26 | "choices": null,
27 | "additional_attributes": null
28 | }
29 | ]
30 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/expected_linear_regression_option_parameters.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "fit_intercept",
4 | "option_type": "",
5 | "default": null,
6 | "description": "Whether to calculate the intercept for this model.",
7 | "required": false,
8 | "choices": null,
9 | "additional_attributes": null
10 | },
11 | {
12 | "name": "copy_X",
13 | "option_type": "",
14 | "default": null,
15 | "description": "If True, X will be copied; else, it may be overwritten.",
16 | "required": false,
17 | "choices": null,
18 | "additional_attributes": null
19 | },
20 | {
21 | "name": "n_jobs",
22 | "option_type": "",
23 | "default": null,
24 | "description": "The number of jobs to use for the computation.",
25 | "required": false,
26 | "choices": null,
27 | "additional_attributes": null
28 | },
29 | {
30 | "name": "positive",
31 | "option_type": "",
32 | "default": null,
33 | "description": "When set to True, forces the coefficients to be positive.",
34 | "required": false,
35 | "choices": null,
36 | "additional_attributes": null
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/test_model.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from typing import Any
3 |
4 | from nextmv_sklearn.dummy import DummyRegressor, DummyRegressorOptions
5 | from nextmv_sklearn.ensemble import (
6 | GradientBoostingRegressor,
7 | GradientBoostingRegressorOptions,
8 | RandomForestRegressor,
9 | RandomForestRegressorOptions,
10 | )
11 | from nextmv_sklearn.linear_model import LinearRegression, LinearRegressionOptions
12 | from nextmv_sklearn.neural_network import MLPRegressor, MLPRegressorOptions
13 | from nextmv_sklearn.tree import DecisionTreeRegressor, DecisionTreeRegressorOptions
14 | from sklearn.datasets import load_diabetes
15 |
16 |
17 | class TestModel(unittest.TestCase):
18 | def setUp(self):
19 | X, y = load_diabetes(return_X_y=True)
20 | self.X = X
21 | self.y = y
22 |
23 | def test_dummy(self):
24 | dum_opt = DummyRegressorOptions().to_nextmv()
25 | dum_reg = DummyRegressor(dum_opt)
26 | self.assert_model(dum_reg)
27 |
28 | def test_ensemble(self):
29 | gb_opt = GradientBoostingRegressorOptions().to_nextmv()
30 | gb_reg = GradientBoostingRegressor(gb_opt)
31 | self.assert_model(gb_reg)
32 |
33 | rf_opt = RandomForestRegressorOptions().to_nextmv()
34 | rf_reg = RandomForestRegressor(rf_opt)
35 | self.assert_model(rf_reg)
36 |
37 | def test_linear_model(self):
38 | lm_opt = LinearRegressionOptions().to_nextmv()
39 | lm_reg = LinearRegression(lm_opt)
40 | self.assert_model(lm_reg)
41 |
42 | def test_neural_network(self):
43 | nn_opt = MLPRegressorOptions().to_nextmv()
44 | nn_reg = MLPRegressor(nn_opt)
45 | self.assert_model(nn_reg)
46 |
47 | def test_tree(self):
48 | dt_opt = DecisionTreeRegressorOptions().to_nextmv()
49 | dt_reg = DecisionTreeRegressor(dt_opt)
50 | self.assert_model(dt_reg)
51 |
52 | def assert_model(self, model: Any):
53 | fit = model.fit(self.X, self.y)
54 | pred = model.predict(self.X[:1])
55 | self.assertIsNotNone(fit)
56 | self.assertIsNotNone(pred)
57 | self.assertEqual(len(pred), 1)
58 | self.assertIsInstance(pred[0], float)
59 | self.assertGreaterEqual(pred[0], 0)
60 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/test_options.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import unittest
4 | from typing import Any
5 |
6 | from nextmv_sklearn.dummy import DummyRegressorOptions
7 | from nextmv_sklearn.ensemble import GradientBoostingRegressorOptions, RandomForestRegressorOptions
8 | from nextmv_sklearn.linear_model import LinearRegressionOptions
9 | from nextmv_sklearn.neural_network import MLPRegressorOptions
10 | from nextmv_sklearn.tree import DecisionTreeRegressorOptions
11 |
12 |
13 | class TestOptions(unittest.TestCase):
14 | def test_dummy(self):
15 | opt = DummyRegressorOptions()
16 | self.assertIsNotNone(opt)
17 | self.compare(opt, "dummy")
18 |
19 | def test_ensemble(self):
20 | opt = GradientBoostingRegressorOptions()
21 | self.assertIsNotNone(opt)
22 | self.compare(opt, "gradient_boosting")
23 |
24 | opt = RandomForestRegressorOptions()
25 | self.assertIsNotNone(opt)
26 | self.compare(opt, "random_forest")
27 |
28 | def test_linear_model(self):
29 | opt = LinearRegressionOptions()
30 | self.assertIsNotNone(opt)
31 | self.compare(opt, "linear_regression")
32 |
33 | def test_neural_network(self):
34 | opt = MLPRegressorOptions()
35 | self.assertIsNotNone(opt)
36 | self.compare(opt, "mlp_regressor")
37 |
38 | def test_tree(self):
39 | opt = DecisionTreeRegressorOptions()
40 | self.assertIsNotNone(opt)
41 | self.compare(opt, "decision_tree")
42 |
43 | def compare(self, opt: Any, expected_path: str):
44 | n_opt = opt.to_nextmv()
45 | got = n_opt.options_dict()
46 |
47 | path = os.path.join(os.path.dirname(__file__), f"expected_{expected_path}_option_parameters.json")
48 | with open(path) as f:
49 | expected = json.load(f)
50 |
51 | self.assertListEqual(got, expected)
52 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/test_solution.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 | from typing import Any
4 |
5 | from nextmv_sklearn.dummy import DummyRegressor, DummyRegressorOptions, DummyRegressorSolution, DummyRegressorStatistics
6 | from nextmv_sklearn.ensemble import (
7 | GradientBoostingRegressor,
8 | GradientBoostingRegressorOptions,
9 | GradientBoostingRegressorSolution,
10 | GradientBoostingRegressorStatistics,
11 | RandomForestRegressor,
12 | RandomForestRegressorOptions,
13 | RandomForestRegressorSolution,
14 | RandomForestRegressorStatistics,
15 | )
16 | from nextmv_sklearn.linear_model import (
17 | LinearRegression,
18 | LinearRegressionOptions,
19 | LinearRegressionSolution,
20 | LinearRegressionStatistics,
21 | )
22 | from nextmv_sklearn.neural_network import (
23 | MLPRegressor,
24 | MLPRegressorOptions,
25 | MLPRegressorSolution,
26 | MLPRegressorStatistics,
27 | )
28 | from nextmv_sklearn.tree import (
29 | DecisionTreeRegressor,
30 | DecisionTreeRegressorOptions,
31 | DecisionTreeRegressorSolution,
32 | DecisionTreeRegressorStatistics,
33 | )
34 | from sklearn.datasets import load_diabetes
35 |
36 | import nextmv
37 |
38 |
39 | class TestModel(unittest.TestCase):
40 | SOLUTION_FILE_NAME = "solution_output.json"
41 |
42 | def setUp(self):
43 | X, y = load_diabetes(return_X_y=True)
44 | self.X = X
45 | self.y = y
46 |
47 | def tearDown(self):
48 | if os.path.exists(self.SOLUTION_FILE_NAME):
49 | os.remove(self.SOLUTION_FILE_NAME)
50 |
51 | def test_dummy(self):
52 | dum_opt = DummyRegressorOptions().to_nextmv()
53 | dum_reg = DummyRegressor(dum_opt)
54 | self.assert_load_model(
55 | opt=dum_opt,
56 | model=dum_reg,
57 | solution_class=DummyRegressorSolution,
58 | statistics=DummyRegressorStatistics,
59 | )
60 |
61 | def test_ensemble(self):
62 | gb_opt = GradientBoostingRegressorOptions().to_nextmv()
63 | gb_reg = GradientBoostingRegressor(gb_opt)
64 | self.assert_load_model(
65 | opt=gb_opt,
66 | model=gb_reg,
67 | solution_class=GradientBoostingRegressorSolution,
68 | statistics=GradientBoostingRegressorStatistics,
69 | )
70 |
71 | rf_opt = RandomForestRegressorOptions().to_nextmv()
72 | rf_reg = RandomForestRegressor(rf_opt)
73 | self.assert_load_model(
74 | opt=rf_opt,
75 | model=rf_reg,
76 | solution_class=RandomForestRegressorSolution,
77 | statistics=RandomForestRegressorStatistics,
78 | )
79 |
80 | def test_linear_model(self):
81 | lm_opt = LinearRegressionOptions().to_nextmv()
82 | lm_reg = LinearRegression(lm_opt)
83 | self.assert_load_model(
84 | opt=lm_opt,
85 | model=lm_reg,
86 | solution_class=LinearRegressionSolution,
87 | statistics=LinearRegressionStatistics,
88 | )
89 |
90 | def test_neural_network(self):
91 | nn_opt = MLPRegressorOptions().to_nextmv()
92 | nn_reg = MLPRegressor(nn_opt)
93 | self.assert_load_model(
94 | opt=nn_opt,
95 | model=nn_reg,
96 | solution_class=MLPRegressorSolution,
97 | statistics=MLPRegressorStatistics,
98 | )
99 |
100 | def test_tree(self):
101 | dt_opt = DecisionTreeRegressorOptions().to_nextmv()
102 | dt_reg = DecisionTreeRegressor(dt_opt)
103 | self.assert_load_model(
104 | opt=dt_opt,
105 | model=dt_reg,
106 | solution_class=DecisionTreeRegressorSolution,
107 | statistics=DecisionTreeRegressorStatistics,
108 | )
109 |
110 | def assert_load_model(
111 | self,
112 | opt: nextmv.Options,
113 | model: Any,
114 | solution_class: Any,
115 | statistics: callable,
116 | ):
117 | fit = model.fit(self.X, self.y)
118 | self.assertIsNotNone(fit)
119 |
120 | sol = solution_class.from_model(fit)
121 | self.assertIsNotNone(sol)
122 |
123 | stats: nextmv.Statistics = statistics(fit, self.X, self.y)
124 |
125 | output = nextmv.Output(
126 | options=opt,
127 | solution=sol.to_dict(),
128 | statistics=stats,
129 | )
130 |
131 | nextmv.write(output, self.SOLUTION_FILE_NAME)
132 |
133 | nm_input = nextmv.load(path=self.SOLUTION_FILE_NAME)
134 |
135 | sol_2 = nextmv.from_dict(nm_input.data["solution"])
136 | self.assertIsNotNone(sol_2)
137 |
138 | fit_2 = sol_2.to_model()
139 | self.assertIsNotNone(fit_2)
140 |
141 | pred = fit_2.predict(self.X[:1])
142 | self.assertIsNotNone(pred)
143 | self.assertEqual(len(pred), 1)
144 | self.assertIsInstance(pred[0], float)
145 | self.assertGreaterEqual(pred[0], 0)
146 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/test_statistics.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from typing import Any
3 |
4 | from nextmv_sklearn.dummy import DummyRegressor, DummyRegressorOptions, DummyRegressorStatistics
5 | from nextmv_sklearn.ensemble import (
6 | GradientBoostingRegressor,
7 | GradientBoostingRegressorOptions,
8 | GradientBoostingRegressorStatistics,
9 | RandomForestRegressor,
10 | RandomForestRegressorOptions,
11 | RandomForestRegressorStatistics,
12 | )
13 | from nextmv_sklearn.linear_model import LinearRegression, LinearRegressionOptions, LinearRegressionStatistics
14 | from nextmv_sklearn.neural_network import MLPRegressor, MLPRegressorOptions, MLPRegressorStatistics
15 | from nextmv_sklearn.tree import DecisionTreeRegressor, DecisionTreeRegressorOptions, DecisionTreeRegressorStatistics
16 | from sklearn.datasets import load_diabetes
17 |
18 | import nextmv
19 |
20 |
21 | class TestModel(unittest.TestCase):
22 | def setUp(self):
23 | X, y = load_diabetes(return_X_y=True)
24 | self.X = X
25 | self.y = y
26 |
27 | def test_dummy(self):
28 | dum_opt = DummyRegressorOptions().to_nextmv()
29 | dum_reg = DummyRegressor(dum_opt)
30 | self.assert_statistics(dum_reg, DummyRegressorStatistics)
31 |
32 | def test_ensemble(self):
33 | gb_opt = GradientBoostingRegressorOptions().to_nextmv()
34 | gb_reg = GradientBoostingRegressor(gb_opt)
35 | self.assert_statistics(gb_reg, GradientBoostingRegressorStatistics)
36 |
37 | rf_opt = RandomForestRegressorOptions().to_nextmv()
38 | rf_reg = RandomForestRegressor(rf_opt)
39 | self.assert_statistics(rf_reg, RandomForestRegressorStatistics)
40 |
41 | def test_linear_model(self):
42 | lm_opt = LinearRegressionOptions().to_nextmv()
43 | lm_reg = LinearRegression(lm_opt)
44 | self.assert_statistics(lm_reg, LinearRegressionStatistics)
45 |
46 | def test_neural_network(self):
47 | nn_opt = MLPRegressorOptions().to_nextmv()
48 | nn_reg = MLPRegressor(nn_opt)
49 | self.assert_statistics(nn_reg, MLPRegressorStatistics)
50 |
51 | def test_tree(self):
52 | dt_opt = DecisionTreeRegressorOptions().to_nextmv()
53 | dt_reg = DecisionTreeRegressor(dt_opt)
54 | self.assert_statistics(dt_reg, DecisionTreeRegressorStatistics)
55 |
56 | def assert_statistics(self, model: Any, statistics: callable):
57 | fit = model.fit(self.X, self.y)
58 | stats: nextmv.Statistics = statistics(fit, self.X, self.y)
59 | self.assertIsNotNone(fit)
60 | self.assertIsNotNone(stats)
61 |
62 | stats_dict = stats.to_dict()
63 | self.assertIsInstance(stats_dict, dict)
64 | self.assertGreaterEqual(len(stats_dict), 3)
65 |
66 | custom = stats.result.custom
67 | self.assertIsInstance(custom, dict)
68 | self.assertGreaterEqual(len(custom), 1)
69 |
--------------------------------------------------------------------------------
/nextmv-scikit-learn/tests/test_version.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import nextmv_sklearn as nsklearn
4 |
5 |
6 | class TestLogger(unittest.TestCase):
7 | def test_version(self):
8 | exported_version = nsklearn.VERSION
9 | expected_version = nsklearn.__about__.__version__
10 | self.assertEqual(exported_version, expected_version)
11 |
--------------------------------------------------------------------------------
/nextmv/README.md:
--------------------------------------------------------------------------------
1 | # Nextmv Python SDK
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Nextmv: The home for all your optimization work
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Welcome to `nextmv`, the general Python SDK for the Nextmv Platform.
23 |
24 | 📖 To learn more about the `nextmv`, visit the [docs][docs].
25 |
26 | ## Installation
27 |
28 | Requires Python `>=3.9`. Install using `pip`:
29 |
30 | ```bash
31 | pip install nextmv
32 | ```
33 |
34 | Install all optional dependencies (recommended):
35 |
36 | ```bash
37 | pip install "nextmv[all]"
38 | ```
39 |
40 | [docs]: https://nextmv-py.readthedocs.io/en/latest/nextmv/
41 |
--------------------------------------------------------------------------------
/nextmv/nextmv/__about__.py:
--------------------------------------------------------------------------------
1 | __version__ = "v0.28.2"
2 |
--------------------------------------------------------------------------------
/nextmv/nextmv/__entrypoint__.py:
--------------------------------------------------------------------------------
1 | """
2 | When working in a notebook environment, we don't really create a `main.py` file
3 | with the main entrypoint of the program. Because the logic is mostly encoded
4 | inside the `Model` class, we need to create a `main.py` file that we can run in
5 | Nextmv Cloud. This file is used as that entrypoint. It is not intended for a
6 | human to use it during local development. It is the standard way in which a
7 | `nextmv.Model` is loaded by using mlflow.
8 | """
9 |
10 | from mlflow.pyfunc import load_model
11 |
12 | import nextmv
13 | from nextmv import cloud
14 |
15 |
16 | def main() -> None:
17 | """Entry point for the program."""
18 |
19 | manifest = cloud.Manifest.from_yaml(".")
20 |
21 | # Load the options from the manifest.
22 | options = manifest.extract_options()
23 |
24 | # Load the model.
25 | loaded_model = load_model(
26 | model_uri=manifest.python.model.name,
27 | suppress_warnings=True,
28 | )
29 |
30 | # Load the input and solve the model by using mlflow's inference API.
31 | input = nextmv.load(options=options)
32 | output = loaded_model.predict(input)
33 |
34 | # Write the output.
35 | nextmv.write(output)
36 |
37 |
38 | if __name__ == "__main__":
39 | main()
40 |
--------------------------------------------------------------------------------
/nextmv/nextmv/__init__.py:
--------------------------------------------------------------------------------
1 | """Nextmv Python SDK."""
2 |
3 | from .__about__ import __version__
4 | from .base_model import BaseModel as BaseModel
5 | from .base_model import from_dict as from_dict
6 | from .input import Input as Input
7 | from .input import InputFormat as InputFormat
8 | from .input import InputLoader as InputLoader
9 | from .input import LocalInputLoader as LocalInputLoader
10 | from .input import load as load
11 | from .input import load_local as load_local
12 | from .logger import log as log
13 | from .logger import redirect_stdout as redirect_stdout
14 | from .logger import reset_stdout as reset_stdout
15 | from .model import Model as Model
16 | from .model import ModelConfiguration as ModelConfiguration
17 | from .options import Option as Option
18 | from .options import Options as Options
19 | from .options import Parameter as Parameter
20 | from .output import Asset as Asset
21 | from .output import DataPoint as DataPoint
22 | from .output import LocalOutputWriter as LocalOutputWriter
23 | from .output import Output as Output
24 | from .output import OutputFormat as OutputFormat
25 | from .output import OutputWriter as OutputWriter
26 | from .output import ResultStatistics as ResultStatistics
27 | from .output import RunStatistics as RunStatistics
28 | from .output import Series as Series
29 | from .output import SeriesData as SeriesData
30 | from .output import Statistics as Statistics
31 | from .output import Visual as Visual
32 | from .output import VisualSchema as VisualSchema
33 | from .output import write as write
34 | from .output import write_local as write_local
35 |
36 | VERSION = __version__
37 | """The version of the Nextmv Python SDK."""
38 |
--------------------------------------------------------------------------------
/nextmv/nextmv/base_model.py:
--------------------------------------------------------------------------------
1 | """
2 | Provides base functionality for handling JSON data in models.
3 |
4 | This module contains utilities for converting between dictionaries and model
5 | instances, facilitating data serialization and deserialization.
6 |
7 | Classes
8 | -------
9 | BaseModel:
10 | A base class extending Pydantic's BaseModel with additional methods for
11 | JSON data serialization and deserialization.
12 |
13 | Functions
14 | ---------
15 | from_dict:
16 | Load a data model instance from a dictionary containing class information
17 | and attributes.
18 | """
19 |
20 | from importlib import import_module
21 | from typing import Any, Optional
22 |
23 | from pydantic import BaseModel as PydanticBaseModel
24 |
25 |
26 | class BaseModel(PydanticBaseModel):
27 | """
28 | Base class for data wrangling tasks with JSON.
29 |
30 | This class extends Pydantic's `BaseModel` to provide additional methods
31 | for converting between Python objects and JSON/dictionary representations.
32 | """
33 |
34 | @classmethod
35 | def from_dict(cls, data: Optional[dict[str, Any]] = None):
36 | """
37 | Instantiate the class from a dictionary.
38 |
39 | Parameters
40 | ----------
41 | data : dict[str, Any], optional
42 | The dictionary containing the data to instantiate the class.
43 | If None, returns None.
44 |
45 | Returns
46 | -------
47 | cls or None
48 | An instance of the class with the given data or None if data is None.
49 | """
50 |
51 | if data is None:
52 | return None
53 |
54 | return cls(**data)
55 |
56 | def to_dict(self) -> dict[str, Any]:
57 | """
58 | Convert the class instance to a dictionary.
59 |
60 | The conversion uses Pydantic's model_dump method, excluding None values
61 | and using field aliases if defined.
62 |
63 | Returns
64 | -------
65 | dict[str, Any]
66 | Dictionary representation of the class instance.
67 | """
68 |
69 | return self.model_dump(mode="json", exclude_none=True, by_alias=True)
70 |
71 |
72 | def from_dict(data: dict[str, Any]) -> Any:
73 | """
74 | Load a data model instance from a `dict` with associated class info.
75 |
76 | Parameters
77 | ----------
78 | data : dict[str, Any]
79 | The data to load.
80 |
81 | Returns
82 | -------
83 | Any
84 | The loaded data model instance.
85 | """
86 |
87 | module = import_module(data["class"]["module"])
88 | cls = getattr(module, data["class"]["name"])
89 |
90 | return cls.from_dict(data["attributes"])
91 |
--------------------------------------------------------------------------------
/nextmv/nextmv/cloud/__init__.py:
--------------------------------------------------------------------------------
1 | """Functionality for interacting with the Nextmv Cloud."""
2 |
3 | from .acceptance_test import AcceptanceTest as AcceptanceTest
4 | from .acceptance_test import AcceptanceTestResults as AcceptanceTestResults
5 | from .acceptance_test import Comparison as Comparison
6 | from .acceptance_test import ComparisonInstance as ComparisonInstance
7 | from .acceptance_test import DistributionPercentiles as DistributionPercentiles
8 | from .acceptance_test import DistributionSummaryStatistics as DistributionSummaryStatistics
9 | from .acceptance_test import ExperimentStatus as ExperimentStatus
10 | from .acceptance_test import Metric as Metric
11 | from .acceptance_test import MetricParams as MetricParams
12 | from .acceptance_test import MetricResult as MetricResult
13 | from .acceptance_test import MetricStatistics as MetricStatistics
14 | from .acceptance_test import MetricTolerance as MetricTolerance
15 | from .acceptance_test import MetricType as MetricType
16 | from .acceptance_test import ResultStatistics as ResultStatistics
17 | from .acceptance_test import StatisticType as StatisticType
18 | from .acceptance_test import ToleranceType as ToleranceType
19 | from .account import Account as Account
20 | from .account import Queue as Queue
21 | from .account import QueuedRun as QueuedRun
22 | from .application import Application as Application
23 | from .application import DownloadURL as DownloadURL
24 | from .application import PollingOptions as PollingOptions
25 | from .application import UploadURL as UploadURL
26 | from .application import poll as poll
27 | from .batch_experiment import BatchExperiment as BatchExperiment
28 | from .batch_experiment import BatchExperimentInformation as BatchExperimentInformation
29 | from .batch_experiment import BatchExperimentMetadata as BatchExperimentMetadata
30 | from .batch_experiment import BatchExperimentRun as BatchExperimentRun
31 | from .client import Client as Client
32 | from .client import get_size as get_size
33 | from .input_set import InputSet as InputSet
34 | from .input_set import ManagedInput as ManagedInput
35 | from .instance import Instance as Instance
36 | from .instance import InstanceConfiguration as InstanceConfiguration
37 | from .manifest import MANIFEST_FILE_NAME as MANIFEST_FILE_NAME
38 | from .manifest import Manifest as Manifest
39 | from .manifest import ManifestBuild as ManifestBuild
40 | from .manifest import ManifestOption as ManifestOption
41 | from .manifest import ManifestPython as ManifestPython
42 | from .manifest import ManifestPythonModel as ManifestPythonModel
43 | from .manifest import ManifestRuntime as ManifestRuntime
44 | from .manifest import ManifestType as ManifestType
45 | from .run import ErrorLog as ErrorLog
46 | from .run import ExternalRunResult as ExternalRunResult
47 | from .run import Format as Format
48 | from .run import FormatInput as FormatInput
49 | from .run import Metadata as Metadata
50 | from .run import RunConfiguration as RunConfiguration
51 | from .run import RunInformation as RunInformation
52 | from .run import RunLog as RunLog
53 | from .run import RunQueuing as RunQueuing
54 | from .run import RunResult as RunResult
55 | from .run import RunType as RunType
56 | from .run import RunTypeConfiguration as RunTypeConfiguration
57 | from .run import TrackedRun as TrackedRun
58 | from .run import TrackedRunStatus as TrackedRunStatus
59 | from .run import run_duration as run_duration
60 | from .scenario import Scenario as Scenario
61 | from .scenario import ScenarioConfiguration as ScenarioConfiguration
62 | from .scenario import ScenarioInput as ScenarioInput
63 | from .scenario import ScenarioInputType as ScenarioInputType
64 | from .secrets import Secret as Secret
65 | from .secrets import SecretsCollection as SecretsCollection
66 | from .secrets import SecretsCollectionSummary as SecretsCollectionSummary
67 | from .secrets import SecretType as SecretType
68 | from .status import Status as Status
69 | from .status import StatusV2 as StatusV2
70 | from .version import Version as Version
71 | from .version import VersionExecutable as VersionExecutable
72 | from .version import VersionExecutableRequirements as VersionExecutableRequirements
73 |
--------------------------------------------------------------------------------
/nextmv/nextmv/cloud/input_set.py:
--------------------------------------------------------------------------------
1 | """Definitions for input sets and related cloud objects.
2 |
3 | This module provides classes for managing inputs and input sets in the Nextmv Cloud.
4 |
5 | Classes
6 | -------
7 | ManagedInput
8 | An input created for experimenting with an application.
9 | InputSet
10 | A collection of inputs from associated runs.
11 | """
12 |
13 | from datetime import datetime
14 | from typing import Optional
15 |
16 | from nextmv.base_model import BaseModel
17 | from nextmv.cloud.run import Format
18 |
19 |
20 | class ManagedInput(BaseModel):
21 | """An input created for experimenting with an application.
22 |
23 | You can import the `ManagedInput` class directly from `cloud`:
24 |
25 | ```python
26 | from nextmv.cloud import ManagedInput
27 | ```
28 |
29 | This class represents an input that was uploaded to the Nextmv Cloud
30 | for experimentation purposes. It contains metadata about the input,
31 | such as its ID, name, description, and creation time.
32 |
33 | Parameters
34 | ----------
35 | id : str
36 | Unique identifier of the input.
37 | name : str, optional
38 | User-defined name of the input.
39 | description : str, optional
40 | User-defined description of the input.
41 | run_id : str, optional
42 | Identifier of the run that created this input.
43 | upload_id : str, optional
44 | Identifier of the upload that created this input.
45 | format : Format, optional
46 | Format of the input (e.g., JSON, CSV).
47 | created_at : datetime, optional
48 | Timestamp when the input was created.
49 | updated_at : datetime, optional
50 | Timestamp when the input was last updated.
51 |
52 | Examples
53 | --------
54 | >>> input = ManagedInput(id="inp_123456789")
55 | >>> print(input.id)
56 | inp_123456789
57 | """
58 |
59 | id: str
60 | """ID of the input."""
61 |
62 | name: Optional[str] = None
63 | """Name of the input."""
64 | description: Optional[str] = None
65 | """Description of the input."""
66 | run_id: Optional[str] = None
67 | """ID of the run that created the input."""
68 | upload_id: Optional[str] = None
69 | """ID of the upload that created the input."""
70 | format: Optional[Format] = None
71 | """Format of the input."""
72 | created_at: Optional[datetime] = None
73 | """Creation time of the input."""
74 | updated_at: Optional[datetime] = None
75 | """Last update time of the input."""
76 |
77 |
78 | class InputSet(BaseModel):
79 | """A collection of inputs from associated runs.
80 |
81 | You can import the `InputSet` class directly from `cloud`:
82 |
83 | ```python
84 | from nextmv.cloud import InputSet
85 | ```
86 |
87 | An input set aggregates multiple inputs used for experimentation with an application
88 | in the Nextmv Cloud. It allows organizing and managing related inputs
89 | for comparison and analysis.
90 |
91 | Parameters
92 | ----------
93 | app_id : str
94 | Identifier of the application that the input set belongs to.
95 | created_at : datetime
96 | Timestamp when the input set was created.
97 | description : str
98 | User-defined description of the input set.
99 | id : str
100 | Unique identifier of the input set.
101 | input_ids : list[str]
102 | List of identifiers of the inputs in the input set.
103 | name : str
104 | User-defined name of the input set.
105 | updated_at : datetime
106 | Timestamp when the input set was last updated.
107 | inputs : list[ManagedInput]
108 | List of ManagedInput objects contained in this input set.
109 |
110 | Examples
111 | --------
112 | >>> input_set = InputSet(
113 | ... app_id="app_123456789",
114 | ... id="is_987654321",
115 | ... name="My Input Set",
116 | ... description="A collection of routing inputs",
117 | ... input_ids=["inp_111", "inp_222"],
118 | ... created_at=datetime.now(),
119 | ... updated_at=datetime.now(),
120 | ... inputs=[]
121 | ... )
122 | >>> print(input_set.name)
123 | My Input Set
124 | >>> print(len(input_set.input_ids))
125 | 2
126 | """
127 |
128 | app_id: str
129 | """ID of the application that the input set belongs to."""
130 | created_at: datetime
131 | """Creation time of the input set."""
132 | description: str
133 | """Description of the input set."""
134 | id: str
135 | """ID of the input set."""
136 | input_ids: list[str]
137 | """IDs of the inputs in the input set."""
138 | name: str
139 | """Name of the input set."""
140 | updated_at: datetime
141 | """Last update time of the input set."""
142 | inputs: list[ManagedInput]
143 | """List of inputs in the input set."""
144 |
--------------------------------------------------------------------------------
/nextmv/nextmv/cloud/instance.py:
--------------------------------------------------------------------------------
1 | """Classes for working with Nextmv Cloud Instances.
2 |
3 | This module provides classes for interacting with instances in Nextmv Cloud.
4 | It defines the core data structures for both instance configuration and the
5 | instance itself.
6 |
7 | Classes
8 | -------
9 | InstanceConfiguration
10 | Configuration settings for a Nextmv Cloud instance.
11 | Instance
12 | Representation of a Nextmv Cloud instance tied to an application version.
13 | """
14 |
15 | from datetime import datetime
16 | from typing import Optional
17 |
18 | from nextmv.base_model import BaseModel
19 |
20 |
21 | class InstanceConfiguration(BaseModel):
22 | """Configuration for a Nextmv Cloud instance.
23 |
24 | You can import the `InstanceConfiguration` class directly from `cloud`:
25 |
26 | ```python
27 | from nextmv.cloud import InstanceConfiguration
28 | ```
29 |
30 | This class represents the configuration settings that can be applied to a
31 | Nextmv Cloud instance, including execution class, options, and secrets.
32 |
33 | Parameters
34 | ----------
35 | execution_class : str, optional
36 | The execution class for the instance, which determines compute resources.
37 | options : dict, optional
38 | Runtime options/parameters for the application.
39 | secrets_collection_id : str, optional
40 | ID of the secrets collection to use with this instance.
41 |
42 | Examples
43 | --------
44 | >>> config = InstanceConfiguration(
45 | ... execution_class="small",
46 | ... options={"max_runtime": 30},
47 | ... secrets_collection_id="sc_1234567890"
48 | ... )
49 | """
50 |
51 | execution_class: Optional[str] = None
52 | """Execution class for the instance."""
53 | options: Optional[dict] = None
54 | """Options of the app that the instance uses."""
55 | secrets_collection_id: Optional[str] = None
56 | """ID of the secrets collection that the instance uses."""
57 |
58 |
59 | class Instance(BaseModel):
60 | """An instance of an application tied to a version with configuration.
61 |
62 | You can import the `Instance` class directly from `cloud`:
63 |
64 | ```python
65 | from nextmv.cloud import Instance
66 | ```
67 |
68 | A Nextmv Cloud instance represents a deployable configuration of an application
69 | version. Instances have their own unique identity and can be used to run jobs
70 | with specific configurations.
71 |
72 | Parameters
73 | ----------
74 | id : str
75 | The unique identifier of the instance.
76 | application_id : str
77 | ID of the application that this instance belongs to.
78 | version_id : str
79 | ID of the application version this instance uses.
80 | name : str
81 | Human-readable name of the instance.
82 | description : str
83 | Detailed description of the instance.
84 | configuration : InstanceConfiguration
85 | Configuration settings for this instance.
86 | locked : bool
87 | Whether the instance is locked for modifications.
88 | created_at : datetime
89 | Timestamp when the instance was created.
90 | updated_at : datetime
91 | Timestamp when the instance was last updated.
92 |
93 | Examples
94 | --------
95 | >>> from nextmv.cloud import Instance, InstanceConfiguration
96 | >>> instance = Instance(
97 | ... id="inst_1234567890",
98 | ... application_id="app_1234567890",
99 | ... version_id="ver_1234567890",
100 | ... name="Production Routing Instance",
101 | ... description="Instance for daily production routing jobs",
102 | ... configuration=InstanceConfiguration(execution_class="small"),
103 | ... locked=False,
104 | ... created_at=datetime.now(),
105 | ... updated_at=datetime.now()
106 | ... )
107 | """
108 |
109 | id: str
110 | """ID of the instance."""
111 | application_id: str
112 | """ID of the application that this is an instance of."""
113 | version_id: str
114 | """ID of the version that this instance is uses."""
115 | name: str
116 | """Name of the instance."""
117 | description: str
118 | """Description of the instance."""
119 | configuration: InstanceConfiguration
120 | """Configuration for the instance."""
121 | locked: bool
122 | """Whether the instance is locked."""
123 | created_at: datetime
124 | """Creation time of the instance."""
125 | updated_at: datetime
126 | """Last update time of the instance."""
127 |
--------------------------------------------------------------------------------
/nextmv/nextmv/cloud/safe.py:
--------------------------------------------------------------------------------
1 | """
2 | Utilities for generating “safe” IDs and huma-readable names
3 | """
4 |
5 | import re
6 | import secrets
7 | import string
8 |
9 | ENTITY_ID_CHAR_COUNT_MAX: int = 40
10 | INDEX_TAG_CHAR_COUNT: int = 3 # room reserved for “-001”, “-xyz”, etc.
11 | RE_NON_ALNUM = re.compile(r"[^A-Za-z0-9]+")
12 |
13 |
14 | def _kebab_case(value: str) -> str:
15 | """Convert arbitrary text to `kebab-case` (lower-case, hyphen-separated)."""
16 |
17 | cleaned = RE_NON_ALNUM.sub(" ", value).strip()
18 | return "-".join(word.lower() for word in cleaned.split())
19 |
20 |
21 | def _start_case(value: str) -> str:
22 | """Convert `kebab-case` (or any hyphen/underscore string) to `Start Case`."""
23 |
24 | cleaned = re.sub(r"[-_]+", " ", value)
25 | return " ".join(word.capitalize() for word in cleaned.split())
26 |
27 |
28 | def _nanoid(size: int = 8, alphabet: str = string.ascii_lowercase + string.digits) -> str:
29 | """Simple nanoid clone using the std-lib `secrets` module."""
30 |
31 | return "".join(secrets.choice(alphabet) for _ in range(size))
32 |
33 |
34 | def _name_and_id(prefix: str, entity_id: str) -> tuple[str, str]:
35 | """
36 | Generate a safe ID and human-readable name from a prefix and user-supplied
37 | identifier.
38 |
39 | Parameters
40 | ----------
41 | prefix : str
42 | Prefix to use for the ID.
43 | entity_id : str
44 | User-supplied identifier. This will be converted to `kebab-case` and
45 | truncated to fit within the safe ID length.
46 |
47 | Returns
48 | -------
49 | tuple[str, str]
50 | A tuple containing the human-readable name and the safe ID.
51 | """
52 |
53 | if not prefix or not entity_id:
54 | return "", ""
55 |
56 | safe_user_defined_id = _kebab_case(entity_id)
57 | random_slug = _nanoid(8)
58 |
59 | # Space available for user text once prefix, random slug and separator "-"
60 | # are accounted for
61 | safe_id_max = (
62 | ENTITY_ID_CHAR_COUNT_MAX
63 | - INDEX_TAG_CHAR_COUNT
64 | - len(prefix)
65 | - (len(random_slug) + 1) # +1 for the hyphen before the slug
66 | )
67 |
68 | safe_id_parts: list[str] = [prefix]
69 |
70 | for word in safe_user_defined_id.split("-"):
71 | # Trim individual word if it alone would overflow
72 | safe_slug = word[: safe_id_max - 1] if len(word) > safe_id_max else word
73 |
74 | # Will the combined ID (so far) overflow if we add this slug?
75 | prospective_len = len("-".join(safe_id_parts + [safe_slug]))
76 | if prospective_len >= safe_id_max:
77 | break
78 | safe_id_parts.append(safe_slug)
79 |
80 | safe_id = "-".join(filter(None, safe_id_parts)) + f"-{random_slug}"
81 | safe_name = _start_case(safe_id)
82 |
83 | return safe_name, safe_id
84 |
--------------------------------------------------------------------------------
/nextmv/nextmv/cloud/status.py:
--------------------------------------------------------------------------------
1 | """
2 | Provides status enums for Nextmv Cloud runs.
3 |
4 | This module defines enumerations for representing the status of a run in
5 | Nextmv Cloud. It includes a deprecated `Status` enum and the current `StatusV2`
6 | enum.
7 |
8 | Classes
9 | -------
10 | Status
11 | Deprecated status of a run.
12 | StatusV2
13 | Represents the status of a run.
14 | """
15 |
16 | from enum import Enum
17 |
18 |
19 | class Status(str, Enum):
20 | """
21 | !!! warning
22 | `Status` is deprecated, use `StatusV2` instead.
23 |
24 | Status of a run.
25 |
26 | You can import the `Status` class directly from `cloud`:
27 |
28 | ```python
29 | from nextmv.cloud import Status
30 | ```
31 |
32 | This enum represents the possible states of a run. It is deprecated and
33 | `StatusV2` should be used for new implementations.
34 |
35 | Attributes
36 | ----------
37 | failed : str
38 | Run failed.
39 | running : str
40 | Run is running.
41 | succeeded : str
42 | Run succeeded.
43 |
44 | Examples
45 | --------
46 | >>> from nextmv.cloud import Status
47 | >>> current_status = Status.running
48 | >>> if current_status == Status.succeeded:
49 | ... print("Run completed successfully.")
50 | ... elif current_status == Status.failed:
51 | ... print("Run failed.")
52 | ... else:
53 | ... print(f"Run is currently {current_status.value}.")
54 | Run is currently running.
55 |
56 | """
57 |
58 | failed = "failed"
59 | """Run failed."""
60 | running = "running"
61 | """Run is running."""
62 | succeeded = "succeeded"
63 | """Run succeeded."""
64 |
65 |
66 | class StatusV2(str, Enum):
67 | """
68 | Status of a run.
69 |
70 | You can import the `StatusV2` class directly from `cloud`:
71 |
72 | ```python
73 | from nextmv.cloud import StatusV2
74 | ```
75 |
76 | This enum represents the comprehensive set of possible states for a run
77 | in Nextmv Cloud.
78 |
79 | Attributes
80 | ----------
81 | canceled : str
82 | Run was canceled.
83 | failed : str
84 | Run failed.
85 | none : str
86 | Run has no status.
87 | queued : str
88 | Run is queued.
89 | running : str
90 | Run is running.
91 | succeeded : str
92 | Run succeeded.
93 |
94 | Examples
95 | --------
96 | >>> from nextmv.cloud import StatusV2
97 | >>> run_status = StatusV2.queued
98 | >>> print(f"The run status is: {run_status.value}")
99 | The run status is: queued
100 |
101 | >>> if run_status == StatusV2.succeeded:
102 | ... print("Processing complete.")
103 | ... elif run_status in [StatusV2.queued, StatusV2.running]:
104 | ... print("Processing in progress.")
105 | ... else:
106 | ... print("Processing has not started or has ended with issues.")
107 | Processing in progress.
108 |
109 | """
110 |
111 | canceled = "canceled"
112 | """Run was canceled."""
113 | failed = "failed"
114 | """Run failed."""
115 | none = "none"
116 | """Run has no status."""
117 | queued = "queued"
118 | """Run is queued."""
119 | running = "running"
120 | """Run is running."""
121 | succeeded = "succeeded"
122 | """Run succeeded."""
123 |
--------------------------------------------------------------------------------
/nextmv/nextmv/deprecated.py:
--------------------------------------------------------------------------------
1 | """Utilities for handling deprecated functionality within the Nextmv Python SDK.
2 |
3 | This module provides tools to mark functions, methods, or features as deprecated,
4 | emitting appropriate warnings to users. These warnings inform users that the
5 | functionality will be removed in a future release and suggest alternative approaches.
6 |
7 | The main purpose of this module is to help with the smooth transition when
8 | API changes are necessary, giving users time to update their code before
9 | functionality is removed completely.
10 | """
11 |
12 | import warnings
13 |
14 |
15 | def deprecated(name: str, reason: str) -> None:
16 | """Mark functionality as deprecated with a warning message.
17 |
18 | This function emits a DeprecationWarning when called, indicating that
19 | the functionality will be removed in a future release.
20 |
21 | Parameters
22 | ----------
23 | name : str
24 | The name of the function, method, or feature being deprecated.
25 | reason : str
26 | The reason why the functionality is being deprecated, possibly
27 | with suggestions for alternative approaches.
28 |
29 | Notes
30 | -----
31 | This function temporarily changes the warning filter to ensure the
32 | deprecation warning is shown, then resets it afterward.
33 |
34 | Examples
35 | --------
36 | >>> def some_function():
37 | ... deprecated("feature_x", "Use feature_y instead")
38 | ... # function implementation
39 | """
40 |
41 | warnings.simplefilter("always", DeprecationWarning)
42 | warnings.warn(
43 | f"{name}: {reason}. This functionality will be removed in a future release",
44 | category=DeprecationWarning,
45 | stacklevel=2,
46 | )
47 | warnings.simplefilter("default", DeprecationWarning)
48 |
--------------------------------------------------------------------------------
/nextmv/nextmv/logger.py:
--------------------------------------------------------------------------------
1 | """
2 | Logger module that writes to stderr.
3 |
4 | This module provides utilities for redirecting standard output to standard error
5 | and for writing log messages directly to stderr.
6 |
7 | Functions
8 | ---------
9 | redirect_stdout
10 | Redirect all messages written to stdout to stderr.
11 | reset_stdout
12 | Reset stdout to its original value.
13 | log
14 | Log a message to stderr.
15 | """
16 |
17 | import sys
18 |
19 | # Original stdout reference held when redirection is active
20 | __original_stdout = None
21 | # Flag to track if stdout has been redirected
22 | __stdout_redirected = False
23 |
24 |
25 | def redirect_stdout() -> None:
26 | """
27 | Redirect all messages written to stdout to stderr.
28 |
29 | You can import the `redirect_stdout` function directly from `nextmv`:
30 |
31 | ```python
32 | from nextmv import redirect_stdout
33 | ```
34 |
35 | This function captures the current sys.stdout and replaces it with sys.stderr.
36 | When redirection is no longer needed, call `reset_stdout()` to restore the
37 | original stdout.
38 |
39 | Examples
40 | --------
41 | >>> redirect_stdout()
42 | >>> print("This will go to stderr")
43 | >>> reset_stdout()
44 | >>> print("This will go to stdout")
45 | """
46 |
47 | global __original_stdout, __stdout_redirected
48 | if __stdout_redirected:
49 | return
50 | __stdout_redirected = True
51 |
52 | __original_stdout = sys.stdout
53 | sys.stdout = sys.stderr
54 |
55 |
56 | def reset_stdout() -> None:
57 | """
58 | Reset stdout to its original value.
59 |
60 | You can import the `reset_stdout` function directly from `nextmv`:
61 |
62 | ```python
63 | from nextmv import reset_stdout
64 | ```
65 |
66 | This function should always be called after `redirect_stdout()` to avoid
67 | unexpected behavior. It restores the original stdout that was captured
68 | during redirection.
69 |
70 | Examples
71 | --------
72 | >>> redirect_stdout()
73 | >>> print("This will go to stderr")
74 | >>> reset_stdout()
75 | >>> print("This will go to stdout")
76 | """
77 | global __original_stdout, __stdout_redirected
78 | if not __stdout_redirected:
79 | return
80 | __stdout_redirected = False
81 |
82 | if __original_stdout is None:
83 | sys.stdout = sys.__stdout__
84 | return
85 |
86 | sys.stdout = __original_stdout
87 | __original_stdout = None
88 |
89 |
90 | def log(message: str) -> None:
91 | """
92 | Log a message to stderr.
93 |
94 | You can import the `log` function directly from `nextmv`:
95 |
96 | ```python
97 | from nextmv import log
98 | ```
99 |
100 | Parameters
101 | ----------
102 | message : str
103 | The message to log.
104 |
105 | Examples
106 | --------
107 | >>> log("An error occurred")
108 | An error occurred
109 | """
110 |
111 | print(message, file=sys.stderr)
112 |
--------------------------------------------------------------------------------
/nextmv/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | build-backend = "hatchling.build"
3 | requires = ["hatchling >= 1.13.0"]
4 |
5 | [project]
6 | authors = [
7 | { email = "tech@nextmv.io", name = "Nextmv" }
8 | ]
9 | classifiers = [
10 | "License :: OSI Approved :: Apache Software License",
11 | "Operating System :: OS Independent",
12 | "Programming Language :: Python :: 3",
13 | "Programming Language :: Python :: 3.9",
14 | "Programming Language :: Python :: 3.10",
15 | "Programming Language :: Python :: 3.11",
16 | "Programming Language :: Python :: 3.12",
17 | "Programming Language :: Python :: 3.13"
18 | ]
19 | dependencies = [
20 | "pydantic>=2.5.2",
21 | "urllib3>=2.1.0",
22 | "requests>=2.31.0",
23 | "pyyaml>=6.0.1",
24 | ]
25 | description = "The all-purpose Python SDK for Nextmv"
26 | dynamic = [
27 | "version",
28 | ]
29 | keywords = [
30 | "decision engineering",
31 | "decision science",
32 | "decisions",
33 | "nextmv",
34 | "optimization",
35 | "operations research",
36 | "shift scheduling",
37 | "solver",
38 | "vehicle routing problem",
39 | ]
40 | license = { file = "LICENSE" }
41 | maintainers = [
42 | { email = "tech@nextmv.io", name = "Nextmv" }
43 | ]
44 | name = "nextmv"
45 | readme = "README.md"
46 | requires-python = ">=3.9"
47 |
48 | [project.urls]
49 | Homepage = "https://www.nextmv.io"
50 | Documentation = "https://www.nextmv.io/docs/python-sdks/nextmv/installation"
51 | Repository = "https://github.com/nextmv-io/nextmv-py"
52 |
53 | [project.optional-dependencies]
54 | all = [
55 | "mlflow>=2.17.2",
56 | ]
57 |
58 | [tool.hatch.version]
59 | path = "nextmv/__about__.py"
60 |
--------------------------------------------------------------------------------
/nextmv/requirements.txt:
--------------------------------------------------------------------------------
1 | build>=1.0.3
2 | pydantic>=2.5.2
3 | pyyaml>=6.0.1
4 | urllib3>=2.1.0
5 | requests>=2.31.0
6 | ruff>=0.1.7
7 | twine>=4.0.2
8 | mlflow>=2.19.0
9 |
10 | # We need nextroute for the `test_entrypoint`, where we are testing that an
11 | # mlflow model can be run using the __entrypoint__.py file as a main.py script.
12 | nextroute>=1.11.1
13 |
--------------------------------------------------------------------------------
/nextmv/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/nextmv/tests/__init__.py
--------------------------------------------------------------------------------
/nextmv/tests/cloud/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/nextmv/tests/cloud/__init__.py
--------------------------------------------------------------------------------
/nextmv/tests/cloud/app.yaml:
--------------------------------------------------------------------------------
1 | files:
2 | - main.py
3 | - super_cool_model/**
4 | python:
5 | model:
6 | name: super_cool_model
7 | options:
8 | - name: duration
9 | default: 30
10 | description: Max runtime duration (in seconds).
11 | param_type:
12 | required: false
13 | pip-requirements: model_requirements.txt
14 | runtime: ghcr.io/nextmv-io/runtime/python:3.11
15 | type: python
16 | pre-push: echo 'hello world - pre-push'
17 | build:
18 | command: echo 'hello world - build'
19 | environment:
20 | SUPER: COOL
21 | EXTRA: AWESOME
22 | configuration:
23 | options:
24 | strict: false
25 | items:
26 | - name: a string parameter
27 | option_type: string
28 | default: a default value
29 | description: a description
30 | required: true
31 | - name: a boolean parameter
32 | option_type: bool
33 | default: true
34 | description: a description
35 | required: false
36 | - name: an integer parameter
37 | option_type: int
38 | default: 42
39 | description: a description
40 | required: false
41 | - name: a float parameter
42 | option_type: float
43 | default: 3.14
44 | description: a description
45 | required: false
46 |
--------------------------------------------------------------------------------
/nextmv/tests/cloud/test_application.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from typing import Any
3 |
4 | from nextmv.cloud.application import PollingOptions, poll
5 |
6 |
7 | class TestApplication(unittest.TestCase):
8 | def test_poll(self):
9 | counter = 0
10 |
11 | def polling_func() -> tuple[Any, bool]:
12 | nonlocal counter
13 | counter += 1
14 |
15 | if counter < 4:
16 | return "result", False
17 |
18 | return "result", True
19 |
20 | polling_options = PollingOptions(verbose=True)
21 |
22 | result = poll(polling_options, polling_func)
23 |
24 | self.assertEqual(result, "result")
25 |
26 | def test_poll_stop_callback(self):
27 | counter = 0
28 |
29 | # The polling func would stop after 9 calls.
30 | def polling_func() -> tuple[Any, bool]:
31 | nonlocal counter
32 | counter += 1
33 |
34 | if counter < 10:
35 | return "result", False
36 |
37 | return "result", True
38 |
39 | # The stop callback makes sure that the polling stops sooner, after 3
40 | # calls.
41 | def stop() -> bool:
42 | if counter == 3:
43 | return True
44 |
45 | polling_options = PollingOptions(verbose=True, stop=stop)
46 |
47 | result = poll(polling_options, polling_func)
48 |
49 | self.assertIsNone(result)
50 |
--------------------------------------------------------------------------------
/nextmv/tests/cloud/test_client.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | from nextmv.cloud import Client
5 |
6 |
7 | class TestClient(unittest.TestCase):
8 | def test_api_key(self):
9 | client1 = Client(api_key="foo")
10 | self.assertEqual(client1.api_key, "foo")
11 |
12 | os.environ["NEXTMV_API_KEY"] = "bar"
13 | client2 = Client()
14 | self.assertEqual(client2.api_key, "bar")
15 | os.environ.pop("NEXTMV_API_KEY")
16 |
17 | with self.assertRaises(ValueError):
18 | Client(api_key="")
19 |
20 | with self.assertRaises(ValueError):
21 | Client(configuration_file="")
22 |
23 | os.environ["NEXTMV_PROFILE"] = "i-like-turtles"
24 | with self.assertRaises(ValueError):
25 | Client()
26 |
27 | def test_headers(self):
28 | client1 = Client(api_key="foo")
29 | self.assertIsNotNone(client1.headers)
30 |
31 | os.environ["NEXTMV_API_KEY"] = "bar"
32 | client2 = Client()
33 | self.assertEqual(client2.api_key, "bar")
34 | self.assertIsNotNone(client2.headers)
35 | os.environ.pop("NEXTMV_API_KEY")
36 |
--------------------------------------------------------------------------------
/nextmv/tests/cloud/test_package.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import tempfile
4 | import unittest
5 |
6 | from nextmv.cloud.manifest import Manifest, ManifestType
7 | from nextmv.cloud.package import _package
8 |
9 |
10 | class TestPackageOneFile(unittest.TestCase):
11 | def setUp(self):
12 | self.app_dir = tempfile.mkdtemp()
13 | self.manifest = Manifest(type=ManifestType.PYTHON, files=["main.py"], python=None)
14 | with open(os.path.join(self.app_dir, "main.py"), "w") as f:
15 | f.write("print('Hello, World!')")
16 |
17 | def tearDown(self):
18 | shutil.rmtree(self.app_dir)
19 |
20 | def test_package_creates_tarball(self):
21 | tar_file, output_dir = _package(self.app_dir, self.manifest, verbose=False)
22 | self.assertTrue(os.path.isfile(tar_file))
23 | self.assertTrue(os.path.isdir(output_dir))
24 |
25 | def test_package_missing_files(self):
26 | self.manifest.files.append("missing_file.py")
27 | with self.assertRaises(Exception) as context:
28 | _package(self.app_dir, self.manifest, verbose=False)
29 | self.assertIn("could not find files listed in manifest", str(context.exception))
30 |
31 | def test_package_mandatory_files(self):
32 | self.manifest.type = ManifestType.GO
33 | with self.assertRaises(Exception) as context:
34 | _package(self.app_dir, self.manifest, verbose=False)
35 | self.assertIn("missing mandatory files", str(context.exception))
36 |
37 |
38 | class TestPackageDir(unittest.TestCase):
39 | def setUp(self):
40 | self.app_dir = tempfile.mkdtemp()
41 | self.manifest = Manifest(type=ManifestType.PYTHON, files=["main.py", "app/"], python=None)
42 | with open(os.path.join(self.app_dir, "main.py"), "w") as f:
43 | f.write("print('Hello, World!')")
44 |
45 | os.makedirs(os.path.join(self.app_dir, "app"))
46 | with open(os.path.join(self.app_dir, "app", "main.py"), "w") as f:
47 | f.write("print('Hello, World!')")
48 |
49 | def tearDown(self):
50 | shutil.rmtree(self.app_dir)
51 |
52 | def test_package_creates_tarball(self):
53 | tar_file, output_dir = _package(self.app_dir, self.manifest, verbose=False)
54 | self.assertTrue(os.path.isfile(tar_file))
55 | self.assertTrue(os.path.isdir(output_dir))
56 |
57 | with tempfile.TemporaryDirectory() as tmpdir:
58 | shutil.unpack_archive(tar_file, tmpdir)
59 | self.assertTrue(os.path.isfile(os.path.join(tmpdir, "main.py")))
60 | self.assertTrue(os.path.isdir(os.path.join(tmpdir, "app")))
61 | self.assertTrue(os.path.isfile(os.path.join(tmpdir, "app", "main.py")))
62 |
63 | def test_package_missing_files(self):
64 | self.manifest.files.append("missing_file.py")
65 | with self.assertRaises(Exception) as context:
66 | _package(self.app_dir, self.manifest, verbose=False)
67 | self.assertIn("could not find files listed in manifest", str(context.exception))
68 |
69 | def test_package_mandatory_files(self):
70 | self.manifest.type = ManifestType.GO
71 | with self.assertRaises(Exception) as context:
72 | _package(self.app_dir, self.manifest, verbose=False)
73 | self.assertIn("missing mandatory files", str(context.exception))
74 |
--------------------------------------------------------------------------------
/nextmv/tests/cloud/test_run.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import time
3 | import unittest
4 |
5 | from nextmv.cloud.run import run_duration
6 |
7 |
8 | class TestRunDuration(unittest.TestCase):
9 | def test_run_duration_convenience_func(self):
10 | before_t, before_dt = time.time(), datetime.datetime.now()
11 | diff = 0.25
12 | after_t, after_dt = before_t + diff, before_dt + datetime.timedelta(seconds=diff)
13 | duration_t = run_duration(before_t, after_t)
14 | duration_dt = run_duration(before_dt, after_dt)
15 | self.assertAlmostEqual(duration_t, 250.0, delta=1.0)
16 | self.assertAlmostEqual(duration_dt, 250.0, delta=1.0)
17 | self.assertIsInstance(duration_t, int)
18 | self.assertIsInstance(duration_dt, int)
19 |
--------------------------------------------------------------------------------
/nextmv/tests/cloud/test_safe_name_id.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from nextmv.cloud.safe import _name_and_id
4 |
5 |
6 | class TestSafeNameID(unittest.TestCase):
7 | def test_safe_name_id(self):
8 | name, id = _name_and_id(prefix="inpset", entity_id="scenario-1")
9 | self.assertIn("Inpset Scenario 1", name)
10 | self.assertIn("inpset-scenario-1", id)
11 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/nextmv/tests/scripts/__init__.py
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options1.py:
--------------------------------------------------------------------------------
1 | import nextmv
2 |
3 | options = nextmv.Options(
4 | nextmv.Option("duration", str, "30s", description="solver duration"),
5 | nextmv.Option("threads", int, 4, description="computer threads"),
6 | )
7 |
8 | print(options.to_dict())
9 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options2.py:
--------------------------------------------------------------------------------
1 | import nextmv
2 |
3 | options = nextmv.Options(
4 | nextmv.Option("duration", str, description="solver duration", required=True),
5 | nextmv.Option("threads", int, description="computer threads", required=True),
6 | )
7 |
8 | print(options.to_dict())
9 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options3.py:
--------------------------------------------------------------------------------
1 | import nextmv
2 |
3 | options = nextmv.Options(
4 | nextmv.Option("duration", str),
5 | nextmv.Option("threads", int),
6 | )
7 |
8 | print(options.to_dict())
9 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options4.py:
--------------------------------------------------------------------------------
1 | import nextmv
2 |
3 | options = nextmv.Options(
4 | nextmv.Option("bool_opt", bool, default=True),
5 | )
6 |
7 | print(options.to_dict())
8 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options5.py:
--------------------------------------------------------------------------------
1 | import nextmv
2 |
3 | options = nextmv.Options(
4 | nextmv.Option("str_opt", str, default=None),
5 | )
6 |
7 | print(f"str_opt: {options.str_opt}")
8 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options6.py:
--------------------------------------------------------------------------------
1 | import nextmv
2 |
3 | options = nextmv.Options(
4 | nextmv.Option("-dash-opt", str, default="dash"),
5 | nextmv.Option("underscore_opt", str, default="underscore"),
6 | nextmv.Option("camelCaseOpt", str, default="camel"),
7 | )
8 |
9 | print(options.to_dict())
10 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options7.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 | import nextmv
4 |
5 |
6 | # Define choice enum
7 | class ChoiceEnum(Enum):
8 | choice1 = "choice1"
9 | choice2 = "choice2"
10 |
11 |
12 | options = nextmv.Options(
13 | nextmv.Option("choice_opt", ChoiceEnum, default=ChoiceEnum.choice1),
14 | )
15 |
16 | print(options.to_dict())
17 |
--------------------------------------------------------------------------------
/nextmv/tests/scripts/options_deprecated.py:
--------------------------------------------------------------------------------
1 | import nextmv
2 |
3 | options = nextmv.Options(
4 | nextmv.Option("duration", str, description="solver duration", required=True),
5 | nextmv.Parameter("threads", int, description="computer threads", required=True),
6 | )
7 |
8 | print(options.to_dict())
9 |
--------------------------------------------------------------------------------
/nextmv/tests/test_base_model.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from typing import Optional
3 |
4 | from nextmv.base_model import BaseModel
5 |
6 |
7 | class Foo(BaseModel):
8 | bar: str
9 | baz: Optional[int] = None
10 |
11 |
12 | class Roh(BaseModel):
13 | foo: Foo
14 | qux: Optional[list[str]] = None
15 | lorem: Optional[str] = None
16 |
17 |
18 | class TestBaseModel(unittest.TestCase):
19 | valid_dict = {
20 | "foo": {
21 | "bar": "bar",
22 | "baz": 1,
23 | },
24 | "qux": ["qux"],
25 | "lorem": "lorem",
26 | }
27 |
28 | def test_from_dict(self):
29 | roh = Roh.from_dict(self.valid_dict)
30 | self.assertTrue(isinstance(roh, Roh))
31 | self.assertTrue(isinstance(roh.foo, Foo))
32 |
33 | def test_change_attributes(self):
34 | roh = Roh.from_dict(self.valid_dict)
35 | self.assertEqual(roh.foo.bar, "bar")
36 |
37 | different_str = "different_str"
38 | roh.foo.bar = different_str
39 | self.assertEqual(roh.foo.bar, different_str)
40 |
41 | def test_invalid_dict(self):
42 | invalid = {
43 | "foo": {
44 | "bar": "bar",
45 | "baz": "1",
46 | },
47 | "qux": "qux",
48 | "lorem": "lorem",
49 | }
50 | with self.assertRaises(ValueError):
51 | Roh.from_dict(invalid)
52 |
53 | def test_to_dict(self):
54 | some_none = {
55 | "foo": {
56 | "bar": "bar",
57 | },
58 | "lorem": "lorem",
59 | }
60 | roh = Roh.from_dict(some_none)
61 | parsed = roh.to_dict()
62 | self.assertEqual(parsed, some_none)
63 |
--------------------------------------------------------------------------------
/nextmv/tests/test_entrypoint/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextmv-io/nextmv-py/fa6b50517bd95da7eec18d60ca949cdd67fb7213/nextmv/tests/test_entrypoint/__init__.py
--------------------------------------------------------------------------------
/nextmv/tests/test_entrypoint/test_entrypoint.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import shutil
4 | import subprocess
5 | import sys
6 | import unittest
7 |
8 | import nextmv
9 | import nextmv.cloud
10 |
11 |
12 | class SimpleDecisionModel(nextmv.Model):
13 | def solve(self, input: nextmv.Input) -> nextmv.Output:
14 | return nextmv.Output(
15 | solution={"foo": "bar"},
16 | statistics={"baz": "qux"},
17 | )
18 |
19 |
20 | class TestEntrypoint(unittest.TestCase):
21 | TWO_DIRS_UP = os.path.join("..", "..")
22 | MODEL_NAME = "simple_decision_model"
23 |
24 | def setUp(self):
25 | """Copies the entrypoint script as the main script in the root of an
26 | app."""
27 |
28 | # Copy the entrypoint.
29 | src = self._file_name("__entrypoint__.py", os.path.join(self.TWO_DIRS_UP, "nextmv"))
30 | dst = self._file_name("main.py", self.TWO_DIRS_UP)
31 | shutil.copy(src, dst)
32 |
33 | def tearDown(self):
34 | """Removes the newly created main script elements."""
35 |
36 | filenames = [
37 | self._file_name("main.py", self.TWO_DIRS_UP),
38 | self._file_name("app.yaml", self.TWO_DIRS_UP),
39 | ]
40 |
41 | for filename in filenames:
42 | os.remove(filename)
43 |
44 | shutil.rmtree(self._file_name(self.MODEL_NAME, self.TWO_DIRS_UP))
45 | shutil.rmtree(self._file_name("mlruns", self.TWO_DIRS_UP))
46 |
47 | def test_entrypoint(self):
48 | """
49 | Test that the __entrypoint__.py script runs successfully by mimicking
50 | the unpacking of an app and running the main script. We are using a
51 | sample nextroute app that is already pickled with mlflow in the
52 | "nextroute_model" directory.
53 | """
54 |
55 | model = SimpleDecisionModel()
56 | options = nextmv.Options(nextmv.Option("param1", str, ""))
57 |
58 | model_configuration = nextmv.ModelConfiguration(
59 | name=self.MODEL_NAME,
60 | options=options,
61 | )
62 | destination = os.path.join(os.path.dirname(__file__), self.TWO_DIRS_UP)
63 | model.save(destination, model_configuration)
64 |
65 | manifest = nextmv.cloud.Manifest.from_model_configuration(model_configuration)
66 | manifest.to_yaml(dirpath=destination)
67 |
68 | main_file = self._file_name("main.py", self.TWO_DIRS_UP)
69 |
70 | args = [sys.executable, main_file]
71 | try:
72 | result = subprocess.run(
73 | args,
74 | env=os.environ,
75 | check=True,
76 | text=True,
77 | capture_output=True,
78 | input=json.dumps({}),
79 | )
80 | except subprocess.CalledProcessError as e:
81 | print("stderr:\n", e.stderr)
82 | print("stdout:\n", e.stdout)
83 | print("output:\n", e.output)
84 | raise e
85 |
86 | output = result.stdout
87 |
88 | self.assertEqual(result.returncode, 0, result.stderr)
89 |
90 | self.assertNotEqual(output.strip(), "")
91 |
92 | output_data = json.loads(output)
93 | self.assertIn("statistics", output_data)
94 |
95 | @staticmethod
96 | def _file_name(name: str, relative_location: str = ".") -> str:
97 | """
98 | Returns the full path to a file in the current testing directory.
99 |
100 | Parameters
101 | ----------
102 | name : str
103 | The name of the file.
104 | relative_location : str, optional
105 | The relative location of the file. The default is ".".
106 |
107 | Returns
108 | -------
109 | str
110 | The full path to the file.
111 | """
112 |
113 | return os.path.join(os.path.dirname(__file__), relative_location, name)
114 |
--------------------------------------------------------------------------------
/nextmv/tests/test_logger.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import unittest
3 | from io import StringIO
4 | from unittest.mock import patch
5 |
6 | import nextmv
7 |
8 |
9 | class TestLogger(unittest.TestCase):
10 | @patch("sys.stderr", new_callable=StringIO)
11 | @patch("sys.stdout", new_callable=StringIO)
12 | def test_log(self, mock_stdout, mock_stderr):
13 | # Make sure that stdout is not redirected to stderr, this is, it is not
14 | # affected by the log function.
15 | print("I print a message to stdout")
16 | self.assertEqual(
17 | mock_stdout.getvalue(),
18 | "I print a message to stdout\n",
19 | )
20 |
21 | # Test that calling the simple log function is equivalent to printing
22 | # to stderr.
23 | nextmv.log("doing this")
24 | print("is the same as doing this", file=sys.stderr)
25 | self.assertEqual(
26 | mock_stderr.getvalue(),
27 | "doing this\nis the same as doing this\n",
28 | )
29 |
30 | @patch("sys.stderr", new_callable=StringIO)
31 | @patch("sys.stdout", new_callable=StringIO)
32 | def test_redirect_stdout(self, mock_stdout, mock_stderr):
33 | nextmv.redirect_stdout()
34 |
35 | # I can log to stderr.
36 | nextmv.log("0. log directly to stderr")
37 |
38 | # And if I print to stdout, it will actually be redirected to stderr.
39 | print("1. stdout redirected to stderr")
40 |
41 | # I reset stdout to its original value.
42 | nextmv.reset_stdout()
43 |
44 | # Now I can print to stdout again.
45 | print("2. back to stdout")
46 |
47 | self.assertEqual(
48 | mock_stderr.getvalue(),
49 | "0. log directly to stderr\n1. stdout redirected to stderr\n",
50 | )
51 |
52 | self.assertEqual(
53 | mock_stdout.getvalue(),
54 | "2. back to stdout\n",
55 | )
56 |
--------------------------------------------------------------------------------
/nextmv/tests/test_model.py:
--------------------------------------------------------------------------------
1 | import os
2 | import unittest
3 |
4 | import nextmv
5 | from nextmv.model import _cleanup_python_model
6 |
7 |
8 | class ModelForTesting(nextmv.Model):
9 | """Dummy decision model for testing purposes."""
10 |
11 | def solve(self, input: nextmv.Input) -> nextmv.Output:
12 | return nextmv.Output()
13 |
14 |
15 | class TestModel(unittest.TestCase):
16 | def tearDown(self):
17 | """Removes the newly created mlflow elements, while also testing the
18 | "private" cleanup function."""
19 |
20 | model_configuration = nextmv.ModelConfiguration(
21 | name="test_model",
22 | )
23 | _cleanup_python_model(model_dir=".", model_configuration=model_configuration)
24 |
25 | def test_save(self):
26 | model = ModelForTesting()
27 | model_configuration = nextmv.ModelConfiguration(
28 | name="test_model",
29 | )
30 | model.save(model_dir=".", configuration=model_configuration)
31 |
32 | # Assert that the "test_model" directory was created
33 | model_path = os.path.join(".", model_configuration.name)
34 | self.assertTrue(os.path.isdir(model_path))
35 |
36 | # Assert that the "test_model" directory is not empty
37 | self.assertTrue(len(os.listdir(model_path)) > 0)
38 |
39 | @staticmethod
40 | def _file_name(name: str, relative_location: str = ".") -> str:
41 | """
42 | Returns the full path to a file in the current testing directory.
43 |
44 | Parameters
45 | ----------
46 | name : str
47 | The name of the file.
48 | relative_location : str, optional
49 | The relative location of the file. The default is ".".
50 |
51 | Returns
52 | -------
53 | str
54 | The full path to the file.
55 | """
56 |
57 | return os.path.join(os.path.dirname(__file__), relative_location, name)
58 |
--------------------------------------------------------------------------------
/nextmv/tests/test_version.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import nextmv
4 |
5 |
6 | class TestLogger(unittest.TestCase):
7 | def test_version(self):
8 | exported_version = nextmv.VERSION
9 | expected_version = nextmv.__about__.__version__
10 | self.assertEqual(exported_version, expected_version)
11 |
--------------------------------------------------------------------------------
/ruff.toml:
--------------------------------------------------------------------------------
1 | target-version = "py39"
2 |
3 | # Rule selection.
4 | lint.select = [
5 | "E", # pycodestyle errors
6 | "W", # pycodestyle warnings
7 | "F", # pyflakes
8 | "I", # isort
9 | "C", # flake8-comprehensions
10 | "B", # flake8-bugbear
11 | "UP", # pyupgrade
12 | ]
13 |
14 | # Rule configuration.
15 | line-length = 120
16 |
--------------------------------------------------------------------------------