• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

transentis / bptk_py / 15798837443

21 Jun 2025 07:14PM CUT coverage: 90.129%. Remained the same
15798837443

push

github

olivergrasl
update dependencies

0 of 1 new or added line in 1 file covered. (0.0%)

6017 of 6676 relevant lines covered (90.13%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.43
/BPTK_Py/sdcompiler/plugins/expandArrays.py
1
#                                                       /`-
2
# _                                  _   _             /####`-
3
# | |                                | | (_)           /########`-
4
# | |_ _ __ __ _ _ __  ___  ___ _ __ | |_ _ ___       /###########`-
5
# | __| '__/ _` | '_ \/ __|/ _ \ '_ \| __| / __|   ____ -###########/
6
# | |_| | | (_| | | | \__ \  __/ | | | |_| \__ \  |    | `-#######/
7
# \__|_|  \__,_|_| |_|___/\___|_| |_|\__|_|___/  |____|    `- # /
8
#
9
# Copyright (c) 2019 transentis labs GmbH
10
# MIT License
11

12
from copy import deepcopy
1✔
13
import itertools
1✔
14

15
from .stockExpressions import DimJoinedExpression
1✔
16

17

18
def cartesian_product(listoflists):
1✔
19
    """
20
    Helper for Cartesian product
21
    :param listoflists:
22
    :return:
23
    """
24
    if len(listoflists) == 1:
1✔
25
        return listoflists[0]
1✔
26
    res = list(itertools.product(*listoflists))
1✔
27

28
    if len(res) == 1:
1✔
29
        return res[0]
×
30

31
    return res
1✔
32

33

34
def arrayed_identifiers(expression, dimension, entity, dimensions):
1✔
35
    dim =str(dimension).replace("'","").replace("(","").replace(")","").replace(" ","")
1✔
36
    if type(expression) is int or type(expression) is str or type(expression) is float:
1✔
37
        return expression
1✔
38

39
    if type(expression) is list:
1✔
40
        return [arrayed_identifiers(x,dimension,entity, dimensions) for x in expression]
1✔
41

42
    if "args" in expression.keys():
1✔
43
        args = []
1✔
44
        for arg in expression["args"]:
1✔
45
            args+= [arrayed_identifiers(arg, dimension, entity, dimensions)]
1✔
46

47
        expression["args"] = args
1✔
48

49
    if expression["type"] == "identifier" and all([expression["name"] in [variable["name"] for variable in dimensions[dimension]["variables"]] for dimension in entity["dimensions"]]):
1✔
50
        expression["name"] = expression["name"] + "[{}]".format(dim)
1✔
51

52
    return expression
1✔
53

54

55

56
def extract_labels(arg, dimensions, index):
1✔
57
    """
58

59
    :param arg:
60
    :param dimensions:
61
    :param index:
62
    :return:
63
    """
64
    labels = dimensions[index]
1✔
65

66
    if arg["type"] == "asterisk":
1✔
67
        return labels
1✔
68

69
    elif arg["type"] == "range":
1✔
70
        range = [ar["name"] for ar in arg["args"]]
×
71
        start = labels.rfind(range[0])
×
72
        end = labels.rfind(range[1]) + 1
×
73
        return labels[start:end]
×
74

75
    elif arg["type"] == "label":
1✔
76
        return arg["name"]
1✔
77

78
    elif arg["type"] == "identifier":
1✔
79

80
        return arg
1✔
81

82
    else:
83
        class ExpressionNotSupportedException(Exception):
1✔
84
            pass
1✔
85

86
        raise (ExpressionNotSupportedException("Expressions in Array are not supported yet."))
1✔
87

88

89
def alter_identifier(IR, entity, expression, model_name):
1✔
90
    """
91
    Change idenfitifiers to elementName[Dimension]
92
    :param IR:
93
    :param entity:
94
    :param expression:
95
    :param model_name:
96
    :return:
97
    """
98
    if type(expression) is str or type(expression) is float:
1✔
99
        return entity
1✔
100

101
    if type(expression) is list:
1✔
102
        for elem in expression:
×
103
            alter_identifier(IR, entity, elem, model_name)
×
104

105
    if type(expression) is dict:
1✔
106
        name_ = expression["name"]
1✔
107
        type_ = expression["type"]
1✔
108

109
        # if "args" in expression.keys():
110
        # alter_identifier(IR,entity, expression["args"],model_name)
111

112
        if type_ == "identifier":
1✔
113

114
            labels_ = []
1✔
115

116
            for index, dim in enumerate(entity["dimensions"]):
1✔
117
                dimension = IR["dimensions"][dim]
1✔
118
                found = 0
1✔
119
                for var in dimension["variables"]:
1✔
120
                    if var["model"] == model_name and var["name"] == name_:
1✔
121
                        found += 1
×
122

123
                if found > 0:
1✔
124
                    labels_ += [entity["labels"][index]]
×
125

126
                if len(labels_) > 0:
1✔
127
                    expression["type"] = "array"
×
128
                    expression["args"] = toLabelObjects(labels_)
×
129

130

131
def spread_function_arguments(expression, model_name, IR):
1✔
132
    if type(expression) is str or type(expression) is float:
1✔
133
        return expression
×
134

135
    if type(expression) is list:
1✔
136
        for elem in expression:
×
137
            spread_function_arguments(elem, model_name, IR=IR)
×
138

139
    if type(expression) is dict:
1✔
140
        name_ = expression["name"]
1✔
141
        type_ = expression["type"]
1✔
142

143
        if type_ == "call":
1✔
144
            args = []
×
145

146
            for arg in expression["args"]:
×
147

148
                if type(arg) == list:
×
149
                    arg = arg[0]
×
150

151
                '''
×
152
                Asterisks for Identifiers
153
                '''
154
                if arg["type"] == 'identifier':
×
155

156
                    count = 0
×
157
                    for dim in IR["dimensions"]:
×
158
                        for variable in dim["variables"]:
×
159
                            if variable["model"] == model_name and variable["name"] == arg["name"]:
×
160
                                count += 1
×
161

162
                    asterisks = [{"name": '*', "type": 'asterisk'} for _ in range(0, count)]
×
163

164
                    if len(asterisks) > 0:
×
165
                        arg["args"] = asterisks
×
166
                        arg["type"] = "array"
×
167

168
                '''
×
169
                Array Expressions
170
                '''
171
                if arg["type"] == "array":
×
172

173
                    dimensions = []
×
174

175
                    for dimension in IR["dimensions"]:
×
176

177
                        variables = dimension["variables"]
×
178
                        var_count = 0
×
179
                        for variable in variables:
×
180
                            if variable["model"] == model_name and variable["name"] == arg["name"] and len(
×
181
                                    dimension["labels"]) > 0:
182
                                var_count += 1
×
183

184
                        if var_count > 0: dimensions += [dimension]
×
185

186
                    arg_args = arg["args"]
×
187
                    arg_args_labels = [extract_labels(dimensions=dimensions, index=index, arg=ar) for index, ar in
×
188
                                       enumerate(arg_args)]
189
                    products = cartesian_product(arg_args_labels)
×
190

191
                    args += [{"name": arg["name"], "type": "array", "args": toLabelObjects(product)} for product in
×
192
                             products]
193
                else:
194
                    args += [arg]
×
195
            expression["args"] = args
×
196
    return expression
1✔
197

198

199
def clone_entity(model_name, entity, idx, product=None, connects=None,dimensions={},entities=None):
1✔
200
    ent = deepcopy(entity)
1✔
201
    try:
1✔
202
        from ..parsers.smile.grammar import SMILEVisitor, grammar
1✔
203
        from .makeAbsolute import makeExpressionAbsolute
1✔
204
    except:
×
205
        from parsers.smile.grammar import SMILEVisitor, grammar
×
206
        from plugins.makeAbsolute import makeExpressionAbsolute
×
207

208
    visitor = SMILEVisitor()
1✔
209

210
    if product:
1✔
211
        # Here is the most complex part. We need to
212
        # a) Find all inflows and outflows
213
        # b) Array index all inflows and outflows
214
        # c) rebuild the equation based on the base equation, inflows and outflows. Similar to stockExpression Plugin but with dimensions!
215

216
        ent["labels"] = product
1✔
217
        prod = str(product).replace(" ", "").replace("[", "").replace("]", "").replace("'", "")
1✔
218

219
        try:
1✔
220
            ent["equation"] = deepcopy(ent["equation"][idx])
1✔
221
        except IndexError:
1✔
222
                try:
1✔
223
                    ent["equation"] = deepcopy(ent["equation"][0])
1✔
224
                except:
×
225
                    ent["equation"] = "0"
×
226

227
        inflows = DimJoinedExpression(ent["inflow"], "+", dim=prod)
1✔
228
        outflows = DimJoinedExpression(ent["outflow"], "+", dim=prod)
1✔
229

230
        ent["equation_parsed"] = makeExpressionAbsolute(model_name, visitor.visit(grammar.parse(ent["equation"])),
1✔
231
                                                            connects=connects,entity=ent,dimensions=dimensions)
232

233
        # Fix dimension names again for arrayed variables
234
        from .replaceDimensionNames import resolve as replaceDimensionNames
1✔
235
        ent["equation_parsed"] = replaceDimensionNames(ent["equation_parsed"], ent, dimensions)
1✔
236

237
        # Array all identifiers
238
        ent["equation_parsed"] = arrayed_identifiers(ent["equation_parsed"], dimension=product,entity=entity,dimensions=dimensions)
1✔
239

240
        # Build equation from inflows and outflows
241
        if (not inflows["type"] == "nothing") and (outflows["type"] == "nothing"):  # Only inflows
1✔
242
            sum = {"name": "()", "type": 'operator', "args": [inflows]}
1✔
243

244

245
        elif (inflows["type"] == "nothing") and (not outflows["type"] == "nothing"):  # Only outflows
1✔
246

247
            sum = {"name": '()', "type": 'operator', "args": [
×
248
                {"name": '*', "type": 'operator', "args": [
249
                    -1,
250
                    {"name": '()', "type": 'operator', "args": [outflows]}
251
                ]}
252
            ]}
253
        elif inflows["type"] == outflows["type"] == "nothing": # No inflows nor outflows. nothing to do!
1✔
254
            return ent
1✔
255
        else:
256

257
            sum = {"name": '()', "type": 'operator', "args": [
1✔
258
                {"name": '-', "type": 'operator', "args": [
259
                    inflows,
260
                    {"name": '()', "type": 'operator', "args": [outflows]}
261
                ]}
262
            ]}
263

264
        prod = str(product).replace(" ","").replace("[","").replace("]","").replace("'","").replace("(","").replace(")","")
1✔
265

266
        ent["equation_parsed"] = {"name": 'IF', "type": 'call', "args": [
1✔
267
            {"name": '<=', "type": 'operator', "args": [
268
                {"name": 'TIME', "type": 'call', "args": []},
269
                {"name": 'STARTTIME', "type": 'call', "args": []}
270
            ]},
271
            deepcopy(ent["equation_parsed"]),  # initial
272
            {"name": '+', "type": 'operator', "args": [
273
                {"name": 'PREVIOUS', "type": 'call', "args": [
274
                    {"name": entity["name"] + "[{}]".format(prod), "type": 'identifier'}
275
                ]},
276
                {"name": '*', "type": 'operator', "args": [
277
                    {"name": 'DT', "type": 'call', "args": []},
278
                    {"name": 'PREVIOUS', "type": 'call', "args": [sum]}
279
                ]}
280
            ]}
281
        ]}
282

283

284
    elif len(entity["labels"]) > 0:
1✔
285
        ent["labels"] = entity["labels"][idx]
1✔
286
        ent["equation"] = deepcopy(entity["equation"][idx])
1✔
287
        ent["equation_parsed"] = deepcopy(entity["equation_parsed"][idx])
1✔
288

289

290
    return ent
1✔
291

292

293
def toLabelObjects(labels):
1✔
294
    if type(labels) is list or type(labels) is tuple:
1✔
295
        return [{"name": label, "type": 'label'} for label in labels]
1✔
296
    else:
297
        return [{"name": labels, "type": 'label'}]
1✔
298

299

300
def ExpandArrays(IR):
1✔
301
    """
302
    Actual plugin. Traverses the IR and finds all array expressions. Creates stocks such as stock1[Dimensions1,Dim1] for each dimension equation
303
    :param IR:
304
    :return:
305
    """
306

307
    for name, model in IR["models"].items():
1✔
308
        '''
1✔
309
        Traverse the IR
310
        '''
311
        for entity_type, entities in model["entities"].items():
1✔
312

313
            '''
1✔
314
            Build new entities
315
            '''
316
            for index, entity in enumerate(deepcopy(entities)):
1✔
317

318
                _entities = []
1✔
319

320
                #TODO investigate this
321
                try:
1✔
322
                    if type(entity["equation_parsed"]) is list and type(entity["equation_parsed"][0]) is dict and entity["equation_parsed"][0]["name"] == "size":
1✔
323
                        continue
1✔
324
                except:
×
325
                    pass
×
326

327
                if type(entity["equation_parsed"]) is list and len(entity["equation_parsed"]) > 1:
1✔
328

329
                    _entities = [deepcopy(
1✔
330
                        clone_entity(model_name=name, entity=entity, idx=i, product=None, connects=IR["assignments"],dimensions=IR["dimensions"]))
331
                        for i in range(0, len(entity["equation_parsed"]))]
332

333
                elif ("dimensions" in entity.keys()) and len(entity["dimensions"]) > 0:
1✔
334

335
                    labels = [IR["dimensions"][name]["labels"] for name in entity["dimensions"]]
1✔
336
                    products = cartesian_product(labels)
1✔
337

338
                    _entities = [deepcopy(clone_entity(model_name=name, entity=entity, idx=i, product=products[i],
1✔
339
                                                       connects=IR["assignments"],dimensions=IR["dimensions"])) for i in range(0, len(products))]
340

341

342
                else:
343
                    continue
1✔
344

345
                '''
1✔
346
                Create new Expressions based on new entities
347
                '''
348
                for elem in _entities:
1✔
349
                    alter_identifier(IR, elem, elem["equation_parsed"], name)
1✔
350

351
                if len(_entities) == 1:
1✔
352
                    entities[index]["equation_parsed"] = {
×
353
                        "name": _entities[0]["name"],
354
                        "type": 'array',
355
                        "args": toLabelObjects(_entities[0]["labels"])
356
                    }
357

358
                elif len(_entities) == 2:
1✔
359
                    entities[index]["equation_parsed"] = {
×
360
                        "name": '+',
361
                        "type": 'operator',
362
                        "args": [
363
                            {"name": _entities[0]["name"], "type": 'array',
364
                             "args": toLabelObjects(_entities[0]["labels"])},
365
                            {"name": _entities[1]["name"], "type": 'array',
366
                             "args": toLabelObjects(_entities[1]["labels"])}
367
                        ]
368
                    }
369

370
                elif len(_entities) > 2:
1✔
371
                    tail = _entities[-2:]
1✔
372
                    rest = _entities[:-2]
1✔
373

374
                    already_reduced = initial = {
1✔
375
                        "name": "+",
376
                        "type": 'operator',
377
                        "args": [
378
                            {"name": tail[0]["name"], "type": 'array', "args": toLabelObjects(tail[0]["labels"])},
379
                            {"name": tail[1]["name"], "type": 'array', "args": toLabelObjects(tail[1]["labels"])}
380
                        ]
381
                    }
382

383
                    def reduce(already_reduced, rhs):
1✔
384
                        """
385
                        Reducer
386
                        :param already_reduced:
387
                        :param rhs:
388
                        :return:
389
                        """
390
                        return {
1✔
391
                            "name": "+",
392
                            "type": 'operator',
393
                            "args": [{"name": rhs["name"], "type": 'array', "args": toLabelObjects(rhs["labels"])},
394
                                     already_reduced]
395
                        }
396

397
                    for i, elem in enumerate(reversed(rest)):
1✔
398
                        already_reduced = reduce(already_reduced, elem)
1✔
399

400
                    entities[index]["equation_parsed"] = already_reduced
1✔
401

402
                spread_function_arguments(expression=model["entities"][entity_type][index]["equation_parsed"], model_name=model["name"], IR=IR)
1✔
403
                entities[index]["labels"] = []
1✔
404
                model["entities"][entity_type] += _entities
1✔
405

406
    return IR
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc