File: coin_api.py

package info (click to toggle)
python-pulp 2.6.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 14,720 kB
  • sloc: python: 7,505; makefile: 16; sh: 16
file content (861 lines) | stat: -rw-r--r-- 30,989 bytes parent folder | download
1
2
3
4
5
6
7
8
9
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
# PuLP : Python LP Modeler
# Version 1.4.2

# Copyright (c) 2002-2005, Jean-Sebastien Roy (js@jeannot.org)
# Modifications Copyright (c) 2007- Stuart Anthony Mitchell (s.mitchell@auckland.ac.nz)
# $Id:solvers.py 1791 2008-04-23 22:54:34Z smit023 $

# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:

# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""

from .core import LpSolver_CMD, LpSolver, subprocess, PulpSolverError, clock, log
from .core import cbc_path, pulp_cbc_path, coinMP_path, devnull
import os
from .. import constants
from tempfile import mktemp
import ctypes
import warnings


class COIN_CMD(LpSolver_CMD):
    """The COIN CLP/CBC LP solver
    now only uses cbc
    """

    name = "COIN_CMD"

    def defaultPath(self):
        return self.executableExtension(cbc_path)

    def __init__(
        self,
        mip=True,
        msg=True,
        timeLimit=None,
        fracGap=None,
        maxSeconds=None,
        gapRel=None,
        gapAbs=None,
        presolve=None,
        cuts=None,
        strong=None,
        options=None,
        warmStart=False,
        keepFiles=False,
        path=None,
        threads=None,
        logPath=None,
        timeMode="elapsed",
        mip_start=False,
    ):
        """
        :param bool mip: if False, assume LP even if integer variables
        :param bool msg: if False, no log is shown
        :param float timeLimit: maximum time for solver (in seconds)
        :param float gapRel: relative gap tolerance for the solver to stop (in fraction)
        :param float gapAbs: absolute gap tolerance for the solver to stop
        :param int threads: sets the maximum number of threads
        :param list options: list of additional options to pass to solver
        :param bool warmStart: if True, the solver will use the current value of variables as a start
        :param bool keepFiles: if True, files are saved in the current directory and not deleted after solving
        :param str path: path to the solver binary
        :param str logPath: path to the log file
        :param bool presolve: if True, adds presolve on
        :param bool cuts: if True, adds gomory on knapsack on probing on
        :param bool strong: if True, adds strong
        :param float fracGap: deprecated for gapRel
        :param float maxSeconds: deprecated for timeLimit
        :param str timeMode: "elapsed": count wall-time to timeLimit; "cpu": count cpu-time
        :param bool mip_start: deprecated for warmStart
        """

        if fracGap is not None:
            warnings.warn("Parameter fracGap is being depreciated for gapRel")
            if gapRel is not None:
                warnings.warn("Parameter gapRel and fracGap passed, using gapRel")
            else:
                gapRel = fracGap
        if maxSeconds is not None:
            warnings.warn("Parameter maxSeconds is being depreciated for timeLimit")
            if timeLimit is not None:
                warnings.warn(
                    "Parameter timeLimit and maxSeconds passed, using timeLimit"
                )
            else:
                timeLimit = maxSeconds
        if mip_start:
            warnings.warn("Parameter mip_start is being depreciated for warmStart")
            if warmStart:
                warnings.warn(
                    "Parameter mipStart and mip_start passed, using warmStart"
                )
            else:
                warmStart = mip_start
        LpSolver_CMD.__init__(
            self,
            gapRel=gapRel,
            mip=mip,
            msg=msg,
            timeLimit=timeLimit,
            presolve=presolve,
            cuts=cuts,
            strong=strong,
            options=options,
            warmStart=warmStart,
            path=path,
            keepFiles=keepFiles,
            threads=threads,
            gapAbs=gapAbs,
            logPath=logPath,
            timeMode=timeMode,
        )

    def copy(self):
        """Make a copy of self"""
        aCopy = LpSolver_CMD.copy(self)
        aCopy.optionsDict = self.optionsDict
        return aCopy

    def actualSolve(self, lp, **kwargs):
        """Solve a well formulated lp problem"""
        return self.solve_CBC(lp, **kwargs)

    def available(self):
        """True if the solver is available"""
        return self.executable(self.path)

    def solve_CBC(self, lp, use_mps=True):
        """Solve a MIP problem using CBC"""
        if not self.executable(self.path):
            raise PulpSolverError(
                "Pulp: cannot execute %s cwd: %s" % (self.path, os.getcwd())
            )
        tmpLp, tmpMps, tmpSol, tmpMst = self.create_tmp_files(
            lp.name, "lp", "mps", "sol", "mst"
        )
        if use_mps:
            vs, variablesNames, constraintsNames, objectiveName = lp.writeMPS(
                tmpMps, rename=1
            )
            cmds = " " + tmpMps + " "
            if lp.sense == constants.LpMaximize:
                cmds += "max "
        else:
            vs = lp.writeLP(tmpLp)
            # In the Lp we do not create new variable or constraint names:
            variablesNames = dict((v.name, v.name) for v in vs)
            constraintsNames = dict((c, c) for c in lp.constraints)
            cmds = " " + tmpLp + " "
        if self.optionsDict.get("warmStart", False):
            self.writesol(tmpMst, lp, vs, variablesNames, constraintsNames)
            cmds += "mips {} ".format(tmpMst)
        if self.timeLimit is not None:
            cmds += "sec %s " % self.timeLimit
        options = self.options + self.getOptions()
        for option in options:
            cmds += option + " "
        if self.mip:
            cmds += "branch "
        else:
            cmds += "initialSolve "
        cmds += "printingOptions all "
        cmds += "solution " + tmpSol + " "
        if self.msg:
            pipe = None
        else:
            pipe = open(os.devnull, "w")
        logPath = self.optionsDict.get("logPath")
        if logPath:
            if self.msg:
                warnings.warn(
                    "`logPath` argument replaces `msg=1`. The output will be redirected to the log file."
                )
            pipe = open(self.optionsDict["logPath"], "w")
        log.debug(self.path + cmds)
        args = []
        args.append(self.path)
        args.extend(cmds[1:].split())
        cbc = subprocess.Popen(args, stdout=pipe, stderr=pipe, stdin=devnull)
        if cbc.wait() != 0:
            if pipe:
                pipe.close()
            raise PulpSolverError(
                "Pulp: Error while trying to execute, use msg=True for more details"
                + self.path
            )
        if pipe:
            pipe.close()
        if not os.path.exists(tmpSol):
            raise PulpSolverError("Pulp: Error while executing " + self.path)
        (
            status,
            values,
            reducedCosts,
            shadowPrices,
            slacks,
            sol_status,
        ) = self.readsol_MPS(tmpSol, lp, vs, variablesNames, constraintsNames)
        lp.assignVarsVals(values)
        lp.assignVarsDj(reducedCosts)
        lp.assignConsPi(shadowPrices)
        lp.assignConsSlack(slacks, activity=True)
        lp.assignStatus(status, sol_status)
        self.delete_tmp_files(tmpMps, tmpLp, tmpSol, tmpMst)
        return status

    def getOptions(self):
        params_eq = dict(
            gapRel="ratio {}",
            gapAbs="allow {}",
            threads="threads {}",
            presolve="presolve on",
            strong="strong {}",
            cuts="gomory on knapsack on probing on",
            timeMode="timeMode {}",
        )

        return [
            v.format(self.optionsDict[k])
            for k, v in params_eq.items()
            if self.optionsDict.get(k) is not None
        ]

    def readsol_MPS(
        self, filename, lp, vs, variablesNames, constraintsNames, objectiveName=None
    ):
        """
        Read a CBC solution file generated from an mps or lp file (possible different names)
        """
        values = dict((v.name, 0) for v in vs)

        reverseVn = dict((v, k) for k, v in variablesNames.items())
        reverseCn = dict((v, k) for k, v in constraintsNames.items())

        reducedCosts = {}
        shadowPrices = {}
        slacks = {}
        status, sol_status = self.get_status(filename)
        with open(filename) as f:
            for l in f:
                if len(l) <= 2:
                    break
                l = l.split()
                # incase the solution is infeasible
                if l[0] == "**":
                    l = l[1:]
                vn = l[1]
                val = l[2]
                dj = l[3]
                if vn in reverseVn:
                    values[reverseVn[vn]] = float(val)
                    reducedCosts[reverseVn[vn]] = float(dj)
                if vn in reverseCn:
                    slacks[reverseCn[vn]] = float(val)
                    shadowPrices[reverseCn[vn]] = float(dj)
        return status, values, reducedCosts, shadowPrices, slacks, sol_status

    def writesol(self, filename, lp, vs, variablesNames, constraintsNames):
        """
        Writes a CBC solution file generated from an mps / lp file (possible different names)
        returns True on success
        """
        values = dict((v.name, v.value() if v.value() is not None else 0) for v in vs)
        value_lines = []
        value_lines += [
            (i, v, values[k], 0) for i, (k, v) in enumerate(variablesNames.items())
        ]
        lines = ["Stopped on time - objective value 0\n"]
        lines += ["{0:>7} {1} {2:>15} {3:>23}\n".format(*tup) for tup in value_lines]

        with open(filename, "w") as f:
            f.writelines(lines)

        return True

    def readsol_LP(self, filename, lp, vs):
        """
        Read a CBC solution file generated from an lp (good names)
        returns status, values, reducedCosts, shadowPrices, slacks, sol_status
        """
        variablesNames = dict((v.name, v.name) for v in vs)
        constraintsNames = dict((c, c) for c in lp.constraints)
        return self.readsol_MPS(filename, lp, vs, variablesNames, constraintsNames)

    def get_status(self, filename):
        cbcStatus = {
            "Optimal": constants.LpStatusOptimal,
            "Infeasible": constants.LpStatusInfeasible,
            "Integer": constants.LpStatusInfeasible,
            "Unbounded": constants.LpStatusUnbounded,
            "Stopped": constants.LpStatusNotSolved,
        }

        cbcSolStatus = {
            "Optimal": constants.LpSolutionOptimal,
            "Infeasible": constants.LpSolutionInfeasible,
            "Unbounded": constants.LpSolutionUnbounded,
            "Stopped": constants.LpSolutionNoSolutionFound,
        }

        with open(filename) as f:
            statusstrs = f.readline().split()

        status = cbcStatus.get(statusstrs[0], constants.LpStatusUndefined)
        sol_status = cbcSolStatus.get(
            statusstrs[0], constants.LpSolutionNoSolutionFound
        )
        # here we could use some regex expression.
        # Not sure what's more desirable
        if status == constants.LpStatusNotSolved and len(statusstrs) >= 5:
            if statusstrs[4] == "objective":
                status = constants.LpStatusOptimal
                sol_status = constants.LpSolutionIntegerFeasible
        return status, sol_status


