algorithm.py 20.7 KB
Newer Older
André Anjos's avatar
André Anjos committed
1 2 3
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

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
###################################################################################
#                                                                                 #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
# Contact: beat.support@idiap.ch                                                  #
#                                                                                 #
# Redistribution and use in source and binary forms, with or without              #
# modification, are permitted provided that the following conditions are met:     #
#                                                                                 #
# 1. Redistributions of source code must retain the above copyright notice, this  #
# list of conditions and the following disclaimer.                                #
#                                                                                 #
# 2. Redistributions in binary form must reproduce the above copyright notice,    #
# this list of conditions and the following disclaimer in the documentation       #
# and/or other materials provided with the distribution.                          #
#                                                                                 #
# 3. Neither the name of the copyright holder nor the names of its contributors   #
# may be used to endorse or promote products derived from this software without   #
# specific prior written permission.                                              #
#                                                                                 #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
#                                                                                 #
###################################################################################

André Anjos's avatar
André Anjos committed
36

37 38 39 40 41 42
"""
=========
algorithm
=========

Validation for algorithms
43 44 45 46

Forward importing from :py:mod:`beat.backend.python.algorithm`
:py:class:`beat.backend.python.algorithm.Storage`
:py:class:`beat.backend.python.algorithm.Runner`
47
"""
André Anjos's avatar
André Anjos committed
48 49


50
import os
André Anjos's avatar
André Anjos committed
51
import six
52
import json
André Anjos's avatar
André Anjos committed
53
import numpy
54
import pkg_resources
André Anjos's avatar
André Anjos committed
55 56 57 58 59 60

from . import dataformat
from . import library
from . import schema
from . import prototypes

61
from beat.backend.python.algorithm import Storage
62
from beat.backend.python.algorithm import Runner  # noqa
63
from beat.backend.python.algorithm import Algorithm as BackendAlgorithm
André Anjos's avatar
André Anjos committed
64 65


66 67 68 69
def load_algorithm_prototype(prefix):
    algorithm_data = json.loads(
        pkg_resources.resource_string(__name__, "prototypes/algorithm.json")
    )
70
    ref_dataformats = ["integer", "integers"]
71 72
    dataformat = None

73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    for ref_dataformat in ref_dataformats:
        for root, dirs, _ in os.walk(os.path.join(prefix, "dataformats")):
            if ref_dataformat in dirs:
                dataformat_versions = sorted(
                    os.listdir(os.path.join(root, ref_dataformat))
                )
                version = dataformat_versions[-1].split(".")[0]
                dataformat = "{}/{}/{}".format(
                    os.path.basename(root), ref_dataformat, version
                )
                break

    if dataformat is None:
        raise RuntimeError(
            "Reference data formats [{}] not found".format(",".join(ref_dataformats))
        )
89 90 91 92 93
    algorithm_data["groups"][0]["inputs"]["in_data"]["type"] = dataformat
    algorithm_data["groups"][0]["outputs"]["out_data"]["type"] = dataformat
    return algorithm_data


