rtide-optimizer 1.0.0__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.
RTIDE/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .optimizer import rti_de
2
+
3
+ __all__ = ["rti_de"]
RTIDE/optimizer.py ADDED
@@ -0,0 +1,189 @@
1
+ """
2
+ RTI-DE: Rejected-Trial Interpolation Differential Evolution.
3
+
4
+ A differential evolution variant in which the evaluation of a rejected trial point is used
5
+ instead of being discarded.
6
+
7
+ Components
8
+ ----------
9
+ 1. Trial point
10
+ v = x + F (x_e - x) + F (x_r1 - x_r2),
11
+ x_e drawn from the best p N agents, r1 != r2 != i: the DE/current-to-pbest/1 mutation of JADE
12
+ (Zhang and Sanderson, 2009) without the external archive, followed by the binomial crossover
13
+ of classic DE (Storn and Price, 1997) with the fixed rate CR: component j of the trial point
14
+ is v_j with probability CR and for one random j, and x_j otherwise. The displacement is
15
+ d = trial point - x.
16
+ 2. Selection: v replaces x when f(v) <= f(x).
17
+ 3. Rejected-trial chain, used after the fraction ``chain_from`` of the evaluation budget.
18
+ An agent whose trial point was rejected does not draw a new displacement at once:
19
+ second point x + d / 2
20
+ third point x + t* d, t* the minimiser of the parabola through the three collinear
21
+ points x, x + d / 2, x + d, limited to [-1, 1]; t* = -1/2 when the parabola
22
+ is not convex. The point is skipped when t* is closer than 0.02 to a point
23
+ already evaluated (0, 1/2 or 1).
24
+ An accepted point ends the chain; after the third point a new displacement is drawn.
25
+ 4. Step factor: F ~ N(mu_F, 0.15) clipped to [0.05, 1], mu_F = 0.5 at the start. After a
26
+ generation in which first trial points were accepted,
27
+ mu_F <- 0.9 mu_F + 0.1 L, L = sum(w F^2) / sum(w F),
28
+ over the accepted first trial points, w proportional to the improvement (the adaptation rule
29
+ of JADE with the improvement weights of SHADE; points of the chain do not enter).
30
+ 5. Population size: reduced linearly in the evaluations used from n_init to n_min, the worst
31
+ agents being removed (linear population size reduction of L-SHADE, Tanabe and Fukunaga, 2014).
32
+
33
+ Bounds: a component of v outside the box is set to the midpoint between the bound and x (before
34
+ the crossover), and d is the displacement actually made; the third point is clipped to the box.
35
+
36
+ CR = 0.9 was chosen on a development setting (CEC2017, D = 30, 300,000 evaluations, seeds 0-4,
37
+ among 0.5, 0.7, 0.9 and no crossover) and confirmed on seeds 5-14 on 2026-10-10.
38
+ """
39
+
40
+ import numpy as np
41
+
42
+
43
+ def rti_de(func, lb, ub, max_fes, seed=None, n_init=None, n_min=4, p=0.11, chain=2, chain_from=0.7,
44
+ CR=0.9, lpsr=True, adapt=True, trace=None):
45
+ """Minimise ``func`` (vectorised: (k, D) array -> k values) within ``max_fes`` evaluations.
46
+
47
+ n_init initial population size (default 18 D)
48
+ n_min final population size
49
+ p fraction of the population from which x_e is drawn
50
+ chain points of the rejected-trial chain: 2 (half step and parabola), 1 (half step
51
+ only) or 0 (no chain); values below 2 are for the ablation
52
+ chain_from fraction of the budget after which the chain is used
53
+ CR rate of the binomial crossover (None: no crossover; for the ablation)
54
+
55
+ Switches for the ablation (the algorithm is the default):
56
+ lpsr False: the population keeps its initial size
57
+ adapt False: mu_F stays at 0.5
58
+
59
+ trace None, or a list to which one dict is appended per generation (the run itself is
60
+ not changed: no random number is drawn and nothing is evaluated for the trace):
61
+ fes, n, mu_F, f_best evaluations used, population size, mean step factor and
62
+ best value after the generation (n before the reduction)
63
+ evaluated, accepted, candidates of the generation by stage [first trial point,
64
+ improved half step, parabola point]: evaluated, accepted
65
+ (f <= f of the agent), strictly better
66
+ skipped third points left out by the skip rule
67
+ theta, theta_accepted factor t* of every third point evaluated and whether it
68
+ was accepted
69
+ The list is filled as the run goes, so it is valid when ``func`` ends the run early.
70
+
71
+ Returns (x_best, f_best, evaluations used)."""
72
+ rng = np.random.default_rng(seed)
73
+ lb, ub = np.asarray(lb, float), np.asarray(ub, float)
74
+ dim = lb.size
75
+ n_init = int(n_init or 18 * dim)
76
+ n = n_init
77
+
78
+ def evaluate(X):
79
+ f = np.asarray(func(X), dtype=float)
80
+ return np.where(np.isnan(f), np.inf, f)
81
+
82
+ X = rng.uniform(lb, ub, (n, dim))
83
+ fit = evaluate(X)
84
+ fes = n
85
+ mu_F = 0.5
86
+ stage = np.zeros(n, dtype=int) # 0: first trial point, 1: half step, 2: parabola
87
+ D = np.zeros((n, dim)) # displacement of the running chain
88
+ T = np.ones(n) # factor of d in the current candidate
89
+ F = np.full(n, 0.5) # step factor with which d was drawn
90
+ f_one, f_half = np.zeros(n), np.zeros(n) # values at x + d and x + d / 2
91
+
92
+ while fes < max_fes:
93
+ # --- third point: minimiser of the parabola through (0, fit), (1/2, f_half), (1, f_one)
94
+ s2 = np.nonzero(stage == 2)[0]
95
+ n_skip = 0
96
+ if s2.size:
97
+ d_one, d_half = f_one[s2] - fit[s2], f_half[s2] - fit[s2]
98
+ with np.errstate(all="ignore"):
99
+ beta = (d_half - 0.5 * d_one) / -0.25
100
+ t = -(d_one - beta) / (2.0 * beta)
101
+ t = np.where(np.isfinite(t) & (beta > 0), t, -0.5)
102
+ ok = np.isfinite(d_one) & np.isfinite(d_half)
103
+ t = np.clip(np.where(ok, t, 0.0), -1.0, 1.0)
104
+ ok &= (np.abs(t) >= 0.02) & (np.abs(t - 0.5) >= 0.02) & (np.abs(t - 1.0) >= 0.02)
105
+ T[s2[ok]] = t[ok]
106
+ stage[s2[~ok]] = 0
107
+ n_skip = int((~ok).sum())
108
+
109
+ # --- second point: half step
110
+ T[stage == 1] = 0.5
111
+
112
+ # --- first trial point: new displacement
113
+ s0 = np.nonzero(stage == 0)[0]
114
+ if s0.size:
115
+ k = s0.size
116
+ order = np.argsort(fit, kind="stable")
117
+ elite = X[order[rng.integers(max(2, int(round(p * n))), size=k)]]
118
+ f = np.clip(rng.normal(mu_F, 0.15, k), 0.05, 1.0)
119
+ r1 = rng.integers(n - 1, size=k)
120
+ r1 += r1 >= s0
121
+ r2 = rng.integers(n, size=k)
122
+ while True:
123
+ bad = (r2 == s0) | (r2 == r1)
124
+ if not bad.any():
125
+ break
126
+ r2[bad] = rng.integers(n, size=int(bad.sum()))
127
+ V = X[s0] + f[:, None] * (elite - X[s0]) + f[:, None] * (X[r1] - X[r2])
128
+ V = np.where(V < lb, (lb + X[s0]) / 2.0, V)
129
+ V = np.where(V > ub, (ub + X[s0]) / 2.0, V)
130
+ if CR is not None:
131
+ cross = rng.random((k, dim)) < CR
132
+ cross[np.arange(k), rng.integers(dim, size=k)] = True
133
+ V = np.where(cross, V, X[s0])
134
+ D[s0], F[s0], T[s0] = V - X[s0], f, 1.0
135
+
136
+ U = np.clip(X + T[:, None] * D, lb, ub)
137
+ k = min(n, max_fes - fes) # the last generation is cut at the budget
138
+ f_new = np.full(n, np.inf)
139
+ f_new[:k] = evaluate(U[:k])
140
+ fes += k
141
+
142
+ better = f_new < fit
143
+ accept = f_new <= fit
144
+ accept[k:] = False
145
+
146
+ # --- step factor: accepted first trial points only
147
+ counted = better & (stage == 0)
148
+ if adapt and counted.any():
149
+ gain = fit[counted] - f_new[counted]
150
+ gain = np.where(np.isfinite(gain), gain, 0.0)
151
+ w = gain / gain.sum() if gain.sum() > 0 else np.full(gain.size, 1.0 / gain.size)
152
+ s_f = F[counted] * np.abs(T[counted])
153
+ den = np.sum(w * s_f)
154
+ lehmer = np.sum(w * s_f ** 2) / den if den > 0 else 0.0
155
+ mu_F = float(np.clip(0.9 * mu_F + 0.1 * lehmer, 0.05, 1.0))
156
+
157
+ if trace is not None:
158
+ done = np.arange(n) < k
159
+ by_stage = [done & (stage == s) for s in (0, 1, 2)]
160
+ trace.append(dict(
161
+ fes=fes, n=n, mu_F=mu_F, f_best=float(min(fit.min(), f_new[:k].min())),
162
+ evaluated=[int(m.sum()) for m in by_stage],
163
+ accepted=[int((m & accept).sum()) for m in by_stage],
164
+ improved=[int((m & better).sum()) for m in by_stage],
165
+ skipped=n_skip, theta=T[by_stage[2]].copy(), theta_accepted=accept[by_stage[2]].copy()))
166
+
167
+ # --- selection and next stage of the chain
168
+ rejected = ~accept
169
+ rejected[k:] = False
170
+ f_one = np.where(rejected & (stage == 0), f_new, f_one)
171
+ f_half = np.where(rejected & (stage == 1), f_new, f_half)
172
+ X[accept], fit[accept] = U[accept], f_new[accept]
173
+ nxt = np.where(rejected, stage + 1, 0)
174
+ nxt[nxt > chain] = 0
175
+ if fes < chain_from * max_fes:
176
+ nxt[:] = 0
177
+ nxt[k:] = stage[k:]
178
+ stage = nxt
179
+
180
+ # --- linear population size reduction
181
+ n_next = max(n_min, int(round((n_min - n_init) / max_fes * fes + n_init))) if lpsr else n
182
+ if n_next < n:
183
+ keep = np.argsort(fit, kind="stable")[:n_next]
184
+ X, fit, stage, D, T, F = X[keep], fit[keep], stage[keep], D[keep], T[keep], F[keep]
185
+ f_one, f_half = f_one[keep], f_half[keep]
186
+ n = n_next
187
+
188
+ best = int(np.argmin(fit))
189
+ return X[best].copy(), float(fit[best]), fes
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: rtide-optimizer
3
+ Version: 1.0.0
4
+ Summary: RTI-DE: Rejected-Trial Interpolation Differential Evolution for box-constrained continuous minimisation
5
+ Author: Emine Ayaz
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/ayazemine/rti-de
8
+ Keywords: differential evolution,evolutionary algorithm,metaheuristic,global optimization,black-box optimization
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy>=1.17
17
+ Dynamic: license-file
18
+
19
+ # RTI-DE: Rejected-Trial Interpolation Differential Evolution
20
+
21
+ RTI-DE is a differential evolution (DE) variant for the minimisation of a black-box function over a
22
+ box `[lb, ub]` within a fixed budget of function evaluations. Unlike classic DE, it does not discard
23
+ the evaluation of a rejected trial point. Late in the run (by default after 70 % of the budget), an
24
+ agent whose trial point was rejected evaluates the half step along the same displacement. If that
25
+ is rejected as well, it evaluates the minimiser of the parabola through the three values known on
26
+ that line (the *rejected-trial chain*). The other components come from established DE variants:
27
+
28
+ - DE/current-to-pbest/1 mutation (JADE) without the external archive, with midpoint bound repair;
29
+ - binomial crossover with a fixed rate CR = 0.9;
30
+ - step factor F ~ N(mu_F, 0.15) clipped to [0.05, 1], where mu_F is adapted from the successful
31
+ trial points (weighted Lehmer mean);
32
+ - linear population size reduction from 18 D to 4 agents (L-SHADE).
33
+
34
+ The complete rules are in the docstring of `python/RTIDE/optimizer.py` and in Section 3 of the paper.
35
+
36
+ This repository contains:
37
+
38
+ ```
39
+ python/RTIDE/ the Python package (import name RTIDE, distribution name rtide-optimizer)
40
+ matlab/ MATLAB / GNU Octave port (rti_de.m), example and verification against Python
41
+ tests/ unit tests of the algorithm (Python)
42
+ experiments/ code of the experiments of the paper (benchmarks, engineering applications,
43
+ experiment runner, table and figure scripts, their tests)
44
+ ```
45
+
46
+ ## Publishing to PyPI
47
+
48
+ Releases are built and published from GitHub tags using PyPI Trusted Publishing. To enable the
49
+ first release, sign in to PyPI as `emineayaz` and add a pending publisher at
50
+ <https://pypi.org/manage/account/publishing/> with these values:
51
+
52
+ - PyPI project name: `rtide-optimizer`
53
+ - GitHub owner: `ayazemine`
54
+ - GitHub repository: `rti-de`
55
+ - Workflow: `publish.yml`
56
+ - GitHub environment: `pypi`
57
+
58
+ Create the `pypi` environment in the GitHub repository settings and require a reviewer before
59
+ publishing. The pending publisher does not reserve the project name until its first successful
60
+ upload.
61
+
62
+ For each release, update `version` in `pyproject.toml`, create a matching `v<version>` tag, and
63
+ push it to GitHub. For example, the current `1.0.0` version can be published with:
64
+
65
+ ```bash
66
+ git tag v1.0.0
67
+ git push origin v1.0.0
68
+ ```
69
+
70
+ The workflow builds and checks the distributions before publishing them to PyPI.
71
+
72
+ ## Installation (Python)
73
+
74
+ The only dependency is NumPy; the code was tested with Python 3.11.9 and NumPy 1.26.0.
75
+
76
+ ```bash
77
+ pip install rtide-optimizer # from PyPI
78
+ pip install . # from a clone of this repository
79
+ ```
80
+
81
+ ## Usage (Python)
82
+
83
+ ```python
84
+ import numpy as np
85
+ from RTIDE import rti_de
86
+
87
+ def rastrigin(X): # vectorised: (k, D) array -> k values
88
+ return 10 * X.shape[1] + np.sum(X**2 - 10 * np.cos(2 * np.pi * X), axis=1)
89
+
90
+ D = 10
91
+ lb, ub = np.full(D, -5.12), np.full(D, 5.12)
92
+ x_best, f_best, fes = rti_de(rastrigin, lb, ub, max_fes=50_000, seed=1)
93
+ ```
94
+
95
+ Optional arguments (defaults): `n_init=None` (18 D), `n_min=4`, `p=0.11`, `chain=2`,
96
+ `chain_from=0.7`, `CR=0.9` (`None`: no crossover), `lpsr=True`, `adapt=True`, and `trace=None`
97
+ (a list that receives one record per generation). A NaN objective value is treated as +inf. The
98
+ function returns `(x_best, f_best, evaluations_used)`.
99
+
100
+ ## Usage (MATLAB / GNU Octave)
101
+
102
+ ```matlab
103
+ addpath('matlab')
104
+ rastrigin = @(X) 10 * size(X, 2) + sum(X .^ 2 - 10 * cos(2 * pi * X), 2); % one point per row
105
+ D = 10;
106
+ [x_best, f_best, fes] = rti_de(rastrigin, -5.12 * ones(1, D), 5.12 * ones(1, D), 50000, 1);
107
+ [x_best, f_best, fes, trace] = rti_de(rastrigin, -5.12 * ones(1, D), 5.12 * ones(1, D), 50000, 1, ...
108
+ 'chain_from', 0.5, 'CR', 0.9);
109
+ ```
110
+
111
+ `rti_de.m` takes the same options as name-value pairs (or a struct) and also needs `rtide_rules.m`.
112
+ See `matlab/example_rti_de.m`. MATLAB R2016b or later is required (implicit expansion). Because the
113
+ two languages use different random number generators, a MATLAB run with a given seed differs from
114
+ the Python run with that seed. With the same random numbers, the two implementations give
115
+ bit-identical runs. `matlab/VERIFICATION.md` reports this check (GNU Octave 11.3.0).
116
+
117
+ ## Tests
118
+
119
+ ```bash
120
+ python -m unittest discover -s tests -v # algorithm; also imports DE and L-SHADE from experiments/
121
+ cd experiments && python -W ignore -m unittest discover -s tests -v
122
+ ```
123
+
124
+ Install `experiments/requirements-experiments.txt` first. The CEC tests compile the official C
125
+ code with `g++` on first use (into `experiments/benchmarks/official_cec/_build/`). The reference-run
126
+ test in `tests/test_rtide.py` compares a stored CEC2017 result bit-exactly on Windows (MinGW build)
127
+ and with a relative tolerance of 1e-3 elsewhere.
128
+
129
+ MATLAB/Octave verification (from `matlab/verification/`):
130
+
131
+ ```bash
132
+ python verify_rtide.py prepare
133
+ octave-cli --no-gui --quiet --eval "run_octave_checks"
134
+ python verify_rtide.py compare # writes output/summary.json and ../VERIFICATION.md
135
+ ```
136
+
137
+ ## Reproducing the experiments of the paper
138
+
139
+ All commands are run from `experiments/` in a Python 3.11 environment:
140
+
141
+ ```bash
142
+ cd experiments
143
+ pip install -r requirements-experiments.txt
144
+ python benchmarks/cec_official_functions.py # builds and checks the official CEC libraries (g++)
145
+
146
+ # CEC2022 (D = 20) and CEC2017 (D = 100): 7 algorithms, 30 runs, 1,000,000 evaluations
147
+ python -W ignore run_experiments.py --suites cec2022 cec2017 --budget fe --max-fes 1000000 --runs 30 --workers 31
148
+
149
+ # engineering problems (composite laminate, with and without contiguity constraint; UAV path planning)
150
+ python -W ignore run_experiments.py --suites composite composite_contiguity uav --runs 30 --workers 31
151
+
152
+ # ablation (6 variants) and parameter sensitivity (8 variants)
153
+ python -W ignore run_experiments.py --suites cec2022 cec2017 --budget fe --max-fes 1000000 --runs 30 --workers 31 --algorithms RTI-DE-noChain RTI-DE-halfStep RTI-DE-chainAlways RTI-DE-noLPSR RTI-DE-fixedF RTI-DE-noCR --out results_cache/paper_ablation
154
+ python -W ignore run_experiments.py --suites cec2022 cec2017 --budget fe --max-fes 1000000 --runs 30 --workers 31 --algorithms RTI-DE-cf50 RTI-DE-cf90 RTI-DE-p05 RTI-DE-p20 RTI-DE-n10D RTI-DE-n25D RTI-DE-cr70 RTI-DE-cr95 --out results_cache/paper_sensitivity
155
+
156
+ # tables and figures (written to experiments/outputs/)
157
+ python -W ignore scripts/make_tables.py
158
+ python -W ignore scripts/make_figures.py
159
+ ```
160
+
161
+ - `--dry-run` lists the work and a rough duration without running anything.
162
+ - An interrupted command can be restarted; finished runs are skipped.
163
+ - `--workers` should be at most the number of cores minus one.
164
+ - The runner refuses to continue a result directory if `python/RTIDE/optimizer.py` or the
165
+ reference implementations in `experiments/benchmarks/` have changed (SHA-256 recorded in
166
+ `config.json`).
167
+ - `experiments/applications/composite/exhaustive_search.py` recomputes the exact reference optima of
168
+ the laminate problems (`reference_optimum.json`).
169
+ - `experiments/scripts/supplementary_stats.py` computes supplementary statistics (seed-paired Wilcoxon
170
+ tests, rank-sum tests with a tie tolerance, Holm adjustment over a suite) from the generated
171
+ tables and from the `run_log.txt` and `summary.csv` files of `run_experiments.py`, copied to
172
+ `experiments/results_summary/<set>/`.
173
+
174
+ The values of all tables of the paper, at full precision, are in `experiments/results_tables/`
175
+ (one CSV file per table; the README there maps the files to the table numbers). The raw results of
176
+ the individual runs are not included in this repository. The problems are described in
177
+ `experiments/applications/README.md` and the CEC code in
178
+ `experiments/benchmarks/official_cec/README.md`.
179
+
180
+ ## License and citation
181
+
182
+ The RTI-DE code is released under the MIT License (`LICENSE`). If you use RTI-DE in research, please
183
+ cite the paper:
184
+
185
+ > E. Ayaz, "Rejected-Trial Interpolation Differential Evolution with Applications to Composite
186
+ > Laminate Design and UAV Path Planning."
187
+
188
+ Citation metadata: `CITATION.cff`.
189
+
190
+ The official CEC 2017/2022 test-function code in `experiments/benchmarks/official_cec/` and the SPSO
191
+ terrain data in `experiments/applications/uav/data/` are third-party material under their own
192
+ terms; see `NOTICE.md`.
@@ -0,0 +1,7 @@
1
+ RTIDE/__init__.py,sha256=v_LuPRyAgPLUbfG3qW4vEfyf_ZldTMUuty_id2c8T5c,52
2
+ RTIDE/optimizer.py,sha256=PAm4k2BeQmiHiWnNMzaKMb2ypSty0G-T_sCv9L6op24,9428
3
+ rtide_optimizer-1.0.0.dist-info/licenses/LICENSE,sha256=cN3NcGe-TVaYqib8P29hN1v1OxGOERIsA8CwBVcn9TY,1067
4
+ rtide_optimizer-1.0.0.dist-info/METADATA,sha256=B38EqMcuJ65_6xuGBMTKh2ahCyiA86OO_9fLcbgN6ec,9097
5
+ rtide_optimizer-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
6
+ rtide_optimizer-1.0.0.dist-info/top_level.txt,sha256=EJJZskk3sFcik0oLGyoeh5_KpUTQEXMqfMx0JLpY96g,6
7
+ rtide_optimizer-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emine Ayaz
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 @@
1
+ RTIDE