COIN = COIN_CMD


class PULP_CBC_CMD(COIN_CMD):
    """
    This solver uses a precompiled version of cbc provided with the package
    """

    name = "PULP_CBC_CMD"
    pulp_cbc_path = pulp_cbc_path
    try:
        if os.name != "nt":
            if not os.access(pulp_cbc_path, os.X_OK):
                import stat

                os.chmod(pulp_cbc_path, stat.S_IXUSR + stat.S_IXOTH)
    except:  # probably due to incorrect permissions

        def available(self):
            """True if the solver is available"""
            return False

        def actualSolve(self, lp, callback=None):
            """Solve a well formulated lp problem"""
            raise PulpSolverError(
                "PULP_CBC_CMD: Not Available (check permissions on %s)"
                % self.pulp_cbc_path
            )

    else:

        def __init__(
            self,
            mip=True,
            msg=True,
            timeLimit=None,
            fracGap=None,
            maxSeconds=None,
            gapRel=None,
            gapAbs=None,
            presolve=None,
            cuts=None,
            strong=None,
            options=None,
            warmStart=False,
            keepFiles=False,
            path=None,
            threads=None,
            logPath=None,
            mip_start=False,
            timeMode="elapsed",
        ):
            if path is not None:
                raise PulpSolverError("Use COIN_CMD if you want to set a path")
            # check that the file is executable
            COIN_CMD.__init__(
                self,
                path=self.pulp_cbc_path,
                mip=mip,
                msg=msg,
                timeLimit=timeLimit,
                fracGap=fracGap,
                maxSeconds=maxSeconds,
                gapRel=gapRel,
                gapAbs=gapAbs,
                presolve=presolve,
                cuts=cuts,
                strong=strong,
                options=options,
                warmStart=warmStart,
                keepFiles=keepFiles,
                threads=threads,
                logPath=logPath,
                mip_start=mip_start,
                timeMode=timeMode,
            )