94
class Algorithm(BackendAlgorithm):
Philip ABBET's avatar
Philip ABBET committed
95
    """Algorithms represent runnable components within the platform.
André Anjos's avatar
André Anjos committed
96

Philip ABBET's avatar
Philip ABBET committed
97 98
    This class can only parse the meta-parameters of the algorithm (i.e., input
    and output declaration, grouping, synchronization details, parameters and
99 100
    splittability). The actual algorithm is not directly treated by this class.
    It can, however, provide you with a loader for actually running the
André Anjos's avatar
André Anjos committed
101
    algorithmic code (see :py:meth:`.runner`).
André Anjos's avatar
André Anjos committed
102 103


Philip ABBET's avatar
Philip ABBET committed
104
    Parameters:
André Anjos's avatar
André Anjos committed
105

Philip ABBET's avatar
Philip ABBET committed
106
      prefix (str): Establishes the prefix of your installation.
André Anjos's avatar
André Anjos committed
107

André Anjos's avatar
André Anjos committed
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
      data (:py:class:`object`, Optional): The piece of data representing the
        algorithm. It must validate against the schema defined for algorithms.
        If a string is passed, it is supposed to be a valid path to an
        algorithm in the designated prefix area. If a tuple is passed (or a
        list), then we consider that the first element represents the algorithm
        declaration, while the second, the code for the algorithm (either in
        its source format or as a binary blob). If ``None`` is passed, loads
        our default prototype for algorithms (source code will be in Python).

      dataformat_cache (:py:class:`dict`, Optional): A dictionary mapping
        dataformat names to loaded dataformats. This parameter is optional and,
        if passed, may greatly speed-up algorithm loading times as dataformats
        that are already loaded may be re-used.

      library_cache (:py:class:`dict`, Optional): A dictionary mapping library
        names to loaded libraries. This parameter is optional and, if passed,
        may greatly speed-up library loading times as libraries that are
        already loaded may be re-used.
André Anjos's avatar
André Anjos committed
126 127


Philip ABBET's avatar
Philip ABBET committed
128
    Attributes:
André Anjos's avatar
André Anjos committed
129

Philip ABBET's avatar
Philip ABBET committed
130
      name (str): The algorithm name
André Anjos's avatar
André Anjos committed
131

Philip ABBET's avatar
Philip ABBET committed
132 133
      description (str): The short description string, loaded from the JSON
        file if one was set.
André Anjos's avatar
André Anjos committed
134

Philip ABBET's avatar
Philip ABBET committed
135
      documentation (str): The full-length docstring for this object.
André Anjos's avatar
André Anjos committed
136

Philip ABBET's avatar
Philip ABBET committed
137 138
      storage (object): A simple object that provides information about file
        paths for this algorithm
André Anjos's avatar
André Anjos committed
139

140 141
      dataformats (dict): A dictionary containing all pre-loaded dataformats
        used by this algorithm. Data format objects will be of type
Philip ABBET's avatar
Philip ABBET committed
142
        :py:class:`beat.core.dataformat.DataFormat`.
André Anjos's avatar
André Anjos committed
143

144 145
      libraries (dict): A mapping object defining other libraries this
        algorithm needs to load so it can work properly.
André Anjos's avatar
André Anjos committed
146

Philip ABBET's avatar
Philip ABBET committed
147 148
      uses (dict): A mapping object defining the required library import name
        (keys) and the full-names (values).
André Anjos's avatar
André Anjos committed
149

150 151
      parameters (dict): A dictionary containing all pre-defined parameters
        that this algorithm accepts.
André Anjos's avatar
André Anjos committed
152

Philip ABBET's avatar
Philip ABBET committed
153 154
      splittable (bool): A boolean value that indicates if this algorithm is
        automatically parallelizeable by our backend.
André Anjos's avatar
André Anjos committed
155

Philip ABBET's avatar
Philip ABBET committed
156
      input_map (dict): A dictionary where the key is the input name and the
157 158
        value, its type. All input names (potentially from different groups)
        are comprised in this dictionary.
André Anjos's avatar
André Anjos committed
159

Philip ABBET's avatar
Philip ABBET committed
160
      output_map (dict): A dictionary where the key is the output name and the
161 162
        value, its type. All output names (potentially from different groups)
        are comprised in this dictionary.
André Anjos's avatar
André Anjos committed
163

164 165 166 167
      results (dict): If this algorithm is actually an analyzer (i.e., there
        are no formal outputs, but results that must be saved by the platform),
        then this dictionary contains the names and data types of those
        elements.
André Anjos's avatar
André Anjos committed
168

Philip ABBET's avatar
Philip ABBET committed
169 170
      groups (dict): A list containing dictionaries with inputs and outputs
        belonging to the same synchronization group.
André Anjos's avatar
André Anjos committed
171

Philip ABBET's avatar
Philip ABBET committed
172 173
      errors (list): A list containing errors found while loading this
        algorithm.
André Anjos's avatar
André Anjos committed
174

Philip ABBET's avatar
Philip ABBET committed
175 176
      data (dict): The original data for this algorithm, as loaded by our JSON
        decoder.
André Anjos's avatar
André Anjos committed
177

Philip ABBET's avatar
Philip ABBET committed
178 179
      code (str): The code that is associated with this algorithm, loaded as a
        text (or binary) file.
André Anjos's avatar
André Anjos committed
180

Philip ABBET's avatar
Philip ABBET committed
181
    """
