scipplan 0.1.0a0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scipplan/__init__.py +5 -0
- scipplan/config.py +152 -0
- scipplan/helpers.py +30 -0
- scipplan/parse_model.py +275 -0
- scipplan/plan_model.py +257 -0
- scipplan/scipplan.py +196 -0
- scipplan/translation/constants_infection_1.txt +17 -0
- scipplan/translation/constants_navigation_1.txt +2 -0
- scipplan/translation/constants_navigation_2.txt +2 -0
- scipplan/translation/constants_navigation_3.txt +1 -0
- scipplan/translation/constants_pandemic_1.txt +17 -0
- scipplan/translation/goals_infection_1.txt +3 -0
- scipplan/translation/goals_navigation_1.txt +2 -0
- scipplan/translation/goals_navigation_2.txt +2 -0
- scipplan/translation/goals_navigation_3.txt +2 -0
- scipplan/translation/goals_pandemic_1.txt +3 -0
- scipplan/translation/initials_infection_1.txt +5 -0
- scipplan/translation/initials_navigation_1.txt +4 -0
- scipplan/translation/initials_navigation_2.txt +4 -0
- scipplan/translation/initials_navigation_3.txt +4 -0
- scipplan/translation/initials_pandemic_1.txt +5 -0
- scipplan/translation/instantaneous_constraints_infection_1.txt +12 -0
- scipplan/translation/instantaneous_constraints_navigation_1.txt +9 -0
- scipplan/translation/instantaneous_constraints_navigation_2.txt +10 -0
- scipplan/translation/instantaneous_constraints_navigation_3.txt +11 -0
- scipplan/translation/instantaneous_constraints_pandemic_1.txt +12 -0
- scipplan/translation/pvariables_infection_1.txt +12 -0
- scipplan/translation/pvariables_navigation_1.txt +8 -0
- scipplan/translation/pvariables_navigation_2.txt +8 -0
- scipplan/translation/pvariables_navigation_3.txt +8 -0
- scipplan/translation/pvariables_pandemic_1.txt +12 -0
- scipplan/translation/reward_infection_1.txt +1 -0
- scipplan/translation/reward_navigation_1.txt +1 -0
- scipplan/translation/reward_navigation_2.txt +1 -0
- scipplan/translation/reward_navigation_3.txt +1 -0
- scipplan/translation/reward_pandemic_1.txt +1 -0
- scipplan/translation/temporal_constraints_infection_1.txt +1 -0
- scipplan/translation/temporal_constraints_navigation_1.txt +6 -0
- scipplan/translation/temporal_constraints_navigation_2.txt +6 -0
- scipplan/translation/temporal_constraints_navigation_3.txt +7 -0
- scipplan/translation/temporal_constraints_pandemic_1.txt +1 -0
- scipplan/translation/transitions_infection_1.txt +7 -0
- scipplan/translation/transitions_navigation_1.txt +4 -0
- scipplan/translation/transitions_navigation_2.txt +4 -0
- scipplan/translation/transitions_navigation_3.txt +4 -0
- scipplan/translation/transitions_pandemic_1.txt +7 -0
- scipplan/variables.py +91 -0
- scipplan/zero_crossing.py +28 -0
- scipplan-0.1.0a0.dist-info/LICENSE +19 -0
- scipplan-0.1.0a0.dist-info/METADATA +215 -0
- scipplan-0.1.0a0.dist-info/RECORD +54 -0
- scipplan-0.1.0a0.dist-info/WHEEL +6 -0
- scipplan-0.1.0a0.dist-info/entry_points.txt +2 -0
- scipplan-0.1.0a0.dist-info/top_level.txt +1 -0
scipplan/__init__.py
ADDED
scipplan/config.py
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from textwrap import dedent
|
5
|
+
import argparse
|
6
|
+
|
7
|
+
@dataclass
|
8
|
+
class Config:
|
9
|
+
"""
|
10
|
+
The Config class provides an interface to set the configuration for SCIPPlan.
|
11
|
+
Config can be set by creating an instance of the class adding the variables desired.
|
12
|
+
Alternatively, the `get_config` class method will return a config instance using variables set by the programs args (e.g. -D 'domain').
|
13
|
+
"""
|
14
|
+
domain: str
|
15
|
+
instance: int
|
16
|
+
horizon: int = field(default=None)
|
17
|
+
epsilon: float = field(default=None)
|
18
|
+
gap: float = field(default=None)
|
19
|
+
show_output: bool = False
|
20
|
+
save_sols: bool = False
|
21
|
+
bigM: float = 1000.0
|
22
|
+
dt_var: str = "Dt"
|
23
|
+
_defaults: dict[str, bool] = field(default_factory=dict, repr=False)
|
24
|
+
|
25
|
+
def __post_init__(self) -> None:
|
26
|
+
# Set all defaults to False and if the value is None then it will be updated to true
|
27
|
+
self._defaults = {
|
28
|
+
"domain": False,
|
29
|
+
"instance": False,
|
30
|
+
"horizon": False,
|
31
|
+
"epsilon": False,
|
32
|
+
"gap": False
|
33
|
+
}
|
34
|
+
if self.horizon is None:
|
35
|
+
print("Horizon is not provided, and is set to 1")
|
36
|
+
self.horizon = 1
|
37
|
+
self._defaults["horizon"] = True
|
38
|
+
|
39
|
+
if self.epsilon is None:
|
40
|
+
print("Epsilon is not provided, and is set to 0.1")
|
41
|
+
self.epsilon = 0.1
|
42
|
+
self._defaults["epsilon"] = True
|
43
|
+
|
44
|
+
if self.gap is None:
|
45
|
+
print("Gap is not provided, and is set to 10.0%")
|
46
|
+
self.gap = 0.1
|
47
|
+
self._defaults["gap"] = True
|
48
|
+
|
49
|
+
def __str__(self) -> str:
|
50
|
+
text = f"""
|
51
|
+
Configuration:
|
52
|
+
|
53
|
+
Display SCIP Output: {self.show_output}
|
54
|
+
Save Solutions: {self.show_output}
|
55
|
+
Dt Variable Name: {self.dt_var}
|
56
|
+
|
57
|
+
Domain (str): {self.domain}
|
58
|
+
Instance (int): {self.instance}
|
59
|
+
Horizon (int): {self.horizon} {'(default)' if self._defaults['horizon'] is True else ''}
|
60
|
+
Epsilon (float): {self.epsilon} {'(default)' if self._defaults['epsilon'] is True else ''}
|
61
|
+
Gap (float): {self.gap * 100}% {'(default)' if self._defaults['gap'] is True else ''}
|
62
|
+
BigM (float): {self.bigM}
|
63
|
+
"""
|
64
|
+
return dedent(text)
|
65
|
+
|
66
|
+
def increment_horizon(self, value: int = 1):
|
67
|
+
self._defaults["horizon"] = False
|
68
|
+
self.horizon += value
|
69
|
+
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def get_config(cls) -> Config:
|
73
|
+
parser = argparse.ArgumentParser(
|
74
|
+
prog="SCIPPlan"
|
75
|
+
)
|
76
|
+
parser.add_argument(
|
77
|
+
"-D",
|
78
|
+
"--domain",
|
79
|
+
required=True,
|
80
|
+
type=str,
|
81
|
+
help="This variable is the name of the domain (e.g. pandemic or navigation)"
|
82
|
+
)
|
83
|
+
parser.add_argument(
|
84
|
+
"-I",
|
85
|
+
"--instance",
|
86
|
+
required=True,
|
87
|
+
type=int,
|
88
|
+
help="This is the instance number of the domain (e.g. navigation has instances 1, 2 and 3)"
|
89
|
+
)
|
90
|
+
parser.add_argument(
|
91
|
+
"-H",
|
92
|
+
"--horizon",
|
93
|
+
required=False,
|
94
|
+
# default=1,
|
95
|
+
type=int,
|
96
|
+
help="The initial horizon. The solve method will initially begin with this horizon until it finds a feasible solution"
|
97
|
+
)
|
98
|
+
parser.add_argument(
|
99
|
+
"-E",
|
100
|
+
"--epsilon",
|
101
|
+
required=False,
|
102
|
+
# default=0.1,
|
103
|
+
type=float,
|
104
|
+
help="SCIPPlan iteratively checks solution for violations at each epsilon value"
|
105
|
+
)
|
106
|
+
parser.add_argument(
|
107
|
+
"-G",
|
108
|
+
"--gap",
|
109
|
+
required=False,
|
110
|
+
# default=0.1,
|
111
|
+
type=float,
|
112
|
+
help="SCIP will search for solution with an optimality gap by at least this value"
|
113
|
+
)
|
114
|
+
|
115
|
+
parser.add_argument(
|
116
|
+
"--bigM",
|
117
|
+
required=False,
|
118
|
+
default=1000.0,
|
119
|
+
type=float,
|
120
|
+
help="A large value which is used for some constraint encoding formulations, defaults to 1000.0 and can be changed as needed"
|
121
|
+
)
|
122
|
+
|
123
|
+
parser.add_argument(
|
124
|
+
"--dt-var",
|
125
|
+
required=False,
|
126
|
+
default="Dt",
|
127
|
+
type=str,
|
128
|
+
help="When writing the constraints, dt_var is the variable name for Dt, defaults to 'Dt' and can be changed based on users preference (e.g. 'dt')"
|
129
|
+
)
|
130
|
+
|
131
|
+
parser.add_argument(
|
132
|
+
"--show-output",
|
133
|
+
action="store_true",
|
134
|
+
default=False,
|
135
|
+
help="Include this flag to show output from SCIP"
|
136
|
+
)
|
137
|
+
|
138
|
+
parser.add_argument(
|
139
|
+
"--save-sols",
|
140
|
+
action="store_true",
|
141
|
+
default=False,
|
142
|
+
help="Include this flag to save the solutions from each of the scipplan iterations as well as constraints generated (note, only saves for horizon which has been solved)"
|
143
|
+
)
|
144
|
+
|
145
|
+
args = parser.parse_args()
|
146
|
+
|
147
|
+
return Config(**vars(args))
|
148
|
+
|
149
|
+
|
150
|
+
if __name__ == "__main__":
|
151
|
+
|
152
|
+
print(Config.get_config())
|
scipplan/helpers.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
import os
|
2
|
+
import csv
|
3
|
+
|
4
|
+
from typing import Generator
|
5
|
+
from .config import Config
|
6
|
+
|
7
|
+
class InfeasibilityError(Exception):
|
8
|
+
"""Raise this error when there are no valid solutions for the given horizon"""
|
9
|
+
|
10
|
+
def iterate(start: float, stop: float, step: float = 1) -> Generator[float, None, None]:
|
11
|
+
n = start
|
12
|
+
while n <= stop:
|
13
|
+
yield n
|
14
|
+
n += step
|
15
|
+
|
16
|
+
|
17
|
+
def list_accessible_files(directory):
|
18
|
+
try:
|
19
|
+
files = os.listdir(directory)
|
20
|
+
return files
|
21
|
+
except FileNotFoundError:
|
22
|
+
return [] # Return an empty list if the directory doesn't exist
|
23
|
+
|
24
|
+
def write_to_csv(file_name: str, data: list[dict], config: Config) -> None:
|
25
|
+
with open(f"{file_name}_{config.domain}_{config.instance}.csv", 'w', encoding='utf8', newline='') as output_file:
|
26
|
+
fc = csv.DictWriter(output_file,
|
27
|
+
fieldnames=data[0].keys(),
|
28
|
+
)
|
29
|
+
fc.writeheader()
|
30
|
+
fc.writerows(data)
|
scipplan/parse_model.py
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
import ast
|
2
|
+
import operator as op
|
3
|
+
from typing import Union
|
4
|
+
|
5
|
+
from .variables import Variable
|
6
|
+
|
7
|
+
from dataclasses import dataclass, field
|
8
|
+
from enum import Enum
|
9
|
+
from math import exp, log, sqrt, sin, cos, isclose
|
10
|
+
|
11
|
+
from pyscipopt.scip import Model, SumExpr
|
12
|
+
|
13
|
+
def switch_comparator(comparator):
|
14
|
+
if isinstance(comparator, ast.Eq): return ast.NotEq()
|
15
|
+
if isinstance(comparator, ast.NotEq): return ast.Eq()
|
16
|
+
if isinstance(comparator, ast.Lt): return ast.Gt()
|
17
|
+
if isinstance(comparator, ast.LtE): return ast.GtE()
|
18
|
+
if isinstance(comparator, ast.Gt): return ast.Lt()
|
19
|
+
if isinstance(comparator, ast.GtE): return ast.LtE()
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class Expressions:
|
23
|
+
expr_str: str
|
24
|
+
horizon: int
|
25
|
+
expressions: list
|
26
|
+
aux_vars: list
|
27
|
+
|
28
|
+
def add_expressions(self, *expressions):
|
29
|
+
for expression in expressions:
|
30
|
+
self.expressions.append(expression)
|
31
|
+
def __len__(self) -> int:
|
32
|
+
return len(self.expressions)
|
33
|
+
def __iter__(self):
|
34
|
+
for expr in self.expressions:
|
35
|
+
yield expr
|
36
|
+
|
37
|
+
|
38
|
+
class ParserType(Enum):
|
39
|
+
"""ParserType
|
40
|
+
"enum type CALCULATOR: Used to calculate using feastol
|
41
|
+
"enum type PARSER: Used to parse an expression and create the correct minlp constraints
|
42
|
+
"""
|
43
|
+
CALCULATOR = "calculator"
|
44
|
+
PARSER = "parser"
|
45
|
+
|
46
|
+
|
47
|
+
@dataclass
|
48
|
+
class EvalParams:
|
49
|
+
variables: dict = field(default_factory={})
|
50
|
+
functions: dict = field(default_factory={})
|
51
|
+
operators: dict = field(default_factory={})
|
52
|
+
parser_type: ParserType = ParserType.PARSER
|
53
|
+
rounds_vars: bool = False
|
54
|
+
model: Union[Model, None] = None
|
55
|
+
add_aux_vars: bool = False
|
56
|
+
|
57
|
+
@classmethod
|
58
|
+
def as_calculator(cls, variables: dict, functions: dict, operators: dict, model: Model, add_aux_vars: bool = False):
|
59
|
+
return EvalParams(variables, functions, operators, ParserType.CALCULATOR, False, model, add_aux_vars)
|
60
|
+
@classmethod
|
61
|
+
def as_parser(cls, variables: dict, functions: dict, operators: dict, model: Model, add_aux_vars: bool = False):
|
62
|
+
return EvalParams(variables, functions, operators, ParserType.PARSER, True, model, add_aux_vars)
|
63
|
+
|
64
|
+
|
65
|
+
class ParseModel:
|
66
|
+
|
67
|
+
def __init__(self, eval_params: EvalParams):
|
68
|
+
self.params = eval_params
|
69
|
+
self.variables = eval_params.variables
|
70
|
+
self.functions = eval_params.functions
|
71
|
+
|
72
|
+
self.model_feastol = eval_params.model.feastol()
|
73
|
+
self.variables["feastol"] = self.model_feastol
|
74
|
+
|
75
|
+
# Calculate operators (use evaluate operators for non calculations)
|
76
|
+
feastol = self.model_feastol if eval_params.parser_type is ParserType.CALCULATOR else 0
|
77
|
+
self.operators = {
|
78
|
+
ast.Add: op.add,
|
79
|
+
ast.Sub: op.sub,
|
80
|
+
ast.Mult: op.mul,
|
81
|
+
ast.Div: op.truediv,
|
82
|
+
ast.Pow: op.pow,
|
83
|
+
|
84
|
+
ast.UAdd: op.pos,
|
85
|
+
ast.USub: op.neg,
|
86
|
+
|
87
|
+
ast.Not: op.not_,
|
88
|
+
ast.Eq: lambda x, y: op.eq(x, y) if self.params.parser_type is ParserType.PARSER else isclose(x, y, rel_tol=feastol),
|
89
|
+
# ast.Eq: op.eq,
|
90
|
+
ast.NotEq: lambda x, y: op.ne(x, y) if self.params.parser_type is ParserType.PARSER else not isclose(x, y, rel_tol=feastol),
|
91
|
+
# ast.NotEq: op.ne,
|
92
|
+
ast.Lt: lambda x, y: op.lt(x, y + feastol),
|
93
|
+
ast.LtE: lambda x, y: op.le(x, y + feastol),
|
94
|
+
ast.Gt: lambda x, y: op.gt(x + feastol, y),
|
95
|
+
ast.GtE: lambda x, y: op.ge(x + feastol, y),
|
96
|
+
} | eval_params.operators
|
97
|
+
|
98
|
+
self.expressions = Expressions("", -1, [], [])
|
99
|
+
|
100
|
+
|
101
|
+
def evaluate(self, eqtn, aux_vars: Union[list, None] = None, expr_name: Union[str, None] = None, horizon: int = -1):
|
102
|
+
|
103
|
+
if isinstance(eqtn, str):
|
104
|
+
# Reset self.expressions for every new equation evaluated
|
105
|
+
self.expressions = Expressions(
|
106
|
+
"" if expr_name is None else expr_name,
|
107
|
+
horizon,
|
108
|
+
[],
|
109
|
+
[] if aux_vars is None else aux_vars
|
110
|
+
)
|
111
|
+
|
112
|
+
return self.evaluate(ast.parse(eqtn))
|
113
|
+
|
114
|
+
if isinstance(eqtn, ast.Module):
|
115
|
+
self.expressions.add_expressions(*(self.evaluate(expr) for expr in eqtn.body))
|
116
|
+
return self.expressions
|
117
|
+
|
118
|
+
if isinstance(eqtn, ast.Expr):
|
119
|
+
return self.evaluate(eqtn.value)
|
120
|
+
|
121
|
+
if isinstance(eqtn, ast.BoolOp):
|
122
|
+
# And expressions are decomposed into their individual segments and evaluated separately
|
123
|
+
if isinstance(eqtn.op, ast.And):
|
124
|
+
# Evaluate and add all expressions except the first to the expressions object
|
125
|
+
self.expressions.add_expressions(*(self.evaluate(expr) for expr in eqtn.values[1:]))
|
126
|
+
# Evaluate and return the first expression
|
127
|
+
return self.evaluate(eqtn.values[0])
|
128
|
+
|
129
|
+
if isinstance(eqtn.op, ast.Or):
|
130
|
+
if self.params.parser_type is ParserType.CALCULATOR:
|
131
|
+
results = [self.evaluate(expr) for expr in eqtn.values]
|
132
|
+
return any(results)
|
133
|
+
else:
|
134
|
+
aux_vars = self.expressions.aux_vars
|
135
|
+
if self.params.add_aux_vars is True and len(aux_vars) > 0:
|
136
|
+
raise Exception("aux_vars should be empty to add new aux vars")
|
137
|
+
|
138
|
+
for idx, expr in enumerate(eqtn.values):
|
139
|
+
if isinstance(expr, ast.Compare):
|
140
|
+
|
141
|
+
if self.params.add_aux_vars is True:
|
142
|
+
aux_var = Variable.create_var(
|
143
|
+
model=self.params.model,
|
144
|
+
name=f"Aux_{idx}_{self.expressions.expr_str}",
|
145
|
+
vtype="auxiliary_boolean",
|
146
|
+
time=self.expressions.horizon,
|
147
|
+
const_vals={}
|
148
|
+
)
|
149
|
+
else:
|
150
|
+
aux_var: Variable = self.expressions.aux_vars[idx]
|
151
|
+
|
152
|
+
aux_vars.append(aux_var)
|
153
|
+
self.variables[aux_var.name] = aux_var.model_var
|
154
|
+
|
155
|
+
expr.left = ast.BinOp(
|
156
|
+
left=expr.left,
|
157
|
+
op=(ast.Add() if isinstance(expr.ops[0], (ast.Gt, ast.GtE)) else ast.Sub()),
|
158
|
+
right=ast.parse(f"bigM * {aux_var.name}").body[0].value
|
159
|
+
)
|
160
|
+
self.expressions.add_expressions(self.evaluate(expr))
|
161
|
+
|
162
|
+
expr.ops[0] = switch_comparator(expr.ops[0])
|
163
|
+
|
164
|
+
expr.comparators[0] = ast.BinOp(
|
165
|
+
left=expr.comparators[0],
|
166
|
+
op=(ast.Add() if isinstance(expr.ops[0], (ast.Gt, ast.GtE)) else ast.Sub()),
|
167
|
+
right=ast.parse(f"feastol - bigM").body[0].value
|
168
|
+
)
|
169
|
+
|
170
|
+
self.expressions.add_expressions(self.evaluate(expr))
|
171
|
+
else:
|
172
|
+
raise Exception("or expressions may only be made up of inequalities")
|
173
|
+
lhs = SumExpr()
|
174
|
+
for var in aux_vars:
|
175
|
+
lhs += var.model_var
|
176
|
+
return lhs <= len(aux_vars) - 1
|
177
|
+
|
178
|
+
if isinstance(eqtn, ast.IfExp):
|
179
|
+
raise Exception("Can't use if else")
|
180
|
+
|
181
|
+
if isinstance(eqtn, ast.Compare):
|
182
|
+
if len(eqtn.comparators) != 1:
|
183
|
+
raise Exception("Too many comparator operators, please don't use more than 1 per equation")
|
184
|
+
|
185
|
+
left = self.evaluate(eqtn.left)
|
186
|
+
right = self.evaluate(eqtn.comparators[0])
|
187
|
+
comp_type = type(eqtn.ops[0])
|
188
|
+
comparator = self.operators[comp_type]
|
189
|
+
|
190
|
+
return comparator(left, right)
|
191
|
+
|
192
|
+
if isinstance(eqtn, ast.Name):
|
193
|
+
return self.variables[eqtn.id]
|
194
|
+
|
195
|
+
if isinstance(eqtn, ast.BinOp):
|
196
|
+
left = self.evaluate(eqtn.left)
|
197
|
+
right = self.evaluate(eqtn.right)
|
198
|
+
operator = type(eqtn.op)
|
199
|
+
return self.operators[operator](left, right)
|
200
|
+
|
201
|
+
if isinstance(eqtn, ast.Call):
|
202
|
+
if not isinstance(eqtn.func, ast.Name):
|
203
|
+
raise Exception("Attributes such as main.secondary() functions are not allowed")
|
204
|
+
|
205
|
+
func_name = eqtn.func.id
|
206
|
+
args = (self.evaluate(arg) for arg in eqtn.args)
|
207
|
+
func = self.functions[func_name]
|
208
|
+
return func(*args)
|
209
|
+
|
210
|
+
if isinstance(eqtn, ast.Constant):
|
211
|
+
return eqtn.value
|
212
|
+
|
213
|
+
if isinstance(eqtn, ast.UnaryOp):
|
214
|
+
op_type = type(eqtn.op)
|
215
|
+
operator = self.operators[op_type]
|
216
|
+
return operator(self.evaluate(eqtn.operand))
|
217
|
+
|
218
|
+
raise Exception("Unknown ast type")
|
219
|
+
|
220
|
+
|
221
|
+
functions = {
|
222
|
+
'exp': exp,
|
223
|
+
'log': log,
|
224
|
+
'sqrt': sqrt,
|
225
|
+
'sin': sin,
|
226
|
+
'cos': cos,
|
227
|
+
}
|
228
|
+
variables = {
|
229
|
+
'c': 0.15,
|
230
|
+
'Removed': 634.9055920077616,
|
231
|
+
'Removed_dash': 1811.3128718631383,
|
232
|
+
'b2': 0.1,
|
233
|
+
'Makespan': 180.0,
|
234
|
+
'Susceptible': 1218.7473517769768,
|
235
|
+
'Susceptible_dash': 87.1003300803328,
|
236
|
+
'DurationMax': 90.0,
|
237
|
+
'N': 2000.0,
|
238
|
+
'Dt': 0.0,
|
239
|
+
'Infected': 146.34705621526638,
|
240
|
+
'Infected_dash': 101.58679805652946,
|
241
|
+
'TotalTime': 90.87927389935685,
|
242
|
+
'TotalTime_dash': 136.34829117841537,
|
243
|
+
'b': 0.19999999999999973,
|
244
|
+
'Epsilon': 1.0,
|
245
|
+
'Percentage': 0.8,
|
246
|
+
'DurationMin': 30.0,
|
247
|
+
'b1': 0.2,
|
248
|
+
'Duration': 100.0,
|
249
|
+
'D': 150.0,
|
250
|
+
'InitialInfected': 50.0,
|
251
|
+
'Lockdown': 0.0
|
252
|
+
}
|
253
|
+
|
254
|
+
constraint = "Infected * exp((b / (b - c)) * log(1 + (Infected / Susceptible))) * exp(-1.0 * (b / (b - c)) * log(1 + (Infected / Susceptible) * exp((b - c) * Dt))) * exp((b - c) * Dt) <= D"
|
255
|
+
|
256
|
+
|
257
|
+
|
258
|
+
if __name__ == "__main__":
|
259
|
+
params = EvalParams.as_calculator(variables, functions, {})
|
260
|
+
calc = ParseModel(params).evaluate
|
261
|
+
|
262
|
+
print(calc(constraint))
|
263
|
+
eqtns = """
|
264
|
+
1 + 2
|
265
|
+
1 + 5 < 5
|
266
|
+
"""
|
267
|
+
print(calc(eqtns))
|
268
|
+
# print(calc(
|
269
|
+
# """
|
270
|
+
# a >= b if a + b + c >= 5 else a - b - c == 10
|
271
|
+
# """))
|
272
|
+
# print(calc(
|
273
|
+
# """
|
274
|
+
# a + b + c >= 5 or a - b - c == 10
|
275
|
+
# """))
|