primalsolver 0.1.1__py3-none-win_amd64.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.
- primalsolver/__init__.py +373 -0
- primalsolver/_bindings.py +557 -0
- primalsolver/_lib/libprimal.dll +0 -0
- primalsolver/_native.py +53 -0
- primalsolver-0.1.1.dist-info/METADATA +244 -0
- primalsolver-0.1.1.dist-info/RECORD +9 -0
- primalsolver-0.1.1.dist-info/WHEEL +5 -0
- primalsolver-0.1.1.dist-info/licenses/LICENSE +202 -0
- primalsolver-0.1.1.dist-info/top_level.txt +1 -0
primalsolver/__init__.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""PrimalSolver from Python — a thin, convenience wrapper over the C99 library.
|
|
2
|
+
|
|
3
|
+
from primalsolver import Model, BK, SOL_ITR
|
|
4
|
+
|
|
5
|
+
with Model(maxcon=2, maxvar=2) as m:
|
|
6
|
+
m.obj([0, 1], [-3.0, -2.0])
|
|
7
|
+
m.a_ij(0, 0, 1.0); m.a_ij(0, 1, 1.0) # x0 + x1 <= 4
|
|
8
|
+
m.a_ij(1, 0, 1.0); m.a_ij(1, 1, 3.0) # x0 + 3x1 <= 6
|
|
9
|
+
m.con_bounds(0, BK.UP, up=4.0)
|
|
10
|
+
m.con_bounds(1, BK.UP, up=6.0)
|
|
11
|
+
m.var_bounds(0, BK.RA, 0.0, 3.0)
|
|
12
|
+
m.var_bounds(1, BK.RA, 0.0, 3.0)
|
|
13
|
+
r = m.solve()
|
|
14
|
+
print(r.x, r.objective, r.solsta) # [3.0, 1.0] -11.0 1
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import ctypes as C
|
|
20
|
+
import types
|
|
21
|
+
from collections import namedtuple
|
|
22
|
+
|
|
23
|
+
from . import _native as _n
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.1"
|
|
26
|
+
|
|
27
|
+
LIB = _n.LIB
|
|
28
|
+
c_void_p = C.c_void_p
|
|
29
|
+
byref = C.byref
|
|
30
|
+
|
|
31
|
+
# The full generated binding is re-exported at package level:
|
|
32
|
+
# primalsolver.PRIMAL_optimize(...), primalsolver.FUNCTIONS, primalsolver.MISSING
|
|
33
|
+
FUNCTIONS = _n.FUNCTIONS
|
|
34
|
+
MISSING = _n.MISSING
|
|
35
|
+
globals().update(FUNCTIONS)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SolverError(RuntimeError):
|
|
39
|
+
"""A PrimalSolver call returned a non-OK resource code (or a wrapper bug)."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# --- enums, numbered as the C header (and as the vendor reference) -----------
|
|
43
|
+
class BK:
|
|
44
|
+
LO, UP, FX, FR, RA = 0, 1, 2, 3, 4 # PRIMALboundkeye
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SENSE:
|
|
48
|
+
MIN, MAX = 0, 1 # PRIMALobjsensee
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CT:
|
|
52
|
+
QUAD, RQUAD, PEXP, DEXP, PPOW, RPOW = 0, 1, 2, 3, 4, 7 # PRIMALconetypee
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class VAR_TYPE:
|
|
56
|
+
CONT, INT, INT_BIN, SEMI_CONT, SEMI_INT = 0, 1, 2, 3, 4 # PRIMALvariabletypee
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SK: # PRIMALstakeye (basis status)
|
|
60
|
+
LOW, UPR, FIX, BAS, SUP, UNDEF = 0, 1, 2, 3, 4, 5
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class APPROX: # PRIMALdomaintypee (linear domains)
|
|
64
|
+
R, RZERO, RPLUS, RMINUS = 0, 1, 2, 3
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SOL:
|
|
68
|
+
ITR, BAS, ITG = 0, 1, 2 # PRIMALsolt
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SOLSTA:
|
|
72
|
+
UNKNOWN = 0
|
|
73
|
+
OPTIMAL = 1
|
|
74
|
+
PRIM_FEAS = 2
|
|
75
|
+
PRIM_INFEAS_CER = 5
|
|
76
|
+
DUAL_INFEAS_CER = 6
|
|
77
|
+
INTEGER_OPTIMAL = 9
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
SOL_ITR = SOL.ITR
|
|
81
|
+
|
|
82
|
+
Result = namedtuple("Result", "x y objective dual solsta prosta pinf rc")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def version() -> tuple[int, int, int]:
|
|
86
|
+
a, b, c = (C.c_int(), C.c_int(), C.c_int())
|
|
87
|
+
_n.getversion(byref(a), byref(b), byref(c))
|
|
88
|
+
return a.value, b.value, c.value
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Model:
|
|
92
|
+
"""A task builder + solver. Use as a context manager to free the C handles."""
|
|
93
|
+
|
|
94
|
+
def __init__(self, maxcon: int = 0, maxvar: int = 0):
|
|
95
|
+
self.env = c_void_p()
|
|
96
|
+
self.task = c_void_p()
|
|
97
|
+
self._check(_n.makeenv(byref(self.env), None), "makeenv")
|
|
98
|
+
self._check(_n.maketask(self.env, int(maxcon), int(maxvar), byref(self.task)), "maketask")
|
|
99
|
+
|
|
100
|
+
# -- helpers -------------------------------------------------------------
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _check(rc: int, what: str = "") -> None:
|
|
103
|
+
if rc != 0:
|
|
104
|
+
raise SolverError(f"PRIMAL {what} failed (rc={rc})")
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _ivec(seq):
|
|
108
|
+
return (C.c_int * len(seq))(*seq)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _dvec(seq):
|
|
112
|
+
return (C.c_double * len(seq))(*[float(v) for v in seq])
|
|
113
|
+
|
|
114
|
+
def _numcon(self) -> int:
|
|
115
|
+
n = C.c_int(); self._check(_n.getnumcon(self.task, byref(n))); return n.value
|
|
116
|
+
|
|
117
|
+
def _numvar(self) -> int:
|
|
118
|
+
n = C.c_int(); self._check(_n.getnumvar(self.task, byref(n))); return n.value
|
|
119
|
+
|
|
120
|
+
# -- model building ------------------------------------------------------
|
|
121
|
+
def add_cons(self, n: int) -> None:
|
|
122
|
+
self._check(_n.appendcons(self.task, int(n)), "appendcons")
|
|
123
|
+
|
|
124
|
+
def add_vars(self, n: int) -> None:
|
|
125
|
+
self._check(_n.appendvars(self.task, int(n)), "appendvars")
|
|
126
|
+
|
|
127
|
+
def obj(self, idx, vals) -> None:
|
|
128
|
+
"""Set objective coefficients c[idx] = vals (replaces those entries)."""
|
|
129
|
+
idx = list(idx); vals = list(vals)
|
|
130
|
+
self._check(_n.putclist(self.task, len(idx), self._ivec(idx), self._dvec(vals)), "putclist")
|
|
131
|
+
|
|
132
|
+
def a_ij(self, i: int, j: int, value: float) -> None:
|
|
133
|
+
self._check(_n.putaij(self.task, int(i), int(j), float(value)), "putaij")
|
|
134
|
+
|
|
135
|
+
def a_row(self, i: int, cols, vals) -> None:
|
|
136
|
+
"""Replace row i with the sparse coefficients (cols, vals)."""
|
|
137
|
+
cols = list(cols); vals = list(vals)
|
|
138
|
+
self._check(_n.putarow(self.task, int(i), len(cols), self._ivec(cols), self._dvec(vals)), "putarow")
|
|
139
|
+
|
|
140
|
+
def cj(self, j: int, value: float) -> None:
|
|
141
|
+
"""Set a single objective coefficient."""
|
|
142
|
+
self._check(_n.putcj(self.task, int(j), float(value)), "putcj")
|
|
143
|
+
|
|
144
|
+
def qobj(self, terms) -> None:
|
|
145
|
+
"""Set the quadratic objective from (i, j, value) terms (upper triangle)."""
|
|
146
|
+
terms = list(terms)
|
|
147
|
+
qi = self._ivec([int(t[0]) for t in terms])
|
|
148
|
+
qj = self._ivec([int(t[1]) for t in terms])
|
|
149
|
+
qv = self._dvec([float(t[2]) for t in terms])
|
|
150
|
+
self._check(_n.putqobj(self.task, len(terms), qi, qj, qv), "putqobj")
|
|
151
|
+
|
|
152
|
+
def cone(self, ctype: int, members, param: float = 0.0) -> None:
|
|
153
|
+
"""Append a cone whose members are the given variable indices."""
|
|
154
|
+
members = list(members)
|
|
155
|
+
self._check(_n.appendcone(self.task, int(ctype), float(param), len(members), self._ivec(members)), "appendcone")
|
|
156
|
+
|
|
157
|
+
def a_col(self, j: int, rows, vals) -> None:
|
|
158
|
+
rows = list(rows); vals = list(vals)
|
|
159
|
+
self._check(_n.putacol(self.task, int(j), len(rows), self._ivec(rows), self._dvec(vals)), "putacol")
|
|
160
|
+
|
|
161
|
+
def qconk(self, k: int, terms) -> None:
|
|
162
|
+
"""Quadratic terms of constraint k: iterable of (i, j, value), i<=j."""
|
|
163
|
+
terms = list(terms)
|
|
164
|
+
self._check(_n.putqconk(self.task, int(k), len(terms),
|
|
165
|
+
self._ivec([int(t[0]) for t in terms]),
|
|
166
|
+
self._ivec([int(t[1]) for t in terms]),
|
|
167
|
+
self._dvec([float(t[2]) for t in terms])), "putqconk")
|
|
168
|
+
|
|
169
|
+
def qobj_ij(self, i: int, j: int, value: float) -> None:
|
|
170
|
+
self._check(_n.putqobjij(self.task, int(i), int(j), float(value)), "putqobjij")
|
|
171
|
+
|
|
172
|
+
def var_type(self, j: int, vt: int) -> None:
|
|
173
|
+
self._check(_n.putvartype(self.task, int(j), int(vt)), "putvartype")
|
|
174
|
+
|
|
175
|
+
def var_types(self, cols, types) -> None:
|
|
176
|
+
types = list(types)
|
|
177
|
+
self._check(_n.putvartypelist(self.task, len(cols), self._ivec(list(cols)),
|
|
178
|
+
(C.c_int * len(types))(*types)), "putvartypelist")
|
|
179
|
+
|
|
180
|
+
def cfix(self, value: float) -> None:
|
|
181
|
+
self._check(_n.putcfix(self.task, float(value)), "putcfix")
|
|
182
|
+
|
|
183
|
+
def int_param(self, param: int, value: int) -> None:
|
|
184
|
+
self._check(_n.putintparam(self.task, int(param), int(value)), "putintparam")
|
|
185
|
+
|
|
186
|
+
def dou_param(self, param: int, value: float) -> None:
|
|
187
|
+
self._check(_n.putdouparam(self.task, int(param), float(value)), "putdouparam")
|
|
188
|
+
|
|
189
|
+
def warm_start(self, x=None, y=None) -> None:
|
|
190
|
+
if x is not None:
|
|
191
|
+
self._check(_n.putxx(self.task, SOL.ITR, self._dvec(list(x))), "putxx")
|
|
192
|
+
if y is not None:
|
|
193
|
+
self._check(_n.puty(self.task, SOL.ITR, self._dvec(list(y))), "puty")
|
|
194
|
+
|
|
195
|
+
def sos(self, kind: int, members, weights=None) -> None:
|
|
196
|
+
members = list(members)
|
|
197
|
+
w = self._dvec(list(weights) if weights is not None else [1.0] * len(members))
|
|
198
|
+
fn = _n.PRIMAL_appendsos1 if int(kind) == 1 else _n.PRIMAL_appendsos2
|
|
199
|
+
self._check(fn(self.task, len(members), self._ivec(members), w), "appendsos")
|
|
200
|
+
|
|
201
|
+
# -- SDP (semi-definite bar variables) -----------------------------------
|
|
202
|
+
def barvars(self, dims) -> None:
|
|
203
|
+
dims = list(dims)
|
|
204
|
+
self._check(_n.appendbarvars(self.task, len(dims), self._ivec(dims)), "appendbarvars")
|
|
205
|
+
|
|
206
|
+
def sparsesymmat(self, dim: int, terms) -> int:
|
|
207
|
+
"""Store a sparse symmetric matrix; returns its id (for baraij/barcj)."""
|
|
208
|
+
terms = list(terms)
|
|
209
|
+
idx = C.c_int()
|
|
210
|
+
self._check(_n.appendsparsesymmat(self.task, int(dim), len(terms),
|
|
211
|
+
self._ivec([int(t[0]) for t in terms]),
|
|
212
|
+
self._ivec([int(t[1]) for t in terms]),
|
|
213
|
+
self._dvec([float(t[2]) for t in terms]), byref(idx)), "appendsparsesymmat")
|
|
214
|
+
return idx.value
|
|
215
|
+
|
|
216
|
+
def baraij(self, i: int, j: int, terms) -> None:
|
|
217
|
+
terms = list(terms) # (symmat_id, coefficient)
|
|
218
|
+
self._check(_n.putbaraij(self.task, int(i), int(j), len(terms),
|
|
219
|
+
self._ivec([int(t[0]) for t in terms]),
|
|
220
|
+
self._dvec([float(t[1]) for t in terms])), "putbaraij")
|
|
221
|
+
|
|
222
|
+
def barcj(self, j: int, terms) -> None:
|
|
223
|
+
terms = list(terms)
|
|
224
|
+
self._check(_n.putbarcj(self.task, int(j), len(terms),
|
|
225
|
+
self._ivec([int(t[0]) for t in terms]),
|
|
226
|
+
self._dvec([float(t[1]) for t in terms])), "putbarcj")
|
|
227
|
+
|
|
228
|
+
# -- affine expressions / conic + disjunctive constraints ----------------
|
|
229
|
+
def afes(self, num: int) -> None:
|
|
230
|
+
self._check(_n.appendafes(self.task, int(num)), "appendafes")
|
|
231
|
+
|
|
232
|
+
def afe_entry(self, i: int, j: int, value: float) -> None:
|
|
233
|
+
self._check(_n.putafefentry(self.task, int(i), int(j), float(value)), "putafefentry")
|
|
234
|
+
|
|
235
|
+
def afe_g(self, i: int, value: float) -> None:
|
|
236
|
+
self._check(_n.putafeg(self.task, int(i), float(value)), "putafeg")
|
|
237
|
+
|
|
238
|
+
def domain(self, kind: int, n: int = 1) -> int:
|
|
239
|
+
fn = {0: _n.PRIMAL_appendrdomain, 1: _n.PRIMAL_appendrzerodomain,
|
|
240
|
+
2: _n.PRIMAL_appendrplusdomain, 3: _n.PRIMAL_appendrminusdomain}[int(kind)]
|
|
241
|
+
idx = C.c_longlong()
|
|
242
|
+
self._check(fn(self.task, int(n), byref(idx)), "appenddomain")
|
|
243
|
+
return idx.value
|
|
244
|
+
|
|
245
|
+
def acc(self, domidx: int, afeidx, b=None) -> None:
|
|
246
|
+
afeidx = list(afeidx)
|
|
247
|
+
bb = self._dvec(list(b) if b is not None else [0.0] * len(afeidx))
|
|
248
|
+
a64 = (C.c_longlong * len(afeidx))(*[int(a) for a in afeidx])
|
|
249
|
+
self._check(_n.appendacc(self.task, int(domidx), len(afeidx), a64, bb), "appendacc")
|
|
250
|
+
|
|
251
|
+
def djcs(self, num: int) -> None:
|
|
252
|
+
self._check(_n.appenddjcs(self.task, int(num)), "appenddjcs")
|
|
253
|
+
|
|
254
|
+
def djc(self, idx: int, domidxlist, afeidxlist, b, termsizelist) -> None:
|
|
255
|
+
dom = (C.c_longlong * len(domidxlist))(*[int(d) for d in domidxlist])
|
|
256
|
+
afe = (C.c_longlong * len(afeidxlist))(*[int(a) for a in afeidxlist])
|
|
257
|
+
bv = self._dvec(list(b))
|
|
258
|
+
ts = (C.c_longlong * len(termsizelist))(*[int(t) for t in termsizelist])
|
|
259
|
+
self._check(_n.putdjc(self.task, int(idx), len(domidxlist), dom,
|
|
260
|
+
len(afeidxlist), afe, bv, len(termsizelist), ts), "putdjc")
|
|
261
|
+
|
|
262
|
+
# -- data getters (for independent verification) -------------------------
|
|
263
|
+
def getcj(self, j: int) -> float:
|
|
264
|
+
v = C.c_double(); self._check(_n.getcj(self.task, int(j), byref(v))); return v.value
|
|
265
|
+
|
|
266
|
+
def getaij(self, i: int, j: int) -> float:
|
|
267
|
+
v = C.c_double(); self._check(_n.getaij(self.task, int(i), int(j), byref(v))); return v.value
|
|
268
|
+
|
|
269
|
+
def getvarbound(self, j: int):
|
|
270
|
+
bk = C.c_int(); bl = C.c_double(); bu = C.c_double()
|
|
271
|
+
self._check(_n.getvarbound(self.task, int(j), byref(bk), byref(bl), byref(bu)))
|
|
272
|
+
return bk.value, bl.value, bu.value
|
|
273
|
+
|
|
274
|
+
def getconbound(self, i: int):
|
|
275
|
+
bk = C.c_int(); bl = C.c_double(); bu = C.c_double()
|
|
276
|
+
self._check(_n.getconbound(self.task, int(i), byref(bk), byref(bl), byref(bu)))
|
|
277
|
+
return bk.value, bl.value, bu.value
|
|
278
|
+
|
|
279
|
+
def getcfix(self) -> float:
|
|
280
|
+
v = C.c_double(); self._check(_n.getcfix(self.task, byref(v))); return v.value
|
|
281
|
+
|
|
282
|
+
def getdualinfeas(self) -> float:
|
|
283
|
+
v = C.c_double(); self._check(_n.getdualinfeas(self.task, SOL.ITR, byref(v))); return v.value
|
|
284
|
+
|
|
285
|
+
def getreducedcosts(self, first: int = 0, last: int = None) -> list:
|
|
286
|
+
if last is None:
|
|
287
|
+
last = self._numvar()
|
|
288
|
+
buf = (C.c_double * (last - first))()
|
|
289
|
+
self._check(_n.getreducedcosts(self.task, SOL.ITR, int(first), int(last), buf))
|
|
290
|
+
return list(buf)
|
|
291
|
+
|
|
292
|
+
def getintparam(self, param: int) -> int:
|
|
293
|
+
v = C.c_int(); self._check(_n.getintparam(self.task, int(param), byref(v))); return v.value
|
|
294
|
+
|
|
295
|
+
def getdouparam(self, param: int) -> float:
|
|
296
|
+
v = C.c_double(); self._check(_n.getdouparam(self.task, int(param), byref(v))); return v.value
|
|
297
|
+
|
|
298
|
+
def numvar(self) -> int:
|
|
299
|
+
return self._numvar()
|
|
300
|
+
|
|
301
|
+
def numcon(self) -> int:
|
|
302
|
+
return self._numcon()
|
|
303
|
+
|
|
304
|
+
def getbarxj(self, j: int = 0) -> list:
|
|
305
|
+
"""Bar variable j as a full dim x dim row-major matrix (dim*dim values)."""
|
|
306
|
+
dim = C.c_int(); self._check(_n.getdimbarvarj(self.task, int(j), byref(dim)))
|
|
307
|
+
buf = (C.c_double * (dim.value * dim.value))()
|
|
308
|
+
self._check(_n.getbarxj(self.task, SOL.ITR, int(j), buf))
|
|
309
|
+
return list(buf)
|
|
310
|
+
|
|
311
|
+
def getbarsj(self, j: int = 0) -> list:
|
|
312
|
+
dim = C.c_int(); self._check(_n.getdimbarvarj(self.task, int(j), byref(dim)))
|
|
313
|
+
buf = (C.c_double * (dim.value * dim.value))()
|
|
314
|
+
self._check(_n.getbarsj(self.task, SOL.ITR, int(j), buf))
|
|
315
|
+
return list(buf)
|
|
316
|
+
|
|
317
|
+
def var_bounds(self, j: int, bk: int, lo: float = 0.0, up: float = 0.0) -> None:
|
|
318
|
+
self._check(_n.putvarbound(self.task, int(j), int(bk), float(lo), float(up)), "putvarbound")
|
|
319
|
+
|
|
320
|
+
def con_bounds(self, i: int, bk: int, lo: float = 0.0, up: float = 0.0) -> None:
|
|
321
|
+
self._check(_n.putconbound(self.task, int(i), int(bk), float(lo), float(up)), "putconbound")
|
|
322
|
+
|
|
323
|
+
def sense(self, sense: int) -> None:
|
|
324
|
+
self._check(_n.putobjsense(self.task, int(sense)), "putobjsense")
|
|
325
|
+
|
|
326
|
+
# -- file I/O ------------------------------------------------------------
|
|
327
|
+
def read(self, path: str) -> None:
|
|
328
|
+
self._check(_n.readdataautoformat(self.task, str(path).encode()), "readdataautoformat")
|
|
329
|
+
|
|
330
|
+
def write(self, path: str) -> None:
|
|
331
|
+
self._check(_n.writedata(self.task, str(path).encode()), "writedata")
|
|
332
|
+
|
|
333
|
+
# -- solve ---------------------------------------------------------------
|
|
334
|
+
def solve(self, which: int = SOL.ITR) -> Result:
|
|
335
|
+
rc = _n.optimize(self.task)
|
|
336
|
+
nv, nc = self._numvar(), self._numcon()
|
|
337
|
+
x = (C.c_double * nv)() if nv else (C.c_double * 0)()
|
|
338
|
+
y = (C.c_double * nc)() if nc else (C.c_double * 0)()
|
|
339
|
+
obj = C.c_double()
|
|
340
|
+
dual = C.c_double()
|
|
341
|
+
pinf = C.c_double()
|
|
342
|
+
sta = C.c_int()
|
|
343
|
+
pro = C.c_int()
|
|
344
|
+
# A non-OK rc can still carry a verdict (e.g. infeasible/unbounded): read
|
|
345
|
+
# the status regardless and let the caller decide.
|
|
346
|
+
if nv:
|
|
347
|
+
_n.getxx(self.task, int(which), x)
|
|
348
|
+
if nc:
|
|
349
|
+
_n.gety(self.task, int(which), y)
|
|
350
|
+
_n.getprimalobj(self.task, int(which), byref(obj))
|
|
351
|
+
_n.getdualobj(self.task, int(which), byref(dual))
|
|
352
|
+
_n.getprimalinfeas(self.task, int(which), byref(pinf))
|
|
353
|
+
_n.getsolsta(self.task, int(which), byref(sta))
|
|
354
|
+
_n.getprosta(self.task, int(which), byref(pro))
|
|
355
|
+
return Result(list(x), list(y), obj.value, dual.value, sta.value, pro.value, pinf.value, rc)
|
|
356
|
+
|
|
357
|
+
def close(self) -> None:
|
|
358
|
+
if self.task:
|
|
359
|
+
_n.deletetask(byref(self.task))
|
|
360
|
+
self.task = c_void_p()
|
|
361
|
+
if self.env:
|
|
362
|
+
_n.deleteenv(byref(self.env))
|
|
363
|
+
self.env = c_void_p()
|
|
364
|
+
|
|
365
|
+
def __enter__(self) -> "Model":
|
|
366
|
+
return self
|
|
367
|
+
|
|
368
|
+
def __exit__(self, *exc) -> None:
|
|
369
|
+
self.close()
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
__all__ = ["Model", "Result", "SolverError", "version", "BK", "SENSE", "SOL", "SOLSTA", "CT",
|
|
373
|
+
"SOL_ITR", "FUNCTIONS", "MISSING"]
|