André Anjos's avatar
André Anjos committed
182

Philip ABBET's avatar
Philip ABBET committed
183 184
    def __init__(self, prefix, data, dataformat_cache=None, library_cache=None):
        super(Algorithm, self).__init__(prefix, data, dataformat_cache, library_cache)
André Anjos's avatar
André Anjos committed
185

Philip ABBET's avatar
Philip ABBET committed
186 187
    def _load(self, data, dataformat_cache, library_cache):
        """Loads the algorithm"""
André Anjos's avatar
André Anjos committed
188

Philip ABBET's avatar
Philip ABBET committed
189 190 191
        self.errors = []
        self.data = None
        self.code = None
André Anjos's avatar
André Anjos committed
192

Philip ABBET's avatar
Philip ABBET committed
193 194
        self._name = None
        self.storage = None
195 196
        self.dataformats = {}  # preloaded dataformats
        self.libraries = {}  # preloaded libraries
Philip ABBET's avatar
Philip ABBET committed
197
        code = None
André Anjos's avatar
André Anjos committed
198

199
        if data is None:  # loads prototype and validates it
André Anjos's avatar
André Anjos committed
200

Philip ABBET's avatar
Philip ABBET committed
201 202
            data = None
            code = None
André Anjos's avatar
André Anjos committed
203

204
        elif isinstance(data, (tuple, list)):  # user has passed individual info
André Anjos's avatar
André Anjos committed
205

206
            data, code = data  # break down into two components
André Anjos's avatar
André Anjos committed
207

208
        if isinstance(data, six.string_types):  # user has passed a file pointer
André Anjos's avatar
André Anjos committed
209

Philip ABBET's avatar
Philip ABBET committed
210 211 212
            self._name = data
            self.storage = Storage(self.prefix, self._name)
            if not self.storage.json.exists():
213
                self.errors.append("Algorithm declaration file not found: %s" % data)
Philip ABBET's avatar
Philip ABBET committed
214
                return
André Anjos's avatar
André Anjos committed
215

216
            data = self.storage.json.path  # loads data from JSON declaration
André Anjos's avatar
André Anjos committed
217

Philip ABBET's avatar
Philip ABBET committed
218
        # At this point, `data' can be a dictionary or ``None``
219
        if data is None:  # loads the default declaration for an algorithm
220 221 222
            algorithm_data = load_algorithm_prototype(self.prefix)
            self.data, self.errors = schema.validate("algorithm", algorithm_data)
            assert not self.errors, "\n  * %s" % "\n  *".join(self.errors)  # nosec
223
        else:  # just assign it
Philip ABBET's avatar
Philip ABBET committed
224
            # this runs basic validation, including JSON loading if required
225
            self.data, self.errors = schema.validate("algorithm", data)
André Anjos's avatar
André Anjos committed
226

227 228
        if self.errors:
            return  # don't proceed with the rest of validation
André Anjos's avatar
André Anjos committed
229

230
        if self.storage is not None:  # loading from the disk, check code
Philip ABBET's avatar
Philip ABBET committed
231
            if not self.storage.code.exists():
232 233 234 235
                if self.data["language"] != "cxx":
                    self.errors.append(
                        "Algorithm code not found: %s" % self.storage.code.path
                    )
Philip ABBET's avatar
Philip ABBET committed
236 237 238
                    return
            else:
                code = self.storage.code.load()
André Anjos's avatar
André Anjos committed
239

Philip ABBET's avatar
Philip ABBET committed
240
        # At this point, `code' can be a string (or a binary blob) or ``None``
241
        if code is None:  # loads the default code for an algorithm
242 243
            self.code = prototypes.binary_load("algorithm.py")
            self.data["language"] = "python"
André Anjos's avatar
André Anjos committed
244

245
        else:  # just assign it - notice that in this case, no language is set
Philip ABBET's avatar
Philip ABBET committed
246
            self.code = code
