Cheers Andrew,

Have made a (hopefully) reasonable patch -
https://github.com/cdk/cdk/pull/1305.

On Fri, 25 Sept 2026 at 16:15, John Mayfield <[email protected]>
wrote:

> I think it should be deduplication identical paths 4_C_O vs 4_O_C are the
> same but both get captured but then also double counted!
>
> If you do that you get the following for your molecule.
>
> hash: 236 2 4_C_O
> hash: 156 6 3_C_C
> hash: 78 12 2_C_C
> hash: 0 12 1_C_C
> hash: 80 4 2_C_O
> hash: 2 2 1_C_O
> hash: 158 4 3_C_O
>
> The hash being a bit id also isn't ideal.
>
> Anyways it's broken in multiple ways, is an atom pair fingerprint that
> important? I'm wonder if we just deprecate/delete it.
>
> Best,
> John
>
> On Fri, 25 Sept 2026 at 16:10, John Mayfield <[email protected]>
> wrote:
>
>> Just had a quick look, yeah it's (very) broken :-).
>>
>> The issue is there is a "fixed" set of features that it wants to capture
>> for binary but count mode gets *all* the features.
>>
>> So the fix is either the count fingerprint should only report the
>> "selected features" or it should hash the path directly. I'll have a bit
>> more a think but and may need to check the original PaDEL implementation.
>>
>> Best,
>> John
>>
>> On Wed, 23 Sept 2026 at 10:25, Andrew Dalke <[email protected]>
>> wrote:
>>
>>> Hi everyone,
>>>
>>>   I'm confused about how to get the count information from the
>>> AtomPairs2D fingerprint in CDK and wondered if anyone here could help.
>>>
>>> Once I get the fingerprint, how do I get the feature index and
>>> associated count?
>>>
>>> When I try it out with my test case, below, I find there are 11
>>> populated bins but I can only pull out 7 hashes (the other indices raise an
>>> exception), while I can get all 11 counts.
>>>
>>> I don't know if this is an error in the CDK implementation, or I simply
>>> don't know how to get the data.
>>>
>>> Assuming the code is working correctly, my two ideas for how to map hash
>>> to count are:
>>>
>>> 1)  use fp.getHash(i), fp.getCount(i) when fp.getHash(i) does not throw
>>> an exception
>>>
>>>    (I think this is the correct one as fp.getCount(i) matches
>>> fp.getCountForHash(fp.getHash(i)))
>>>
>>> 2)  use fp.getHash(i), fp.getCount(j) where j is the j^th fp.getHash(i)
>>> which does not throw an exception
>>>
>>>
>>> Here is my reproducible, which is in Python accessing cdk-2.13.jar via
>>> the JPype Java/Python bridge, which I set up in my chemfp package for
>>> working with cheminformatics fingerprints.
>>>
>>> ====
>>> # This does the work to initialize the Java/Python bridge
>>> from chemfp import cdk_types
>>>
>>> from org.openscience import cdk
>>> from org.openscience.cdk.fingerprint import AtomPairs2DFingerprinter
>>>
>>> fingerprinter = AtomPairs2DFingerprinter()
>>> # prints:
>>> #  fingerprinter.getSize()=780
>>> print(f"{fingerprinter.getSize()=}")
>>>
>>> from org.openscience.cdk.smiles import SmilesParser
>>> parser = SmilesParser(cdk.silent.SilentChemObjectBuilder.getInstance())
>>> mol = parser.parseSmiles("c1ccccc1O")
>>>
>>> fp = fingerprinter.getCountFingerprint(mol)
>>> # prints:
>>> #  fp.size()=780
>>> print(f"{fp.size()=}")
>>>
>>> # prints:
>>> #  fp.numOfPopulatedbins()=11
>>> print(f"{fp.numOfPopulatedbins()=}")
>>>
>>> # I expected to get the feature indices as getHash(i) for 0 <= i < 11,
>>> # but if I do that manually I get failures for getHash(2) and a few
>>> others
>>>
>>> # >>> fp.getHash(0)
>>> # 236
>>> # >>> fp.getHash(1)
>>> # 156
>>> # >>> fp.getHash(2)
>>> # Traceback (most recent call last):
>>> #   File "AtomPairs2DFingerprinter.java", line 216, in
>>> org.openscience.cdk.fingerprint.AtomPairs2DFingerprinter$1.getHash
>>> # Exception: Java Exception
>>> #
>>> # The above exception was the direct cause of the following exception:
>>> #
>>> # Traceback (most recent call last):
>>> #   File "<stdin>", line 1, in <module>
>>> # java.lang.java.lang.NullPointerException:
>>> java.lang.NullPointerException: Cannot invoke
>>> "java.lang.Integer.intValue()" because the return value of
>>> "java.util.Map.get(Object)" is null
>>>
>>> # Instead, I'll scan all of the indices 0 <= i < 780 and get
>>> # the sparse index of (i, value) for both the hashes and counts
>>>
>>> def get_hashes_and_counts(fp):
>>>   hashes = []
>>>   counts = []
>>>   for i in range(780):
>>>     try:
>>>       hashes.append((i, fp.getHash(i)))
>>>     except Exception:
>>>       pass
>>>     try:
>>>       counts.append((i, fp.getCount(i)))
>>>     except Exception:
>>>       pass
>>>
>>>   return hashes, counts
>>>
>>> hashes, counts = get_hashes_and_counts(fp)
>>>
>>> # prints:
>>> #  hashes=[(0, 236), (1, 156), (3, 78), (4, 0), (5, 80),
>>> #          (7, 2), (10, 158)]
>>> print(f"{hashes=}")
>>>
>>> # prints:
>>> #  counts=[(0, 1), (1, 6), (2, 1), (3, 12), (4, 12), (5, 2),
>>> #          (6, 2), (7, 1), (8, 1), (9, 2), (10, 2)]
>>> print(f"{counts=}")
>>>
>>> # I can see three interpretations:
>>>
>>> ## == Option 1 ==
>>> # use (fp.GetHash(i), fp.GetCount(i) if GetHash(i) is valid
>>>
>>> def get_sparse_features1(fp):
>>>     features = []
>>>     for i in range(fp.numOfPopulatedbins()):
>>>         try:
>>>             feature_idx = fp.getHash(i)
>>>         except Exception:
>>>             continue
>>>         # Or use fp.getCountForHash(feature_idx)
>>>         features.append((feature_idx, fp.getCount(i)))
>>>     return features
>>>
>>> # prints:
>>> #  get_sparse_features1(fp)=[(236, 1), (156, 6), (78, 12), (0, 12),
>>> #                            (80, 2), (2, 1), (158, 2)]
>>> print(f"{get_sparse_features1(fp)=}")
>>>
>>> ## == Option 2 ==
>>> # use (fp.GetHash(i), fp.GetCount(j) for the j-th valid GetHash(i)
>>>
>>> def get_sparse_features2(fp):
>>>     features = []
>>>     j = 0
>>>     for i in range(fp.numOfPopulatedbins()):
>>>         try:
>>>             feature_idx = fp.getHash(i)
>>>         except Exception:
>>>             continue
>>>         features.append((feature_idx, fp.getCount(j)))
>>>         j += 1
>>>     return features
>>>
>>> # prints:
>>> #     get_sparse_features2(fp)=[(236, 1), (156, 6), (78, 1), (0, 12),
>>> #                               (80, 12), (2, 2), (158, 2)]
>>> print(f"{get_sparse_features2(fp)=}")
>>>
>>> # (These give different counts than option 1, because of the
>>> # different way of using getCount)
>>>
>>> ## == Option 3 ==
>>> # Something is wrong with the implementation
>>>
>>> def get_sparse_features3(fp):
>>>     raise NotImplementedError("It is not possible to get the count
>>> features")
>>>
>>>
>>>                                 Andrew
>>>                                 [email protected]
>>>
>>>
>>>
>>>
>>>
>>> _______________________________________________
>>> Cdk-user mailing list
>>> [email protected]
>>> https://lists.sourceforge.net/lists/listinfo/cdk-user
>>>
>>
_______________________________________________
Cdk-user mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/cdk-user

Reply via email to