File: mpsolve.py

package info (click to toggle)
mpsolve 3.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,100 kB
  • sloc: ansic: 25,748; sh: 4,925; cpp: 3,155; makefile: 914; python: 407; yacc: 158; lex: 85; xml: 41
file content (256 lines) | stat: -rw-r--r-- 9,633 bytes parent folder | download | duplicates (4)
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
import sys
import ctypes
import ctypes.util
# from gmpy2 import mpq

# Load the libmps shared library. We should keep the .so version update
# in case we bump it in the future.
_mps = ctypes.CDLL("libmps.so.3")

class Cplx(ctypes.Structure):
    _mps.mps_chebyshev_poly_new.restype = ctypes.c_void_p
    _mps.mps_context_new.restype = ctypes.c_void_p
    _mps.mps_monomial_poly_new.restype = ctypes.c_void_p

    """Wrapper around the cplx_t type of MPSolve, that is usually
    a direct mapping onto the complex type of C99, but has a fallback
    custom implementation for systems that do not provide the type"""

    _fields_ = [("real", ctypes.c_double),
                ("imag", ctypes.c_double)]

    def __repr__(self):
        return "%e + %ei" % (self.real, self.imag)

    def __complex__(self):
        return complex(self.real + 1j*self.imag)


class Goal:
    """ Goal to reach before returning the result.
    """
    MPS_OUTPUT_GOAL_ISOLATE = 0
    MPS_OUTPUT_GOAL_APPROXIMATE = 1
    MPS_OUTPUT_GOAL_COUNT = 2


class Algorithm:
    """Here you can find all the available algorithms in MPSolve.
    You can use these contants to specify the algorithm of your choice
    when you call Context.solve() or Context.mpsolve(). For example:

     poly = MonomialPoly(ctx, n)
     roots = ctx.solve(poly, Algorithm.SECULAR_GA)
    """

    STANDARD_MPSOLVE = 0
    SECULAR_GA = 1


class Context:
    """The Context class is a wrapper around the mps_context type
    in libmps. A Context instance must be instantiated before
    allocating and/or solving polynomials and secular equations,
    and can then be used to specify the desired property of the
    solution. """

    def __init__(self):
        self._c_ctx = ctypes.c_void_p(_mps.mps_context_new())

    def __del__(self):
        if self._c_ctx is not None:
            _mps.mps_context_free(self._c_ctx)

    def set_input_poly(self, poly):
        """Select the polynomial that should be solved when mpsolve() is
        called. Note that each Context can only solve one polynomial
        at a time."""
        self._set_input_poly(poly)

    def _set_input_poly(self, poly):
        _mps.mps_context_set_input_poly(self._c_ctx, poly._c_polynomial)

    def mpsolve(self, poly=None, algorithm=Algorithm.SECULAR_GA):
        """Calling this method will trigger the solution of the polynomial
        previously loaded by a call to set_input_poly, or to the one passed
        as second argument to this function.

        An optional third argument specify the desired algorithm. """
        if poly is not None:
            self.set_input_poly(poly)

        # Select the proper algorithm for this polynomial
        if isinstance(poly, ChebyshevPoly):
            algorithm = Algorithm.SECULAR_GA

        _mps.mps_context_select_algorithm(self._c_ctx, algorithm)
        _mps.mps_context_set_output_prec(self._c_ctx, ctypes.c_long(100))
        _mps.mps_context_set_output_goal(self._c_ctx,
                                         Goal.MPS_OUTPUT_GOAL_APPROXIMATE)
        _mps.mps_mpsolve(self._c_ctx)

    def solve(self, poly = None, algorithm = Algorithm.SECULAR_GA):
        """Simple shorthand for the combination of set_input_poly() and mpsolve(). 
        This function directly returns the approximations that could otherwise be
        obtained by a call to the get_roots() method. """
        self.mpsolve(poly, algorithm)
        return self.get_roots()

    def get_roots(self):
        """Returns  the approximations obtained by MPSolve after a call
        to the mpsolve method. Consider using the convienience solve() method
        instead."""
        degree = _mps.mps_context_get_degree(self._c_ctx)
        roots = (Cplx*degree)()

        _mps.mps_context_get_roots_d(self._c_ctx,
                                     ctypes.pointer(ctypes.pointer(roots)),
                                     None)
        return [complex(x) for x in roots]

    def get_inclusion_radii(self):
        """Return a set of guaranteed inclusion radii for the
        approximations obtained through a call to get_roots()"""
        degree = _mps.mps_context_get_degree(self._c_ctx)
        roots = (Cplx*degree)()
        radii = (ctypes.c_double*degree)()

        _mps.mps_context_get_roots_d(self._c_ctx,
                                     ctypes.pointer(ctypes.pointer(roots)),
                                     ctypes.pointer(ctypes.pointer(radii)))

        return list(radii)