André Anjos's avatar
André Anjos committed
247

248 249
        if self.errors:
            return  # don't proceed with the rest of validation
André Anjos's avatar
André Anjos committed
250

Philip ABBET's avatar
Philip ABBET committed
251
        # if no errors so far, make sense out of the declaration data
252
        self.groups = self.data["groups"]
André Anjos's avatar
André Anjos committed
253

Philip ABBET's avatar
Philip ABBET committed
254 255
        # now we check for consistence
        self._check_endpoint_uniqueness()
André Anjos's avatar
André Anjos committed
256

Philip ABBET's avatar
Philip ABBET committed
257
        # create maps for easy access to data
258 259 260 261 262 263 264 265 266 267 268 269 270
        self.input_map = dict(
            [(k, v["type"]) for g in self.groups for k, v in g["inputs"].items()]
        )
        self.output_map = dict(
            [
                (k, v["type"])
                for g in self.groups
                for k, v in g.get("outputs", {}).items()
            ]
        )
        self.loop_map = dict(
            [(k, v["type"]) for g in self.groups for k, v in g.get("loop", {}).items()]
        )
André Anjos's avatar
André Anjos committed
271

Philip ABBET's avatar
Philip ABBET committed
272 273
        self._validate_required_dataformats(dataformat_cache)
        self._convert_parameter_types()
André Anjos's avatar
André Anjos committed
274

Philip ABBET's avatar
Philip ABBET committed
275 276 277
        # finally, the libraries
        self._validate_required_libraries(library_cache)
        self._check_language_consistence()
André Anjos's avatar
André Anjos committed
278

Philip ABBET's avatar
Philip ABBET committed
279 280 281
    def _check_endpoint_uniqueness(self):
        """Checks for name clashes accross input/output groups
        """
André Anjos's avatar
André Anjos committed
282

Philip ABBET's avatar
Philip ABBET committed
283
        all_input_names = []
284 285
        for group in self.groups:
            all_input_names.extend(group["inputs"].keys())
Philip ABBET's avatar
Philip ABBET committed
286
        if len(set(all_input_names)) != len(all_input_names):
287 288 289 290
            self.errors.append(
                "repeated input name in algorithm `%s' "
                "declaration: %s" % (self.name, ", ".join(all_input_names))
            )
André Anjos's avatar
André Anjos committed
291

Philip ABBET's avatar
Philip ABBET committed
292 293 294
        # all outputs must have unique names
        all_output_names = []
        for group in self.groups:
295 296 297
            if "outputs" not in group:
                continue
            all_output_names.extend(group["outputs"].keys())
Philip ABBET's avatar
Philip ABBET committed
298
        if len(set(all_output_names)) != len(all_output_names):
299 300 301 302
            self.errors.append(
                "repeated output name in algorithm `%s' "
                "declaration: %s" % (self.name, ", ".join(all_output_names))
            )
André Anjos's avatar
André Anjos committed
303

Philip ABBET's avatar
Philip ABBET committed
304 305 306
    def _validate_required_dataformats(self, dataformat_cache):
        """Makes sure we can load all requested formats
        """
André Anjos's avatar
André Anjos committed
307

Philip ABBET's avatar
Philip ABBET committed
308
        for group in self.groups:
André Anjos's avatar
André Anjos committed
309

310 311 312
            for name, input in group["inputs"].items():
                if input["type"] in self.dataformats:
                    continue
André Anjos's avatar
André Anjos committed
313

314 315
                if dataformat_cache and input["type"] in dataformat_cache:  # reuse
                    thisformat = dataformat_cache[input["type"]]
316
                else:  # load it
317
                    thisformat = dataformat.DataFormat(self.prefix, input["type"])
318
                    if dataformat_cache is not None:  # update it
319
                        dataformat_cache[input["type"]] = thisformat
André Anjos's avatar
André Anjos committed
320

321
                self.dataformats[input["type"]] = thisformat
André Anjos's avatar
André Anjos committed
322

Philip ABBET's avatar
Philip ABBET committed
323
                if thisformat.errors:
