$darkmode
DENOPTIM
SocketProvidedDescriptor.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.descriptors;
20
21import java.io.BufferedReader;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.InputStreamReader;
25import java.io.OutputStream;
26import java.io.PrintWriter;
27import java.net.Socket;
28
29import org.openscience.cdk.exception.CDKException;
30import org.openscience.cdk.interfaces.IAtomContainer;
31import org.openscience.cdk.qsar.AbstractMolecularDescriptor;
32import org.openscience.cdk.qsar.DescriptorSpecification;
33import org.openscience.cdk.qsar.DescriptorValue;
34import org.openscience.cdk.qsar.IMolecularDescriptor;
35import org.openscience.cdk.qsar.result.DoubleResult;
36import org.openscience.cdk.qsar.result.DoubleResultType;
37import org.openscience.cdk.qsar.result.IDescriptorResult;
38
39import com.google.gson.Gson;
40import com.google.gson.GsonBuilder;
41import com.google.gson.JsonObject;
42import com.google.gson.JsonSyntaxException;
43
44import denoptim.constants.DENOPTIMConstants;
45import denoptim.fitness.IDenoptimDescriptor;
46
47
62// WARNING: any change to the format convention for the communication to the
63// socket server must be reflected in the RequestHandler of the unit test.
64
65public class SocketProvidedDescriptor extends AbstractMolecularDescriptor
66implements IMolecularDescriptor, IDenoptimDescriptor
67{
71 private final int version = 1;
72
77 public final static String KEYJSONMEMBERSMILES = "SMILES";
78
83 public final static String KEYJSONMEMBERSCORE = "SCORE";
84
89 public final static String KEYJSONMEMBERERR = "ERROR";
90
95 private String hostname;
96
100 private Integer port;
101
105 private static final String[] PARAMNAMES = new String[] {
106 "hostname","port"};
107
111 private static final String[] NAMES = {"SocketProvidedDescriptor"};
112
113//------------------------------------------------------------------------------
114
119
120//------------------------------------------------------------------------------
121
126 @Override
127 public DescriptorSpecification getSpecification()
128 {
129 String paramID = "";
130 if (hostname!=null && port!=null)
131 {
132 paramID = "" + hostname + port;
133 }
134 return new DescriptorSpecification("Denoptim source code",
135 this.getClass().getName(), paramID, "DENOPTIM project");
136 }
137
138//------------------------------------------------------------------------------
139
144 @Override
145 public String[] getParameterNames() {
146 return PARAMNAMES;
147 }
148
149//------------------------------------------------------------------------------
150
152 @Override
153 public Object getParameterType(String name)
154 {
155 if (name.equals(PARAMNAMES[1])) //port
156 {
157 return 0;
158 } else if (name.equals(PARAMNAMES[0])) // hostname
159 {
160 return "";
161 } else {
162 throw new IllegalArgumentException("No parameter for name: "+name);
163 }
164 }
165
166//------------------------------------------------------------------------------
167
173 @Override
174 public void setParameters(Object[] params) throws CDKException
175 {
176 if (params.length != 2)
177 {
178 throw new IllegalArgumentException("SocketProvidedDescriptor only "
179 + "expects two parameter");
180 }
181 if (!(params[0] instanceof String))
182 {
183 throw new IllegalArgumentException("Parameter is not String ("
184 + params[0].getClass().getName() + ").");
185 }
186 if (!(params[1] instanceof Integer))
187 {
188 throw new IllegalArgumentException("Parameter is not Integer ("
189 + params[0].getClass().getName() + ").");
190 }
191
192 hostname = (String) params[0];
193 port = (Integer) params[1];
194 }
195
196//------------------------------------------------------------------------------
197
199 @Override
200 public Object[] getParameters()
201 {
202 Object[] params = new Object[2];
203 params[0] = hostname;
204 params[1] = port;
205 return params;
206 }
207
208//------------------------------------------------------------------------------
209
211 @Override
212 public String[] getDescriptorNames()
213 {
214 return NAMES;
215 }
216
217//------------------------------------------------------------------------------
218
220 @Override
221 public DescriptorValue calculate(IAtomContainer mol)
222 {
223 Socket socket;
224 try
225 {
226 socket = new Socket(hostname, port);
227 } catch (IOException e1)
228 {
229 throw new IllegalArgumentException("Could not connect to socket",e1);
230 }
231
232 Runtime.getRuntime().addShutdownHook(new Thread(){public void run(){
233 try {
234 socket.close();
235 } catch (IOException e) { /* failed */ }
236 }});
237
238 PrintWriter writerToSocket;
239 try
240 {
241 OutputStream outputSocket = socket.getOutputStream();
242 writerToSocket = new PrintWriter(outputSocket, true);
243 } catch (IOException e1)
244 {
245 try
246 {
247 socket.close();
248 } catch (IOException e)
249 {
250 e.printStackTrace();
251 }
252 throw new IllegalArgumentException("Could not connect to socket",e1);
253 }
254
255 BufferedReader readerFromSocket;
256 try
257 {
258 InputStream inputFromSocket = socket.getInputStream();
259 readerFromSocket = new BufferedReader(
260 new InputStreamReader(inputFromSocket));
261 } catch (IOException e1)
262 {
263 try
264 {
265 socket.close();
266 } catch (IOException e)
267 {
268 e.printStackTrace();
269 }
270 throw new IllegalArgumentException("Could not read from socket",e1);
271 }
272
273 JsonObject jsonObj = new JsonObject();
274 Object smilesProp = mol.getProperty("SMILES");
275 if (smilesProp==null)
276 {
277 try
278 {
279 socket.close();
280 } catch (IOException e)
281 {
282 e.printStackTrace();
283 }
284 throw new IllegalArgumentException("AtomContainers fed to "
285 + this.getClass().getName() + " are expected to contain "
286 + "property '" + KEYJSONMEMBERSMILES
287 + "', but it was not found.");
288 }
289 jsonObj.addProperty("SMILES", smilesProp.toString());
290 jsonObj.addProperty("version", version);
291
292 // So far there is no need of DENOPTIM's customized Gson builder.
293 Gson jsonConverted = new GsonBuilder().create();
294
295 // Here we send the request to the socket
296 writerToSocket.println(jsonConverted.toJson(jsonObj));
297 try
298 {
299 socket.shutdownOutput();
300 } catch (IOException e1)
301 {
302 try
303 {
304 socket.close();
305 } catch (IOException e)
306 {
307 // At this point the socket is probably closed already...
308 }
309 throw new IllegalStateException("Could not half-close socket from "
310 + this.getClass().getName(),e1);
311 }
312
313 JsonObject answer = null;
314 DoubleResult result;
315 Exception potentialProblem = null;
316 try {
317 answer = jsonConverted.fromJson(readerFromSocket.readLine(),
318 JsonObject.class);
319 if (answer.has(KEYJSONMEMBERSCORE))
320 {
321 double value = Double.parseDouble(
322 answer.get(KEYJSONMEMBERSCORE).toString());
323 result = new DoubleResult(value);
324 } else if (answer.has(KEYJSONMEMBERERR)) {
325 result = new DoubleResult(Double.NaN);
326 mol.setProperty(DENOPTIMConstants.MOLERRORTAG,
327 answer.get(KEYJSONMEMBERERR).toString());
328 } else {
329 System.err.println("ERROR: Socket server replied without "
330 + "providing either " + KEYJSONMEMBERSCORE + " or "
331 + KEYJSONMEMBERERR + " member. Setting desctriptor "
332 + "'" + NAMES[0] + "'to NaN.");
333
334 result = new DoubleResult(Double.NaN);
335 potentialProblem = new Exception(
336 "Wrong syntax in answer from socket server.");
337 }
338 } catch (JsonSyntaxException | IOException e) {
339 e.printStackTrace();
340 result = new DoubleResult(Double.NaN);
341 potentialProblem = e;
342 }
343
344 try
345 {
346 socket.close();
347 } catch (IOException e)
348 {
349 e.printStackTrace();
350 }
351
352 return new DescriptorValue(getSpecification(),
355 result,
357 potentialProblem);
358 }
359
360//------------------------------------------------------------------------------
361
363 @Override
364 public IDescriptorResult getDescriptorResultType()
365 {
366 return new DoubleResultType();
367 }
368
369//------------------------------------------------------------------------------
370
372 @Override
373 public String getDictionaryTitle()
374 {
375 return "Socket-provided descriptor";
376 }
377
378//------------------------------------------------------------------------------
379
381 @Override
383 {
384 return "We ignore how the descriptor is calculated. We only know we "
385 + "can ask a socket server for a score. The connection can be "
386 + "parametrized so that we can define that a server is "
387 + "reachable at host name <code>" + PARAMNAMES[0] + "</code>, "
388 + "and port <code>" + PARAMNAMES[1] + "</code>. By convention, "
389 + "the communication deploys a JSON string. "
390 + "Such format is expected for both "
391 + "the request for a score (the request generated by "
392 + "DENOPTIM, i.e., the client) and for "
393 + "the answer to such request (produced by the "
394 + "server). The requst contains always the SMILES of the "
395 + "candidate (e.g., "
396 + "<code>{\"" + KEYJSONMEMBERSMILES + "\": \"CCO\"}</code>). "
397 + "The answer may "
398 + "contain a <code>" + KEYJSONMEMBERSCORE + "</code> "
399 + "or an <code>" + KEYJSONMEMBERERR + "</code> "
400 + "(e.g., <code>{\"" + KEYJSONMEMBERSCORE + "\": 1.23}</code>). "
401 + "Failure in the "
402 + "communication protocol will produce a <code>NaN</code> "
403 + "score.";
404 }
405
406//------------------------------------------------------------------------------
407
409 @Override
410 public String[] getDictionaryClass()
411 {
412 return new String[] {"molecular"};
413 }
414
415//------------------------------------------------------------------------------
416
417}
General set of constants used in DENOPTIM.
static final String MOLERRORTAG
SDF tag containing errors during execution of molecule specific tasks.
Sends the request to produce a numerical descriptor to a defined socket and receives back the respons...
void setParameters(Object[] params)
Set the parameters attributes.
DescriptorSpecification getSpecification()
Get the specification attribute of socket-based descriptor provider.
SocketProvidedDescriptor()
Constructor for a SocketProvidedDescriptor object.
Integer port
The identifier of the port used to communicate with the socket server.
String[] getParameterNames()
Gets the parameterNames attribute of the TanimotoMolSimilarity object.
static final String[] PARAMNAMES
Name of the input parameters.
String hostname
The name of the host or ID address used to communicate with the socket server.
static final String KEYJSONMEMBERSCORE
The key of the JSON member defining the score/s for the descriptor calculated.
static final String KEYJSONMEMBERSMILES
The key of the JSON member defining the SMILES of the candidate for which the socket server should pr...
static final String[] NAMES
NAme of the descriptor produced by this class.
String[] getDictionaryClass()
Get the classification of this descriptor.A descriptor can belong to one or more classes simultaneous...
String getDictionaryTitle()
Gets the title of this descriptor as it should be in the dictionary.the title
static final String KEYJSONMEMBERERR
The key of the JSON member defining an error in the calculation of the score.
String getDictionaryDefinition()
Get a string that describes the descriptor in detail.Might contain mathematical formulation....
This interface forces descriptors that are not defined in the CDK ontology to provide information tha...