Skip to content

Commit

Permalink
#1436 Expose Fold/Unfold hydrogens function in indigo API (#1437)
Browse files Browse the repository at this point in the history
  • Loading branch information
AliaksandrDziarkach authored Dec 15, 2023
1 parent bbb607f commit 81744f3
Show file tree
Hide file tree
Showing 13 changed files with 376 additions and 103 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/indigo-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1318,14 +1318,15 @@ jobs:
path: utils/indigo-service/backend/lib/
- name: Build
run: docker build -f ./utils/indigo-service/backend/Dockerfile -t epmlsop/indigo-service:latest ./utils/indigo-service/backend
- name: Test Imago
- name: Test Imago & Indigo
run: |
docker run --rm=true -d -p 8080:80 --name=indigo_service epmlsop/indigo-service:latest
sleep 10
docker logs indigo_service
docker ps
export INDIGO_SERVICE_URL=http://localhost:8080/v2
python3 utils/indigo-service/backend/service/tests/api/imago_test.py
python3 utils/indigo-service/backend/service/tests/api/indigo_test.py
docker logs indigo_service
docker stop indigo_service
# TODO: add indigo tests
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ Indigo/build>cmake --build . --config Release --target all
or any of the following targets could be specified: --target { indigo-dotnet | indigo-java | indigo-python }
Build results could be collected from Indigo/dist folder.

## Run tests ##

Befo run any test you have to build and install indigo-python
1) Build indigo-python using '--target all' or '--target indigo=python'.
Package should be in 'build' directory, it will be named like 'epam.indigo-version-arch.whl'
3) Install package using pip `python -m pip uninstall epam.indigo -y ; python -m pip install dist/epam.indigo-version-arch.whl`

Run integration test using `python api/tests/integration/test.py -t 1` for all test, or `python api/tests/integration/test.py -t 1 -p test_name` to run tests by mask `test_name`.

To run backend API test:
1) Install epam-indigo
2) Install waitress `python pip install waitress`
3) Run backend service :
* `cd utils/indigo-service/backend/service`
* `cp v2/common/config.py .`
* `waitress-serve --listen="127.0.0.1:5000 [::1]:5000" app:app` you may use any port instead of 5000
4) Run backend API test:
* set environment variable `export INDIGO_SERVICE_URL=http://localhost:5000/v2` (in powershell `$env:INDIGO_SERVICE_URL="http://localhost:5000/v2"`)
* run test `python utils/indigo-service/backend/service/tests/api/indigo_test.py` use `-k test_name` to run test by pattern.

## How to build Indigo-WASM ##