324 325 326 327 328
                    self.errors.append(
                        "found error validating data format `%s' "
                        "for input `%s' on algorithm `%s': %s"
                        % (input["type"], name, self.name, "\n".join(thisformat.errors))
                    )
André Anjos's avatar
André Anjos committed
329

330 331
            if "outputs" not in group:
                continue
André Anjos's avatar
André Anjos committed
332

333 334 335
            for name, output in group["outputs"].items():
                if output["type"] in self.dataformats:
                    continue
André Anjos's avatar
André Anjos committed
336

337 338
                if dataformat_cache and output["type"] in dataformat_cache:  # reuse
                    thisformat = dataformat_cache[output["type"]]
339
                else:  # load it
340
                    thisformat = dataformat.DataFormat(self.prefix, output["type"])
341
                    if dataformat_cache is not None:  # update it
342
                        dataformat_cache[output["type"]] = thisformat
André Anjos's avatar
André Anjos committed
343

344
                self.dataformats[output["type"]] = thisformat
André Anjos's avatar
André Anjos committed
345

Philip ABBET's avatar
Philip ABBET committed
346
                if thisformat.errors:
347 348 349 350 351 352 353 354 355 356
                    self.errors.append(
                        "found error validating data format `%s' "
                        "for output `%s' on algorithm `%s': %s"
                        % (
                            output["type"],
                            name,
                            self.name,
                            "\n".join(thisformat.errors),
                        )
                    )
André Anjos's avatar
André Anjos committed
357

Philip ABBET's avatar
Philip ABBET committed
358
        if self.results:
André Anjos's avatar
André Anjos committed
359

Philip ABBET's avatar
Philip ABBET committed
360
            for name, result in self.results.items():
André Anjos's avatar
André Anjos committed
361

362
                if result["type"].find("/") != -1:
André Anjos's avatar
André Anjos committed
363

364 365
                    if result["type"] in self.dataformats:
                        continue
André Anjos's avatar
André Anjos committed
366

367 368
                    if dataformat_cache and result["type"] in dataformat_cache:  # reuse
                        thisformat = dataformat_cache[result["type"]]
Philip ABBET's avatar
Philip ABBET committed
369
                    else:
370
                        thisformat = dataformat.DataFormat(self.prefix, result["type"])
371
                        if dataformat_cache is not None:  # update it
372
                            dataformat_cache[result["type"]] = thisformat
André Anjos's avatar
André Anjos committed
373

374
                    self.dataformats[result["type"]] = thisformat
André Anjos's avatar
André Anjos committed
375

Philip ABBET's avatar
Philip ABBET committed
376
                    if thisformat.errors:
377 378 379 380 381 382 383 384 385 386
                        self.errors.append(
                            "found error validating data format `%s' "
                            "for result `%s' on algorithm `%s': %s"
                            % (
                                result["type"],
                                name,
                                self.name,
                                "\n".join(thisformat.errors),
                            )
                        )
André Anjos's avatar
André Anjos committed
387

Philip ABBET's avatar
Philip ABBET committed
388
    def _convert_parameter_types(self):
389 390
        """Converts types to numpy equivalents, checks defaults, ranges and
        choices
Philip ABBET's avatar
Philip ABBET committed
391
        """
André Anjos's avatar
André Anjos committed
392

Philip ABBET's avatar
Philip ABBET committed
393 394 395 396
        def _try_convert(name, tp, value, desc):
            try:
                return tp.type(value)
            except Exception as e:
397 398 399 400
                self.errors.append(
                    "%s for parameter `%s' cannot be cast to type "
                    "`%s': %s" % (desc, name, tp.name, e)
                )
André Anjos's avatar
André Anjos committed
401

402 403
        if self.parameters is None:
            return
André Anjos's avatar
André Anjos committed
404

Philip ABBET's avatar
Philip ABBET committed
405
        for name, parameter in self.parameters.items():
406 407
            if parameter["type"] == "string":
                parameter["type"] = numpy.dtype("str")
