$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.fitness.IDenoptimDescriptor;
45
46
61// WARNING: any change to the format convention for the communication to the
62// socket server must be reflected in the RequestHandler of the unit test.
63
64public class SocketProvidedDescriptor extends AbstractMolecularDescriptor
65implements IMolecularDescriptor, IDenoptimDescriptor
66{
70 private final int version = 1;
71
76 public final static String KEYJSONMEMBERSMILES = "SMILES";
77
82 public final static String KEYJSONMEMBERSCORE = "SCORE";
83
88 public final static String KEYJSONMEMBERERR = "ERROR";
89
94 private String hostname;
95
99 private Integer port;
100
104 private static final String[] PARAMNAMES = new String[] {
105 "hostname","port"};
106
110 private static final String[] NAMES = {"SocketProvidedDescriptor"};
111
112//------------------------------------------------------------------------------
113
118
119//------------------------------------------------------------------------------
120
125 @Override
126 public DescriptorSpecification getSpecification()
127 {
128 String paramID = "";
129 if (hostname!=null && port!=null)
130 {
131 paramID = "" + hostname + port;
132 }
133 return new DescriptorSpecification("Denoptim source code",
134 this.getClass().getName(), paramID, "DENOPTIM project");
135 }
136
137//------------------------------------------------------------------------------
138
143 @Override
144 public String[] getParameterNames() {
145 return PARAMNAMES;
146 }
147
148//------------------------------------------------------------------------------
149
151 @Override
152 public Object getParameterType(String name)
153 {
154 if (name.equals(PARAMNAMES[1])) //port
155 {
156 return 0;
157 } else if (name.equals(PARAMNAMES[0])) // hostname
158 {
159 return "";
160 } else {
161 throw new IllegalArgumentException("No parameter for name: "+name);
162 }
163 }
164
165//------------------------------------------------------------------------------
166
172 @Override
173 public void setParameters(Object[] params) throws CDKException
174 {
175 if (params.length != 2)
176 {
177 throw new IllegalArgumentException("SocketProvidedDescriptor only "
178 + "expects two parameter");
179 }
180 if (!(params[0] instanceof String))
181 {
182 throw new IllegalArgumentException("Parameter is not String ("
183 + params[0].getClass().getName() + ").");
184 }
185 if (!(params[1] instanceof Integer))
186 {
187 throw new IllegalArgumentException("Parameter is not Integer ("
188 + params[0].getClass().getName() + ").");
189 }
190
191 hostname = (String) params[0];
192 port = (Integer) params[1];
193 }
194
195//------------------------------------------------------------------------------
196
198 @Override
199 public Object[] getParameters()
200 {
201 Object[] params = new Object[2];
202 params[0] = hostname;
203 params[1] = port;
204 return params;
205 }
206
207//------------------------------------------------------------------------------
208
210 @Override
211 public String[] getDescriptorNames()
212 {
213 return NAMES;
214 }
215
216//------------------------------------------------------------------------------
217
219 @Override
220 public DescriptorValue calculate(IAtomContainer mol)
221 {
222 Socket socket;
223 try
224 {
225 socket = new Socket(hostname, port);
226 } catch (IOException e1)
227 {
228 throw new IllegalArgumentException("Could not connect to socket",e1);
229 }
230
231 Runtime.getRuntime().addShutdownHook(new Thread(){public void run(){
232 try {
233 socket.close();
234 } catch (IOException e) { /* failed */ }
235 }});
236
237 PrintWriter writerToSocket;
238 try
239 {
240 OutputStream outputSocket = socket.getOutputStream();
241 writerToSocket = new PrintWriter(outputSocket, true);
242 } catch (IOException e1)
243 {
244 try
245 {
246 socket.close();
247 } catch (IOException e)
248 {
249 e.printStackTrace();
250 }
251 throw new IllegalArgumentException("Could not connect to socket",e1);
252 }
253
254 BufferedReader readerFromSocket;
255 try
256 {
257 InputStream inputFromSocket = socket.getInputStream();
258 readerFromSocket = new BufferedReader(
259 new InputStreamReader(inputFromSocket));
260 } catch (IOException e1)
261 {
262 try
263 {
264 socket.close();
265 } catch (IOException e)
266 {
267 e.printStackTrace();
268 }
269 throw new IllegalArgumentException("Could not read from socket",e1);
270 }
271
272 JsonObject jsonObj = new JsonObject();
273 Object smilesProp = mol.getProperty("SMILES");
274 if (smilesProp==null)
275 {
276 try
277 {
278 socket.close();
279 } catch (IOException e)
280 {
281 e.printStackTrace();
282 }
283 throw new IllegalArgumentException("AtomContainers fed to "
284 + this.getClass().getName() + " are expected to contain "
285 + "property '" + KEYJSONMEMBERSMILES
286 + "', but it was not found.");
287 }
288 jsonObj.addProperty("SMILES", smilesProp.toString());
289 jsonObj.addProperty("version", version);
290
291 // So far there is no need of DENOPTIM's customized Gson builder.
292 Gson jsonConverted = new GsonBuilder().create();
293
294 // Here we send the request to the socket
295 writerToSocket.println(jsonConverted.toJson(jsonObj));
296 try
297 {
298 socket.shutdownOutput();
299 } catch (IOException e1)
300 {
301 try
302 {
303 socket.close();
304 } catch (IOException e)
305 {
306 // At this point the socket is probably closed already...
307 }
308 throw new IllegalStateException("Could not half-close socket from "
309 + this.getClass().getName(),e1);
310 }
311
312 JsonObject answer = null;
313 DoubleResult result;
314 try {
315 answer = jsonConverted.fromJson(readerFromSocket.readLine(),
316 JsonObject.class);
317 if (answer.has(KEYJSONMEMBERSCORE))
318 {
319 double value = Double.parseDouble(
320 answer.get(KEYJSONMEMBERSCORE).toString());
321 result = new DoubleResult(value);
322 } else if (answer.has(KEYJSONMEMBERERR)) {
323 //System.err.println(KEYJSONMEMBERERR + " from socket server.");
324 result = new DoubleResult(Double.NaN);
325 } else {
326 System.err.println("WARNING: Socket server replied without "
327 + "providing either " + KEYJSONMEMBERSCORE + " or "
328 + KEYJSONMEMBERERR + " member. Setting desctriptor "
329 + "'" + NAMES[0] + "'to NaN.");
330 result = new DoubleResult(Double.NaN);
331 }
332 } catch (JsonSyntaxException | IOException e) {
333 e.printStackTrace();
334 result = new DoubleResult(Double.NaN);
335 }
336
337 try
338 {
339 socket.close();
340 } catch (IOException e)
341 {
342 e.printStackTrace();
343 }
344
345 return new DescriptorValue(getSpecification(),
348 result,
350 }
351
352//------------------------------------------------------------------------------
353
355 @Override
356 public IDescriptorResult getDescriptorResultType()
357 {
358 return new DoubleResultType();
359 }
360
361//------------------------------------------------------------------------------
362
364 @Override
365 public String getDictionaryTitle()
366 {
367 return "Socket-provided descriptor";
368 }
369
370//------------------------------------------------------------------------------
371
373 @Override
375 {
376 return "We ignore how the descriptor is calculated. We only know we "
377 + "can ask a socket server for a score. The connection can be "
378 + "parametrized so that we can define that a server is "
379 + "reachable at host name <code>" + PARAMNAMES[0] + "</code>, "
380 + "and port <code>" + PARAMNAMES[1] + "</code>. By convention, "
381 + "the communication deploys a JSON string. "
382 + "Such format is expected for both "
383 + "the request for a score (the request generated by "
384 + "DENOPTIM, i.e., the client) and for "
385 + "the answer to such request (produced by the "
386 + "server). The requst contains always the SMILES of the "
387 + "candidate (e.g., "
388 + "<code>{\"" + KEYJSONMEMBERSMILES + "\": \"CCO\"}</code>). "
389 + "The answer may "
390 + "contain a <code>" + KEYJSONMEMBERSCORE + "</code> "
391 + "or an <code>" + KEYJSONMEMBERERR + "</code> "
392 + "(e.g., <code>{\"" + KEYJSONMEMBERSCORE + "\": 1.23}</code>). "
393 + "Failure in the "
394 + "communication protocol will produce a <code>NaN</code> "
395 + "score.";
396 }
397
398//------------------------------------------------------------------------------
399
401 @Override
402 public String[] getDictionaryClass()
403 {
404 return new String[] {"molecular"};
405 }
406
407//------------------------------------------------------------------------------
408
409}
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...