### Build tools prerequisites ###
Expand Down Expand Up @@ -124,7 +144,7 @@ Make sure it's running from path:
>source ./emsdk_env.sh
```

Note: On Windows, run `emsdk` instead of `./emsdk`, and `emsdk_env.bat` instead of source `./emsdk_env.sh`.
Note: On Windows, run `emsdk` instead of `./emsdk`, and `emsdk_env.bat` instead of source `./emsdk_env.sh`, use `cmd` instead of `powershell`.

### Get Indigo sources ###

Expand Down
1 change: 1 addition & 0 deletions api/c/indigo/src/indigo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ void Indigo::init()
max_embeddings = 10000;

layout_max_iterations = 0;
layout_preserve_existing = false;

molfile_saving_skip_date = false;

Expand Down
1 change: 1 addition & 0 deletions api/c/indigo/src/indigo_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ class DLLEXPORT Indigo
int layout_max_iterations; // default is zero -- no limit
bool smart_layout = false;
float layout_horintervalfactor = ReactionLayout::DEFAULT_HOR_INTERVAL_FACTOR;
bool layout_preserve_existing = false;

int layout_orientation = 0;

Expand Down
4 changes: 3 additions & 1 deletion api/c/indigo/src/indigo_layout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ CEXPORT int indigoLayout(int object)
ml.max_iterations = self.layout_max_iterations;
ml.bond_length = MoleculeLayout::DEFAULT_BOND_LENGTH;
ml.layout_orientation = (layout_orientation_value)self.layout_orientation;
if (mol->hasAtropoStereoBonds())
if (self.layout_preserve_existing || mol->hasAtropoStereoBonds())
ml.respect_existing_layout = true;

TimeoutCancellationHandler cancellation(self.cancellation_timeout);
Expand Down Expand Up @@ -107,6 +107,8 @@ CEXPORT int indigoLayout(int object)
rl.layout_orientation = (layout_orientation_value)self.layout_orientation;
rl.bond_length = MoleculeLayout::DEFAULT_BOND_LENGTH;
rl.horizontal_interval_factor = self.layout_horintervalfactor;
if (self.layout_preserve_existing)
rl.preserve_molecule_layout = true;
rl.make();
try
{
Expand Down
2 changes: 1 addition & 1 deletion api/c/indigo/src/indigo_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ void IndigoOptionHandlerSetter::setBasicOptionHandlers(const qword id)
mgr->setOptionHandlerInt("max-embeddings", indigoSetMaxEmbeddings, indigoGetMaxEmbeddings);

mgr->setOptionHandlerInt("layout-max-iterations", SETTER_GETTER_INT_OPTION(indigo.layout_max_iterations));

mgr->setOptionHandlerInt("layout-preserve-existing", SETTER_GETTER_BOOL_OPTION(indigo.layout_preserve_existing));
mgr->setOptionHandlerFloat("layout-horintervalfactor", indigoSetLayoutHorIntervalFactor, indigoGetLayoutHorIntervalFactor);

mgr->setOptionHandlerInt("aam-timeout", SETTER_GETTER_INT_OPTION(indigo.aam_cancellation_timeout));
Expand Down
79 changes: 26 additions & 53 deletions api/python/indigo/indigo/indigo.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,68 +129,41 @@ def setOption(self, option, value1, value2=None, value3=None):
IndigoException: if option does not exist
"""

opt = option.encode()
if (
(
type(value1).__name__ == "str"
or type(value1).__name__ == "unicode"
)
and value2 is None
and value3 is None
):
IndigoLib.checkResult(
self._lib().indigoSetOption(
option.encode(),
value1.encode(),
)
)
elif (
type(value1).__name__ == "int"
and value2 is None
and value3 is None
isinstance(value1, float)
and isinstance(value2, float)
and isinstance(value3, float)
):
IndigoLib.checkResult(
self._lib().indigoSetOptionInt(option.encode(), value1)
self._lib().indigoSetOptionColor(opt, value1, value2, value3)
)
elif (
type(value1).__name__ == "float"
and value2 is None
isinstance(value1, int)
and isinstance(value2, int)
and value3 is None
):
IndigoLib.checkResult(
self._lib().indigoSetOptionFloat(option.encode(), value1)
)
elif (
type(value1).__name__ == "bool"
and value2 is None
and value3 is None
):
value1_b = 0
if value1:
value1_b = 1
IndigoLib.checkResult(
self._lib().indigoSetOptionBool(option.encode(), value1_b)
)
elif (
type(value1).__name__ == "int"
and value2
and type(value2).__name__ == "int"
and value3 is None
):
IndigoLib.checkResult(
self._lib().indigoSetOptionXY(option.encode(), value1, value2)
)
elif (
type(value1).__name__ == "float"
and value2
and type(value2).__name__ == "float"
and value3
and type(value3).__name__ == "float"
):
IndigoLib.checkResult(
self._lib().indigoSetOptionColor(
option.encode(), value1, value2, value3
)
self._lib().indigoSetOptionXY(opt, value1, value2)
)
elif value2 is None and value3 is None:
if isinstance(value1, str):
setOpt = self._lib().indigoSetOption
value = value1.encode()
elif isinstance(value1, int):
setOpt = self._lib().indigoSetOptionInt
value = value1
elif isinstance(value1, float):
setOpt = self._lib().indigoSetOptionFloat
value = value1
elif isinstance(value1, bool):
value1 = 0
if value1:
value1 = 1
setOpt = self._lib().indigoSetOptionBool
else:
raise IndigoException("bad option")
IndigoLib.checkResult(setOpt(opt, value))
else:
raise IndigoException("bad option")

Expand Down
1 change: 0 additions & 1 deletion api/python/indigo/indigo/indigo_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -3373,7 +3373,6 @@ def layout(self):
Returns:
int: 1 if there are no errors
"""

return IndigoLib.checkResult(self._lib().indigoLayout(self.id))

def smiles(self):
Expand Down
49 changes: 49 additions & 0 deletions api/wasm/indigo-ketcher/indigo-ketcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,54 @@ namespace indigo
return iko.toString(options, outputFormat.size() ? outputFormat : "ket");
}

std::string convert_explicit_hydrogens(const std::string& data, const std::string& mode, const std::string& outputFormat,
const std::map<std::string, std::string>& options)
{
const IndigoSession session;
indigoSetOptions(options);
std::map<std::string, std::string> options_copy = options;
if (outputFormat.find("smarts") != std::string::npos)
{
options_copy["query"] = "true";
}
IndigoKetcherObject iko = loadMoleculeOrReaction(data, options_copy);
bool fold = false;
if (mode == "fold")
{
fold = true;
}
else if (mode == "unfold")
{
fold = false;
}
else if (mode == "auto")
{
IndigoObject iatoms(_checkResult(indigoIterateAtoms(iko.id())));
while (_checkResult(indigoHasNext(iatoms.id)))
{
IndigoObject atom(_checkResult(indigoNext(iatoms.id)));
// indigoAtomicNumber can return -1 for non-standard atoms
// just skip these atoms
if (indigoAtomicNumber(atom.id) == 1) // hydrogen
{
fold = true;
break;
}
}
}
if (fold)
{
_checkResult(indigoFoldHydrogens(iko.id()));
}
else
{
indigoSetOptionBool("layout-preserve-existing", true);
_checkResult(indigoUnfoldHydrogens(iko.id()));
indigoSetOptionBool("layout-preserve-existing", false);
}
return iko.toString(options, outputFormat.size() ? outputFormat : "ket");
}

std::string aromatize(const std::string& data, const std::string& outputFormat, const std::map<std::string, std::string>& options)
{
const IndigoSession session;
Expand Down Expand Up @@ -842,6 +890,7 @@ namespace indigo
emscripten::function("version", &version);
emscripten::function("versionInfo", &versionInfo);
emscripten::function("convert", &convert);
emscripten::function("convert_explicit_hydrogens", &convert_explicit_hydrogens);
emscripten::function("aromatize", &aromatize);
emscripten::function("dearomatize", &dearomatize);
emscripten::function("layout", &layout);
Expand Down
28 changes: 28 additions & 0 deletions api/wasm/indigo-ketcher/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,34 @@ M END

}

// Convert explicit hydrogens
{
test("convert_explicit_hydrogens", "auto", () => {
let options = new indigo.MapStringString();
options.set("output-content-type", "application/json");
const unfold_smiles = indigo.convert_explicit_hydrogens("CC", "auto", "smiles", options);
assert.equal(unfold_smiles, '{"struct":"C([H])([H])([H])C([H])([H])[H]","format":"smiles","original_format":"chemical/x-daylight-smiles"}');
const fold_smiles = indigo.convert_explicit_hydrogens("C([H])([H])([H])C([H])([H])[H]", "auto", "smiles", options);
assert.equal(fold_smiles, '{"struct":"CC","format":"smiles","original_format":"chemical/x-daylight-smiles"}');
options.delete();
});

test("convert_explicit_hydrogens", "fold", () => {
let options = new indigo.MapStringString();
options.set("output-content-type", "application/json");
const fold_smiles = indigo.convert_explicit_hydrogens("C([H])([H])([H])C([H])([H])[H]", "fold", "smiles", options);
assert.equal(fold_smiles, '{"struct":"CC","format":"smiles","original_format":"chemical/x-daylight-smiles"}');
options.delete();
});

test("convert_explicit_hydrogens", "unfold", () => {
let options = new indigo.MapStringString();
options.set("output-content-type", "application/json");
const unfold_smiles = indigo.convert_explicit_hydrogens("CC", "unfold", "smiles", options);
assert.equal(unfold_smiles, '{"struct":"C([H])([H])([H])C([H])([H])[H]","format":"smiles","original_format":"chemical/x-daylight-smiles"}');
options.delete();
});
}

// Dearomatize
{
Expand Down
Loading

0 comments on commit 81744f3

Please sign in to comment.