class Polynomial:
    """This is a wrapper around mps_polynomial struct. """

    def __init__(self, ctx, degree):
        self._degree = int(degree)
        self._ctx = ctx

    def __del__(self):
        _mps.mps_polynomial_free(self._ctx._c_ctx, self._c_polynomial)


class MonomialPoly(Polynomial):
    """A polynomial specified with its monomial coefficients. """

    def __init__(self, ctx, degree):
        Polynomial.__init__(self, ctx, degree)
        self._c_polynomial = \
            ctypes.c_void_p(_mps.mps_monomial_poly_new (ctx._c_ctx, degree))

    def set_coefficient(self, n, coeff_re, coeff_im=None):
        """Set coefficient of degree n of the polynomial
        to the value of coeff. Please note that you should use
        the same data type for all the coefficients, and you
        should use integers when possible. """

        if coeff_im is not None and type(coeff_re) != type(coeff_im):
            raise ValueError("Coefficient's real and imaginary parts \
have different types")

        mp = self._c_polynomial
        cntxt = self._ctx._c_ctx
        if n < 0 or n > self._degree:
            raise RuntimeError("Coefficient degree is out of bounds")

        if isinstance(coeff_re, int):
            if coeff_im is None:
                coeff_im = 0
            coeff_re = ctypes.c_longlong(coeff_re)
            coeff_im = ctypes.c_longlong(coeff_im)
            _mps.mps_monomial_poly_set_coefficient_int(cntxt, mp, n,
                                                       coeff_re, coeff_im)
        elif isinstance(coeff_re, float):
            if coeff_im is None:
                coeff_im = 0.0
            coeff_re = ctypes.c_double(coeff_re)
            coeff_im = ctypes.c_double(coeff_im)
            _mps.mps_monomial_poly_set_coefficient_d(cntxt, mp, n,
                                                     coeff_re, coeff_im)
        elif isinstance(coeff_re, str):
            if coeff_im is None:
                coeff_im = "0.0"
            if sys.version_info.major > 2:
                coeff_re = bytes(coeff_re, "ASCII")
                coeff_im = bytes(coeff_im, "ASCII")
            _mps.mps_monomial_poly_set_coefficient_s(cntxt, mp, n,
                                                     coeff_re, coeff_im)
        # elif isinstance(coeff_re, type(mpq())):
        #     if coeff_im is None:
        #         coeff_im = mpq()
        #     _mps.mps_monomial_poly_set_coefficient_q(cntxt, mp, n,
        #                                              coeff_re, coeff_im)

        else:
            raise RuntimeError("Coefficient type not supported")

    def get_coefficient(self, n):
        """ Get a coefficient of the polynomial
        """
        mp = self._c_polynomial
        if n < 0 or n > self._degree or not isinstance(n, int):
            raise ValueError("Invalid coefficient degree")

        cf = (Cplx)()
        _mps.mps_monomial_poly_get_coefficient_d(self._ctx._c_ctx, mp, n,
                                                 ctypes.pointer(cf))
        return complex(cf)

    def get_coefficients(self):
        """ Get list of coefficients of the polynomial
        """
        mp = self._c_polynomial
        cf = (Cplx)()
        coeffs = []
        for n in range(self._degree + 1):
            _mps.mps_monomial_poly_get_coefficient_d(self._ctx._c_ctx, mp, n,
                                                     ctypes.pointer(cf))
            cf_n = complex(cf)
            coeffs.append(cf_n)
        return coeffs


class ChebyshevPoly(Polynomial):
    """A polynomial represented in the Chebyshev base."""

    def __init__(self, ctx, degree):
        Polynomial.__init__(self, ctx, degree)

        # 5 here is the equivalent of MPS_STRUCTURE_COMPLEX_RATIONAL
        self._c_polynomial = \
            ctypes.c_void_p(_mps.mps_chebyshev_poly_new (ctx._c_ctx,
                                                          degree, 5))

    def set_coefficient(self, n, coeff_re, coeff_im=None):
        """Set the coefficient of degree n of the polynomial"""
        cb = self._c_polynomial

        if coeff_im is not None and type(coeff_re) != type(coeff_im):
            raise ValueError("Coefficient's real and imaginary parts \
have different types")

        mp = self._c_polynomial
        cntxt = self._ctx._c_ctx

        if n < 0 or n > self._degree:
            raise RuntimeError("Coefficient degree is out of bounds")

        if isinstance(coeff_re, int):
            if coeff_im is None:
                coeff_im = 0
            _mps.mps_chebyshev_poly_set_coefficient_i(cntxt, cb, n,
                                                      coeff_re, coeff_im)
        elif isinstance(coeff, float):
            if coeff_im is None:
                coeff_im = 0.0
            _mps.mpc_set_d(ccoeff, coeff_re, coeff_im)
        # elif isinstance(coeff, str):
        #     if coeff_im is None:
        #         coeff_im = "0.0"
        #     _mps.mps_chebyshev_poly_set_coefficient_s(cntxt, cb, n,
        #                                               coeff_re, coeff_im)
        else:
            raise RuntimeError("Unsupported type for coefficient")