$darkmode
DENOPTIM
FitnessProvider.java
Go to the documentation of this file.
1/*
2 * DENOPTIM
3 * Copyright (C) 2022 Marco Foscato <marco.foscato@uib.no>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package denoptim.fitness;
20
21import java.util.ArrayList;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Map;
25import java.util.logging.Level;
26import java.util.logging.Logger;
27
28import org.openscience.cdk.IImplementationSpecification;
29import org.openscience.cdk.exception.CDKException;
30import org.openscience.cdk.interfaces.IAtom;
31import org.openscience.cdk.interfaces.IAtomContainer;
32import org.openscience.cdk.interfaces.IBond;
33import org.openscience.cdk.isomorphism.Mappings;
34import org.openscience.cdk.qsar.DescriptorEngine;
35import org.openscience.cdk.qsar.DescriptorValue;
36import org.openscience.cdk.qsar.IAtomPairDescriptor;
37import org.openscience.cdk.qsar.IAtomicDescriptor;
38import org.openscience.cdk.qsar.IBondDescriptor;
39import org.openscience.cdk.qsar.IDescriptor;
40import org.openscience.cdk.qsar.IMolecularDescriptor;
41import org.openscience.cdk.qsar.result.DoubleArrayResult;
42import org.openscience.cdk.qsar.result.DoubleResult;
43import org.openscience.cdk.qsar.result.IDescriptorResult;
44import org.openscience.cdk.qsar.result.IntegerArrayResult;
45import org.openscience.cdk.qsar.result.IntegerResult;
46
47import denoptim.constants.DENOPTIMConstants;
48import denoptim.exception.DENOPTIMException;
49import denoptim.utils.DummyAtomHandler;
50import denoptim.utils.ManySMARTSQuery;
51import denoptim.utils.MathUtils;
52import jakarta.el.ELContext;
53import jakarta.el.ELException;
54import jakarta.el.ELResolver;
55import jakarta.el.ExpressionFactory;
56import jakarta.el.FunctionMapper;
57import jakarta.el.ValueExpression;
58import jakarta.el.VariableMapper;
59
70public class FitnessProvider
71{
75 protected DescriptorEngine engine;
76
80 private List<DescriptorForFitness> descriptors;
81
85 private String expression;
86
90 private Logger logger = null;
91
92
93//------------------------------------------------------------------------------
94
109 public FitnessProvider(List<DescriptorForFitness> descriptors,
110 String expression, Logger logger)
111 {
112 this.logger = logger;
113 this.expression = expression;
114
115 // We cannot use the list<DescriptorsForFitness> parameter directly
116 // because they are not thread-safe.
117 // otherwise we hit concurrent modification exception because multiple
118 // threads will run the exact same instance of the implementation
119 this.descriptors = new ArrayList<DescriptorForFitness>();
120
121 // Make new instances of DescriptorForFitness
122 ArrayList<String> classnames = new ArrayList<String>();
123 for (int i=0; i<descriptors.size(); i++)
124 {
126 classnames.add(dff.getClassName());
127 this.descriptors.add(dff.cloneAllButImpl());
128 }
129 // Instantiate new instances of the descriptors implementations
130 engine = new DescriptorEngine(classnames, null);
131 List<IDescriptor> newInstances = engine.getDescriptorInstances();
132
133 for (int i=0; i<this.descriptors.size(); i++)
134 {
135 this.descriptors.get(i).implementation = newInstances.get(i);
136 try
137 {
138 this.descriptors.get(i).implementation.setParameters(
139 descriptors.get(i).implementation.getParameters());
140 } catch (CDKException e)
141 {
142 // This should never happen
143 e.printStackTrace();
144 }
145 }
146
147 engine.setDescriptorInstances(newInstances);
148 engine.setDescriptorSpecifications(engine.initializeSpecifications(
149 newInstances));
150 }
151
152//------------------------------------------------------------------------------
153
164 public double getFitness(IAtomContainer iac) throws Exception
165 {
166 if (engine == null)
167 {
168 throw new DENOPTIMException("Internal fitness provider has not been"
169 + " configured.");
170 }
171
172 // Preparation of the chemical representation
173
174 // TODO add possibility to submit external molecular modeling task
175 // this could be done by a Modeller class
176
177 // Cleanup: remove dummy atoms
180 iac = dah.removeDummyInHapto(iac);
181 iac = dah.removeDummy(iac);
182
183 // Calculate all descriptors.
184 // The results are put in the properties of
185 // the IAtomContainer (as DescriptorValue identified by
186 // DescriptorSpecification keys) and we later translate these into
187 // plain human readable strings.
188 engine.process(iac);
189
190 logger.log(Level.FINE, "Descriptor instances: "
191 + engine.getDescriptorInstances().size());
192
193 // Collect numerical values needed to calculate the fitness
194
195 // Just to simplify retrieval of the values
196 HashMap<String, Double> valuesMap = new HashMap<String,Double>();
197 // first we initialize them to null
198 for (DescriptorForFitness d : this.descriptors)
199 {
200 for (Variable v : d.getVariables())
201 {
202 valuesMap.put(v.getName(), null);
203 }
204 }
205 for (int i=0; i<engine.getDescriptorInstances().size(); i++)
206 {
207 DescriptorForFitness descriptor = descriptors.get(i);
208 IDescriptor desc = engine.getDescriptorInstances().get(i);
209
210 String descName = descriptor.shortName;
211 logger.log(Level.FINE, "Working on descriptor '"
212 + descName + "'");
213
214 IImplementationSpecification descSpec =
215 engine.getDescriptorSpecifications().get(i);
216
217 // Identify specific atom and bonds
218 Map<String, String> smarts = new HashMap<String, String>();
219 for (Variable variable : descriptor.getVariables())
220 {
221 logger.log(Level.FINE, "-Processing varName = '"
222 + variable.getName() + "'");
223 if (variable.smarts != null)
224 {
225 if (variable.smarts.size()!=1)
226 {
227 throw new DENOPTIMException("Handling of multiple "
228 + "SMARTS identifiers is not implemented yet. "
229 + "Please, let the DENOPTIM developers know "
230 + "about your interest in this "
231 + "functionality.");
232 }
233 smarts.put(variable.getName(), variable.smarts.get(0));
234 }
235 }
236
237 Map<String, Mappings> allMatches = new HashMap<String, Mappings>();
238 if (smarts.size() != 0)
239 {
240 ManySMARTSQuery msq = new ManySMARTSQuery(iac, smarts);
241 if (msq.hasProblems())
242 {
243 String msg = "WARNING! Problems while searching for "
244 + "specific atoms/bonds using SMARTS: "
245 + msq.getMessage();
246 throw new DENOPTIMException(msg,msq.getProblem());
247 }
248 allMatches = msq.getAllMatches();
249 }
250
251 logger.log(Level.FINE, "Collecting value of variables "
252 + "derived from descriptor #" + i + ": " + descName);
253
254 //Molecular/Atomic/bond descriptors are stored accordingly
255 DescriptorValue value = null;
256 if (desc instanceof IMolecularDescriptor)
257 {
258 for (Variable variable : descriptor.getVariables())
259 {
260 String varName = variable.getName();
261 value = (DescriptorValue) iac.getProperty(descSpec);
262 double val = processValue(descName, descriptor, desc,
263 descSpec, value, varName, iac);
264 valuesMap.put(varName, val);
265 iac.setProperty(varName,val);
266 }
267 } else if (desc instanceof IAtomicDescriptor) {
268 for (Variable variable : descriptor.getVariables())
269 {
270 String varName = variable.getName();
271 Mappings hits = allMatches.get(varName);
272 if (hits==null)
273 {
274 String msg = "No hits for SMARTS of " + varName + ": "
275 + "setting variable value to 0.0";
276 logger.log(Level.WARNING ,msg);
277 valuesMap.put(varName, 0.0);
278 continue;
279 }
280 logger.log(Level.FINE, "-AtomIDs contributing to "
281 + varName + ":" + hits);
282 if (hits.count() > 1)
283 {
284 String msg = "Multiple hits with SMARTS identifier for "
285 + varName + ". Taking average of all values.";
286 logger.log(Level.WARNING ,msg);
287 }
288 int valCounter = -1;
289 List<Double> vals = new ArrayList<Double>();
290 for (int[] singleMatch : hits)
291 {
292 if (singleMatch.length != 1)
293 {
294 String msg = "Multiple entries in a single hit "
295 + "with SMARTS identifier for "
296 + varName + ". Taking average of values.";
297 logger.log(Level.WARNING ,msg);
298 }
299 for (Integer atmId : singleMatch)
300 {
301 IAtom atm = iac.getAtom(atmId);
302 value = (DescriptorValue) atm.getProperty(descSpec);
303 double val = processValue(descName, descriptor,
304 desc, descSpec, value, varName, iac);
305 vals.add(val);
306 valCounter++;
307 iac.setProperty(varName+"_"+valCounter,val);
308 }
309 }
310 logger.log(Level.FINE, "-Values contributing to "
311 + varName + ": " + vals);
312 double overallValue = MathUtils.mean(vals);
313 valuesMap.put(varName, overallValue);
314 iac.setProperty(varName,overallValue);
315 }
316 } else if (desc instanceof IBondDescriptor) {
317 for (Variable variable : descriptor.getVariables())
318 {
319 String varName = variable.getName();
320 Mappings hits = allMatches.get(varName);
321 if (hits==null)
322 {
323 String msg = "No hits for SMARTS of " + varName + ": "
324 + "setting variable value to 0.0";
325 logger.log(Level.WARNING, msg);
326 valuesMap.put(varName, 0.0);
327 continue;
328 }
329 logger.log(Level.FINE, "-AtomIDs contributing to "
330 + varName + ":" + hits);
331 if (hits.count() > 1)
332 {
333 String msg = "Multiple hits with SMARTS identifier for "
334 + varName + ". Taking average of all values.";
335 logger.log(Level.WARNING, msg);
336 }
337 int valCounter = -1;
338 List<Double> vals = new ArrayList<Double>();
339 for (int[] singleMatch : hits)
340 {
341 if (singleMatch.length != 2)
342 {
343 String msg = "Number of entries is != 2 for a "
344 + "single hit with SMARTS identifier for "
345 + varName + ". I do not know how to deal "
346 + "with this.";
347 throw new DENOPTIMException(msg);
348 }
349 IBond bnd = iac.getBond(iac.getAtom(singleMatch[0]),
350 iac.getAtom(singleMatch[1]));
351 value = (DescriptorValue) bnd.getProperty(descSpec);
352 double val = processValue(descName, descriptor,
353 desc, descSpec, value, varName, iac);
354 vals.add(val);
355 valCounter++;
356 iac.setProperty(varName+"_"+valCounter,val);
357 }
358 logger.log(Level.FINE, "-Values contributing to "
359 + varName + ": "+vals);
360 double overallValue = MathUtils.mean(vals);
361 valuesMap.put(varName, overallValue);
362 iac.setProperty(varName,overallValue);
363 }
364 } else if (desc instanceof IAtomPairDescriptor) {
365 throw new Exception("AtomPair-kind of descriptors are not yet "
366 + " usable. Upgrade the code. ");
367 //TODO: implement this part...
368 } else {
369 throw new Exception("Type of descriptor "+ descName + " is "
370 + "unknown. Cannot understand if it should be threated "
371 + "as molecular, atomic, or bond descriptor.");
372 }
373 }
374 logger.log(Level.FINE, "VARIABLES: "+valuesMap);
375
376 // Calculate the fitness from the expression and descriptor values
377 ExpressionFactory expFactory = ExpressionFactory.newInstance();
378 ELContext ncc = new ELContext() {
379
380 VariableMapper vm = new VariableMapper() {
381
382 @SuppressWarnings("serial")
383 @Override
384 public ValueExpression resolveVariable(String varName)
385 {
386 ValueExpression ve = new ValueExpression() {
387
388 @Override
389 public Object getValue(ELContext context)
390 {
391 Double value = null;
392 if (!valuesMap.containsKey(varName))
393 {
394 throw new ELException("Variable '" + varName
395 + "' cannot be resolved");
396 } else {
397 value = valuesMap.get(varName);
398 }
399 return value;
400 }
401
402 @Override
403 public void setValue(ELContext context, Object value)
404 {}
405
406 @Override
407 public boolean isReadOnly(ELContext context)
408 {
409 return true;
410 }
411
412 @Override
413 public Class<?> getType(ELContext context)
414 {
415 return Double.class;
416 }
417
418 @Override
419 public Class<?> getExpectedType()
420 {
421 return Double.class;
422 }
423
424 @Override
425 public String getExpressionString()
426 {
427 return null;
428 }
429
430 @Override
431 public boolean equals(Object obj)
432 {
433 return false;
434 }
435
436 @Override
437 public int hashCode()
438 {
439 return 0;
440 }
441
442 @Override
443 public boolean isLiteralText()
444 {
445 return false;
446 }
447 };
448 return ve;
449 }
450
451 @Override
452 public ValueExpression setVariable(String variable,
453 ValueExpression expression)
454 {
455 return null;
456 }};
457
458 @Override
459 public ELResolver getELResolver()
460 {
461 return null;
462 }
463
464 @Override
465 public FunctionMapper getFunctionMapper()
466 {
467 return null;
468 }
469
470 @Override
471 public VariableMapper getVariableMapper()
472 {
473 return vm;
474 }};
475 ValueExpression ve = expFactory.createValueExpression(ncc, expression,
476 Double.class);
477 double fitness = (double) ve.getValue(ncc);
478 iac.setProperty(DENOPTIMConstants.FITNESSTAG,fitness);
479 return fitness;
480 }
481
482//------------------------------------------------------------------------------
483
491 private double processValue(String descName,
492 DescriptorForFitness descriptor,
493 IDescriptor implementation,
494 IImplementationSpecification descSpec, DescriptorValue value,
495 String varName, IAtomContainer iac) throws Exception
496 {
497 if (value == null)
498 {
499 throw new Exception("Null value from calcualation of descriptor"
500 + " " + descName + " (for variable '" + varName + "'");
501 }
502 IDescriptorResult result = value.getValue();
503 if (result == null)
504 {
505 throw new Exception("Null result from calcualation of "
506 + "descriptor " + descName + "(for variable '"
507 + varName + "'");
508 }
509
510 double valueToFitness = Double.NaN;
511
512 if (result instanceof DoubleResult)
513 {
514 valueToFitness = ((DoubleResult) result).doubleValue();
515 } else if (result instanceof IntegerResult)
516 {
517 valueToFitness = ((IntegerResult)result).intValue();
518 } else if (result instanceof DoubleArrayResult)
519 {
520 DoubleArrayResult a = (DoubleArrayResult) result;
521 int id = descriptor.resultId;
522 if (id >= a.length())
523 {
524 throw new Exception("Value ID out of range for descriptor "
525 + descName);
526 }
527 valueToFitness = a.get(id);
528
529 //We also keep track of the entire vector
530 List<String> list = new ArrayList<String>();
531 for (int j=0; j<a.length(); j++)
532 {
533 list.add(String.valueOf(a.get(j)));
534 }
535 iac.setProperty(implementation.getClass().getName() + "#" + varName,
536 list);
537 } else if (result instanceof IntegerArrayResult)
538 {
539 IntegerArrayResult array = (IntegerArrayResult) result;
540 int id = descriptor.resultId;
541 if (id >= array.length())
542 {
543 throw new Exception("Value ID out of range for descriptor "
544 + descName);
545 }
546 valueToFitness = array.get(id);
547
548 //We also keep track of the entire vector
549 List<String> list = new ArrayList<String>();
550 for (int j=0; j<array.length(); j++)
551 {
552 list.add(String.valueOf(array.get(j)));
553 }
554 iac.setProperty(implementation.getClass().getName()+ "#" + varName,
555 list);
556 }
557
558 return valueToFitness;
559 }
560
561//------------------------------------------------------------------------------
562
563}
General set of constants used in DENOPTIM.
static final String DUMMYATMSYMBOL
Symbol of dummy atom.
static final String FITNESSTAG
SDF tag containing the fitness of a candidate.
This is a reference to a specific descriptor value.
List< Variable > getVariables()
Get the variables that make use of values produced by this descriptor.
DescriptorForFitness cloneAllButImpl()
This is a sort of cloning that returns a new DescriptorForFitness with the same field content of this...
String shortName
Descriptor short name.
DENOPTIM's (internal) fitness provider calculates the value of Variables that are used in an expressi...
DescriptorEngine engine
The engine that collects and calculates descriptors.
double processValue(String descName, DescriptorForFitness descriptor, IDescriptor implementation, IImplementationSpecification descSpec, DescriptorValue value, String varName, IAtomContainer iac)
Takes the value and checks that it is all good, then processes the value to extract the result define...
List< DescriptorForFitness > descriptors
The collection of descriptors to consider.
double getFitness(IAtomContainer iac)
Calculated the fitness according to the current configuration.
FitnessProvider(List< DescriptorForFitness > descriptors, String expression, Logger logger)
Constructs an instance that will calculate the fitness according to the given parameters.
Logger logger
Program-specific logger.
String expression
The equation used to calculate the fitness value.
A variable in the expression defining the fitness.
Definition: Variable.java:42
Toll to add/remove dummy atoms from linearities or multi-hapto sites.
IAtomContainer removeDummy(IAtomContainer mol)
Removes all dummy atoms and the bonds connecting them to other atoms.
IAtomContainer removeDummyInHapto(IAtomContainer mol)
Container of lists of atoms matching a list of SMARTS.
Map< String, Mappings > getAllMatches()
Some useful math operations.
Definition: MathUtils.java:39
static double mean(double[] a)
Calculate mean value.
Definition: MathUtils.java:396