def COINMP_DLL_load_dll(path):
    """
    function that loads the DLL useful for debugging installation problems
    """
    if os.name == "nt":
        lib = ctypes.windll.LoadLibrary(str(path[-1]))
    else:
        # linux hack to get working
        mode = ctypes.RTLD_GLOBAL
        for libpath in path[:-1]:
            # RTLD_LAZY = 0x00001
            ctypes.CDLL(libpath, mode=mode)
        lib = ctypes.CDLL(path[-1], mode=mode)
    return lib


class COINMP_DLL(LpSolver):
    """
    The COIN_MP LP MIP solver (via a DLL or linux so)

    :param timeLimit: The number of seconds before forcing the solver to exit
    :param epgap: The fractional mip tolerance
    """

    name = "COINMP_DLL"
    try:
        lib = COINMP_DLL_load_dll(coinMP_path)
    except (ImportError, OSError):

        @classmethod
        def available(cls):
            """True if the solver is available"""
            return False

        def actualSolve(self, lp):
            """Solve a well formulated lp problem"""
            raise PulpSolverError("COINMP_DLL: Not Available")

    else:
        COIN_INT_LOGLEVEL = 7
        COIN_REAL_MAXSECONDS = 16
        COIN_REAL_MIPMAXSEC = 19
        COIN_REAL_MIPFRACGAP = 34
        lib.CoinGetInfinity.restype = ctypes.c_double
        lib.CoinGetVersionStr.restype = ctypes.c_char_p
        lib.CoinGetSolutionText.restype = ctypes.c_char_p
        lib.CoinGetObjectValue.restype = ctypes.c_double
        lib.CoinGetMipBestBound.restype = ctypes.c_double

        def __init__(
            self,
            cuts=1,
            presolve=1,
            dual=1,
            crash=0,
            scale=1,
            rounding=1,
            integerPresolve=1,
            strong=5,
            epgap=None,
            *args,
            **kwargs
        ):
            LpSolver.__init__(self, *args, **kwargs)
            self.fracGap = None
            if epgap is not None:
                self.fracGap = float(epgap)
            if self.timeLimit is not None:
                self.timeLimit = float(self.timeLimit)
            # Todo: these options are not yet implemented
            self.cuts = cuts
            self.presolve = presolve
            self.dual = dual
            self.crash = crash
            self.scale = scale
            self.rounding = rounding
            self.integerPresolve = integerPresolve
            self.strong = strong

        def copy(self):
            """Make a copy of self"""
            aCopy = LpSolver.copy(self)
            aCopy.cuts = self.cuts
            aCopy.presolve = self.presolve
            aCopy.dual = self.dual
            aCopy.crash = self.crash
            aCopy.scale = self.scale
            aCopy.rounding = self.rounding
            aCopy.integerPresolve = self.integerPresolve
            aCopy.strong = self.strong
            return aCopy

        @classmethod
        def available(cls):
            """True if the solver is available"""
            return True

        def getSolverVersion(self):
            """
            returns a solver version string

            example:
            >>> COINMP_DLL().getSolverVersion() # doctest: +ELLIPSIS
            '...'
            """
            return self.lib.CoinGetVersionStr()

        def actualSolve(self, lp):
            """Solve a well formulated lp problem"""
            # TODO alter so that msg parameter is handled correctly
            self.debug = 0
            # initialise solver
            self.lib.CoinInitSolver("")
            # create problem
            self.hProb = hProb = self.lib.CoinCreateProblem(lp.name)
            # set problem options
            self.lib.CoinSetIntOption(
                hProb, self.COIN_INT_LOGLEVEL, ctypes.c_int(self.msg)
            )

            if self.timeLimit:
                if self.mip:
                    self.lib.CoinSetRealOption(
                        hProb, self.COIN_REAL_MIPMAXSEC, ctypes.c_double(self.timeLimit)
                    )
                else:
                    self.lib.CoinSetRealOption(
                        hProb,
                        self.COIN_REAL_MAXSECONDS,
                        ctypes.c_double(self.timeLimit),
                    )
            if self.fracGap:
                # Hopefully this is the bound gap tolerance
                self.lib.CoinSetRealOption(
                    hProb, self.COIN_REAL_MIPFRACGAP, ctypes.c_double(self.fracGap)
                )
            # CoinGetInfinity is needed for varibles with no bounds
            coinDblMax = self.lib.CoinGetInfinity()
            if self.debug:
                print("Before getCoinMPArrays")
            (
                numVars,
                numRows,
                numels,
                rangeCount,
                objectSense,
                objectCoeffs,
                objectConst,
                rhsValues,
                rangeValues,
                rowType,
                startsBase,
                lenBase,
                indBase,
                elemBase,
                lowerBounds,
                upperBounds,
                initValues,
                colNames,
                rowNames,
                columnType,
                n2v,
                n2c,
            ) = self.getCplexStyleArrays(lp)
            self.lib.CoinLoadProblem(
                hProb,
                numVars,
                numRows,
                numels,
                rangeCount,
                objectSense,
                objectConst,
                objectCoeffs,
                lowerBounds,
                upperBounds,
                rowType,
                rhsValues,
                rangeValues,
                startsBase,
                lenBase,
                indBase,
                elemBase,
                colNames,
                rowNames,
                "Objective",
            )
            if lp.isMIP() and self.mip:
                self.lib.CoinLoadInteger(hProb, columnType)

            if self.msg == 0:
                self.lib.CoinRegisterMsgLogCallback(
                    hProb, ctypes.c_char_p(""), ctypes.POINTER(ctypes.c_int)()
                )
            self.coinTime = -clock()
            self.lib.CoinOptimizeProblem(hProb, 0)
            self.coinTime += clock()

            # TODO: check Integer Feasible status
            CoinLpStatus = {
                0: constants.LpStatusOptimal,
                1: constants.LpStatusInfeasible,
                2: constants.LpStatusInfeasible,
                3: constants.LpStatusNotSolved,
                4: constants.LpStatusNotSolved,
                5: constants.LpStatusNotSolved,
                -1: constants.LpStatusUndefined,
            }
            solutionStatus = self.lib.CoinGetSolutionStatus(hProb)
            solutionText = self.lib.CoinGetSolutionText(hProb)
            objectValue = self.lib.CoinGetObjectValue(hProb)

            # get the solution values
            NumVarDoubleArray = ctypes.c_double * numVars
            NumRowsDoubleArray = ctypes.c_double * numRows
            cActivity = NumVarDoubleArray()
            cReducedCost = NumVarDoubleArray()
            cSlackValues = NumRowsDoubleArray()
            cShadowPrices = NumRowsDoubleArray()
            self.lib.CoinGetSolutionValues(
                hProb,
                ctypes.byref(cActivity),
                ctypes.byref(cReducedCost),
                ctypes.byref(cSlackValues),
                ctypes.byref(cShadowPrices),
            )

            variablevalues = {}
            variabledjvalues = {}
            constraintpivalues = {}
            constraintslackvalues = {}
            if lp.isMIP() and self.mip:
                lp.bestBound = self.lib.CoinGetMipBestBound(hProb)
            for i in range(numVars):
                variablevalues[self.n2v[i].name] = cActivity[i]
                variabledjvalues[self.n2v[i].name] = cReducedCost[i]
            lp.assignVarsVals(variablevalues)
            lp.assignVarsDj(variabledjvalues)
            # put pi and slack variables against the constraints
            for i in range(numRows):
                constraintpivalues[self.n2c[i]] = cShadowPrices[i]
                constraintslackvalues[self.n2c[i]] = cSlackValues[i]
            lp.assignConsPi(constraintpivalues)
            lp.assignConsSlack(constraintslackvalues)

            self.lib.CoinFreeSolver()
            status = CoinLpStatus[self.lib.CoinGetSolutionStatus(hProb)]
            lp.assignStatus(status)
            return status


