primo-optimizer 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of primo-optimizer might be problematic. Click here for more details.

Files changed (49) hide show
  1. primo/__init__.py +13 -0
  2. primo/data_parser/__init__.py +10 -0
  3. primo/data_parser/data_model.py +197 -0
  4. primo/data_parser/input_parser.py +161 -0
  5. primo/opt_model/__init__.py +13 -0
  6. primo/opt_model/base_model.py +188 -0
  7. primo/opt_model/opt_model.py +583 -0
  8. primo/opt_model/tests/__init__.py +13 -0
  9. primo/opt_model/tests/test_base_model.py +120 -0
  10. primo/opt_model/tests/test_opt_model.py +137 -0
  11. primo/utils/__init__.py +40 -0
  12. primo/utils/census_utils.py +475 -0
  13. primo/utils/clustering_utils.py +111 -0
  14. primo/utils/config_utils.py +518 -0
  15. primo/utils/demo_utils.py +614 -0
  16. primo/utils/download_utils.py +73 -0
  17. primo/utils/elevation_utils.py +269 -0
  18. primo/utils/estimation_utils.py +346 -0
  19. primo/utils/geo_utils.py +335 -0
  20. primo/utils/kpi_utils.py +269 -0
  21. primo/utils/map_utils.py +536 -0
  22. primo/utils/multiple_scenario_util.py +97 -0
  23. primo/utils/opt_utils.py +165 -0
  24. primo/utils/proximity_utils.py +180 -0
  25. primo/utils/raise_exception.py +42 -0
  26. primo/utils/setup_arg_parser.py +144 -0
  27. primo/utils/setup_logger.py +86 -0
  28. primo/utils/solvers.py +156 -0
  29. primo/utils/tests/__init__.py +10 -0
  30. primo/utils/tests/test_census_utils.py +92 -0
  31. primo/utils/tests/test_clustering_utils.py +142 -0
  32. primo/utils/tests/test_config_utils.py +111 -0
  33. primo/utils/tests/test_demo_utils.py +422 -0
  34. primo/utils/tests/test_elevation_utils.py +152 -0
  35. primo/utils/tests/test_estimation_utils.py +388 -0
  36. primo/utils/tests/test_geo_utils.py +47 -0
  37. primo/utils/tests/test_headers.py +71 -0
  38. primo/utils/tests/test_imports.py +12 -0
  39. primo/utils/tests/test_kpi_utils.py +213 -0
  40. primo/utils/tests/test_map_utils.py +574 -0
  41. primo/utils/tests/test_proximity_utils.py +71 -0
  42. primo/utils/tests/weights_toy_example.xlsx +0 -0
  43. primo_optimizer-0.1.dist-info/LICENSE.md +39 -0
  44. primo_optimizer-0.1.dist-info/METADATA +68 -0
  45. primo_optimizer-0.1.dist-info/RECORD +49 -0
  46. primo_optimizer-0.1.dist-info/WHEEL +5 -0
  47. primo_optimizer-0.1.dist-info/entry_points.txt +2 -0
  48. primo_optimizer-0.1.dist-info/top_level.txt +2 -0
  49. stagedfright.py +367 -0