Philip ABBET's avatar
Philip ABBET committed
408
            else:
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
                parameter["type"] = numpy.dtype(parameter["type"])

            if "range" in parameter:
                parameter["range"][0] = _try_convert(
                    name, parameter["type"], parameter["range"][0], "start of range"
                )
                parameter["range"][1] = _try_convert(
                    name, parameter["type"], parameter["range"][1], "end of range"
                )
                if parameter["range"][0] >= parameter["range"][1]:
                    self.errors.append(
                        "range for parameter `%s' has a start greater "
                        "then the end value (%r >= %r)"
                        % (name, parameter["range"][0], parameter["range"][1])
                    )

            if "choice" in parameter:
                for i, choice in enumerate(parameter["choice"]):
                    parameter["choice"][i] = _try_convert(
                        name,
                        parameter["type"],
                        parameter["choice"][i],
                        "choice[%d]" % i,
                    )

            if "default" in parameter:
                parameter["default"] = _try_convert(
                    name, parameter["type"], parameter["default"], "default"
                )

                if "range" in parameter:  # check range
                    if (
                        parameter["default"] < parameter["range"][0]
                        or parameter["default"] > parameter["range"][1]
                    ):
                        self.errors.append(
                            "default for parameter `%s' (%r) is not "
                            "within parameter range [%r, %r]"
                            % (
                                name,
                                parameter["default"],
                                parameter["range"][0],
                                parameter["range"][1],
                            )
                        )

                if "choice" in parameter:  # check choices
                    if parameter["default"] not in parameter["choice"]:
                        self.errors.append(
                            "default for parameter `%s' (%r) is not "
                            "a valid choice `[%s]'"
                            % (
                                name,
                                parameter["default"],
                                ", ".join(["%r" % k for k in parameter["choice"]]),
                            )
                        )
André Anjos's avatar
André Anjos committed
466

Philip ABBET's avatar
Philip ABBET committed
467
    def _validate_required_libraries(self, library_cache):
André Anjos's avatar
André Anjos committed
468

Philip ABBET's avatar
Philip ABBET committed
469
        # all used libraries must be loadable; cannot use self as a library
André Anjos's avatar
André Anjos committed
470

Philip ABBET's avatar
Philip ABBET committed
471
        if self.uses:
André Anjos's avatar
André Anjos committed
472

Philip ABBET's avatar
Philip ABBET committed
473
            for name, value in self.uses.items():
André Anjos's avatar
André Anjos committed
474

475 476 477
                self.libraries[value] = library_cache.setdefault(
                    value, library.Library(self.prefix, value, library_cache)
                )
André Anjos's avatar
André Anjos committed
478

Philip ABBET's avatar
Philip ABBET committed
479
                if not self.libraries[value].valid:
480 481 482 483
                    self.errors.append(
                        "referred library `%s' (%s) is not valid"
                        % (self.libraries[value].name, name)
                    )
André Anjos's avatar
André Anjos committed
484

Philip ABBET's avatar
Philip ABBET committed
485
    def _check_language_consistence(self):
André Anjos's avatar
André Anjos committed
486

Philip ABBET's avatar
Philip ABBET committed
487
        # all used libraries must be programmed with the same language
488 489
        if self.language == "unknown":
            return  # bail out on unknown language
André Anjos's avatar
André Anjos committed
490

Philip ABBET's avatar
Philip ABBET committed
491
        if self.uses:
André Anjos's avatar
André Anjos committed
492

493
            for name, library_name in self.uses.items():
André Anjos's avatar
André Anjos committed
494

495 496
                if library_name not in self.libraries:
                    continue  # invalid
André Anjos's avatar
André Anjos committed
497

498 499 500 501 502 503
                if self.libraries[library_name].data is None:
                    self.errors.append(
                        "language for used library `%s' cannot be "
                        "inferred as the library was not properly loaded"
                        % (library_name,)
                    )
Philip ABBET's avatar
Philip ABBET committed
504
                    continue
André Anjos's avatar
André Anjos committed
505

506 507 508 509 510 511 512 513 514 515
                if self.libraries[library_name].language != self.language:
                    self.errors.append(
                        "language for used library `%s' (`%s') "
                        "differs from current language for this algorithm (`%s')"
                        % (
                            library_name,
                            self.libraries[library_name].language,
                            self.language,
                        )
                    )