if COINMP_DLL.available():
    COIN = COINMP_DLL

yaposib = None


class YAPOSIB(LpSolver):
    """
    COIN OSI (via its python interface)

    Copyright Christophe-Marie Duquesne 2012

    The yaposib variables are available (after a solve) in var.solverVar
    The yaposib constraints are available in constraint.solverConstraint
    The Model is in prob.solverModel
    """

    name = "YAPOSIB"
    try:
        # import the model into the global scope
        global yaposib
        import yaposib
    except ImportError:

        def available(self):
            """True if the solver is available"""
            return False

        def actualSolve(self, lp, callback=None):
            """Solve a well formulated lp problem"""
            raise PulpSolverError("YAPOSIB: Not Available")

    else:

        def __init__(
            self,
            mip=True,
            msg=True,
            timeLimit=None,
            epgap=None,
            solverName=None,
            **solverParams
        ):
            """
            Initializes the yaposib solver.

            @param mip:          if False the solver will solve a MIP as
                                 an LP
            @param msg:          displays information from the solver to
                                 stdout
            @param timeLimit:    not supported
            @param epgap:        not supported
            @param solverParams: not supported
            """
            LpSolver.__init__(self, mip, msg)
            if solverName:
                self.solverName = solverName
            else:
                self.solverName = yaposib.available_solvers()[0]

        def findSolutionValues(self, lp):
            model = lp.solverModel
            solutionStatus = model.status
            yaposibLpStatus = {
                "optimal": constants.LpStatusOptimal,
                "undefined": constants.LpStatusUndefined,
                "abandoned": constants.LpStatusInfeasible,
                "infeasible": constants.LpStatusInfeasible,
                "limitreached": constants.LpStatusInfeasible,
            }
            # populate pulp solution values
            for var in lp.variables():
                var.varValue = var.solverVar.solution
                var.dj = var.solverVar.reducedcost
            # put pi and slack variables against the constraints
            for constr in lp.constraints.values():
                constr.pi = constr.solverConstraint.dual
                constr.slack = -constr.constant - constr.solverConstraint.activity
            if self.msg:
                print("yaposib status=", solutionStatus)
            lp.resolveOK = True
            for var in lp.variables():
                var.isModified = False
            status = yaposibLpStatus.get(solutionStatus, constants.LpStatusUndefined)
            lp.assignStatus(status)
            return status

        def available(self):
            """True if the solver is available"""
            return True

        def callSolver(self, lp, callback=None):
            """Solves the problem with yaposib"""
            savestdout = None
            if self.msg == 0:
                # close stdout to get rid of messages
                tempfile = open(mktemp(), "w")
                savestdout = os.dup(1)
                os.close(1)
                if os.dup(tempfile.fileno()) != 1:
                    raise PulpSolverError("couldn't redirect stdout - dup() error")
            self.solveTime = -clock()
            lp.solverModel.solve(self.mip)
            self.solveTime += clock()
            if self.msg == 0:
                # reopen stdout
                os.close(1)
                os.dup(savestdout)
                os.close(savestdout)

        def buildSolverModel(self, lp):
            """
            Takes the pulp lp model and translates it into a yaposib model
            """
            log.debug("create the yaposib model")
            lp.solverModel = yaposib.Problem(self.solverName)
            prob = lp.solverModel
            prob.name = lp.name
            log.debug("set the sense of the problem")
            if lp.sense == constants.LpMaximize:
                prob.obj.maximize = True
            log.debug("add the variables to the problem")
            for var in lp.variables():
                col = prob.cols.add(yaposib.vec([]))
                col.name = var.name
                if not var.lowBound is None:
                    col.lowerbound = var.lowBound
                if not var.upBound is None:
                    col.upperbound = var.upBound
                if var.cat == constants.LpInteger:
                    col.integer = True
                prob.obj[col.index] = lp.objective.get(var, 0.0)
                var.solverVar = col
            log.debug("add the Constraints to the problem")
            for name, constraint in lp.constraints.items():
                row = prob.rows.add(
                    yaposib.vec(
                        [
                            (var.solverVar.index, value)
                            for var, value in constraint.items()
                        ]
                    )
                )
                if constraint.sense == constants.LpConstraintLE:
                    row.upperbound = -constraint.constant
                elif constraint.sense == constants.LpConstraintGE:
                    row.lowerbound = -constraint.constant
                elif constraint.sense == constants.LpConstraintEQ:
                    row.upperbound = -constraint.constant
                    row.lowerbound = -constraint.constant
                else:
                    raise PulpSolverError("Detected an invalid constraint type")
                row.name = name
                constraint.solverConstraint = row

        def actualSolve(self, lp, callback=None):
            """
            Solve a well formulated lp problem

            creates a yaposib model, variables and constraints and attaches
            them to the lp model which it then solves
            """
            self.buildSolverModel(lp)
            # set the initial solution
            log.debug("Solve the model using yaposib")
            self.callSolver(lp, callback=callback)
            # get the solution information
            solutionStatus = self.findSolutionValues(lp)
            for var in lp.variables():
                var.modified = False
            for constraint in lp.constraints.values():
                constraint.modified = False
            return solutionStatus

        def actualResolve(self, lp, callback=None):
            """
            Solve a well formulated lp problem

            uses the old solver and modifies the rhs of the modified
            constraints
            """
            log.debug("Resolve the model using yaposib")
            for constraint in lp.constraints.values():
                row = constraint.solverConstraint
                if constraint.modified:
                    if constraint.sense == constants.LpConstraintLE:
                        row.upperbound = -constraint.constant
                    elif constraint.sense == constants.LpConstraintGE:
                        row.lowerbound = -constraint.constant
                    elif constraint.sense == constants.LpConstraintEQ:
                        row.upperbound = -constraint.constant
                        row.lowerbound = -constraint.constant
                    else:
                        raise PulpSolverError("Detected an invalid constraint type")
            self.callSolver(lp, callback=callback)
            # get the solution information
            solutionStatus = self.findSolutionValues(lp)
            for var in lp.variables():
                var.modified = False
            for constraint in lp.constraints.values():
                constraint.modified = False
            return solutionStatus