cppmodel 0.9.0__py3-none-manylinux_2_28_aarch64.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.
- cppmodel/__init__.py +23 -0
- cppmodel/_native.py +99 -0
- cppmodel/_simulation.py +391 -0
- cppmodel/lib/libcppmodel.so.1 +0 -0
- cppmodel-0.9.0.dist-info/METADATA +127 -0
- cppmodel-0.9.0.dist-info/RECORD +14 -0
- cppmodel-0.9.0.dist-info/WHEEL +4 -0
- cppmodel-0.9.0.dist-info/licenses/LICENSE +9 -0
- cppmodel-0.9.0.dist-info/licenses/licenses/IXWebSocket-LICENSE.txt +29 -0
- cppmodel-0.9.0.dist-info/licenses/licenses/cpp-httplib-LICENSE.txt +22 -0
- cppmodel-0.9.0.dist-info/licenses/licenses/jwt-cpp-LICENSE.txt +21 -0
- cppmodel-0.9.0.dist-info/licenses/licenses/nlohmann-json-LICENSE.txt +21 -0
- cppmodel-0.9.0.dist-info/licenses/licenses/openssl-LICENSE.txt +16 -0
- cppmodel-0.9.0.dist-info/licenses/licenses/picojson-LICENSE.txt +25 -0
cppmodel/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""CppModel for Python: write CppModel simulations in Python and submit them to your workspace."""
|
|
2
|
+
from ._native import API_VERSION, lib as _lib, library_path
|
|
3
|
+
from ._simulation import CppModelError, Inputs, Outputs, Parameters, Simulation, Status
|
|
4
|
+
|
|
5
|
+
__version__ = "0.9.0"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def library_version():
|
|
9
|
+
"""The release version of the loaded CppModel library, e.g. "0.8.0"."""
|
|
10
|
+
return _lib.cm_version().decode("utf-8")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"API_VERSION",
|
|
15
|
+
"CppModelError",
|
|
16
|
+
"Inputs",
|
|
17
|
+
"Outputs",
|
|
18
|
+
"Parameters",
|
|
19
|
+
"Simulation",
|
|
20
|
+
"Status",
|
|
21
|
+
"library_path",
|
|
22
|
+
"library_version",
|
|
23
|
+
]
|
cppmodel/_native.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""ctypes declarations of the CppModel C API (include/cppmodel.h) and loading of the shared library."""
|
|
2
|
+
import ctypes
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from ctypes import POINTER, c_char_p, c_double, c_int, c_size_t, c_uint64, c_void_p
|
|
6
|
+
|
|
7
|
+
# The major C API version these bindings are written against; the library must report the same.
|
|
8
|
+
API_VERSION = 1
|
|
9
|
+
|
|
10
|
+
# cm_callback: cm_status (*)(cm_simulation simulation, uint64_t time_ms, void *user_data)
|
|
11
|
+
CALLBACK = ctypes.CFUNCTYPE(c_int, c_void_p, c_uint64, c_void_p)
|
|
12
|
+
|
|
13
|
+
_LIBRARY_NAMES = {"win32": "cppmodel.dll", "darwin": "libcppmodel.dylib"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def library_path():
|
|
17
|
+
"""The shared library to load: $CPPMODEL_LIBRARY, else the one bundled in the package."""
|
|
18
|
+
override = os.environ.get("CPPMODEL_LIBRARY")
|
|
19
|
+
if override:
|
|
20
|
+
return override
|
|
21
|
+
name = _LIBRARY_NAMES.get(sys.platform, "libcppmodel.so.1")
|
|
22
|
+
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "lib", name)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_handle = c_void_p
|
|
26
|
+
_out_handle = POINTER(c_void_p)
|
|
27
|
+
_out_text = POINTER(c_char_p)
|
|
28
|
+
_out_int = POINTER(c_int)
|
|
29
|
+
_out_size = POINTER(c_size_t)
|
|
30
|
+
_out_double = POINTER(c_double)
|
|
31
|
+
_out_uint64 = POINTER(c_uint64)
|
|
32
|
+
|
|
33
|
+
# Only the functions the bindings use. Persistent simulations are C/C++ only, and
|
|
34
|
+
# cm_require_non_interactive is left out because it exits the process.
|
|
35
|
+
_SIGNATURES = {
|
|
36
|
+
"cm_api_version": (c_int, []),
|
|
37
|
+
"cm_version": (c_char_p, []),
|
|
38
|
+
"cm_last_error_message": (c_char_p, []),
|
|
39
|
+
"cm_simulation_create": (c_int, [c_char_p, c_uint64, c_uint64, _out_handle]),
|
|
40
|
+
"cm_simulation_destroy": (c_int, [_handle]),
|
|
41
|
+
"cm_simulation_set_name": (c_int, [_handle, c_char_p]),
|
|
42
|
+
"cm_simulation_get_name": (c_int, [_handle, _out_text]),
|
|
43
|
+
"cm_simulation_set_timing": (c_int, [_handle, c_uint64, c_uint64]),
|
|
44
|
+
"cm_simulation_get_timing": (c_int, [_handle, _out_uint64, _out_uint64]),
|
|
45
|
+
"cm_simulation_set_run_offline": (c_int, [_handle, c_int]),
|
|
46
|
+
"cm_simulation_set_fingerprint_file": (c_int, [_handle, c_char_p]),
|
|
47
|
+
"cm_simulation_set_init_callback": (c_int, [_handle, CALLBACK, c_void_p]),
|
|
48
|
+
"cm_simulation_set_step_callback": (c_int, [_handle, CALLBACK, c_void_p]),
|
|
49
|
+
"cm_simulation_set_finalize_callback": (c_int, [_handle, CALLBACK, c_void_p]),
|
|
50
|
+
"cm_simulation_run": (c_int, [_handle]),
|
|
51
|
+
"cm_simulation_reset_data": (c_int, [_handle]),
|
|
52
|
+
"cm_simulation_get_result": (c_int, [_handle, _out_int]),
|
|
53
|
+
"cm_simulation_set_result": (c_int, [_handle, c_int]),
|
|
54
|
+
"cm_simulation_results_json": (c_int, [_handle, _out_text]),
|
|
55
|
+
"cm_simulation_apply_inputs_json": (c_int, [_handle, c_char_p]),
|
|
56
|
+
"cm_simulation_inputs": (c_int, [_handle, _out_handle]),
|
|
57
|
+
"cm_simulation_outputs": (c_int, [_handle, _out_handle]),
|
|
58
|
+
"cm_input_get": (c_int, [_handle, c_char_p, c_double, _out_double]),
|
|
59
|
+
"cm_inputs_load_json": (c_int, [_handle, c_char_p]),
|
|
60
|
+
"cm_inputs_contains": (c_int, [_handle, c_char_p, _out_int]),
|
|
61
|
+
"cm_inputs_count": (c_int, [_handle, _out_size]),
|
|
62
|
+
"cm_inputs_key_at": (c_int, [_handle, c_size_t, _out_text]),
|
|
63
|
+
"cm_output_set": (c_int, [_handle, c_char_p, c_double]),
|
|
64
|
+
"cm_output_get": (c_int, [_handle, c_char_p, _out_double]),
|
|
65
|
+
"cm_outputs_contains": (c_int, [_handle, c_char_p, _out_int]),
|
|
66
|
+
"cm_outputs_count": (c_int, [_handle, _out_size]),
|
|
67
|
+
"cm_outputs_key_at": (c_int, [_handle, c_size_t, _out_text]),
|
|
68
|
+
"cm_param_set_double": (c_int, [_handle, c_char_p, c_double]),
|
|
69
|
+
"cm_param_set_json": (c_int, [_handle, c_char_p, c_char_p]),
|
|
70
|
+
"cm_param_get_json": (c_int, [_handle, c_char_p, _out_text]),
|
|
71
|
+
"cm_param_get_or_json": (c_int, [_handle, c_char_p, c_char_p, _out_text]),
|
|
72
|
+
"cm_param_contains": (c_int, [_handle, c_char_p, _out_int]),
|
|
73
|
+
"cm_param_count": (c_int, [_handle, _out_size]),
|
|
74
|
+
"cm_param_key_at": (c_int, [_handle, c_size_t, _out_text]),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _load():
|
|
79
|
+
path = library_path()
|
|
80
|
+
try:
|
|
81
|
+
library = ctypes.CDLL(path)
|
|
82
|
+
except OSError as error:
|
|
83
|
+
raise ImportError(
|
|
84
|
+
f"cppmodel: cannot load the CppModel library {path}: {error}. "
|
|
85
|
+
"Reinstall the cppmodel package for this platform, or set CPPMODEL_LIBRARY."
|
|
86
|
+
) from error
|
|
87
|
+
for name, (restype, argtypes) in _SIGNATURES.items():
|
|
88
|
+
function = getattr(library, name)
|
|
89
|
+
function.restype = restype
|
|
90
|
+
function.argtypes = argtypes
|
|
91
|
+
if library.cm_api_version() != API_VERSION:
|
|
92
|
+
raise ImportError(
|
|
93
|
+
f"cppmodel: {path} implements C API version {library.cm_api_version()}, "
|
|
94
|
+
f"these bindings need version {API_VERSION}"
|
|
95
|
+
)
|
|
96
|
+
return library
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
lib = _load()
|
cppmodel/_simulation.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""A Pythonic wrapper over the C API: Simulation with its inputs, outputs and parameters."""
|
|
2
|
+
import enum
|
|
3
|
+
import inspect
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from ctypes import byref, c_char_p, c_double, c_int, c_size_t, c_uint64, c_void_p
|
|
8
|
+
|
|
9
|
+
from ._native import CALLBACK, lib
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Status(enum.IntEnum):
|
|
13
|
+
"""cm_status codes of the C API."""
|
|
14
|
+
|
|
15
|
+
OK = 0
|
|
16
|
+
INVALID_ARGUMENT = 1
|
|
17
|
+
NOT_FOUND = 2
|
|
18
|
+
TYPE_MISMATCH = 3
|
|
19
|
+
INVALID_JSON = 4
|
|
20
|
+
CALLBACK = 5
|
|
21
|
+
INTERNAL = 6
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CppModelError(Exception):
|
|
25
|
+
"""A CppModel library call failed. `status` is the Status it returned."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, status, message):
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
try:
|
|
30
|
+
self.status = Status(status)
|
|
31
|
+
except ValueError:
|
|
32
|
+
self.status = status
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _check(status):
|
|
36
|
+
if status != Status.OK:
|
|
37
|
+
raise CppModelError(status, lib.cm_last_error_message().decode("utf-8", "replace"))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _encode(text):
|
|
41
|
+
return text.encode("utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _decode(raw):
|
|
45
|
+
return raw.decode("utf-8") if raw is not None else ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _keys(count_function, key_function, handle):
|
|
49
|
+
count = c_size_t()
|
|
50
|
+
_check(count_function(handle, byref(count)))
|
|
51
|
+
keys = []
|
|
52
|
+
for index in range(count.value):
|
|
53
|
+
key = c_char_p()
|
|
54
|
+
_check(key_function(handle, index, byref(key)))
|
|
55
|
+
keys.append(_decode(key.value))
|
|
56
|
+
return keys
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Inputs:
|
|
60
|
+
"""The simulation's inputs: time series whose value is read at the current step."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, simulation):
|
|
63
|
+
self._simulation = simulation
|
|
64
|
+
handle = c_void_p()
|
|
65
|
+
_check(lib.cm_simulation_inputs(simulation._live(), byref(handle)))
|
|
66
|
+
self._handle = handle
|
|
67
|
+
|
|
68
|
+
def _live(self):
|
|
69
|
+
self._simulation._live()
|
|
70
|
+
return self._handle
|
|
71
|
+
|
|
72
|
+
def get(self, name, fallback=0.0):
|
|
73
|
+
"""The input's value at the current step, or `fallback` (then recorded as its value) when
|
|
74
|
+
nothing was loaded for it."""
|
|
75
|
+
value = c_double()
|
|
76
|
+
_check(lib.cm_input_get(self._live(), _encode(name), float(fallback), byref(value)))
|
|
77
|
+
return value.value
|
|
78
|
+
|
|
79
|
+
def __getitem__(self, name):
|
|
80
|
+
return self.get(name)
|
|
81
|
+
|
|
82
|
+
def load(self, series):
|
|
83
|
+
"""Loads series [{"label": name, "x": [ms, ...], "y": [value, ...]}, ...]."""
|
|
84
|
+
_check(lib.cm_inputs_load_json(self._live(), _encode(json.dumps(series))))
|
|
85
|
+
|
|
86
|
+
def __contains__(self, name):
|
|
87
|
+
found = c_int()
|
|
88
|
+
_check(lib.cm_inputs_contains(self._live(), _encode(name), byref(found)))
|
|
89
|
+
return bool(found.value)
|
|
90
|
+
|
|
91
|
+
def keys(self):
|
|
92
|
+
"""The inputs read or loaded so far, in name order."""
|
|
93
|
+
return _keys(lib.cm_inputs_count, lib.cm_inputs_key_at, self._live())
|
|
94
|
+
|
|
95
|
+
def __iter__(self):
|
|
96
|
+
return iter(self.keys())
|
|
97
|
+
|
|
98
|
+
def __len__(self):
|
|
99
|
+
count = c_size_t()
|
|
100
|
+
_check(lib.cm_inputs_count(self._live(), byref(count)))
|
|
101
|
+
return count.value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class Outputs:
|
|
105
|
+
"""The simulation's outputs. An output holds its value until set again and is recorded at the
|
|
106
|
+
end of every step. Values are numbers; bools are stored as 1 and 0."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, simulation):
|
|
109
|
+
self._simulation = simulation
|
|
110
|
+
handle = c_void_p()
|
|
111
|
+
_check(lib.cm_simulation_outputs(simulation._live(), byref(handle)))
|
|
112
|
+
self._handle = handle
|
|
113
|
+
|
|
114
|
+
def _live(self):
|
|
115
|
+
self._simulation._live()
|
|
116
|
+
return self._handle
|
|
117
|
+
|
|
118
|
+
def __setitem__(self, name, value):
|
|
119
|
+
_check(lib.cm_output_set(self._live(), _encode(name), float(value)))
|
|
120
|
+
|
|
121
|
+
def __getitem__(self, name):
|
|
122
|
+
"""The output's current value; reading an output that was never set creates it with 0."""
|
|
123
|
+
value = c_double()
|
|
124
|
+
_check(lib.cm_output_get(self._live(), _encode(name), byref(value)))
|
|
125
|
+
return value.value
|
|
126
|
+
|
|
127
|
+
def __contains__(self, name):
|
|
128
|
+
found = c_int()
|
|
129
|
+
_check(lib.cm_outputs_contains(self._live(), _encode(name), byref(found)))
|
|
130
|
+
return bool(found.value)
|
|
131
|
+
|
|
132
|
+
def keys(self):
|
|
133
|
+
return _keys(lib.cm_outputs_count, lib.cm_outputs_key_at, self._live())
|
|
134
|
+
|
|
135
|
+
def __iter__(self):
|
|
136
|
+
return iter(self.keys())
|
|
137
|
+
|
|
138
|
+
def __len__(self):
|
|
139
|
+
count = c_size_t()
|
|
140
|
+
_check(lib.cm_outputs_count(self._live(), byref(count)))
|
|
141
|
+
return count.value
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Parameters:
|
|
145
|
+
"""The simulation's parameters. They keep their JSON type: numbers, bools, strings, lists,
|
|
146
|
+
dicts or None."""
|
|
147
|
+
|
|
148
|
+
def __init__(self, simulation):
|
|
149
|
+
self._simulation = simulation
|
|
150
|
+
|
|
151
|
+
def get(self, name, fallback):
|
|
152
|
+
"""The parameter's value, storing `fallback` first when it isn't set. A fetched value
|
|
153
|
+
replaces a stored fallback; a value set with parameters[name] = ... is never replaced."""
|
|
154
|
+
out = c_char_p()
|
|
155
|
+
fallback_json = _encode(json.dumps(fallback, allow_nan=False))
|
|
156
|
+
_check(lib.cm_param_get_or_json(self._simulation._live(), _encode(name), fallback_json, byref(out)))
|
|
157
|
+
return json.loads(_decode(out.value))
|
|
158
|
+
|
|
159
|
+
def __getitem__(self, name):
|
|
160
|
+
out = c_char_p()
|
|
161
|
+
status = lib.cm_param_get_json(self._simulation._live(), _encode(name), byref(out))
|
|
162
|
+
if status == Status.NOT_FOUND:
|
|
163
|
+
raise KeyError(name)
|
|
164
|
+
_check(status)
|
|
165
|
+
return json.loads(_decode(out.value))
|
|
166
|
+
|
|
167
|
+
def __setitem__(self, name, value):
|
|
168
|
+
handle = self._simulation._live()
|
|
169
|
+
if isinstance(value, float):
|
|
170
|
+
_check(lib.cm_param_set_double(handle, _encode(name), value))
|
|
171
|
+
else:
|
|
172
|
+
_check(lib.cm_param_set_json(handle, _encode(name), _encode(json.dumps(value, allow_nan=False))))
|
|
173
|
+
|
|
174
|
+
def __contains__(self, name):
|
|
175
|
+
found = c_int()
|
|
176
|
+
_check(lib.cm_param_contains(self._simulation._live(), _encode(name), byref(found)))
|
|
177
|
+
return bool(found.value)
|
|
178
|
+
|
|
179
|
+
def keys(self):
|
|
180
|
+
return _keys(lib.cm_param_count, lib.cm_param_key_at, self._simulation._live())
|
|
181
|
+
|
|
182
|
+
def __iter__(self):
|
|
183
|
+
return iter(self.keys())
|
|
184
|
+
|
|
185
|
+
def __len__(self):
|
|
186
|
+
count = c_size_t()
|
|
187
|
+
_check(lib.cm_param_count(self._simulation._live(), byref(count)))
|
|
188
|
+
return count.value
|
|
189
|
+
|
|
190
|
+
def to_dict(self):
|
|
191
|
+
return {name: self[name] for name in self.keys()}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Simulation:
|
|
195
|
+
"""A CppModel simulation.
|
|
196
|
+
|
|
197
|
+
Either subclass it and override init(), step(time_ms) and finalize(), or pass those as
|
|
198
|
+
functions taking the simulation as their first argument:
|
|
199
|
+
|
|
200
|
+
sim = Simulation("Heater", total_time_ms=60000, step_ms=100, step=lambda sim, t: ...)
|
|
201
|
+
|
|
202
|
+
run() fetches the pending inputs and parameters from the workspace, calls init(), then step()
|
|
203
|
+
for every step from 0 to total_time_ms, then finalize(), and submits the execution. An
|
|
204
|
+
exception raised in init(), step() or finalize() aborts the run (nothing is submitted) and is
|
|
205
|
+
re-raised by run().
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
def __init__(self, name, total_time_ms, step_ms, *, offline=False, init=None, step=None, finalize=None):
|
|
209
|
+
handle = c_void_p()
|
|
210
|
+
_check(lib.cm_simulation_create(_encode(name), total_time_ms, step_ms, byref(handle)))
|
|
211
|
+
self._handle = handle
|
|
212
|
+
self._error = None
|
|
213
|
+
self._functions = {"init": init, "step": step, "finalize": finalize}
|
|
214
|
+
# ctypes frees a callback when its CFUNCTYPE object is collected, so keep them with the handle.
|
|
215
|
+
self._callbacks = (
|
|
216
|
+
CALLBACK(lambda _sim, _time, _user: self._invoke(self.init)),
|
|
217
|
+
CALLBACK(lambda _sim, time_ms, _user: self._invoke(self.step, time_ms)),
|
|
218
|
+
CALLBACK(lambda _sim, _time, _user: self._invoke(self.finalize)),
|
|
219
|
+
)
|
|
220
|
+
_check(lib.cm_simulation_set_init_callback(handle, self._callbacks[0], None))
|
|
221
|
+
_check(lib.cm_simulation_set_step_callback(handle, self._callbacks[1], None))
|
|
222
|
+
_check(lib.cm_simulation_set_finalize_callback(handle, self._callbacks[2], None))
|
|
223
|
+
self._offline = False
|
|
224
|
+
self.offline = offline
|
|
225
|
+
self._fingerprint_file = None
|
|
226
|
+
default = self._default_fingerprint_file()
|
|
227
|
+
if default is not None and lib.cm_simulation_set_fingerprint_file(handle, _encode(default)) == Status.OK:
|
|
228
|
+
self._fingerprint_file = default
|
|
229
|
+
self.inputs = Inputs(self)
|
|
230
|
+
self.outputs = Outputs(self)
|
|
231
|
+
self.parameters = Parameters(self)
|
|
232
|
+
|
|
233
|
+
# ── Overridables ─────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
def init(self):
|
|
236
|
+
"""Called once before the first step."""
|
|
237
|
+
if self._functions["init"] is not None:
|
|
238
|
+
self._functions["init"](self)
|
|
239
|
+
|
|
240
|
+
def step(self, time_ms):
|
|
241
|
+
"""Called every step with the simulation time in milliseconds."""
|
|
242
|
+
if self._functions["step"] is not None:
|
|
243
|
+
self._functions["step"](self, time_ms)
|
|
244
|
+
|
|
245
|
+
def finalize(self):
|
|
246
|
+
"""Called once after the last step."""
|
|
247
|
+
if self._functions["finalize"] is not None:
|
|
248
|
+
self._functions["finalize"](self)
|
|
249
|
+
|
|
250
|
+
# ── Running ──────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
def _invoke(self, method, *args):
|
|
253
|
+
try:
|
|
254
|
+
method(*args)
|
|
255
|
+
return Status.OK
|
|
256
|
+
except BaseException as error: # also KeyboardInterrupt: Ctrl-C aborts the run between steps
|
|
257
|
+
self._error = error
|
|
258
|
+
return Status.CALLBACK
|
|
259
|
+
|
|
260
|
+
def run(self):
|
|
261
|
+
"""Runs the simulation and returns its result: 0 when every step passed, otherwise the
|
|
262
|
+
CppModel.Step of the first failing step (or -1)."""
|
|
263
|
+
self._error = None
|
|
264
|
+
status = lib.cm_simulation_run(self._live())
|
|
265
|
+
if self._error is not None:
|
|
266
|
+
error, self._error = self._error, None
|
|
267
|
+
raise error
|
|
268
|
+
_check(status)
|
|
269
|
+
return self.result
|
|
270
|
+
|
|
271
|
+
def reset_data(self):
|
|
272
|
+
"""Deletes the simulation on the server (same as deleting it in the UI). Call before run()."""
|
|
273
|
+
_check(lib.cm_simulation_reset_data(self._live()))
|
|
274
|
+
|
|
275
|
+
def results(self):
|
|
276
|
+
"""The execution as it is (or would be) submitted: {"inputs", "results", "parameters", ...}."""
|
|
277
|
+
out = c_char_p()
|
|
278
|
+
_check(lib.cm_simulation_results_json(self._live(), byref(out)))
|
|
279
|
+
return json.loads(_decode(out.value))
|
|
280
|
+
|
|
281
|
+
def apply_inputs(self, response):
|
|
282
|
+
"""Applies a GET /simulations/{id}/inputs response ({"inputs": [...], "parameters": {...}}).
|
|
283
|
+
Inputs and parameters already set are kept."""
|
|
284
|
+
_check(lib.cm_simulation_apply_inputs_json(self._live(), _encode(json.dumps(response))))
|
|
285
|
+
|
|
286
|
+
# ── Properties ───────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def name(self):
|
|
290
|
+
out = c_char_p()
|
|
291
|
+
_check(lib.cm_simulation_get_name(self._live(), byref(out)))
|
|
292
|
+
return _decode(out.value)
|
|
293
|
+
|
|
294
|
+
@name.setter
|
|
295
|
+
def name(self, value):
|
|
296
|
+
_check(lib.cm_simulation_set_name(self._live(), _encode(value)))
|
|
297
|
+
|
|
298
|
+
def _timing(self):
|
|
299
|
+
total, step = c_uint64(), c_uint64()
|
|
300
|
+
_check(lib.cm_simulation_get_timing(self._live(), byref(total), byref(step)))
|
|
301
|
+
return total.value, step.value
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def total_time_ms(self):
|
|
305
|
+
return self._timing()[0]
|
|
306
|
+
|
|
307
|
+
@total_time_ms.setter
|
|
308
|
+
def total_time_ms(self, value):
|
|
309
|
+
_check(lib.cm_simulation_set_timing(self._live(), value, self.step_ms))
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def step_ms(self):
|
|
313
|
+
return self._timing()[1]
|
|
314
|
+
|
|
315
|
+
@step_ms.setter
|
|
316
|
+
def step_ms(self, value):
|
|
317
|
+
_check(lib.cm_simulation_set_timing(self._live(), self.total_time_ms, value))
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def offline(self):
|
|
321
|
+
"""When true, run() doesn't reach the workspace: nothing is fetched or submitted."""
|
|
322
|
+
return self._offline
|
|
323
|
+
|
|
324
|
+
@offline.setter
|
|
325
|
+
def offline(self, value):
|
|
326
|
+
_check(lib.cm_simulation_set_run_offline(self._live(), 1 if value else 0))
|
|
327
|
+
self._offline = bool(value)
|
|
328
|
+
|
|
329
|
+
def _default_fingerprint_file(self):
|
|
330
|
+
# The simulation's code: the subclass's file, else the callbacks' file, else the main script.
|
|
331
|
+
candidates = [type(self)] if type(self) is not Simulation else []
|
|
332
|
+
candidates += [function for function in self._functions.values() if function is not None]
|
|
333
|
+
for candidate in candidates:
|
|
334
|
+
try:
|
|
335
|
+
path = inspect.getsourcefile(candidate)
|
|
336
|
+
except TypeError:
|
|
337
|
+
continue
|
|
338
|
+
if path and os.path.isfile(path):
|
|
339
|
+
return os.path.abspath(path)
|
|
340
|
+
main = getattr(sys.modules.get("__main__"), "__file__", None)
|
|
341
|
+
return os.path.abspath(main) if main and os.path.isfile(main) else None
|
|
342
|
+
|
|
343
|
+
@property
|
|
344
|
+
def fingerprint_file(self):
|
|
345
|
+
"""The file whose fingerprint is recorded as CppModel.BinaryFingerprint, so that runs of the
|
|
346
|
+
same code can be told apart from runs of changed code. Defaults to the file the simulation is
|
|
347
|
+
defined in; None means the Python interpreter's fingerprint."""
|
|
348
|
+
return self._fingerprint_file
|
|
349
|
+
|
|
350
|
+
@fingerprint_file.setter
|
|
351
|
+
def fingerprint_file(self, path):
|
|
352
|
+
encoded = _encode(os.path.abspath(path)) if path is not None else None
|
|
353
|
+
_check(lib.cm_simulation_set_fingerprint_file(self._live(), encoded))
|
|
354
|
+
self._fingerprint_file = os.path.abspath(path) if path is not None else None
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def result(self):
|
|
358
|
+
"""0 when every step passed; otherwise the CppModel.Step of the first failing step (or -1)."""
|
|
359
|
+
out = c_int()
|
|
360
|
+
_check(lib.cm_simulation_get_result(self._live(), byref(out)))
|
|
361
|
+
return out.value
|
|
362
|
+
|
|
363
|
+
@result.setter
|
|
364
|
+
def result(self, value):
|
|
365
|
+
_check(lib.cm_simulation_set_result(self._live(), value))
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def passed(self):
|
|
369
|
+
return self.result == 0
|
|
370
|
+
|
|
371
|
+
# ── Lifetime ─────────────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
def _live(self):
|
|
374
|
+
if self._handle is None:
|
|
375
|
+
raise ValueError("the simulation is closed")
|
|
376
|
+
return self._handle
|
|
377
|
+
|
|
378
|
+
def close(self):
|
|
379
|
+
"""Frees the simulation. It is also freed when garbage-collected."""
|
|
380
|
+
handle, self._handle = getattr(self, "_handle", None), None
|
|
381
|
+
if handle is not None:
|
|
382
|
+
lib.cm_simulation_destroy(handle)
|
|
383
|
+
|
|
384
|
+
def __enter__(self):
|
|
385
|
+
return self
|
|
386
|
+
|
|
387
|
+
def __exit__(self, *exc_info):
|
|
388
|
+
self.close()
|
|
389
|
+
|
|
390
|
+
def __del__(self):
|
|
391
|
+
self.close()
|
|
Binary file
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cppmodel
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Write CppModel simulations in Python
|
|
5
|
+
Project-URL: Homepage, https://cppmodel.com
|
|
6
|
+
Author-email: Control Edge AB <support@cedge.se>
|
|
7
|
+
License-Expression: LicenseRef-CppModel
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: licenses/IXWebSocket-LICENSE.txt
|
|
10
|
+
License-File: licenses/cpp-httplib-LICENSE.txt
|
|
11
|
+
License-File: licenses/jwt-cpp-LICENSE.txt
|
|
12
|
+
License-File: licenses/nlohmann-json-LICENSE.txt
|
|
13
|
+
License-File: licenses/openssl-LICENSE.txt
|
|
14
|
+
License-File: licenses/picojson-LICENSE.txt
|
|
15
|
+
Keywords: cppmodel,embedded,plant model,simulation,testing
|
|
16
|
+
Classifier: Development Status :: 4 - Beta
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Operating System :: MacOS
|
|
19
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
20
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# CppModel for Python
|
|
28
|
+
|
|
29
|
+
Write [CppModel](https://cppmodel.com) simulations in Python. Every run fetches the inputs and
|
|
30
|
+
parameters posted for it in your CppModel workspace and submits its execution there, just like a
|
|
31
|
+
C or C++ simulation built with the CppModel SDK.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install cppmodel
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Wheels are available for Linux (x86_64 and aarch64, glibc 2.28+), Windows (x64 and ARM64) and
|
|
38
|
+
macOS 11+ (Apple silicon and Intel), for Python 3.9 and later. Each wheel contains the CppModel
|
|
39
|
+
library, so nothing else needs installing.
|
|
40
|
+
|
|
41
|
+
## A simulation
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import sys
|
|
45
|
+
import cppmodel
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Heater(cppmodel.Simulation):
|
|
49
|
+
def init(self):
|
|
50
|
+
self.temperature = 20.0
|
|
51
|
+
|
|
52
|
+
def step(self, time_ms):
|
|
53
|
+
setpoint = self.inputs.get("Setpoint", 22.0) # input, with its fallback
|
|
54
|
+
gain = self.parameters.get("HeaterGain", 0.05) # parameter, with its fallback
|
|
55
|
+
|
|
56
|
+
heater_on = self.temperature < setpoint # the controller under test
|
|
57
|
+
self.temperature += gain if heater_on else -0.01
|
|
58
|
+
|
|
59
|
+
self.outputs["Temperature"] = self.temperature
|
|
60
|
+
self.outputs["HeaterOn"] = heater_on
|
|
61
|
+
self.outputs["CppModel.Step"] = time_ms
|
|
62
|
+
self.outputs["CppModel.StepResult"] = self.temperature < setpoint + 1.0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
with Heater("Example - Heater (Python)", total_time_ms=60_000, step_ms=100) as simulation:
|
|
66
|
+
sys.exit(simulation.run())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`run()` fetches the pending inputs and parameters, calls `init()`, then `step(time_ms)` for every
|
|
70
|
+
step from 0 to `total_time_ms`, then `finalize()`, and submits the execution. It returns 0 when every
|
|
71
|
+
step passed, otherwise the `CppModel.Step` of the first step whose `CppModel.StepResult` was 0.
|
|
72
|
+
|
|
73
|
+
An exception raised in `init()`, `step()` or `finalize()` (Ctrl-C included) aborts the run, submits
|
|
74
|
+
nothing and is re-raised by `run()`.
|
|
75
|
+
|
|
76
|
+
Instead of subclassing, you can pass functions that take the simulation as their first argument:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
def ramp(sim, time_ms):
|
|
80
|
+
sim.outputs["Ramp"] = time_ms
|
|
81
|
+
|
|
82
|
+
sim = cppmodel.Simulation("Ramp", total_time_ms=1000, step_ms=100, step=ramp)
|
|
83
|
+
sim.run()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Inputs, outputs and parameters
|
|
87
|
+
|
|
88
|
+
| | |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `sim.inputs.get(name, fallback)` | The input's value at the current step, or `fallback` (recorded as its value) |
|
|
91
|
+
| `sim.inputs[name]` | Same, with fallback 0 |
|
|
92
|
+
| `sim.inputs.load([{"label", "x", "y"}, ...])` | Loads input series locally (x in ms) |
|
|
93
|
+
| `sim.outputs[name] = value` | Sets an output; it holds until set again and is recorded every step. Values are numbers; bools become 1/0 |
|
|
94
|
+
| `sim.parameters.get(name, fallback)` | The parameter, storing `fallback` when it isn't set; a fetched value replaces the fallback |
|
|
95
|
+
| `sim.parameters[name] = value` | Sets a parameter that fetched values never replace |
|
|
96
|
+
| `sim.parameters[name]` | The parameter, or `KeyError` |
|
|
97
|
+
| `sim.results()` | The execution as submitted: `{"inputs", "results", "parameters", ...}` |
|
|
98
|
+
|
|
99
|
+
Inputs and outputs are numbers. Parameters keep their JSON type: numbers, bools, strings, lists,
|
|
100
|
+
dicts and `None`. All three support `in`, `len()`, iteration and `keys()`.
|
|
101
|
+
|
|
102
|
+
## Workspace access
|
|
103
|
+
|
|
104
|
+
A run needs to sign in to your workspace:
|
|
105
|
+
|
|
106
|
+
- **Scripts, CI:** set `CPPMODEL_USERNAME` and `CPPMODEL_PASSWORD`.
|
|
107
|
+
- **Without the workspace:** create the simulation with `offline=True`. Nothing is fetched or
|
|
108
|
+
submitted.
|
|
109
|
+
|
|
110
|
+
Without credentials, the first run prints a sign-in link and waits until you've signed in. The link
|
|
111
|
+
goes to the process's standard output, which Jupyter doesn't show, so use credentials in notebooks.
|
|
112
|
+
|
|
113
|
+
## Fingerprint
|
|
114
|
+
|
|
115
|
+
Each execution records a `CppModel.BinaryFingerprint` of the simulation's code, so the workspace can
|
|
116
|
+
tell runs of the same code from runs of changed code. It is the fingerprint of the file your
|
|
117
|
+
`Simulation` subclass (or step function) is defined in. Set `sim.fingerprint_file` to use another
|
|
118
|
+
file.
|
|
119
|
+
|
|
120
|
+
## Errors
|
|
121
|
+
|
|
122
|
+
A failing library call raises `cppmodel.CppModelError`; its `status` is a `cppmodel.Status`.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
The CppModel library is proprietary software of Control Edge AB, used under the [CppModel Terms of Service](https://www.cppmodel.com/Tos.html); see `LICENSE`. The package also
|
|
127
|
+
contains third-party components under their own licenses, in `licenses/`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
cppmodel/__init__.py,sha256=ROu85RGFEeHsH8hSfT4qVk0Aud3EQoqF7hSI0tgZozw,594
|
|
2
|
+
cppmodel/_native.py,sha256=qUAZuxYVprNMVT0YEekZ7poJlMZkNgP2gyF42Lu09sg,4512
|
|
3
|
+
cppmodel/_simulation.py,sha256=2iogZUPmZO367SNlF7Wvxf253GYVTnHVPNe13UXhaPI,14502
|
|
4
|
+
cppmodel/lib/libcppmodel.so.1,sha256=hF165qMliq9SsePZDXzHhw3u-bJ3a50EQoRHZIvjs_Y,8680696
|
|
5
|
+
cppmodel-0.9.0.dist-info/METADATA,sha256=KF5E1BCwBiiWXhBdrI-gdiU02mhUDYgqiHBs83W4TWQ,5213
|
|
6
|
+
cppmodel-0.9.0.dist-info/WHEEL,sha256=oXgmTSTIbSDIyOVMzS8RHEg6vKUanNsrgMkxt107nkM,107
|
|
7
|
+
cppmodel-0.9.0.dist-info/licenses/LICENSE,sha256=m9J9CSAmn7L5LSEbmwtNhVbRCJin6u75y91XhYRNlgc,382
|
|
8
|
+
cppmodel-0.9.0.dist-info/licenses/licenses/IXWebSocket-LICENSE.txt,sha256=nMRtQmXxirlZt1xEECKF3AS7BOortpaxhY9cOPfrrKA,1506
|
|
9
|
+
cppmodel-0.9.0.dist-info/licenses/licenses/cpp-httplib-LICENSE.txt,sha256=S0XL4W17cbia5hJ-JuDZCgKRmMpelYrY49C4u-02TYs,1075
|
|
10
|
+
cppmodel-0.9.0.dist-info/licenses/licenses/jwt-cpp-LICENSE.txt,sha256=NcLX1oJKHh7SsmbsM0t4YnJq5ZOFXDz6aLlSu3XwgsQ,1075
|
|
11
|
+
cppmodel-0.9.0.dist-info/licenses/licenses/nlohmann-json-LICENSE.txt,sha256=RqZc_9HqlVEy2VqN2SFkBxSo1rU30uTkgtMRRa6VtgM,1076
|
|
12
|
+
cppmodel-0.9.0.dist-info/licenses/licenses/openssl-LICENSE.txt,sha256=QyBwV9TPsX2Di2V-9G25NfX992mLGmN8Hm8ENGqhsu0,611
|
|
13
|
+
cppmodel-0.9.0.dist-info/licenses/licenses/picojson-LICENSE.txt,sha256=VYX-FBzHuwjJU_OFnbYIhSlo0LvGJbm22VwL1jSbrLY,1336
|
|
14
|
+
cppmodel-0.9.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
CppModel
|
|
2
|
+
Copyright (c) Control Edge AB. All rights reserved.
|
|
3
|
+
|
|
4
|
+
This package and the CppModel library it contains are proprietary software of Control Edge AB.
|
|
5
|
+
They are licensed, not sold, and may only be used under the CppModel Terms of Service:
|
|
6
|
+
https://www.cppmodel.com/Tos.html
|
|
7
|
+
|
|
8
|
+
The package also contains third-party components, each under its own license; see the licenses
|
|
9
|
+
folder.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are
|
|
5
|
+
met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the
|
|
13
|
+
distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived
|
|
17
|
+
from this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
20
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
21
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
22
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
23
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
24
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
25
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
26
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
27
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
28
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 yhirose
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Dominik Thalhammer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013-2025 Niels Lohmann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
|
|
4
|
+
Copyright (c) OpenSSL Project Contributors
|
|
5
|
+
|
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
you may not use this file except in compliance with the License.
|
|
8
|
+
You may obtain a copy of the License at
|
|
9
|
+
|
|
10
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
|
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
See the License for the specific language governing permissions and
|
|
16
|
+
limitations under the License.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Copyright 2009-2010 Cybozu Labs, Inc.
|
|
2
|
+
Copyright 2011-2014 Kazuho Oku
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
this list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
18
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
19
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
20
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
21
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
22
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
23
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
24
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
25
|
+
POSSIBILITY OF SUCH DAMAGE.
|