primo/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ #################################################################################
2
+ # PRIMO - The P&A Project Optimizer was produced under the Methane Emissions Reduction Program (MERP)
3
+ # and National Energy Technology Laboratory's (NETL) National Emissions Reduction Initiative (NEMRI).
4
+ #
5
+ # NOTICE. This Software was developed under funding from the U.S. Government and the U.S.
6
+ # Government consequently retains certain rights. As such, the U.S. Government has been granted for
7
+ # itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in
8
+ # the Software to reproduce, distribute copies to the public, prepare derivative works, and perform
9
+ # publicly and display publicly, and to permit others to do so.
10
+ #################################################################################
11
+
12
+ RELEASE = "0.1.dev0"
13
+ VERSION = "0.1"
@@ -0,0 +1,10 @@
1
+ #################################################################################
2
+ # PRIMO - The P&A Project Optimizer was produced under the Methane Emissions Reduction Program (MERP)
3
+ # and National Energy Technology Laboratory's (NETL) National Emissions Reduction Initiative (NEMRI).
4
+ #
5
+ # NOTICE. This Software was developed under funding from the U.S. Government and the U.S.
6
+ # Government consequently retains certain rights. As such, the U.S. Government has been granted for
7
+ # itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in
8
+ # the Software to reproduce, distribute copies to the public, prepare derivative works, and perform
9
+ # publicly and display publicly, and to permit others to do so.
10
+ #################################################################################
@@ -0,0 +1,197 @@
1
+ #################################################################################
2
+ # PRIMO - The P&A Project Optimizer was produced under the Methane Emissions Reduction Program (MERP)
3
+ # and National Energy Technology Laboratory's (NETL) National Emissions Reduction Initiative (NEMRI).
4
+ #
5
+ # NOTICE. This Software was developed under funding from the U.S. Government and the U.S.
6
+ # Government consequently retains certain rights. As such, the U.S. Government has been granted for
7
+ # itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in
8
+ # the Software to reproduce, distribute copies to the public, prepare derivative works, and perform
9
+ # publicly and display publicly, and to permit others to do so.
10
+ #################################################################################
11
+
12
+ # Standard lib
13
+ from __future__ import annotations
14
+ from dataclasses import dataclass
15
+ import logging
16
+ from typing import Dict, Union
17
+
18
+
19
+ # User defined lib
20
+ from primo.utils.geo_utils import get_distance
21
+ from primo.utils.raise_exception import raise_exception
22
+
23
+ LOGGER = logging.getLogger(__name__)
24
+
25
+ # TODO: Write tests for entire module
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class Well:
30
+ """
31
+ A Well dataclass
32
+ """
33
+
34
+ __slots__ = (
35
+ "well_id",
36
+ "lat",
37
+ "long",
38
+ "score",
39
+ "is_disadvantaged",
40
+ )
41
+ well_id: str
42
+ lat: Union[float, None]
43
+ long: Union[float, None]
44
+ score: float
45
+ is_disadvantaged: bool
46
+
47
+ def haversine_distance(self, other_well: Well) -> float:
48
+ """
49
+ Returns the distance in miles between two wells calculated with Haversine formula.
50
+
51
+ Parameters
52
+ ----------
53
+ other_well : Well
54
+ The other well under consideration
55
+
56
+ Returns
57
+ -------
58
+ distance : float
59
+ The distance between self and other_well
60
+ """
61
+ origin = (self.lat, self.long)
62
+ dest = (other_well.lat, other_well.long)
63
+ return get_distance(origin, dest)
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class CampaignCandidates:
68
+ """
69
+ A dataclass that combines wells that are potential candidates to be included
70
+ in a plugging campaign
71
+ """
72
+
73
+ __slots__ = ("campaign_id", "well_dict", "n_wells")
74
+ campaign_id: str
75
+ well_dict: Dict[str, Well]
76
+ n_wells: int
77
+
78
+ def __post_init__(self):
79
+ """
80
+ Run some validation checks
81
+ """
82
+ for well_id, well in self.well_dict.items():
83
+ if well_id != well.well_id:
84
+ msg = (
85
+ f"Dictionary for {self.campaign_id} is corrupted. Expect key "
86
+ "to be the same as well_id"
87
+ )
88
+ raise_exception(msg, ValueError)
89
+
90
+ if self.n_wells != len(self.well_dict):
91
+ LOGGER.warning(
92
+ f"{self.n_wells} incorrectly initialized for {self.campaign_id}"
93
+ )
94
+ LOGGER.warning("Automatically correcting!")
95
+ self.n_wells = len(self.well_dict)
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class OptInputs:
100
+ """
101
+ Succinctly captures all inputs relevant to the optimization model
102
+ """
103
+
104
+ __slots__ = (
105
+ "well_dict",
106
+ "campaign_candidates",
107
+ "budget",
108
+ "mobilization_cost",
109
+ "owner_well_count",
110
+ "max_wells_per_owner",
111
+ "dac_budget_fraction",
112
+ "distance_threshold",
113
+ )
114
+ well_dict: Dict[str, Well]
115
+ campaign_candidates: Dict[str, CampaignCandidates]
116
+ budget: float
117
+ mobilization_cost: Dict[int, float]
118
+ owner_well_count: Dict[str, Well]
119
+ max_wells_per_owner: float
120
+ dac_budget_fraction: float
121
+ distance_threshold: float
122
+
123
+ def __post_init__(self):
124
+ """
125
+ Run some validation checks
126
+ """
127
+ # Create a set to retrieve unique well_id (identifiers that do not repeat).
128
+ well_id_set = set(self.well_dict.keys())
129
+
130
+ if self.budget <= 0:
131
+ msg = f"Budget cannot be negative. Received " f"{self.budget} for budget."
132
+ raise_exception(msg, ValueError)
133
+
134
+ for well_id, well in self.well_dict.items():
135
+ if well_id != well.well_id:
136
+ msg = (
137
+ f"Dictionary for optimization inputs corrupted. "
138
+ f"Expect key: {well_id} to be the same as"
139
+ f" well_id: {well.well_id}"
140
+ )
141
+ raise_exception(msg, ValueError)
142
+
143
+ max_wells = 0
144
+ # Iterate through each campaign using its campaign_id.
145
+ for campaign_id, campaign in self.campaign_candidates.items():
146
+ # Lines 167-173 aim to verify the accuracy of the
147
+ # campaign_candidates construction.
148
+ if campaign_id != campaign.campaign_id:
149
+ msg = (
150
+ f"Dictionary of candidates corrupted. "
151
+ f"Expect key: {campaign_id} to be the same as "
152
+ f"campaign_id: {campaign.campaign_id}"
153
+ )
154
+ raise_exception(msg, ValueError)
155
+
156
+ for well_id in campaign.well_dict:
157
+ # Lines 178-183 aim to verify whether the wells in CampaignCandidates
158
+ # are all sourced from the input well data.
159
+ if well_id not in self.well_dict:
160
+ msg = (
161
+ f"Campaign candidate: {campaign_id} includes well: {well_id}"
162
+ f". This well is not provided in well_dict"
163
+ )
164
+ raise_exception(msg, ValueError)
165
+
166
+ # Lines 187-190 aim to check if a well appears more than once within
167
+ # the same campaign or across different campaigns.
168
+ if well_id not in well_id_set:
169
+ msg = f"Well: {well_id} is included in multiple clusters (campaign candidates)."
170
+ raise_exception(msg, ValueError)
171
+ well_id_set.remove(well_id)
172
+ # Remove a well_id from the well_id_set the first time it is checked. If the well_id
173
+ # appears a second time and has already been removed from the unique well_id_set,
174
+ # an error message on line 188 will be raised.
175
+ # Each cluster represents a campaign candidate.
176
+
177
+ max_wells = max(max_wells, len(campaign.well_dict))
178
+
179
+ # Upon iterating through all campaigns and well_ids within those campaigns, all
180
+ # well_ids in well_id_set should be dropped. If not, it indicates that not all wells
181
+ # are assigned to a cluster during the clustering step. An error message will be raised at
182
+ # Line 202.
183
+ if well_id_set:
184
+ msg = "The clustering is not exhaustive."
185
+ raise_exception(msg, ValueError)
186
+ for n_wells in range(1, max_wells + 1):
187
+ cost = self.mobilization_cost.get(n_wells)
188
+ if cost is None:
189
+ msg = f"Mobilization cost for n_wells: {n_wells} is missing"
190
+ raise_exception(msg, ValueError)
191
+
192
+ if cost < 0:
193
+ msg = (
194
+ f"Mobilization cost for campaign with n_wells: {n_wells}"
195
+ f" is negative: {cost}"
196
+ )
197
+ raise_exception(msg, ValueError)
@@ -0,0 +1,161 @@
1
+ #################################################################################
2
+ # PRIMO - The P&A Project Optimizer was produced under the Methane Emissions Reduction Program (MERP)
3
+ # and National Energy Technology Laboratory's (NETL) National Emissions Reduction Initiative (NEMRI).
4
+ #
5
+ # NOTICE. This Software was developed under funding from the U.S. Government and the U.S.
6
+ # Government consequently retains certain rights. As such, the U.S. Government has been granted for
7
+ # itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in
8
+ # the Software to reproduce, distribute copies to the public, prepare derivative works, and perform
9
+ # publicly and display publicly, and to permit others to do so.
10
+ #################################################################################
11
+
12
+ # Standard libs
13
+ import argparse
14
+ import logging
15
+ import os
16
+ from typing import Dict
17
+
18
+ # Installed libs
19
+ import pandas as pd
20
+
21
+ # User-defined libs
22
+ from primo.data_parser.data_model import CampaignCandidates, OptInputs, Well
23
+ from primo.utils.raise_exception import raise_exception
24
+
25
+ LOGGER = logging.getLogger(__name__)
26
+
27
+
28
+ class InputParser:
29
+ """
30
+ InputParser class
31
+ Implements methods for parsing input data from csv
32
+ """
33
+
34
+ def __init__(self, args: argparse.Namespace):
35
+ """
36
+ Initialize the class and read in a CSV file.
37
+
38
+ Parameters
39
+ ----------
40
+ args : argparse.Namespace
41
+ The arguments parsed from the command line.
42
+
43
+ Raises
44
+ ------
45
+ ValueError
46
+ If the specified file does not exist or is not a CSV file.
47
+ """
48
+ file_path = args.input_file
49
+ # Check 1: Check file exists
50
+ if not os.path.exists(file_path):
51
+ raise_exception(f"File: {file_path} does not exist.", ValueError)
52
+
53
+ # Check 2: Check file is a csv file
54
+ if file_path.suffix != ".csv":
55
+ raise_exception(
56
+ f"File: {file_path} is expected to be an csv file", ValueError
57
+ )
58
+
59
+ self.well_data = pd.read_csv(file_path)
60
+ self.args = args
61
+ LOGGER.info(f"Initialized class and read file: {file_path}")
62
+
63
+ def parse_data(
64
+ self,
65
+ mobilization_cost: Dict[int, float],
66
+ ) -> OptInputs:
67
+ """
68
+ Parse data to create an OptInputs object that models all input parameters
69
+ to set up the optimization model.
70
+
71
+ Parameters
72
+ ----------
73
+ mobilization_cost : Dict[int, float]
74
+ The cost associated with mobilization for a campaign based on the number
75
+ of wells in a campaign.
76
+
77
+ Returns
78
+ -------
79
+ opt_inputs : OptInputs
80
+ A fully instantiated object for inputs to the optimization model.
81
+
82
+ Raises
83
+ ------
84
+ ValueError
85
+ If a well ID is repeated in the CSV file.
86
+ """
87
+ budget = self.args.total_budget
88
+ max_wells_per_owner = self.args.max_wells_per_owner
89
+ well_dict = {}
90
+ candidates = {}
91
+ # dac_budget_fraction is the percentage of budget that needs
92
+ # to be spent on plugging wells in the disadvantaged community
93
+ dac_budget_fraction = self.args.dac_weight_percent
94
+ distance_threshold = self.args.maximum_well_distance
95
+
96
+ # new data
97
+ owner_well_count = (
98
+ self.well_data.groupby("Operator Name")["Well ID"].apply(list).to_dict()
99
+ )
100
+
101
+ dac_weight = self.args.dac_weight
102
+
103
+ if self.args.dac_weight_percent is not None:
104
+ if dac_weight is not None:
105
+ threshold = dac_weight / 100 * self.args.dac_weight_percent
106
+ else:
107
+ raise_exception(
108
+ "Disadvantaged Community Impact must be selected as a "
109
+ "priority factor if you wish to implement the DAC constraint.",
110
+ ValueError,
111
+ )
112
+ else:
113
+ # When the user does not provide a value for disadvantaged community,
114
+ # all wells are set as not disadvantaged.
115
+ # The maximum score possible for disadvantaged_community_score is 100.
116
+ threshold = 100
117
+ for _, row in self.well_data.iterrows():
118
+ well_id = row["Well ID"]
119
+ lat = row["Latitude"]
120
+ long = row["Longitude"]
121
+ score = row["Priority Score [0-100]"]
122
+ if dac_weight is not None:
123
+ disadvantaged_community_score = row[f"DAC Score [0-{int(dac_weight)}]"]
124
+ is_disadvantaged = float(disadvantaged_community_score > threshold)
125
+ else:
126
+ # When the user does not select DAC as a priority factor,
127
+ # all wells are assumed to not be located in a disadvantaged community.
128
+ is_disadvantaged = float(False)
129
+
130
+ if well_id in well_dict:
131
+ msg = f"Well id must be unique. Well id: {well_id} is repeated in csv file"
132
+ raise_exception(msg, ValueError)
133
+
134
+ well_dict[well_id] = Well(well_id, lat, long, score, is_disadvantaged)
135
+
136
+ LOGGER.info("Parsing for campaign candidates")
137
+
138
+ for _, row in self.well_data.iterrows():
139
+ well_id = row["Well ID"]
140
+ cluster = row["cluster"]
141
+ if cluster not in candidates:
142
+ candidates.setdefault(cluster, {})
143
+ candidates[cluster][well_id] = well_dict[well_id]
144
+
145
+ campaign_candidates = {}
146
+ for campaign_id, campaign_dict in candidates.items():
147
+ LOGGER.info(f"Preparing cluster: {campaign_id}")
148
+ campaign_candidates[campaign_id] = CampaignCandidates(
149
+ campaign_id, campaign_dict, len(campaign_dict)
150
+ )
151
+
152
+ return OptInputs(
153
+ well_dict,
154
+ campaign_candidates,
155
+ budget,
156
+ mobilization_cost,
157
+ owner_well_count,
158
+ max_wells_per_owner,
159
+ dac_budget_fraction,
160
+ distance_threshold,
161
+ )
@@ -0,0 +1,13 @@
1
+ #################################################################################
2
+ # PRIMO - The P&A Project Optimizer was produced under the Methane Emissions Reduction Program (MERP)
3
+ # and National Energy Technology Laboratory's (NETL) National Emissions Reduction Initiative (NEMRI).
4
+ #
5
+ # NOTICE. This Software was developed under funding from the U.S. Government and the U.S.
6
+ # Government consequently retains certain rights. As such, the U.S. Government has been granted for
7
+ # itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in
8
+ # the Software to reproduce, distribute copies to the public, prepare derivative works, and perform
9
+ # publicly and display publicly, and to permit others to do so.
10
+ #################################################################################
11
+
12
+ # Optimization Feasibility tolerance
13
+ FEASIBILITY_TOLERANCE = 1e-6
@@ -0,0 +1,188 @@
1
+ #################################################################################
2
+ # PRIMO - The P&A Project Optimizer was produced under the Methane Emissions Reduction Program (MERP)
3
+ # and National Energy Technology Laboratory's (NETL) National Emissions Reduction Initiative (NEMRI).
4
+ #
5
+ # NOTICE. This Software was developed under funding from the U.S. Government and the U.S.
6
+ # Government consequently retains certain rights. As such, the U.S. Government has been granted for
7
+ # itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in
8
+ # the Software to reproduce, distribute copies to the public, prepare derivative works, and perform
9
+ # publicly and display publicly, and to permit others to do so.
10
+ #################################################################################
11
+
12
+ # Standard libs
13
+ from abc import ABC, abstractmethod
14
+ import logging
15
+ import os
16
+ import pathlib
17
+ from typing import Dict, Union
18
+
19
+ # Installed libs
20
+ import pyomo
21
+ import pyomo.environ as pyo
22
+ from pyomo.contrib.iis import write_iis
23
+
24
+ # User defined libs
25
+ from primo.utils import get_solver
26
+
27
+ LOGGER = logging.getLogger(__name__)
28
+
29
+
30
+ class BaseModel(ABC):
31
+ """
32
+ Base Optimization Model class. To be inherited and used as needed
33
+ """
34
+
35
+ def __init__(self, model_name: str):
36
+ """
37
+ Initializes the base model class.
38
+
39
+ Parameters
40
+ ----------
41
+ model_name : str
42
+ A string identifier to be assigned to the optimization model
43
+
44
+ Returns
45
+ -------
46
+ BaseModel
47
+ A fully instantiated BaseModel object
48
+ """
49
+ self.model_name = model_name
50
+ self.model = pyo.ConcreteModel()
51
+
52
+ @abstractmethod
53
+ def _add_variables(self) -> None:
54
+ pass
55
+
56
+ @abstractmethod
57
+ def _add_constraints(self) -> None:
58
+ pass
59
+
60
+ def _set_objective(self) -> None:
61
+ """
62
+ Override method as needed. By default, assumes that we have a
63
+ feasibility problem
64
+ """
65
+ self.model.obj = pyo.Objective(expr=(0), sense=pyo.minimize)
66
+
67
+ def build_model(self) -> None:
68
+ """
69
+ Initializes a model to represent the optimization problem at hand
70
+ """
71
+ self._add_variables()
72
+ self._add_constraints()
73
+ self._set_objective()
74
+
75
+ def solve_model(
76
+ self, solver_params: Dict = {}
77
+ ) -> pyomo.opt.results.results_.SolverResults:
78
+ """
79
+ Solve the optimization model that has previously been set up using
80
+ the `build_model()` method.
81
+
82
+ Parameters
83
+ ----------
84
+ solver_params : Dict, optional
85
+ A dictionary specifying solver parameters and values for them to be used.
86
+ Valid options include "solver," "stream_output," "mip_gap," "time_limit,"
87
+ and "solver_options"
88
+
89
+ Returns
90
+ -------
91
+ results
92
+ The Pyomo results object
93
+ """
94
+ solver = get_solver(**solver_params)
95
+ results = solver.solve(self.model)
96
+
97
+ return results
98
+
99
+ def get_model_objective(self) -> float:
100
+ """
101
+ Return the objective value for a solved model.
102
+
103
+ Note:
104
+ You are responsible for checking if the model was solved successfully
105
+
106
+ Returns
107
+ -------
108
+ float
109
+ The objective value for the solved model. Maybe feasible, optimal,
110
+ or meaningless
111
+ """
112
+ obj_list = []
113
+ for v in self.model.component_data_objects(pyo.Objective):
114
+ obj_list.append(v)
115
+
116
+ if len(obj_list) == 0:
117
+ LOGGER.warning("No objectives found in the model.")
118
+ return
119
+
120
+ elif len(obj_list) == 1:
121
+ return pyo.value(obj_list[0])
122
+
123
+ elif len(obj_list) > 1:
124
+ LOGGER.warning(
125
+ "Found multiple objectives in the model. Returning all of them."
126
+ )
127
+ return {obj.name: pyo.value(obj) for obj in obj_list}
128
+
129
+ def write_model(self, file_name: str) -> None:
130
+ """
131
+ Writes the optimization model to a file that has previously been set up using
132
+ :py:meth:`base_model.BaseModel.build_model()` method.
133
+ The format of the file depends on the filename provided.
134
+ If the filename ends with .mps, MPS format is used. If the filename
135
+ ends with .lp, LP format is used. If the filename ends with .rew, a file in
136
+ MPS format is written with all symbol information stripped.
137
+
138
+ Parameters
139
+ ----------
140
+ file_name : str
141
+ The path where the model is to be written.
142
+ Must end with .mps or .lp extension
143
+ """
144
+ ext = pathlib.Path(file_name).suffix
145
+
146
+ if ext in (".lp", ".mps", ".rew"):
147
+ LOGGER.info(f"The model will be written in {ext[1:].upper()} format")
148
+
149
+ else:
150
+ LOGGER.warning(
151
+ f"Unknown format {ext} encountered for optimization " "model. "
152
+ )
153
+ LOGGER.warning("Skipping write_model")
154
+ return
155
+
156
+ if os.path.exists(file_name):
157
+ LOGGER.warning(f"File: {file_name} exists and will be overwritten")
158
+
159
+ if ext in (".lp", ".mps"):
160
+ # Ask Pyomo to explicitly retain all symbols
161
+ self.model.write(file_name, io_options={"symbolic_solver_labels": True})
162
+ else:
163
+ temp_file_name = file_name[:-4] + ".mps"
164
+ self.model.write(temp_file_name)
165
+ os.rename(temp_file_name, file_name)
166
+
167
+ def write_iis(self, iis_name: str, solver: Union[str, None] = None) -> None:
168
+ """
169
+ For an infeasible model, computes and writes an IIS to the file specified
170
+ by iis_name. This assumes that a model has been built and solved by calling
171
+ methods :py:meth:`base_model.BaseModel.build_model()` and
172
+ :py:meth:`base_model.BaseModel.solved_model()` respectively and has been found
173
+ to be infeasible.
174
+
175
+ Parameters
176
+ ----------
177
+ iis_name : str
178
+ The path where a valid IIS is to be written
179
+
180
+ solver : str, optional
181
+ The solver to be utilized for IIS calculation.
182
+ Used by Pyomo, ignored if method_api is Gurobipy
183
+ """
184
+
185
+ if os.path.exists(iis_name):
186
+ LOGGER.warning(f"File: {iis_name} exists and will be overwritten")
187
+
188
+ write_iis(self.model, iis_name, solver, LOGGER)