LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
matlab
src
opt
+opt
+de
DifferentialEvolution.m
1
classdef DifferentialEvolution < handle
2
% DifferentialEvolution Self-contained port of scipy
's
3
% differential_evolution that reproduces its trajectory bit-for-bit for a
4
% given integer seed, using opt.de.NumpyRandomState for all draws.
5
%
6
% Covers the configuration used by line-opt: binomial strategies (default
7
% best1bin), updating='
immediate
', dithered mutation, latinhypercube
8
% initialization, polish=false, and penalty-based constraints handled inside
9
% the objective. The exact numpy draw order is preserved: LHS init (uniform
10
% grid then per-column permutation), a per-generation dither uniform, and per
11
% candidate randint (fill point), shuffle (sample selection) and uniform
12
% (crossover), plus a data-dependent uniform in the bound-repair step.
13
14
properties
15
objective % function handle: energy = f(x)
16
low % 1xd lower bounds
17
high % 1xd upper bounds
18
paramCount
19
strategy
20
popsizeMult
21
maxiter
22
ditherLow
23
ditherHigh
24
recombination
25
tol
26
atol
27
callback % function handle (bestX, nit) -> logical stop, or []
28
rng % opt.de.NumpyRandomState
29
numMembers
30
population % numMembers x d, scaled to [0,1]
31
energies % 1 x numMembers
32
randomIndex % persistent shuffle buffer (0-based values)
33
scale
34
nfev
35
bestPerGen % cell array of 1xd vectors
36
end
37
38
methods
39
function obj = DifferentialEvolution(objective, low, high, strategy, ...
40
popsizeMult, maxiter, ditherLow, ditherHigh, recombination, tol, seed)
41
obj.objective = objective;
42
obj.low = low(:).'
;
43
obj.high = high(:).
';
44
obj.paramCount = numel(low);
45
obj.strategy = strategy;
46
obj.popsizeMult = popsizeMult;
47
obj.maxiter = maxiter;
48
obj.ditherLow = min(ditherLow, ditherHigh);
49
obj.ditherHigh = max(ditherLow, ditherHigh);
50
obj.recombination = recombination;
51
obj.tol = tol;
52
obj.atol = 0.0;
53
obj.callback = [];
54
obj.scale = ditherLow;
55
obj.rng = opt.de.NumpyRandomState(seed);
56
obj.bestPerGen = {};
57
obj.initPopulationLhs();
58
end
59
60
function s1 = scaleArg1(obj, j)
61
s1 = 0.5 * (obj.low(j) + obj.high(j));
62
end
63
64
function s2 = scaleArg2(obj, j)
65
s2 = abs(obj.low(j) - obj.high(j));
66
end
67
68
function out = scaleParameters(obj, trial)
69
out = zeros(1, obj.paramCount);
70
for j = 1:obj.paramCount
71
out(j) = obj.scaleArg1(j) + (trial(j) - 0.5) * obj.scaleArg2(j);
72
end
73
end
74
75
function initPopulationLhs(obj)
76
ebCount = sum(obj.low == obj.high);
77
obj.numMembers = max(5, obj.popsizeMult * max(1, obj.paramCount - ebCount));
78
segsize = 1.0 / obj.numMembers;
79
flat = obj.rng.uniformN(0, 1, obj.numMembers * obj.paramCount);
80
samples = zeros(obj.numMembers, obj.paramCount);
81
p = 1;
82
for i = 1:obj.numMembers
83
offset = (i - 1) / obj.numMembers;
84
for j = 1:obj.paramCount
85
samples(i, j) = segsize * flat(p) + offset;
86
p = p + 1;
87
end
88
end
89
obj.population = zeros(obj.numMembers, obj.paramCount);
90
for j = 1:obj.paramCount
91
order = obj.rng.permutation(obj.numMembers); % 0-based
92
for i = 1:obj.numMembers
93
obj.population(i, j) = samples(order(i) + 1, j);
94
end
95
end
96
obj.energies = inf(1, obj.numMembers);
97
obj.randomIndex = 0:(obj.numMembers - 1);
98
obj.nfev = 0;
99
end
100
101
function b = best(obj)
102
b = obj.scaleParameters(obj.population(1, :));
103
end
104
105
function calculateInitialEnergies(obj)
106
for i = 1:obj.numMembers
107
obj.energies(i) = obj.objective(obj.scaleParameters(obj.population(i, :)));
108
obj.nfev = obj.nfev + 1;
109
end
110
end
111
112
function promoteLowestEnergy(obj)
113
[~, l] = min(obj.energies);
114
if l ~= 1
115
te = obj.energies(1); obj.energies(1) = obj.energies(l); obj.energies(l) = te;
116
tp = obj.population(1, :); obj.population(1, :) = obj.population(l, :); obj.population(l, :) = tp;
117
end
118
end
119
120
function tf = converged(obj)
121
if any(isinf(obj.energies))
122
tf = false;
123
return;
124
end
125
tf = std(obj.energies, 1) <= obj.atol + obj.tol * abs(mean(obj.energies));
126
end
127
128
function out = selectSamples(obj, candidate, numberSamples)
129
% candidate is 0-based member index
130
obj.randomIndex = obj.rng.shuffle(obj.randomIndex);
131
pick = obj.randomIndex(1:numberSamples + 1);
132
out = zeros(1, numberSamples);
133
k = 0;
134
for i = 1:numel(pick)
135
if k >= numberSamples
136
break;
137
end
138
if pick(i) ~= candidate
139
k = k + 1;
140
out(k) = pick(i);
141
end
142
end
143
end
144
145
function b = bprime(obj, candidate, s)
146
% candidate and s are 0-based member indices; convert to 1-based rows
147
P = obj.population;
148
c = candidate + 1;
149
r = s + 1;
150
switch obj.strategy
151
case {'
rand1bin
', '
rand1exp
'}
152
b = P(r(1), :) + obj.scale * (P(r(2), :) - P(r(3), :));
153
case {'
randtobest1bin
', '
randtobest1exp
'}
154
b = P(r(1), :);
155
b = b + obj.scale * (P(1, :) - b);
156
b = b + obj.scale * (P(r(2), :) - P(r(3), :));
157
case {'
currenttobest1bin
', '
currenttobest1exp
'}
158
b = P(c, :) + obj.scale * (P(1, :) - P(c, :) + P(r(1), :) - P(r(2), :));
159
case {'
best2bin
', '
best2exp
'}
160
b = P(1, :) + obj.scale * (P(r(1), :) + P(r(2), :) - P(r(3), :) - P(r(4), :));
161
case {'
rand2bin
', '
rand2exp
'}
162
b = P(r(1), :) + obj.scale * (P(r(2), :) + P(r(3), :) - P(r(4), :) - P(r(5), :));
163
otherwise % best1bin / best1exp
164
b = P(1, :) + obj.scale * (P(r(1), :) - P(r(2), :));
165
end
166
end
167
168
function trial = mutate(obj, candidate)
169
% candidate is 0-based member index
170
fillPoint = obj.rng.randint(0, obj.paramCount); % 0-based
171
samples = obj.selectSamples(candidate, 5);
172
bp = obj.bprime(candidate, samples);
173
trial = obj.population(candidate + 1, :);
174
cross = obj.rng.uniformN(0, 1, obj.paramCount);
175
doCross = cross < obj.recombination;
176
doCross(fillPoint + 1) = true;
177
trial(doCross) = bp(doCross);
178
end
179
180
function trial = ensureConstraint(obj, trial)
181
mask = (trial > 1) | (trial < 0);
182
oob = sum(mask);
183
if oob > 0
184
repl = obj.rng.uniformN(0, 1, oob);
185
trial(mask) = repl;
186
end
187
end
188
189
function result = solve(obj)
190
warningFlag = false;
191
if any(isinf(obj.energies))
192
obj.calculateInitialEnergies();
193
obj.promoteLowestEnergy();
194
end
195
nit = 0;
196
for nit = 1:obj.maxiter
197
obj.next();
198
if ~isempty(obj.callback)
199
stop = obj.callback(obj.best(), nit);
200
if stop
201
warningFlag = true;
202
end
203
end
204
obj.bestPerGen{end+1} = obj.best(); %#ok<AGROW>
205
if warningFlag || obj.converged()
206
break;
207
end
208
end
209
if nit >= obj.maxiter
210
warningFlag = warningFlag || (nit == obj.maxiter && ~obj.converged());
211
end
212
result = struct();
213
result.x = obj.best();
214
result.fun = obj.energies(1);
215
result.nit = nit;
216
result.nfev = obj.nfev;
217
result.success = ~warningFlag;
218
result.bestPerGen = obj.bestPerGen;
219
end
220
221
function next(obj)
222
obj.scale = obj.rng.uniformScalar(obj.ditherLow, obj.ditherHigh);
223
for candidate = 0:(obj.numMembers - 1)
224
trial = obj.mutate(candidate);
225
trial = obj.ensureConstraint(trial);
226
parameters = obj.scaleParameters(trial);
227
energy = obj.objective(parameters);
228
obj.nfev = obj.nfev + 1;
229
if energy <= obj.energies(candidate + 1)
230
obj.population(candidate + 1, :) = trial;
231
obj.energies(candidate + 1) = energy;
232
if energy <= obj.energies(1)
233
obj.promoteLowestEnergy();
234
end
235
end
236
end
237
end
238
end
239
end
Generated by
1.9.8