Skip to content

📚 Documentation

compound

Compound

Bases: graph3d, Network

material along with properties

Source code in pyMolinfo/docs/compound.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
class Compound(graph3d, Network):
    '''
    material along with properties
    '''
    _mat_cid = ''
    _mat_name = ''
    _mat_formula = ''
    _atom_numbers = 0
    _atom_elements = []
    _atom_xyz = []
    _atom_xyz_center = []
    _atom_bond_block = []
    _atom_bond_block_1d = []
    _atom_bond_numbers = 0
    _atom_block = []
    # distance
    _distance = []

    # *** obs fixed ***
    _limits = {
        'phi': [],
        'teta': [np.deg2rad(0), np.deg2rad(180)]
    }
    _robs = 10
    _tetaNo = OBSERVER_PROPERTY.get('TETA')
    _phiNo = OBSERVER_PROPERTY.get('PHI')
    _dataNo = []
    _obsCoordinate = []

    def __init__(self, parse_prop):
        # all properties
        self.parse_prop = parse_prop
        # super class
        __atom_elements = parse_prop['atom_elements']
        __atom_block = parse_prop['atom_block']
        __atom_bonds = parse_prop['bond_block']
        __atom_xyz = parse_prop['xyz_list']
        __atom_xyz_center = parse_prop['xyz_center_list']
        # limit
        __limits = self._limits

        # TODO: convert atom_bonds to 1d vector
        __atom_bonds_1d = self.convert_atom_bonds(__atom_bonds)
        # update parse_prop
        self.parse_prop['bond_block_1d'] = __atom_bonds_1d

        # ! init parent classes
        # *** raw info (just for visualizing a structure)
        graph3d.__init__(self, __atom_elements, __atom_bonds,
                         __atom_xyz, __atom_xyz_center, self.robs, self.tetaNo, self.phiNo, __limits, __atom_bonds_1d)

        # *** network
        Network.__init__(self, __atom_elements, __atom_bonds,
                         __atom_xyz, __atom_xyz_center, __atom_bonds_1d)

        # update mat prop
        self.__update_atom_prop('mat_cid')
        self.__update_atom_prop('mat_name')
        self.__update_atom_prop('mat_formula')
        self.__update_atom_prop('atom_numbers')
        self.__update_atom_prop('atom_elements')
        self.__update_atom_prop('bond_numbers')
        self.__update_atom_prop('bond_block')
        self.__update_atom_prop('xyz_list')
        self.__update_atom_prop('xyz_center_list')
        self.__update_atom_prop('atom_block')

    def __str__(self):
        '''
        Return info about the mat
        '''
        # check
        if self.parse_prop['mat_name'] is not None:
            return str(self.parse_prop['mat_name'])
        else:
            return str(self.parse_prop['atom_elements'])

    @property
    def mat_cid(self):
        return self.parse_prop['mat_cid']

    @mat_cid.setter
    def mat_cid(self, value):
        self._mat_cid = value

    @property
    def mat_name(self):
        return self.parse_prop['mat_name']

    @mat_name.setter
    def mat_name(self, value):
        self._mat_name = value

    @property
    def mat_formula(self):
        return self.parse_prop['mat_formula']

    @mat_formula.setter
    def mat_formula(self, value):
        self._mat_formula = value

    @property
    def atom_elements(self):
        return np.array(self.parse_prop['atom_elements'])

    @atom_elements.setter
    def atom_elements(self, value):
        self._atom_elements = value

    @property
    def atom_numbers(self):
        return self.parse_prop['atom_numbers']

    @atom_numbers.setter
    def atom_numbers(self, value):
        self._atom_numbers = value

    @property
    def atom_xyz(self):
        return np.array(self.parse_prop['xyz_list'])

    @atom_xyz.setter
    def atom_xyz(self, value):
        self._atom_xyz = value

    @property
    def atom_xyz_center(self):
        return np.array(self.parse_prop['xyz_center_list'])

    @atom_xyz_center.setter
    def atom_xyz_center(self, value):
        self._atom_xyz_center = value

    @property
    def atom_bond_block(self):
        return self.parse_prop['bond_block']

    @atom_bond_block.setter
    def atom_bond_block(self, value):
        self._atom_bond_block = value

    @property
    def atom_bond_block_1d(self):
        return self.parse_prop['bond_block_1d']

    @atom_bond_block_1d.setter
    def atom_bond_block_1d(self, value):
        self._atom_bond_block_1d = value

    @property
    def atom_bond_numbers(self):
        return self.parse_prop['bond_numbers']

    @atom_bond_numbers.setter
    def atom_bond_numbers(self, value):
        self._atom_bond_numbers = value

    @property
    def atom_block(self):
        return self.parse_prop['atom_block']

    @atom_block.setter
    def atom_block(self, value):
        self._atom_block = value

    # *** observer prop
    @property
    def limits(self):
        return self._limits

    @limits.setter
    def limits(self, value):
        self._limits = []
        self._limits = value

    @property
    def robs(self):
        return self._robs

    @robs.setter
    def robs(self, value):
        self._robs = value

    @property
    def tetaNo(self):
        return self._tetaNo

    @tetaNo.setter
    def tetaNo(self, value):
        self._tetaNo = value

    @property
    def phiNo(self):
        return self._phiNo

    @phiNo.setter
    def phiNo(self, value):
        self._phiNo = value

    @property
    def dataNo(self):
        return self._dataNo

    @dataNo.setter
    def dataNo(self, value):
        self._dataNo = value

    @property
    def obsCoordinate(self):
        return self._obsCoordinate

    @obsCoordinate.setter
    def obsCoordinate(self, value):
        self._obsCoordinate = []
        self._obsCoordinate = value

    @property
    def distance(self):
        return self._distance

    @distance.setter
    def distance(self, value):
        self._distance = []
        self._distance = value

    def __update_atom_prop(self, prop_name):
        '''
        Update atom prop

        Parameters
        ----------
        prop_name: str
            atom prop name
        '''
        switchProp = {
            'header_block': 1,
            'counts_line': 1,
            'mat_cid': self.__update_mat_cid,
            'mat_name': self.__update_mat_name,
            'mat_formula': self.__update_mat_formula,
            'atom_numbers': self.__update_atom_numbers,
            'atom_elements': self.__update_atom_elements,
            'bond_numbers': self.__update_atom_bond_numbers,
            'atom_block': self.__update_atom_block,
            'bond_block': self.__update_atom_bond_block,
            'xyz_list': self.__update_atom_xyz,
            'xyz_center_list': self.__update_atom_xyz_center,
            'bond_block_1d': self.__update_atom_bond_block_1d
        }
        # select prop
        propSelection = switchProp.get(prop_name)
        # check
        if propSelection is not None:
            propSelection(self.parse_prop[prop_name])

    def __update_mat_cid(self, prop_val):
        self.mat_cid = prop_val

    def __update_mat_name(self, prop_val):
        self.mat_name = prop_val

    def __update_mat_formula(self, prop_val):
        self.mat_formula = prop_val

    def __update_atom_numbers(self, prop_val):
        self.atom_numbers = prop_val

    def __update_atom_elements(self, prop_val):
        self.atom_elements = prop_val

    def __update_atom_xyz(self, prop_val):
        self.atom_xyz = prop_val

    def __update_atom_xyz_center(self, prop_val):
        self.atom_xyz_center = prop_val

    def __update_atom_bond_block(self, prop_val):
        self.atom_bond_block = prop_val

    def __update_atom_bond_numbers(self, prop_val):
        self.atom_bond_numbers = prop_val

    def __update_atom_block(self, prop_val):
        self.atom_block = prop_val

    def __update_atom_bond_block_1d(self, prop_val):
        self.atom_bond_block_1d = prop_val

    def convert_atom_bonds(self, atom_bonds):
        '''
        Convert atom bonds to 1d list
        '''
        atom_bonds_1d = []
        for atom in atom_bonds:
            for bond in atom['bonds']:
                atom_bonds_1d.append({
                    'id1': atom['id'],
                    'symbol1': atom['symbol'],
                    'id2': bond[0],
                    'symbol2': bond[1],
                    'bond_type': bond[3],
                    'bond_symbol': bond[2]
                })
        return atom_bonds_1d

    def distance_matrix(self, dataframe=False):
        '''
        Build a matrix of atom-atom distance

        Parameters
        ----------
        dataframe: bool
            return a dataframe


        '''
        # res
        matrix, _dict = Compute.atoms_distance_matrix(
            self.xyzList, self.atom_elements)

        if dataframe:
            return pd.DataFrame(matrix, columns=self.atom_elements,
                                index=self.atom_elements)
        else:
            return matrix

    def distance_atoms(self, atoms):
        '''
        Calculate distance between two different atoms

        Parameters
        ----------
        atoms : list
            atom ids such as ['C1','H2']

        Returns
        -------
        distance: float
            distance between two atoms
        '''
        # get letters
        atoms_result = [{"letters": ''.join([c for c in s if c.isalpha(
        )]), "numbers": ''.join([c for c in s if c.isdigit()])} for s in atoms]
        # update atoms
        atom_symbols = [str(i['letters']) for i in atoms_result]
        # atom id
        atom_index = [str(int(i['numbers'])-1) for i in atoms_result]
        # res
        return Compute.atoms_distance(self.xyzList, self.atom_elements,
                                      atom_symbols, atom_index)

    def angle_atoms(self, atoms):
        '''
        Calculate angle between points p1,p2, and p3

        Parameters
        ----------
        atoms : list
            atom ids such as ['C1','H2','O3']

        Returns
        -------
        angle_degrees : float
            angle in degrees
        '''
        # check atoms
        if len(atoms) != 3:
            raise Exception('three atoms not provided!')

        # get letters
        atoms_result = [{"letters": ''.join([c for c in s if c.isalpha(
        )]), "numbers": ''.join([c for c in s if c.isdigit()])} for s in atoms]
        # update atoms
        atom_symbols = [str(i['letters']) for i in atoms_result]
        # atom id
        atom_index = [str(int(i['numbers'])-1) for i in atoms_result]

        return Compute.calculate_angle(self.xyzList, self.atom_elements,
                                       atom_symbols, atom_index)

    def d_angle_atoms(self, atoms):
        '''
        Calculate dihedral angle between points p1,p2,p3, and p4

        Parameters
        ----------
        atoms : list
            atom ids such as ['C1','H2','O3','H4']

        Returns
        -------
        angle_degrees : float
            angle in degrees
        '''
        # check atoms
        if len(atoms) != 4:
            raise Exception('4 atoms not provided!')

        # get letters
        atoms_result = [{"letters": ''.join([c for c in s if c.isalpha(
        )]), "numbers": ''.join([c for c in s if c.isdigit()])} for s in atoms]
        # update atoms
        atom_symbols = [str(i['letters']) for i in atoms_result]
        # atom id
        atom_index = [str(int(i['numbers'])-1) for i in atoms_result]

        return Compute.calculate_angle(self.xyzList, self.atom_elements,
                                       atom_symbols, atom_index)

    def g3d_functional_group(self, functional_group):
        '''
        Visualize a compound graph with a desired functional group

        Parameters
        ----------
        functional_group : str
            functional group

        Returns
        -------
        None
        '''
        # search within graph
        _res = self.search_within_main_graph(functional_group)
        # visualize
        self.view3d(subgraphs=_res)

__str__()

Return info about the mat

Source code in pyMolinfo/docs/compound.py
def __str__(self):
    '''
    Return info about the mat
    '''
    # check
    if self.parse_prop['mat_name'] is not None:
        return str(self.parse_prop['mat_name'])
    else:
        return str(self.parse_prop['atom_elements'])

__update_atom_prop(prop_name)

Update atom prop

Parameters

prop_name: str atom prop name

Source code in pyMolinfo/docs/compound.py
def __update_atom_prop(self, prop_name):
    '''
    Update atom prop

    Parameters
    ----------
    prop_name: str
        atom prop name
    '''
    switchProp = {
        'header_block': 1,
        'counts_line': 1,
        'mat_cid': self.__update_mat_cid,
        'mat_name': self.__update_mat_name,
        'mat_formula': self.__update_mat_formula,
        'atom_numbers': self.__update_atom_numbers,
        'atom_elements': self.__update_atom_elements,
        'bond_numbers': self.__update_atom_bond_numbers,
        'atom_block': self.__update_atom_block,
        'bond_block': self.__update_atom_bond_block,
        'xyz_list': self.__update_atom_xyz,
        'xyz_center_list': self.__update_atom_xyz_center,
        'bond_block_1d': self.__update_atom_bond_block_1d
    }
    # select prop
    propSelection = switchProp.get(prop_name)
    # check
    if propSelection is not None:
        propSelection(self.parse_prop[prop_name])

angle_atoms(atoms)

Calculate angle between points p1,p2, and p3

Parameters

atoms : list atom ids such as ['C1','H2','O3']

Returns

angle_degrees : float angle in degrees

Source code in pyMolinfo/docs/compound.py
def angle_atoms(self, atoms):
    '''
    Calculate angle between points p1,p2, and p3

    Parameters
    ----------
    atoms : list
        atom ids such as ['C1','H2','O3']

    Returns
    -------
    angle_degrees : float
        angle in degrees
    '''
    # check atoms
    if len(atoms) != 3:
        raise Exception('three atoms not provided!')

    # get letters
    atoms_result = [{"letters": ''.join([c for c in s if c.isalpha(
    )]), "numbers": ''.join([c for c in s if c.isdigit()])} for s in atoms]
    # update atoms
    atom_symbols = [str(i['letters']) for i in atoms_result]
    # atom id
    atom_index = [str(int(i['numbers'])-1) for i in atoms_result]

    return Compute.calculate_angle(self.xyzList, self.atom_elements,
                                   atom_symbols, atom_index)

convert_atom_bonds(atom_bonds)

Convert atom bonds to 1d list

Source code in pyMolinfo/docs/compound.py
def convert_atom_bonds(self, atom_bonds):
    '''
    Convert atom bonds to 1d list
    '''
    atom_bonds_1d = []
    for atom in atom_bonds:
        for bond in atom['bonds']:
            atom_bonds_1d.append({
                'id1': atom['id'],
                'symbol1': atom['symbol'],
                'id2': bond[0],
                'symbol2': bond[1],
                'bond_type': bond[3],
                'bond_symbol': bond[2]
            })
    return atom_bonds_1d

d_angle_atoms(atoms)

Calculate dihedral angle between points p1,p2,p3, and p4

Parameters

atoms : list atom ids such as ['C1','H2','O3','H4']

Returns

angle_degrees : float angle in degrees

Source code in pyMolinfo/docs/compound.py
def d_angle_atoms(self, atoms):
    '''
    Calculate dihedral angle between points p1,p2,p3, and p4

    Parameters
    ----------
    atoms : list
        atom ids such as ['C1','H2','O3','H4']

    Returns
    -------
    angle_degrees : float
        angle in degrees
    '''
    # check atoms
    if len(atoms) != 4:
        raise Exception('4 atoms not provided!')

    # get letters
    atoms_result = [{"letters": ''.join([c for c in s if c.isalpha(
    )]), "numbers": ''.join([c for c in s if c.isdigit()])} for s in atoms]
    # update atoms
    atom_symbols = [str(i['letters']) for i in atoms_result]
    # atom id
    atom_index = [str(int(i['numbers'])-1) for i in atoms_result]

    return Compute.calculate_angle(self.xyzList, self.atom_elements,
                                   atom_symbols, atom_index)

distance_atoms(atoms)

Calculate distance between two different atoms

Parameters

atoms : list atom ids such as ['C1','H2']

Returns

distance: float distance between two atoms

Source code in pyMolinfo/docs/compound.py
def distance_atoms(self, atoms):
    '''
    Calculate distance between two different atoms

    Parameters
    ----------
    atoms : list
        atom ids such as ['C1','H2']

    Returns
    -------
    distance: float
        distance between two atoms
    '''
    # get letters
    atoms_result = [{"letters": ''.join([c for c in s if c.isalpha(
    )]), "numbers": ''.join([c for c in s if c.isdigit()])} for s in atoms]
    # update atoms
    atom_symbols = [str(i['letters']) for i in atoms_result]
    # atom id
    atom_index = [str(int(i['numbers'])-1) for i in atoms_result]
    # res
    return Compute.atoms_distance(self.xyzList, self.atom_elements,
                                  atom_symbols, atom_index)

distance_matrix(dataframe=False)

Build a matrix of atom-atom distance

Parameters

dataframe: bool return a dataframe

Source code in pyMolinfo/docs/compound.py
def distance_matrix(self, dataframe=False):
    '''
    Build a matrix of atom-atom distance

    Parameters
    ----------
    dataframe: bool
        return a dataframe


    '''
    # res
    matrix, _dict = Compute.atoms_distance_matrix(
        self.xyzList, self.atom_elements)

    if dataframe:
        return pd.DataFrame(matrix, columns=self.atom_elements,
                            index=self.atom_elements)
    else:
        return matrix

g3d_functional_group(functional_group)

Visualize a compound graph with a desired functional group

Parameters

functional_group : str functional group

Returns

None

Source code in pyMolinfo/docs/compound.py
def g3d_functional_group(self, functional_group):
    '''
    Visualize a compound graph with a desired functional group

    Parameters
    ----------
    functional_group : str
        functional group

    Returns
    -------
    None
    '''
    # search within graph
    _res = self.search_within_main_graph(functional_group)
    # visualize
    self.view3d(subgraphs=_res)

compute

Compute

computational chemistry

Source code in pyMolinfo/docs/compute.py
class Compute():
    '''
    computational chemistry
    '''

    def __init__(self):
        pass

    @staticmethod
    def cal_atoms_distance(xyzAtom1, xyzAtom2):
        '''
        calculate distance between two atoms (center to center)
        '''
        return np.linalg.norm(xyzAtom1-xyzAtom2)

    @staticmethod
    def atoms_distance_matrix(xyzList, atomName):
        '''
        build a matrix containing a matrix of distance between two different atoms

        Parameters
        ----------
        xyzList : list
            xyz list of atoms
        atomName : list
            atom name list such as ['C','H','H','H','H']

        Returns
        -------
        atomLength : list
            distance matrix
        distance_res : list
            distance result
        '''
        # dict for atom index
        distance_res = []
        # atom no
        atomNo = len(atomName)
        atomLength = np.zeros((atomNo, atomNo))
        for i in range(atomNo):
            # atom1
            atom1XYZ = xyzList[i]
            for j in range(atomNo):
                # atom2
                atom2XYZ = xyzList[j]
                if j != i:
                    _length = Compute.cal_atoms_distance(atom1XYZ, atom2XYZ)
                else:
                    _length = 0
                # save
                atomLength[i, j] = _length

                # res
                distance_res.append(
                    {
                        'atom1': atomName[i]+str(i+1),
                        'atom2': atomName[j]+str(j+1),
                        'distance': _length
                    }
                )

        # res
        return atomLength, distance_res

    @staticmethod
    def atoms_distance(xyzList, atomName, atom_symbols, atom_index):
        '''
        calculate distance between two different atoms

        Parameters
        ----------
        xyzList : list
            xyz list of atoms
        atomName : list
            atom name list such as ['C','H','H','H','H']
        atom_symbols : list
            selected atom symbol list such as ['C','H']
        atom_index : list
            selected atom index list such as [0,1]

        Returns
        -------
        _length : float
            distance between two atoms
        '''
        try:
            # size
            atom_symbols_size = len(atom_symbols)
            atom_index_size = len(atom_index)

            # check
            if atom_symbols_size > 2 or atom_index_size > 2:
                raise Exception(
                    'atom symbol/index list must have two elements.')

            if atom_index_size == 0:
                # find the index of the first element
                atom1Index = np.where(atomName == atom_symbols[0])[0][0]
                atom2Index = np.where(atomName == atom_symbols[1])[0][0]
                # set
                atom1XYZ = xyzList[atom1Index]
                atom2XYZ = xyzList[atom2Index]
                _length = Compute.cal_atoms_distance(atom1XYZ, atom2XYZ)
            elif atom_index_size == 2:
                # set
                atom1XYZ = xyzList[int(atom_index[0])]
                atom2XYZ = xyzList[int(atom_index[1])]
                _length = Compute.cal_atoms_distance(atom1XYZ, atom2XYZ)
            elif atom_index_size > 2:
                raise Exception(
                    'atom symbol/index list must have two elements.')

            # res
            return _length
        except Exception as e:
            Exception(e)

    @staticmethod
    def calculate_angle(xyzList, atomName, atom_symbols, atom_index):
        '''
        Calculate angle/dihedral angle between points p1,p2, and p3 - p4

        Parameters
        ----------
        xyzList : list
            xyz list of atoms
        atomName : list
            atom name list such as ['C','H','H','H','H']
        atom_symbols : list
            selected atom symbol list such as ['C','H']
        atom_index : list
            selected atom index list such as [0,1]

        Returns
        -------
        angle_degrees : float
            angle in degrees
        '''
        # check
        atom_index_size = len(atom_index)

        if atom_index_size == 3:
            # angle between 3 points
            p1 = xyzList[int(atom_index[0])]
            p2 = xyzList[int(atom_index[1])]
            p3 = xyzList[int(atom_index[2])]

            # Calculate vectors
            v1 = np.array(p2) - np.array(p1)
            v2 = np.array(p2) - np.array(p3)

            # Calculate angle using cosine formula
            angle = np.arccos(
                np.dot(v1, v2) / (distance.euclidean(p1, p2) * distance.euclidean(p2, p3)))

            # Convert to degrees
            angle_degrees = np.degrees(angle)

        elif atom_index_size == 4:
            # dihedral angle 4 points
            p1 = xyzList[int(atom_index[0])]
            p2 = xyzList[int(atom_index[1])]
            p3 = xyzList[int(atom_index[2])]
            p4 = xyzList[int(atom_index[3])]

            # Calculate vectors
            v1 = p2 - p1
            v2 = p3 - p2
            v3 = p4 - p3

            # Calculate normal vectors
            n1 = np.cross(v1, v2)
            n2 = np.cross(v2, v3)

            # Normalize normal vectors
            n1 = n1 / np.linalg.norm(n1)
            n2 = n2 / np.linalg.norm(n2)

            # Calculate dihedral angle
            dihedral_angle = np.arccos(np.dot(n1, n2))

            # Calculate sign of dihedral angle
            sign = np.sign(np.dot(n1, v3))

            # Convert to degrees
            angle_degrees = np.degrees(dihedral_angle) * sign

        else:
            raise Exception(
                'atom symbol/index list must have three elements.'
            )

        # res
        return angle_degrees

atoms_distance(xyzList, atomName, atom_symbols, atom_index) staticmethod

calculate distance between two different atoms

Parameters

xyzList : list xyz list of atoms atomName : list atom name list such as ['C','H','H','H','H'] atom_symbols : list selected atom symbol list such as ['C','H'] atom_index : list selected atom index list such as [0,1]

Returns

_length : float distance between two atoms

Source code in pyMolinfo/docs/compute.py
@staticmethod
def atoms_distance(xyzList, atomName, atom_symbols, atom_index):
    '''
    calculate distance between two different atoms

    Parameters
    ----------
    xyzList : list
        xyz list of atoms
    atomName : list
        atom name list such as ['C','H','H','H','H']
    atom_symbols : list
        selected atom symbol list such as ['C','H']
    atom_index : list
        selected atom index list such as [0,1]

    Returns
    -------
    _length : float
        distance between two atoms
    '''
    try:
        # size
        atom_symbols_size = len(atom_symbols)
        atom_index_size = len(atom_index)

        # check
        if atom_symbols_size > 2 or atom_index_size > 2:
            raise Exception(
                'atom symbol/index list must have two elements.')

        if atom_index_size == 0:
            # find the index of the first element
            atom1Index = np.where(atomName == atom_symbols[0])[0][0]
            atom2Index = np.where(atomName == atom_symbols[1])[0][0]
            # set
            atom1XYZ = xyzList[atom1Index]
            atom2XYZ = xyzList[atom2Index]
            _length = Compute.cal_atoms_distance(atom1XYZ, atom2XYZ)
        elif atom_index_size == 2:
            # set
            atom1XYZ = xyzList[int(atom_index[0])]
            atom2XYZ = xyzList[int(atom_index[1])]
            _length = Compute.cal_atoms_distance(atom1XYZ, atom2XYZ)
        elif atom_index_size > 2:
            raise Exception(
                'atom symbol/index list must have two elements.')

        # res
        return _length
    except Exception as e:
        Exception(e)

atoms_distance_matrix(xyzList, atomName) staticmethod

build a matrix containing a matrix of distance between two different atoms

Parameters

xyzList : list xyz list of atoms atomName : list atom name list such as ['C','H','H','H','H']

Returns

atomLength : list distance matrix distance_res : list distance result

Source code in pyMolinfo/docs/compute.py
@staticmethod
def atoms_distance_matrix(xyzList, atomName):
    '''
    build a matrix containing a matrix of distance between two different atoms

    Parameters
    ----------
    xyzList : list
        xyz list of atoms
    atomName : list
        atom name list such as ['C','H','H','H','H']

    Returns
    -------
    atomLength : list
        distance matrix
    distance_res : list
        distance result
    '''
    # dict for atom index
    distance_res = []
    # atom no
    atomNo = len(atomName)
    atomLength = np.zeros((atomNo, atomNo))
    for i in range(atomNo):
        # atom1
        atom1XYZ = xyzList[i]
        for j in range(atomNo):
            # atom2
            atom2XYZ = xyzList[j]
            if j != i:
                _length = Compute.cal_atoms_distance(atom1XYZ, atom2XYZ)
            else:
                _length = 0
            # save
            atomLength[i, j] = _length

            # res
            distance_res.append(
                {
                    'atom1': atomName[i]+str(i+1),
                    'atom2': atomName[j]+str(j+1),
                    'distance': _length
                }
            )

    # res
    return atomLength, distance_res

cal_atoms_distance(xyzAtom1, xyzAtom2) staticmethod

calculate distance between two atoms (center to center)

Source code in pyMolinfo/docs/compute.py
@staticmethod
def cal_atoms_distance(xyzAtom1, xyzAtom2):
    '''
    calculate distance between two atoms (center to center)
    '''
    return np.linalg.norm(xyzAtom1-xyzAtom2)

calculate_angle(xyzList, atomName, atom_symbols, atom_index) staticmethod

Calculate angle/dihedral angle between points p1,p2, and p3 - p4

Parameters

xyzList : list xyz list of atoms atomName : list atom name list such as ['C','H','H','H','H'] atom_symbols : list selected atom symbol list such as ['C','H'] atom_index : list selected atom index list such as [0,1]

Returns

angle_degrees : float angle in degrees

Source code in pyMolinfo/docs/compute.py
@staticmethod
def calculate_angle(xyzList, atomName, atom_symbols, atom_index):
    '''
    Calculate angle/dihedral angle between points p1,p2, and p3 - p4

    Parameters
    ----------
    xyzList : list
        xyz list of atoms
    atomName : list
        atom name list such as ['C','H','H','H','H']
    atom_symbols : list
        selected atom symbol list such as ['C','H']
    atom_index : list
        selected atom index list such as [0,1]

    Returns
    -------
    angle_degrees : float
        angle in degrees
    '''
    # check
    atom_index_size = len(atom_index)

    if atom_index_size == 3:
        # angle between 3 points
        p1 = xyzList[int(atom_index[0])]
        p2 = xyzList[int(atom_index[1])]
        p3 = xyzList[int(atom_index[2])]

        # Calculate vectors
        v1 = np.array(p2) - np.array(p1)
        v2 = np.array(p2) - np.array(p3)

        # Calculate angle using cosine formula
        angle = np.arccos(
            np.dot(v1, v2) / (distance.euclidean(p1, p2) * distance.euclidean(p2, p3)))

        # Convert to degrees
        angle_degrees = np.degrees(angle)

    elif atom_index_size == 4:
        # dihedral angle 4 points
        p1 = xyzList[int(atom_index[0])]
        p2 = xyzList[int(atom_index[1])]
        p3 = xyzList[int(atom_index[2])]
        p4 = xyzList[int(atom_index[3])]

        # Calculate vectors
        v1 = p2 - p1
        v2 = p3 - p2
        v3 = p4 - p3

        # Calculate normal vectors
        n1 = np.cross(v1, v2)
        n2 = np.cross(v2, v3)

        # Normalize normal vectors
        n1 = n1 / np.linalg.norm(n1)
        n2 = n2 / np.linalg.norm(n2)

        # Calculate dihedral angle
        dihedral_angle = np.arccos(np.dot(n1, n2))

        # Calculate sign of dihedral angle
        sign = np.sign(np.dot(n1, v3))

        # Convert to degrees
        angle_degrees = np.degrees(dihedral_angle) * sign

    else:
        raise Exception(
            'atom symbol/index list must have three elements.'
        )

    # res
    return angle_degrees

CalculateMolecularMass(atom_elements, element_source)

calculate molecular mass [g/mol]

Parameters

atom_elements : list atom elements such as ['C', 'H'] element_source : object periodic element table

Returns

res : float molecular mass

Source code in pyMolinfo/docs/compute.py
def CalculateMolecularMass(atom_elements, element_source):
    '''
    calculate molecular mass [g/mol]

    Parameters
    ----------
    atom_elements : list
        atom elements such as ['C', 'H']
    element_source : object
        periodic element table

    Returns
    -------
    res : float
        molecular mass
    '''
    try:
        # # check
        # if element_source is None:
        #     # load periodic table of elements
        #     element_source = elements()
        # res
        res = 0
        # atom element size
        atomElementsSize = len(atom_elements)
        # atom property
        _atom_property = ['AtomicMass']
        # loop
        for i in range(atomElementsSize):
            _atom_symbol = atom_elements[i]
            _atom_prop_res = element_source.atom_properties(
                _atom_symbol, _atom_property)
            _atom_prop_val = _atom_prop_res.get(_atom_property[0])
            res += float(_atom_prop_val)

        return res
    except Exception as e:
        raise

element

Element

pub chem elements (periodic table of the elements)

Source code in pyMolinfo/docs/element.py
class Element():
    '''
    pub chem elements (periodic table of the elements)
    '''

    _elementsource = ''
    _ele = ''

    def __init__(self, atom_symbol=''):
        self.elementsource = self.__load_elements()
        self._ele = atom_symbol

    def __call__(self, ):
        print(
            'element object such as Carbon (C), get properties by calling atom_properties()')

    @property
    def elementsource(self):
        return self._elementsource

    @elementsource.setter
    def elementsource(self, value):
        self._elementsource = value

    @property
    def ele(self):
        return self._ele

    @ele.setter
    def ele(self, value):
        pass

    def __load_elements(self):
        '''
        load elements from a csv file
        '''
        try:
            # data file
            dataFile = 'PubChemElements_all.csv'
            # abs path
            pathAbs = os.path.abspath(os.path.dirname(__file__))
            # relative path to database file
            dataPathDirRel = '../data'
            # database file
            dataPath = os.path.join(pathAbs, dataPathDirRel, dataFile)

            with open(dataPath, 'rb') as f:
                df = pd.read_csv(f)

            return df

        except Exception as e:
            raise Exception(e)

    def properties(self):
        '''
        return all atom properties
        '''
        df = self.elementsource
        filt = df['Symbol'] == str(self._ele).strip()
        return df.loc[filt]

    def find_atom(self, atom_symbol):
        '''
        return the selected atom properties
        '''
        df = self.elementsource
        filt = df['Symbol'] == str(atom_symbol).strip()
        return df.loc[filt]

    def atom_properties(self, atom_symbol, atom_properties=[]):
        '''
        find desired atom properties

        Parameters
        ----------
        atom_symbol : str
            atom symbol
        atom_properties : list
            a list of desired properties

        Returns
        -------
        dict
            a dict of property
        '''
        try:
            # property size
            atomPropertySize = len(atom_properties)
            # res
            resDict = {}

            # check
            if atomPropertySize == 0:
                raise Exception('property list is empty.')

            df = self.elementsource
            filt = df['Symbol'] == str(atom_symbol).strip()

            # check
            if atomPropertySize == 1:
                rowRes = df.loc[filt][atom_properties[0]]
                rowRes = rowRes.to_numpy()[0]
                # add prop
                resDict[str(atom_properties[0])] = rowRes
            elif atomPropertySize > 1:
                rowRes = df.loc[filt][atom_properties]
                rowRes = rowRes.to_numpy()[0]
                # add prop
                for i in range(atomPropertySize):
                    resDict[str(atom_properties[i])] = rowRes[i]
            # res
            return resDict

        except Exception as e:
            raise Exception(e)

    def find_atom_by_property(self, atom_property_name, atom_property_value=[]):
        '''
        find desired atom properties

        Parameters
        ----------
        atom_property_name : str
            atom property such as atomic number
        atom_property_value : list
            a list of desired properties such as carbon = 6

        Returns
        -------
        dict
            a dict of property
        '''
        try:
            # size
            atom_property_value_size = len(atom_property_value)
            atom_property_name_size = len(str(atom_property_name))

            # res
            res = []

            # check
            if atom_property_name_size == 0 or atom_property_value_size == 0:
                raise Exception('args error.')

            df = self.elementsource

            if atom_property_value_size == 1:
                filt = df[str(atom_property_name)] == atom_property_value[0]
                _rowRes = df.loc[filt][['Symbol']]
                _rowRes = _rowRes.to_numpy()[0]
                # check
                if len(_rowRes) > 0:
                    # add prop
                    _val = {'symbol': _rowRes[0], str(
                        atom_property_name): atom_property_value[0]}
                    res.append(_val)
                else:
                    raise Exception('element not found.')
            elif atom_property_value_size > 1:
                for i in range(atom_property_value_size):
                    filt = df[str(atom_property_name)
                              ] == atom_property_value[i]
                    _rowRes = df.loc[filt][['Symbol']]
                    _rowRes = _rowRes.to_numpy()[0]
                    # add
                    _val = {'symbol': _rowRes[0], str(
                        atom_property_name): atom_property_value[i]}
                    # save
                    res.append(_val)

            # res
            return res

        except Exception as e:
            raise Exception(e)

__load_elements()

load elements from a csv file

Source code in pyMolinfo/docs/element.py
def __load_elements(self):
    '''
    load elements from a csv file
    '''
    try:
        # data file
        dataFile = 'PubChemElements_all.csv'
        # abs path
        pathAbs = os.path.abspath(os.path.dirname(__file__))
        # relative path to database file
        dataPathDirRel = '../data'
        # database file
        dataPath = os.path.join(pathAbs, dataPathDirRel, dataFile)

        with open(dataPath, 'rb') as f:
            df = pd.read_csv(f)

        return df

    except Exception as e:
        raise Exception(e)

atom_properties(atom_symbol, atom_properties=[])

find desired atom properties

Parameters

atom_symbol : str atom symbol atom_properties : list a list of desired properties

Returns

dict a dict of property

Source code in pyMolinfo/docs/element.py
def atom_properties(self, atom_symbol, atom_properties=[]):
    '''
    find desired atom properties

    Parameters
    ----------
    atom_symbol : str
        atom symbol
    atom_properties : list
        a list of desired properties

    Returns
    -------
    dict
        a dict of property
    '''
    try:
        # property size
        atomPropertySize = len(atom_properties)
        # res
        resDict = {}

        # check
        if atomPropertySize == 0:
            raise Exception('property list is empty.')

        df = self.elementsource
        filt = df['Symbol'] == str(atom_symbol).strip()

        # check
        if atomPropertySize == 1:
            rowRes = df.loc[filt][atom_properties[0]]
            rowRes = rowRes.to_numpy()[0]
            # add prop
            resDict[str(atom_properties[0])] = rowRes
        elif atomPropertySize > 1:
            rowRes = df.loc[filt][atom_properties]
            rowRes = rowRes.to_numpy()[0]
            # add prop
            for i in range(atomPropertySize):
                resDict[str(atom_properties[i])] = rowRes[i]
        # res
        return resDict

    except Exception as e:
        raise Exception(e)

find_atom(atom_symbol)

return the selected atom properties

Source code in pyMolinfo/docs/element.py
def find_atom(self, atom_symbol):
    '''
    return the selected atom properties
    '''
    df = self.elementsource
    filt = df['Symbol'] == str(atom_symbol).strip()
    return df.loc[filt]

find_atom_by_property(atom_property_name, atom_property_value=[])

find desired atom properties

Parameters

atom_property_name : str atom property such as atomic number atom_property_value : list a list of desired properties such as carbon = 6

Returns

dict a dict of property

Source code in pyMolinfo/docs/element.py
def find_atom_by_property(self, atom_property_name, atom_property_value=[]):
    '''
    find desired atom properties

    Parameters
    ----------
    atom_property_name : str
        atom property such as atomic number
    atom_property_value : list
        a list of desired properties such as carbon = 6

    Returns
    -------
    dict
        a dict of property
    '''
    try:
        # size
        atom_property_value_size = len(atom_property_value)
        atom_property_name_size = len(str(atom_property_name))

        # res
        res = []

        # check
        if atom_property_name_size == 0 or atom_property_value_size == 0:
            raise Exception('args error.')

        df = self.elementsource

        if atom_property_value_size == 1:
            filt = df[str(atom_property_name)] == atom_property_value[0]
            _rowRes = df.loc[filt][['Symbol']]
            _rowRes = _rowRes.to_numpy()[0]
            # check
            if len(_rowRes) > 0:
                # add prop
                _val = {'symbol': _rowRes[0], str(
                    atom_property_name): atom_property_value[0]}
                res.append(_val)
            else:
                raise Exception('element not found.')
        elif atom_property_value_size > 1:
            for i in range(atom_property_value_size):
                filt = df[str(atom_property_name)
                          ] == atom_property_value[i]
                _rowRes = df.loc[filt][['Symbol']]
                _rowRes = _rowRes.to_numpy()[0]
                # add
                _val = {'symbol': _rowRes[0], str(
                    atom_property_name): atom_property_value[i]}
                # save
                res.append(_val)

        # res
        return res

    except Exception as e:
        raise Exception(e)

properties()

return all atom properties

Source code in pyMolinfo/docs/element.py
def properties(self):
    '''
    return all atom properties
    '''
    df = self.elementsource
    filt = df['Symbol'] == str(self._ele).strip()
    return df.loc[filt]

molparser

MolParser

Parse different formats of molecule files such as sdf, json, ...

Source code in pyMolinfo/docs/molparser.py
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
class MolParser():
    '''
    Parse different formats of molecule files such as sdf, json, ...
    '''
    _mat = {}

    def __init__(self, filepath):
        self.filepath = filepath

    @property
    def mat(self):
        return self._mat

    @mat.setter
    def mat(self, value):
        self._mat = copy.deepcopy(value)

    def read_file(self, sourceContent={}):
        '''
        Read structure file 
            1. from file
            2. download from api

        Parameters
        ----------
        filepath : str
            full file name with directory 

        Returns
        -------
        res : dict
            mol: mol object
            structure: structure object

        hints:
            mol: mol object
                header_block: header block
                counts_line: counts line
                atom_numbers: atom number
                mat_cid: cid number
                mat_name: IUPAC name
                mat_formula: formula
                mat_mass: molecular mass
                atom_names: atom list
                atom_xyz: atom coordinate
                bond_numbers: bond number
                bond_list: bond list
                bond_matrix: bond matrix
                bond_xyz: bond coordinate

            structure: structure object
                mol: mol object
                mat: mat object
        '''
        try:
            # get file path
            filePath = self.filepath

            # check
            if filePath:
                # open file and get content
                fileContent, fileDir, fileName, fileFormat = Utility.OpenFile(
                    filePath)
            else:
                # read string/json and ... files
                fileContent, fileFormat = Utility.ReadContent(
                    sourceContent)

            # method selection
            parserFun = {
                'sdf': self.sdf_parser,
                'json': self.json_parser
            }

            # parse file
            parserSelection = parserFun.get(fileFormat)
            parserRes = parserSelection(fileContent)
            return parserRes

        except Exception as e:
            raise Exception(e)

    def sdf_parser(self, sdfSource, sdfVersion='V2000'):
        '''
        Parse sdf file

        Parameters
        ----------
        sdfSource : str
            sdf file content

        sdfVersion : str
            sdf file version (default V2000)

        Returns
        -------
        res : dict
            mol: mol object
                header_block: header block
                counts_line: counts line
                atom_numbers: atom number
                mat_cid: cid number
                mat_name: IUPAC name
                mat_formula: formula
                mat_mass: molecular mass
                atom_names: atom list
                atom_elements: atom list
                bond_numbers: bond number
                atom_block: atoms
                bond_block: bond list
                xyz_list: xyz list
                xyz_center_list: xyz center list
                compound_properties: compound properties

        '''
        # decode binary
        # if binary file
        # sdfSource = sdfSource.decode('utf-8')
        # if text file
        sdfSource = sdfSource
        # create list
        sdfSourceList = sdfSource.splitlines()

        # header block
        headerBlock = sdfSourceList[0:3]

        # counts line
        countsLine = sdfSourceList[3]
        if countsLine.find('V2000') == -1:
            raise Exception(
                'SDF file version is not compatible with this method, import 2000 version.')
        # check
        countsLineList = countsLine.split()

        # find 'M END'
        MENDi = -1
        MENDi = sdfSourceList.index('M  END')
        # connection table
        if MENDi != -1:
            connectionTable = sdfSourceList[4:MENDi]

        # *** check
        _countsLineListSize = len(countsLineList)
        if _countsLineListSize == 10:
            # atom no
            atomNo = int(countsLineList[0])
            # bond no
            bondNo = int(countsLineList[1])
        elif _countsLineListSize == 9:
            _connectionTableLines = connectionTable
            _connectionTableLinesSize = len(_connectionTableLines)
            # first record
            _firstRecord = _connectionTableLines[0]
            _firstRecordSize = len(_firstRecord)
            _secondRecord = _connectionTableLines[0]
            _secondRecordSize = len(_secondRecord)
            if _firstRecordSize != _secondRecordSize:
                raise Exception('sdf file is not coded correctly.')
            # loop
            for l in range(_connectionTableLinesSize):
                _loopRecordSize = len(_connectionTableLines[l])
                if _loopRecordSize < _firstRecordSize:
                    # atom no
                    atomNo = int(l)
                    # bond no
                    bondNo = int(str(countsLineList[0]).split(str(atomNo))[1])
                    # break
                    break
        else:
            raise Exception('3rd line of the sdf file is not coded correctly.')

        # element rows
        elementRows = connectionTable[0:atomNo]
        # bond rows
        bondRows = connectionTable[atomNo:]

        # other vars
        compoundPropertiesList = MolParser.__var_finder(sdfSource)
        # dict vars
        compoundProperties = MolParser.__var_analyzer(compoundPropertiesList)
        # extract:
        PUBCHEM_COMPOUND_CID = compoundProperties.get('PUBCHEM_COMPOUND_CID')
        PUBCHEM_IUPAC_NAME = compoundProperties.get('PUBCHEM_IUPAC_NAME')
        PUBCHEM_EXACT_MASS = compoundProperties.get('PUBCHEM_EXACT_MASS')
        PUBCHEM_MOLECULAR_FORMULA = compoundProperties.get(
            'PUBCHEM_MOLECULAR_FORMULA')
        PUBCHEM_MOLECULAR_WEIGHT = compoundProperties.get(
            'PUBCHEM_MOLECULAR_WEIGHT')

        atoms = []
        atomList = []
        xyzList = []

        # atoms position
        for i in range(atomNo):
            _atomRow = elementRows[i].split()
            # position
            _x = float(_atomRow[0])
            _y = float(_atomRow[1])
            _z = float(_atomRow[2])
            # name
            _name = _atomRow[3]

            # atom list
            atomList.append(_name)

            # atom info
            atom = {
                'id': i+1,
                'symbol': _name,
                'index': i+1,
                'x': _x,
                'y': _y,
                'z': _z,
                'position': {
                    'x': _x,
                    'y': _y,
                    'z': _z
                },
                'xyz': [_x, _y, _z]
            }
            # save atom info
            atoms.append(atom)
            # xyz
            xyzList.append([_x, _y, _z])

        # set
        xyzList = np.array(xyzList)

        # object base
        objectBaseCoordinate = Structure.CenterPoints(xyzList)
        # print(f"objectBaseCoordinate: {objectBaseCoordinate}")

        # move to the center [0,0,0]
        xyzCenterList, movingCoordinate = Structure.CenterObject(
            xyzList, objectBaseCoordinate)
        # print(f"xyzCenterList: {xyzCenterList} \n movingCoordinate: \n {movingCoordinate}")

        # create mat formula
        matFormula = Structure.create_formula(atomList)
        # calculate molecular mass
        # matMass = 1

        # bond analysis
        bondList = []
        atomBonds = []

        # atomNo: *** atom id in the structure ***
        for i in range(atomNo):
            # name
            _nameAtom1 = str(atomList[i])
            # index [not duplicated element]
            # _nameAtom1Index = atomList.index(_nameAtom1)
            _nameAtom1Index = i+1
            # check bond type
            for j in range(bondNo):
                _bondRow = bondRows[j].split()
                # starts from 1 to ... (not 0)
                _nameAtomBondRow = int(_bondRow[0])
                # check bond exist
                if _nameAtom1Index == _nameAtomBondRow:
                    # atom 2 index
                    _indexAtom = int(_bondRow[1])
                    # atom 2 name
                    _nameAtom2 = str(atomList[_indexAtom-1])
                    # bound type
                    _bondType = int(_bondRow[2])
                    # str bond
                    _bondName = _nameAtom1 + _nameAtom2
                    # atom bond
                    atomBonds.append(
                        (_indexAtom, _nameAtom2, _bondName, _bondType))

            if len(atomBonds) > 0:
                # save
                _bondList = {
                    'id': _nameAtom1Index,
                    'symbol': _nameAtom1,
                    'bonds': atomBonds
                }
                bondList.append(_bondList)

            # reset
            atomBonds = []
            _bondList = {}

        # res
        res = {
            'header_block': headerBlock,
            'counts_line': countsLine,
            'atom_numbers': atomNo,
            'mat_cid': PUBCHEM_COMPOUND_CID,
            'mat_name': PUBCHEM_IUPAC_NAME if PUBCHEM_IUPAC_NAME is not None else '',
            'mat_formula': PUBCHEM_MOLECULAR_FORMULA if PUBCHEM_MOLECULAR_FORMULA is not None else matFormula,
            'mat_mass': PUBCHEM_EXACT_MASS if PUBCHEM_EXACT_MASS is not None else PUBCHEM_MOLECULAR_WEIGHT,
            'atom_names': atomList,
            'atom_elements': atomList,
            'bond_numbers': bondNo,
            'atom_block': atoms,
            'bond_block': bondList,
            'xyz_list': xyzList,
            'xyz_center_list': xyzCenterList,
            'compound_properties': compoundProperties
        }

        # return
        return res

    def json_parser(self, jsonSource, id_sort=True):
        '''
        parse json file

        Parameters
        ----------
        jsonSource: dict file
            json file content
        id_sort: bool
            if True, sort id

        Returns
        -------
        res: dict
        '''
        try:
            dictSource = jsonSource['PC_Compounds'][0]

            # analyze json file
            # *** id node
            if 'id' in dictSource:
                _idNode = dictSource['id']
                cid = self.__json_parser_id(_idNode)

            # *** atoms
            if 'atoms' in dictSource:
                _atomsNode = dictSource['atoms']
                # atom ids, element atomic number
                atomIds, _elementAtomicNumber = self.__json_parser_atoms(
                    _atomsNode)

            # atom numbers
            atomNo = len(atomIds)

            # atom list (element list)
            elementListRes, elementList = self.__find_element_symbol(
                _elementAtomicNumber)

            # *** bonds
            if 'bonds' in dictSource:
                _bonds = dictSource['bonds']
                # atom1 id, atom2 id, bond types
                atomId1, atomId2, bondTypes, bondMatrix = self.__json_parser_bonds(
                    _bonds)

            # bond numbers
            bondNo = len(atomId1)

            # *** coords
            if 'coords' in dictSource:
                _coords = dictSource['coords']  # list
                # coords type, aid, xyzList
                coords_type, coords_aid, xyzList, structure_type = self.__json_parser_coords(
                    _coords)

            # *** charge
            if 'charge' in dictSource:
                _charge = dictSource['charge']  # scaler
                charge = self.__json_parser_charge(_charge)

            # *** props
            if 'props' in dictSource:
                _props = dictSource['props']  # list of dict
                propDict = self.__json_parser_props(_props)

            # *** count
            if 'count' in dictSource:
                _count = dictSource['count']  # dict
                countList = self.__json_parser_count(_count)

            # interpret
            __json_atom_position_res = self.__json_atom_position(
                atomNo, elementList, xyzList)

            # bond block
            __json_atom_bondblock_res = self.__json_atom_bondblock(
                atomNo, bondNo, elementList, bondMatrix)

            # bondBlock: used for Matview class
            # atomDetails, bondBlock, xyzCenterList = self.__json_atom_analyzer(
            #     atomNo, bondNo, elementList, xyzList, bondMatrix)

            # *** origin info
            origin_info = {
                'atom_elements': __json_atom_position_res.get('elementList'),
                'atom_atomic_number': _elementAtomicNumber,
                'atom_details': __json_atom_position_res.get('atomDetails'),
                'bond_block': __json_atom_bondblock_res.get('bondBlock'),
                'bond_list': __json_atom_bondblock_res.get('bondMatrix'),
                'xyz_list': __json_atom_position_res.get('xyzList'),
                'xyz_center_list': __json_atom_position_res.get('xyzCenterList'),
            }

            # *** define new ids for mat and update
            # *** xyzList, bondMatrix, elementlist, elementAtomicNumber
            # *** bond_list = bondMatrix
            # *** atom_block = atom_details
            # if id_sort:
            mat_position_info, atom_id_conversion, xyz_list_sorted, \
                element_list_sorted, bond_list_sorted = self.SetAtomId(
                    xyzList, elementList, bondMatrix)

            # interpret
            __json_atom_position_res2 = self.__json_atom_position(
                atomNo, element_list_sorted, xyz_list_sorted)
            # set
            atom_block_sorted = __json_atom_position_res2.get('atomDetails')
            xyz_list_sorted = __json_atom_position_res2.get('xyzList')
            xyz_center_list_sorted = __json_atom_position_res2.get(
                'xyzCenterList')

            # bond block
            __json_atom_bondblock_res2 = self.__json_atom_bondblock(
                atomNo, bondNo, element_list_sorted, bond_list_sorted)
            # set
            bond_block_sorted = __json_atom_bondblock_res2.get('bondBlock')

            # atomic number sorted
            element_atomic_number = self.arrange_prop(
                _elementAtomicNumber, atom_id_conversion[:, 0])

            # update properties
            # name
            mat_name = propDict.get('IUPAC Name')
            if mat_name is None:
                mat_name = 'NULL'

            # formula
            mat_formula = propDict.get('Molecular Formula')
            if mat_formula is None:
                # create mat formula
                mat_formula = 'NULL'

            # molecular weight
            mat_mass = propDict.get('Molecular Weight')
            if mat_mass is None:
                # mat molecular weight/mass
                mat_mass = 0
            else:
                mat_mass = float(mat_mass)

            # res
            res = {
                'header_block': '',
                'counts_line': '',
                'mat_structure': structure_type,
                'mat_cid': cid,
                'mat_name': mat_name,
                'mat_formula': mat_formula,
                'mat_mass': mat_mass,
                'atom_numbers': atomNo,
                'atom_elements': element_list_sorted,
                'atom_atomic_number': element_atomic_number,
                'atom_block': atom_block_sorted,
                'bond_numbers': bondNo,
                'bond_block': bond_block_sorted,
                'bond_list': bond_list_sorted,
                'xyz_list': xyz_list_sorted,
                'xyz_center_list': xyz_center_list_sorted,
                'compound_properties': propDict,
                'mat_info_origin': origin_info
            }

            return res

        except Exception as e:
            raise Exception(e)

    def __json_parser_id(self, data):
        '''
        Return cid 

        Parameters
        ----------
        data : dict
            json data

        Returns
        -------
        node : int
            cid
        '''
        # *** id node
        node = data['id']['cid']
        return node

    def __json_parser_atoms(self, data):
        '''
        Return atom id, atom atomic number

        Parameters
        ----------
        data : dict
            json data

        Returns
        -------
        _aid : int
            atom id
        _elementAtomicNumber : int
            atom atomic number
        '''
        # aid (atom id)
        _aid = data['aid']
        # element (atomic number)
        _elementAtomicNumber = data['element']
        return _aid, _elementAtomicNumber

    def __json_parser_bonds(self, data):
        '''
        Return atom1 id, atom2 is, bond type

        Parameters
        ----------
        data : dict
            json data

        Returns
        -------
        _aid1 : int
            atom1 id
        _aid2 : int
            atom2 id
        _order : int
            bond type
        '''
        # aid1 (atom id)
        _aid1 = data['aid1']
        # aid2 (atom id)
        _aid2 = data['aid2']
        # order
        _order = data['order']

        # transform
        bondMatrix = np.array([_aid1, _aid2, _order])
        bondMatrix = np.transpose(bondMatrix)

        return _aid1, _aid2, _order, bondMatrix

    def __json_parser_coords(self, data):
        '''
        Return coordination type, coordination id, xyzList

        Parameters
        ----------
        data : dict
            json data

        Returns
        -------
        _coords_type : str
            coordination type
        _coords_aid : int
            coordination id
        xyzList : np.array
            xyzList
        structureType : str
            2d or 3d
        '''
        data = data[0]
        _coords_type = data['type']  # list
        _coords_aid = data['aid']  # list
        _coords_conformers = data['conformers'][0]
        _x = _coords_conformers.get('x')
        # atomNo
        atomNo = len(_x)
        _y = _coords_conformers.get('y')

        _z = _coords_conformers.get('z') if _coords_conformers.get(
            'z') is not None else np.zeros(atomNo)
        # transform
        xyzList = np.array([_x, _y, _z])
        xyzList = np.transpose(xyzList)

        # 2d/3d structure
        structureType = "2d" if np.count_nonzero(_z) == 0 else '3d'

        return _coords_type, _coords_aid, xyzList, structureType

    def __json_parser_charge(self, data):
        '''
        Get charge

        Parameters
        ----------
        data : dict
            json data

        Returns
        -------
        node : int
            charge
        '''
        # *** id node
        node = data
        return node

    def __json_parser_props(self, data):
        '''
        Return a list of all properties

        Parameters
        ----------
        data : dict
            json data

        Returns
        -------
        res : dict
            a list of all properties
        '''
        res = {}
        for i in range(len(data)):
            _keySet = data[i]['urn']['label']
            _valueSet = list(data[i]['value'].values())
            res[str(_keySet)] = _valueSet[0]

        return res

    def __json_parser_count(self, data):
        '''
        Return a list of all count

        Parameters
        ----------
        data : dict
            json data

        Returns
        -------
        res : dict
            a list of all count
        '''
        res = {}
        for key, value in data.items():
            res[key] = value
        return res

    def __json_atom_analyzer(self, atomNo, bondNo, elementList, xyzList, bondMatrix):
        '''
        Define xyz list and bond list

        Parameters
        ----------
        atomNo : int
            atom number
        bondNo : int
            bond number
        elementList : list
            element list
        xyzList : np.array
            xyz list
        bondMatrix : np.array
            bond matrix

        Returns
        -------
        atomDetails : list
            atom details
        objectBaseCoordinate : list
            object base coordinate
        bondDetails : list
            bond details
        '''
        # vars
        # *** detail about atoms
        atomDetails = []

        # element symbols
        atomList = elementList

        # atoms position
        for i in range(atomNo):
            _atomRow = xyzList[i, :]
            # position
            _x = float(_atomRow[0])
            _y = float(_atomRow[1])
            _z = float(_atomRow[2])
            # name
            _name = atomList[i]

            # atom info
            atom = {
                'id': i,
                'symbol': _name,
                'index': i,
                'x': _x,
                'y': _y,
                'z': _z,
                'position': {
                    'x': _x,
                    'y': _y,
                    'z': _z
                },
                'xyz': [_x, _y, _z]
            }
            # save atom info
            atomDetails.append(atom)

        # object base
        objectBaseCoordinate = Structure.CenterPoints(xyzList)
        # print(f"objectBaseCoordinate: {objectBaseCoordinate}")

        # move to the center [0,0,0]
        xyzCenterList, movingCoordinate = Structure.CenterObject(
            xyzList, objectBaseCoordinate)
        # print(f"xyzCenterList: {xyzCenterList} \n movingCoordinate: \n {movingCoordinate}")

        # bond analysis
        bondBlock = []
        atomBonds = []

        # atomNo: *** atom id in the structure ***
        for i in range(atomNo):
            # name
            _nameAtom1 = str(atomList[i])
            # index [not duplicated element]
            # _nameAtom1Index = atomList.index(_nameAtom1)
            _nameAtom1Index = i+1
            # check bond type
            for j in range(bondNo):
                _bondRow = bondMatrix[j, :]
                _nameAtomBondRow = int(_bondRow[0])
                # check bond exist
                if _nameAtom1Index == _nameAtomBondRow:
                    # atom 2 index
                    _indexAtom2 = int(_bondRow[1])
                    # atom 2 name
                    _nameAtom2 = str(atomList[_indexAtom2-1])
                    # bound type
                    _bondType = int(_bondRow[2])
                    # str bond
                    _bondName = _nameAtom1 + '-' + _nameAtom2
                    # str id bond
                    _bondId = str(_nameAtom1Index) + '-' + str(_indexAtom2)
                    # atom bond
                    atomBonds.append(
                        (_indexAtom2, _nameAtom2, _bondName, _bondType, _bondId))

            if len(atomBonds) > 0:
                # save
                _bondList = {
                    'id': _nameAtom1Index,
                    'symbol': _nameAtom1,
                    'bonds': atomBonds
                }
                bondBlock.append(_bondList)

            # reset
            atomBonds = []
            _bondList = {}

        # result
        return atomDetails, bondBlock, xyzCenterList

    def __json_atom_position(self, atomNo, elementList, xyzList):
        '''
        Set mat position into the center of cartesian coordination

        Parameters
        ----------
        atomNo : int
            atom number
        elementList : list
            element list
        xyzList : np.array
            xyz list

        Returns
        -------
        res : dict
            a list of all count
        '''
        try:
            # vars
            # *** detail about atoms
            atomDetails = []

            # element symbols
            atomList = elementList

            # atoms position
            for i in range(atomNo):
                _atomRow = xyzList[i, :]
                # position
                _x = float(_atomRow[0])
                _y = float(_atomRow[1])
                _z = float(_atomRow[2])
                # name
                _name = atomList[i]

                # atom info
                atom = {
                    'id': i,
                    'symbol': _name,
                    'index': i,
                    'x': _x,
                    'y': _y,
                    'z': _z,
                    'position': {
                        'x': _x,
                        'y': _y,
                        'z': _z
                    },
                    'xyz': [_x, _y, _z]
                }
                # save atom info
                atomDetails.append(atom)

            # object base
            objectBaseCoordinate = Structure.CenterPoints(xyzList)

            # move to the center [0,0,0]
            xyzCenterList, movingCoordinate = Structure.CenterObject(
                xyzList, objectBaseCoordinate)

            # res
            res = {
                'atomDetails': atomDetails,
                'elementList': elementList,
                'xyzList': xyzList,
                'xyzCenterList': xyzCenterList,
                'movingCoordinate': movingCoordinate
            }

            # res
            return res
        except Exception as e:
            raise Exception(e)

    def __json_atom_bondblock(self, atomNo, bondNo, elementList, bondMatrix):
        '''
        Build bond block which is used by mat view to display mat structure

        Parameters
        ----------
        atomNo : int
            atom number
        bondNo : int
            bond number
        elementList : list
            element list
        bondMatrix : np.array
            bond matrix

        Returns
        -------
        res : dict
            a list of all count
        '''
        try:
            # element symbols
            atomList = elementList

            # bond analysis
            bondBlock = []
            atomBonds = []

            # atomNo: *** atom id in the structure ***
            for i in range(atomNo):
                # name
                _nameAtom1 = str(atomList[i])
                # index [not duplicated element]
                _nameAtom1Index = int(i+1)
                # check bond type
                for j in range(bondNo):
                    # name
                    _bondRow = bondMatrix[j, :]
                    # atom id
                    _nameAtomBondRow = int(_bondRow[0])
                    # check bond exist
                    if _nameAtom1Index == _nameAtomBondRow:
                        # atom 2 index
                        _indexAtom2 = int(_bondRow[1])
                        # atom 2 name
                        _nameAtom2 = str(atomList[_indexAtom2-1])
                        # bound type
                        _bondType = int(_bondRow[2])
                        # str bond
                        _bondName = _nameAtom1 + '-' + _nameAtom2
                        # str id bond
                        _bondId = str(_nameAtom1Index) + '-' + str(_indexAtom2)
                        # atom bond
                        atomBonds.append(
                            (_indexAtom2, _nameAtom2, _bondName, _bondType, _bondId))

                if len(atomBonds) > 0:
                    # save
                    _bondList = {
                        'id': _nameAtom1Index,
                        'symbol': _nameAtom1,
                        'bonds': atomBonds
                    }
                    bondBlock.append(_bondList)

                # reset
                atomBonds = []
                _bondList = {}

            # res
            res = {
                'elementList': elementList,
                'bondMatrix': bondMatrix,
                'bondBlock': bondBlock
            }

            # res
            return res
        except Exception as e:
            Exception(e)

    def __find_element_symbol(self, atomic_numbers):
        '''
        Return element symbols

        Parameters
        ----------
        atomic_numbers : list
            atomic numbers

        Returns
        -------
        res : dict
            a list of all count
        atomList : list
            atom list
        '''
        el = Element()
        # loop
        res = el.find_atom_by_property('AtomicNumber', atomic_numbers)

        # interpret
        atomList = [item['symbol'] for item in res]

        # res
        return res, atomList

    def __var_finder(data):
        '''
        Find variables in a sdf file (single)

        Parameters
        ----------
        data : str
            sdf data

        Returns
        -------
        res : dict
            a list of all count
        '''
        res = re.findall(
            r"(\>\s*\<(.*)\s*\>\s*((.*\n)([^\<\>\$\$\$\$])*))", data, re.M)
        return res

    def __var_analyzer(data):
        '''
        Make a dict of all properties

        Parameters
        ----------
        data : str
            sdf data

        Returns
        -------
        res : dict
            a list of all count
        '''
        res = {}
        dataSize = len(data)
        for i in range(dataSize):
            # name
            varName = data[i][1]
            # val
            varVal = (data[i][2]).split('\n')
            varVal = [item.strip() for item in varVal]
            varVal = list(filter(None, varVal))

            # check
            if len(varVal) == 1:
                _varVal = str(varVal[0])
            else:
                _varVal = varVal
            # set
            _keySet = str(varName)
            _valueSet = _varVal
            # res
            res[_keySet] = _valueSet
            # reset

        return res

    def SetAtomId(self, xyzList, elementList, bondList):
        '''
        Set atom id with respect to their position in a 3d frame

        Parameters
        ----------
        xyzList : list
            list of element (atom) position
        elementList : list
            list of elements
        bondList : list
            list of bond ids and types

        Returns
        -------
        matPosition: list
            list of new ids
        xyzListSorted : list
            list of element (atom) position
        elementListSorted : list
            list of elements
        bondListSorted : list
            list of bond ids and types
        '''
        try:
            # robs position
            robs = OBS_POSITIONS
            # res
            disRes = []
            # calculate distance
            for i in range(len(xyzList)):
                _dis = np.linalg.norm(
                    np.array(robs) - np.array(xyzList[i]))
                _idOld = int(i+1)
                _symbol = elementList[i]
                disRes.append([_idOld, _dis, _symbol])

            # sort
            sortRes = sorted(disRes, key=lambda l: l[1], reverse=True)

            # reverse [old id, distance, symbol]
            sortRes.reverse()

            # save new id [old id, new id, distance, symbol]
            for j in range(len(sortRes)):
                _idNew = int(j+1)
                sortRes[j].insert(1, _idNew)

            # sorted id (old id, new id)
            idConversion = []
            for j in range(len(sortRes)):
                idConversion.append([sortRes[j][0], sortRes[j][1]])

            # build xyzList and elementlist with respect to the new ids
            xyzListSorted = []
            elementListSorted = []
            matPosition = {}
            for j in range(len(sortRes)):
                _idOld = sortRes[j][0]
                _idNew = sortRes[j][1]
                _dis = sortRes[j][2]
                _val1 = xyzList[_idOld-1]
                _val2 = elementList[_idOld-1]
                # set
                xyzListSorted.append(_val1)
                elementListSorted.append(_val2)
                # all
                matPosition[str(_idNew)] = [_idOld, _idNew, _dis, _val1, _val2]

            # set
            xyzListSorted = np.array(xyzListSorted)

            # bond id conversion
            bondList = np.array(bondList, dtype='i')
            # size
            bondRow = bondList.shape[0]
            # column 0
            bondColumn0 = np.array(bondList[:, 0])
            # column 1
            bondColumn1 = np.array(bondList[:, 1])
            # column 2
            bondColumn2 = np.array(bondList[:, 2])

            # replace
            for i in range(bondRow):
                # column 0
                _v0 = bondColumn0[i]
                # find new value
                _v1 = [item[1] for item in idConversion if item[0] == _v0][0]
                bondColumn0[i] = _v1

                # column 1
                _v2 = bondColumn1[i]
                # find new value
                _v3 = [item[1] for item in idConversion if item[0] == _v2][0]
                bondColumn1[i] = _v3

            # build bond list with new ids
            bondListSorted = np.zeros_like(bondList)
            bondListSorted[:, 0] = bondColumn0
            bondListSorted[:, 1] = bondColumn1
            bondListSorted[:, 2] = bondColumn2

            # set
            idConversion = np.array(idConversion)

            return matPosition, idConversion, xyzListSorted, elementListSorted, bondListSorted

        except Exception as e:
            Exception(e)

    def arrange_prop(self, property_value_list, atom_index_list):
        '''
        Arrange a new list with respect to the index list

        Parameters
        ----------
        property_value_list: list
            such as atomic number
        atom_index_list: list
            atom id between 1 and ...

        Returns
        -------
        res: list
            sorted list
        '''
        try:
            # convert atom id to list id
            index_list = np.array(atom_index_list) - 1
            property_value_list_sorted = np.take(
                property_value_list, index_list)
            # res
            return property_value_list_sorted
        except Exception as e:
            Exception(e)

SetAtomId(xyzList, elementList, bondList)

Set atom id with respect to their position in a 3d frame

Parameters

xyzList : list list of element (atom) position elementList : list list of elements bondList : list list of bond ids and types

Returns

matPosition: list list of new ids xyzListSorted : list list of element (atom) position elementListSorted : list list of elements bondListSorted : list list of bond ids and types

Source code in pyMolinfo/docs/molparser.py
def SetAtomId(self, xyzList, elementList, bondList):
    '''
    Set atom id with respect to their position in a 3d frame

    Parameters
    ----------
    xyzList : list
        list of element (atom) position
    elementList : list
        list of elements
    bondList : list
        list of bond ids and types

    Returns
    -------
    matPosition: list
        list of new ids
    xyzListSorted : list
        list of element (atom) position
    elementListSorted : list
        list of elements
    bondListSorted : list
        list of bond ids and types
    '''
    try:
        # robs position
        robs = OBS_POSITIONS
        # res
        disRes = []
        # calculate distance
        for i in range(len(xyzList)):
            _dis = np.linalg.norm(
                np.array(robs) - np.array(xyzList[i]))
            _idOld = int(i+1)
            _symbol = elementList[i]
            disRes.append([_idOld, _dis, _symbol])

        # sort
        sortRes = sorted(disRes, key=lambda l: l[1], reverse=True)

        # reverse [old id, distance, symbol]
        sortRes.reverse()

        # save new id [old id, new id, distance, symbol]
        for j in range(len(sortRes)):
            _idNew = int(j+1)
            sortRes[j].insert(1, _idNew)

        # sorted id (old id, new id)
        idConversion = []
        for j in range(len(sortRes)):
            idConversion.append([sortRes[j][0], sortRes[j][1]])

        # build xyzList and elementlist with respect to the new ids
        xyzListSorted = []
        elementListSorted = []
        matPosition = {}
        for j in range(len(sortRes)):
            _idOld = sortRes[j][0]
            _idNew = sortRes[j][1]
            _dis = sortRes[j][2]
            _val1 = xyzList[_idOld-1]
            _val2 = elementList[_idOld-1]
            # set
            xyzListSorted.append(_val1)
            elementListSorted.append(_val2)
            # all
            matPosition[str(_idNew)] = [_idOld, _idNew, _dis, _val1, _val2]

        # set
        xyzListSorted = np.array(xyzListSorted)

        # bond id conversion
        bondList = np.array(bondList, dtype='i')
        # size
        bondRow = bondList.shape[0]
        # column 0
        bondColumn0 = np.array(bondList[:, 0])
        # column 1
        bondColumn1 = np.array(bondList[:, 1])
        # column 2
        bondColumn2 = np.array(bondList[:, 2])

        # replace
        for i in range(bondRow):
            # column 0
            _v0 = bondColumn0[i]
            # find new value
            _v1 = [item[1] for item in idConversion if item[0] == _v0][0]
            bondColumn0[i] = _v1

            # column 1
            _v2 = bondColumn1[i]
            # find new value
            _v3 = [item[1] for item in idConversion if item[0] == _v2][0]
            bondColumn1[i] = _v3

        # build bond list with new ids
        bondListSorted = np.zeros_like(bondList)
        bondListSorted[:, 0] = bondColumn0
        bondListSorted[:, 1] = bondColumn1
        bondListSorted[:, 2] = bondColumn2

        # set
        idConversion = np.array(idConversion)

        return matPosition, idConversion, xyzListSorted, elementListSorted, bondListSorted

    except Exception as e:
        Exception(e)

__find_element_symbol(atomic_numbers)

Return element symbols

Parameters

atomic_numbers : list atomic numbers

Returns

res : dict a list of all count atomList : list atom list

Source code in pyMolinfo/docs/molparser.py
def __find_element_symbol(self, atomic_numbers):
    '''
    Return element symbols

    Parameters
    ----------
    atomic_numbers : list
        atomic numbers

    Returns
    -------
    res : dict
        a list of all count
    atomList : list
        atom list
    '''
    el = Element()
    # loop
    res = el.find_atom_by_property('AtomicNumber', atomic_numbers)

    # interpret
    atomList = [item['symbol'] for item in res]

    # res
    return res, atomList

__json_atom_analyzer(atomNo, bondNo, elementList, xyzList, bondMatrix)

Define xyz list and bond list

Parameters

atomNo : int atom number bondNo : int bond number elementList : list element list xyzList : np.array xyz list bondMatrix : np.array bond matrix

Returns

atomDetails : list atom details objectBaseCoordinate : list object base coordinate bondDetails : list bond details

Source code in pyMolinfo/docs/molparser.py
def __json_atom_analyzer(self, atomNo, bondNo, elementList, xyzList, bondMatrix):
    '''
    Define xyz list and bond list

    Parameters
    ----------
    atomNo : int
        atom number
    bondNo : int
        bond number
    elementList : list
        element list
    xyzList : np.array
        xyz list
    bondMatrix : np.array
        bond matrix

    Returns
    -------
    atomDetails : list
        atom details
    objectBaseCoordinate : list
        object base coordinate
    bondDetails : list
        bond details
    '''
    # vars
    # *** detail about atoms
    atomDetails = []

    # element symbols
    atomList = elementList

    # atoms position
    for i in range(atomNo):
        _atomRow = xyzList[i, :]
        # position
        _x = float(_atomRow[0])
        _y = float(_atomRow[1])
        _z = float(_atomRow[2])
        # name
        _name = atomList[i]

        # atom info
        atom = {
            'id': i,
            'symbol': _name,
            'index': i,
            'x': _x,
            'y': _y,
            'z': _z,
            'position': {
                'x': _x,
                'y': _y,
                'z': _z
            },
            'xyz': [_x, _y, _z]
        }
        # save atom info
        atomDetails.append(atom)

    # object base
    objectBaseCoordinate = Structure.CenterPoints(xyzList)
    # print(f"objectBaseCoordinate: {objectBaseCoordinate}")

    # move to the center [0,0,0]
    xyzCenterList, movingCoordinate = Structure.CenterObject(
        xyzList, objectBaseCoordinate)
    # print(f"xyzCenterList: {xyzCenterList} \n movingCoordinate: \n {movingCoordinate}")

    # bond analysis
    bondBlock = []
    atomBonds = []

    # atomNo: *** atom id in the structure ***
    for i in range(atomNo):
        # name
        _nameAtom1 = str(atomList[i])
        # index [not duplicated element]
        # _nameAtom1Index = atomList.index(_nameAtom1)
        _nameAtom1Index = i+1
        # check bond type
        for j in range(bondNo):
            _bondRow = bondMatrix[j, :]
            _nameAtomBondRow = int(_bondRow[0])
            # check bond exist
            if _nameAtom1Index == _nameAtomBondRow:
                # atom 2 index
                _indexAtom2 = int(_bondRow[1])
                # atom 2 name
                _nameAtom2 = str(atomList[_indexAtom2-1])
                # bound type
                _bondType = int(_bondRow[2])
                # str bond
                _bondName = _nameAtom1 + '-' + _nameAtom2
                # str id bond
                _bondId = str(_nameAtom1Index) + '-' + str(_indexAtom2)
                # atom bond
                atomBonds.append(
                    (_indexAtom2, _nameAtom2, _bondName, _bondType, _bondId))

        if len(atomBonds) > 0:
            # save
            _bondList = {
                'id': _nameAtom1Index,
                'symbol': _nameAtom1,
                'bonds': atomBonds
            }
            bondBlock.append(_bondList)

        # reset
        atomBonds = []
        _bondList = {}

    # result
    return atomDetails, bondBlock, xyzCenterList

__json_atom_bondblock(atomNo, bondNo, elementList, bondMatrix)

Build bond block which is used by mat view to display mat structure

Parameters

atomNo : int atom number bondNo : int bond number elementList : list element list bondMatrix : np.array bond matrix

Returns

res : dict a list of all count

Source code in pyMolinfo/docs/molparser.py
def __json_atom_bondblock(self, atomNo, bondNo, elementList, bondMatrix):
    '''
    Build bond block which is used by mat view to display mat structure

    Parameters
    ----------
    atomNo : int
        atom number
    bondNo : int
        bond number
    elementList : list
        element list
    bondMatrix : np.array
        bond matrix

    Returns
    -------
    res : dict
        a list of all count
    '''
    try:
        # element symbols
        atomList = elementList

        # bond analysis
        bondBlock = []
        atomBonds = []

        # atomNo: *** atom id in the structure ***
        for i in range(atomNo):
            # name
            _nameAtom1 = str(atomList[i])
            # index [not duplicated element]
            _nameAtom1Index = int(i+1)
            # check bond type
            for j in range(bondNo):
                # name
                _bondRow = bondMatrix[j, :]
                # atom id
                _nameAtomBondRow = int(_bondRow[0])
                # check bond exist
                if _nameAtom1Index == _nameAtomBondRow:
                    # atom 2 index
                    _indexAtom2 = int(_bondRow[1])
                    # atom 2 name
                    _nameAtom2 = str(atomList[_indexAtom2-1])
                    # bound type
                    _bondType = int(_bondRow[2])
                    # str bond
                    _bondName = _nameAtom1 + '-' + _nameAtom2
                    # str id bond
                    _bondId = str(_nameAtom1Index) + '-' + str(_indexAtom2)
                    # atom bond
                    atomBonds.append(
                        (_indexAtom2, _nameAtom2, _bondName, _bondType, _bondId))

            if len(atomBonds) > 0:
                # save
                _bondList = {
                    'id': _nameAtom1Index,
                    'symbol': _nameAtom1,
                    'bonds': atomBonds
                }
                bondBlock.append(_bondList)

            # reset
            atomBonds = []
            _bondList = {}

        # res
        res = {
            'elementList': elementList,
            'bondMatrix': bondMatrix,
            'bondBlock': bondBlock
        }

        # res
        return res
    except Exception as e:
        Exception(e)

__json_atom_position(atomNo, elementList, xyzList)

Set mat position into the center of cartesian coordination

Parameters

atomNo : int atom number elementList : list element list xyzList : np.array xyz list

Returns

res : dict a list of all count

Source code in pyMolinfo/docs/molparser.py
def __json_atom_position(self, atomNo, elementList, xyzList):
    '''
    Set mat position into the center of cartesian coordination

    Parameters
    ----------
    atomNo : int
        atom number
    elementList : list
        element list
    xyzList : np.array
        xyz list

    Returns
    -------
    res : dict
        a list of all count
    '''
    try:
        # vars
        # *** detail about atoms
        atomDetails = []

        # element symbols
        atomList = elementList

        # atoms position
        for i in range(atomNo):
            _atomRow = xyzList[i, :]
            # position
            _x = float(_atomRow[0])
            _y = float(_atomRow[1])
            _z = float(_atomRow[2])
            # name
            _name = atomList[i]

            # atom info
            atom = {
                'id': i,
                'symbol': _name,
                'index': i,
                'x': _x,
                'y': _y,
                'z': _z,
                'position': {
                    'x': _x,
                    'y': _y,
                    'z': _z
                },
                'xyz': [_x, _y, _z]
            }
            # save atom info
            atomDetails.append(atom)

        # object base
        objectBaseCoordinate = Structure.CenterPoints(xyzList)

        # move to the center [0,0,0]
        xyzCenterList, movingCoordinate = Structure.CenterObject(
            xyzList, objectBaseCoordinate)

        # res
        res = {
            'atomDetails': atomDetails,
            'elementList': elementList,
            'xyzList': xyzList,
            'xyzCenterList': xyzCenterList,
            'movingCoordinate': movingCoordinate
        }

        # res
        return res
    except Exception as e:
        raise Exception(e)

__json_parser_atoms(data)

Return atom id, atom atomic number

Parameters

data : dict json data

Returns

_aid : int atom id _elementAtomicNumber : int atom atomic number

Source code in pyMolinfo/docs/molparser.py
def __json_parser_atoms(self, data):
    '''
    Return atom id, atom atomic number

    Parameters
    ----------
    data : dict
        json data

    Returns
    -------
    _aid : int
        atom id
    _elementAtomicNumber : int
        atom atomic number
    '''
    # aid (atom id)
    _aid = data['aid']
    # element (atomic number)
    _elementAtomicNumber = data['element']
    return _aid, _elementAtomicNumber

__json_parser_bonds(data)

Return atom1 id, atom2 is, bond type

Parameters

data : dict json data

Returns

_aid1 : int atom1 id _aid2 : int atom2 id _order : int bond type

Source code in pyMolinfo/docs/molparser.py
def __json_parser_bonds(self, data):
    '''
    Return atom1 id, atom2 is, bond type

    Parameters
    ----------
    data : dict
        json data

    Returns
    -------
    _aid1 : int
        atom1 id
    _aid2 : int
        atom2 id
    _order : int
        bond type
    '''
    # aid1 (atom id)
    _aid1 = data['aid1']
    # aid2 (atom id)
    _aid2 = data['aid2']
    # order
    _order = data['order']

    # transform
    bondMatrix = np.array([_aid1, _aid2, _order])
    bondMatrix = np.transpose(bondMatrix)

    return _aid1, _aid2, _order, bondMatrix

__json_parser_charge(data)

Get charge

Parameters

data : dict json data

Returns

node : int charge

Source code in pyMolinfo/docs/molparser.py
def __json_parser_charge(self, data):
    '''
    Get charge

    Parameters
    ----------
    data : dict
        json data

    Returns
    -------
    node : int
        charge
    '''
    # *** id node
    node = data
    return node

__json_parser_coords(data)

Return coordination type, coordination id, xyzList

Parameters

data : dict json data

Returns

_coords_type : str coordination type _coords_aid : int coordination id xyzList : np.array xyzList structureType : str 2d or 3d

Source code in pyMolinfo/docs/molparser.py
def __json_parser_coords(self, data):
    '''
    Return coordination type, coordination id, xyzList

    Parameters
    ----------
    data : dict
        json data

    Returns
    -------
    _coords_type : str
        coordination type
    _coords_aid : int
        coordination id
    xyzList : np.array
        xyzList
    structureType : str
        2d or 3d
    '''
    data = data[0]
    _coords_type = data['type']  # list
    _coords_aid = data['aid']  # list
    _coords_conformers = data['conformers'][0]
    _x = _coords_conformers.get('x')
    # atomNo
    atomNo = len(_x)
    _y = _coords_conformers.get('y')

    _z = _coords_conformers.get('z') if _coords_conformers.get(
        'z') is not None else np.zeros(atomNo)
    # transform
    xyzList = np.array([_x, _y, _z])
    xyzList = np.transpose(xyzList)

    # 2d/3d structure
    structureType = "2d" if np.count_nonzero(_z) == 0 else '3d'

    return _coords_type, _coords_aid, xyzList, structureType

__json_parser_count(data)

Return a list of all count

Parameters

data : dict json data

Returns

res : dict a list of all count

Source code in pyMolinfo/docs/molparser.py
def __json_parser_count(self, data):
    '''
    Return a list of all count

    Parameters
    ----------
    data : dict
        json data

    Returns
    -------
    res : dict
        a list of all count
    '''
    res = {}
    for key, value in data.items():
        res[key] = value
    return res

__json_parser_id(data)

Return cid

Parameters

data : dict json data

Returns

node : int cid

Source code in pyMolinfo/docs/molparser.py
def __json_parser_id(self, data):
    '''
    Return cid 

    Parameters
    ----------
    data : dict
        json data

    Returns
    -------
    node : int
        cid
    '''
    # *** id node
    node = data['id']['cid']
    return node

__json_parser_props(data)

Return a list of all properties

Parameters

data : dict json data

Returns

res : dict a list of all properties

Source code in pyMolinfo/docs/molparser.py
def __json_parser_props(self, data):
    '''
    Return a list of all properties

    Parameters
    ----------
    data : dict
        json data

    Returns
    -------
    res : dict
        a list of all properties
    '''
    res = {}
    for i in range(len(data)):
        _keySet = data[i]['urn']['label']
        _valueSet = list(data[i]['value'].values())
        res[str(_keySet)] = _valueSet[0]

    return res

__var_analyzer(data)

Make a dict of all properties

Parameters

data : str sdf data

Returns

res : dict a list of all count

Source code in pyMolinfo/docs/molparser.py
def __var_analyzer(data):
    '''
    Make a dict of all properties

    Parameters
    ----------
    data : str
        sdf data

    Returns
    -------
    res : dict
        a list of all count
    '''
    res = {}
    dataSize = len(data)
    for i in range(dataSize):
        # name
        varName = data[i][1]
        # val
        varVal = (data[i][2]).split('\n')
        varVal = [item.strip() for item in varVal]
        varVal = list(filter(None, varVal))

        # check
        if len(varVal) == 1:
            _varVal = str(varVal[0])
        else:
            _varVal = varVal
        # set
        _keySet = str(varName)
        _valueSet = _varVal
        # res
        res[_keySet] = _valueSet
        # reset

    return res

__var_finder(data)

Find variables in a sdf file (single)

Parameters

data : str sdf data

Returns

res : dict a list of all count

Source code in pyMolinfo/docs/molparser.py
def __var_finder(data):
    '''
    Find variables in a sdf file (single)

    Parameters
    ----------
    data : str
        sdf data

    Returns
    -------
    res : dict
        a list of all count
    '''
    res = re.findall(
        r"(\>\s*\<(.*)\s*\>\s*((.*\n)([^\<\>\$\$\$\$])*))", data, re.M)
    return res

arrange_prop(property_value_list, atom_index_list)

Arrange a new list with respect to the index list

Parameters

property_value_list: list such as atomic number atom_index_list: list atom id between 1 and ...

Returns

res: list sorted list

Source code in pyMolinfo/docs/molparser.py
def arrange_prop(self, property_value_list, atom_index_list):
    '''
    Arrange a new list with respect to the index list

    Parameters
    ----------
    property_value_list: list
        such as atomic number
    atom_index_list: list
        atom id between 1 and ...

    Returns
    -------
    res: list
        sorted list
    '''
    try:
        # convert atom id to list id
        index_list = np.array(atom_index_list) - 1
        property_value_list_sorted = np.take(
            property_value_list, index_list)
        # res
        return property_value_list_sorted
    except Exception as e:
        Exception(e)

json_parser(jsonSource, id_sort=True)

parse json file

Parameters

jsonSource: dict file json file content id_sort: bool if True, sort id

Returns

res: dict

Source code in pyMolinfo/docs/molparser.py
def json_parser(self, jsonSource, id_sort=True):
    '''
    parse json file

    Parameters
    ----------
    jsonSource: dict file
        json file content
    id_sort: bool
        if True, sort id

    Returns
    -------
    res: dict
    '''
    try:
        dictSource = jsonSource['PC_Compounds'][0]

        # analyze json file
        # *** id node
        if 'id' in dictSource:
            _idNode = dictSource['id']
            cid = self.__json_parser_id(_idNode)

        # *** atoms
        if 'atoms' in dictSource:
            _atomsNode = dictSource['atoms']
            # atom ids, element atomic number
            atomIds, _elementAtomicNumber = self.__json_parser_atoms(
                _atomsNode)

        # atom numbers
        atomNo = len(atomIds)

        # atom list (element list)
        elementListRes, elementList = self.__find_element_symbol(
            _elementAtomicNumber)

        # *** bonds
        if 'bonds' in dictSource:
            _bonds = dictSource['bonds']
            # atom1 id, atom2 id, bond types
            atomId1, atomId2, bondTypes, bondMatrix = self.__json_parser_bonds(
                _bonds)

        # bond numbers
        bondNo = len(atomId1)

        # *** coords
        if 'coords' in dictSource:
            _coords = dictSource['coords']  # list
            # coords type, aid, xyzList
            coords_type, coords_aid, xyzList, structure_type = self.__json_parser_coords(
                _coords)

        # *** charge
        if 'charge' in dictSource:
            _charge = dictSource['charge']  # scaler
            charge = self.__json_parser_charge(_charge)

        # *** props
        if 'props' in dictSource:
            _props = dictSource['props']  # list of dict
            propDict = self.__json_parser_props(_props)

        # *** count
        if 'count' in dictSource:
            _count = dictSource['count']  # dict
            countList = self.__json_parser_count(_count)

        # interpret
        __json_atom_position_res = self.__json_atom_position(
            atomNo, elementList, xyzList)

        # bond block
        __json_atom_bondblock_res = self.__json_atom_bondblock(
            atomNo, bondNo, elementList, bondMatrix)

        # bondBlock: used for Matview class
        # atomDetails, bondBlock, xyzCenterList = self.__json_atom_analyzer(
        #     atomNo, bondNo, elementList, xyzList, bondMatrix)

        # *** origin info
        origin_info = {
            'atom_elements': __json_atom_position_res.get('elementList'),
            'atom_atomic_number': _elementAtomicNumber,
            'atom_details': __json_atom_position_res.get('atomDetails'),
            'bond_block': __json_atom_bondblock_res.get('bondBlock'),
            'bond_list': __json_atom_bondblock_res.get('bondMatrix'),
            'xyz_list': __json_atom_position_res.get('xyzList'),
            'xyz_center_list': __json_atom_position_res.get('xyzCenterList'),
        }

        # *** define new ids for mat and update
        # *** xyzList, bondMatrix, elementlist, elementAtomicNumber
        # *** bond_list = bondMatrix
        # *** atom_block = atom_details
        # if id_sort:
        mat_position_info, atom_id_conversion, xyz_list_sorted, \
            element_list_sorted, bond_list_sorted = self.SetAtomId(
                xyzList, elementList, bondMatrix)

        # interpret
        __json_atom_position_res2 = self.__json_atom_position(
            atomNo, element_list_sorted, xyz_list_sorted)
        # set
        atom_block_sorted = __json_atom_position_res2.get('atomDetails')
        xyz_list_sorted = __json_atom_position_res2.get('xyzList')
        xyz_center_list_sorted = __json_atom_position_res2.get(
            'xyzCenterList')

        # bond block
        __json_atom_bondblock_res2 = self.__json_atom_bondblock(
            atomNo, bondNo, element_list_sorted, bond_list_sorted)
        # set
        bond_block_sorted = __json_atom_bondblock_res2.get('bondBlock')

        # atomic number sorted
        element_atomic_number = self.arrange_prop(
            _elementAtomicNumber, atom_id_conversion[:, 0])

        # update properties
        # name
        mat_name = propDict.get('IUPAC Name')
        if mat_name is None:
            mat_name = 'NULL'

        # formula
        mat_formula = propDict.get('Molecular Formula')
        if mat_formula is None:
            # create mat formula
            mat_formula = 'NULL'

        # molecular weight
        mat_mass = propDict.get('Molecular Weight')
        if mat_mass is None:
            # mat molecular weight/mass
            mat_mass = 0
        else:
            mat_mass = float(mat_mass)

        # res
        res = {
            'header_block': '',
            'counts_line': '',
            'mat_structure': structure_type,
            'mat_cid': cid,
            'mat_name': mat_name,
            'mat_formula': mat_formula,
            'mat_mass': mat_mass,
            'atom_numbers': atomNo,
            'atom_elements': element_list_sorted,
            'atom_atomic_number': element_atomic_number,
            'atom_block': atom_block_sorted,
            'bond_numbers': bondNo,
            'bond_block': bond_block_sorted,
            'bond_list': bond_list_sorted,
            'xyz_list': xyz_list_sorted,
            'xyz_center_list': xyz_center_list_sorted,
            'compound_properties': propDict,
            'mat_info_origin': origin_info
        }

        return res

    except Exception as e:
        raise Exception(e)

read_file(sourceContent={})

Read structure file 1. from file 2. download from api

Parameters

filepath : str full file name with directory

Returns

res : dict mol: mol object structure: structure object

hints

mol: mol object header_block: header block counts_line: counts line atom_numbers: atom number mat_cid: cid number mat_name: IUPAC name mat_formula: formula mat_mass: molecular mass atom_names: atom list atom_xyz: atom coordinate bond_numbers: bond number bond_list: bond list bond_matrix: bond matrix bond_xyz: bond coordinate

structure: structure object mol: mol object mat: mat object

Source code in pyMolinfo/docs/molparser.py
def read_file(self, sourceContent={}):
    '''
    Read structure file 
        1. from file
        2. download from api

    Parameters
    ----------
    filepath : str
        full file name with directory 

    Returns
    -------
    res : dict
        mol: mol object
        structure: structure object

    hints:
        mol: mol object
            header_block: header block
            counts_line: counts line
            atom_numbers: atom number
            mat_cid: cid number
            mat_name: IUPAC name
            mat_formula: formula
            mat_mass: molecular mass
            atom_names: atom list
            atom_xyz: atom coordinate
            bond_numbers: bond number
            bond_list: bond list
            bond_matrix: bond matrix
            bond_xyz: bond coordinate

        structure: structure object
            mol: mol object
            mat: mat object
    '''
    try:
        # get file path
        filePath = self.filepath

        # check
        if filePath:
            # open file and get content
            fileContent, fileDir, fileName, fileFormat = Utility.OpenFile(
                filePath)
        else:
            # read string/json and ... files
            fileContent, fileFormat = Utility.ReadContent(
                sourceContent)

        # method selection
        parserFun = {
            'sdf': self.sdf_parser,
            'json': self.json_parser
        }

        # parse file
        parserSelection = parserFun.get(fileFormat)
        parserRes = parserSelection(fileContent)
        return parserRes

    except Exception as e:
        raise Exception(e)

sdf_parser(sdfSource, sdfVersion='V2000')

Parse sdf file

Parameters

sdfSource : str sdf file content

str

sdf file version (default V2000)

Returns

res : dict mol: mol object header_block: header block counts_line: counts line atom_numbers: atom number mat_cid: cid number mat_name: IUPAC name mat_formula: formula mat_mass: molecular mass atom_names: atom list atom_elements: atom list bond_numbers: bond number atom_block: atoms bond_block: bond list xyz_list: xyz list xyz_center_list: xyz center list compound_properties: compound properties

Source code in pyMolinfo/docs/molparser.py
def sdf_parser(self, sdfSource, sdfVersion='V2000'):
    '''
    Parse sdf file

    Parameters
    ----------
    sdfSource : str
        sdf file content

    sdfVersion : str
        sdf file version (default V2000)

    Returns
    -------
    res : dict
        mol: mol object
            header_block: header block
            counts_line: counts line
            atom_numbers: atom number
            mat_cid: cid number
            mat_name: IUPAC name
            mat_formula: formula
            mat_mass: molecular mass
            atom_names: atom list
            atom_elements: atom list
            bond_numbers: bond number
            atom_block: atoms
            bond_block: bond list
            xyz_list: xyz list
            xyz_center_list: xyz center list
            compound_properties: compound properties

    '''
    # decode binary
    # if binary file
    # sdfSource = sdfSource.decode('utf-8')
    # if text file
    sdfSource = sdfSource
    # create list
    sdfSourceList = sdfSource.splitlines()

    # header block
    headerBlock = sdfSourceList[0:3]

    # counts line
    countsLine = sdfSourceList[3]
    if countsLine.find('V2000') == -1:
        raise Exception(
            'SDF file version is not compatible with this method, import 2000 version.')
    # check
    countsLineList = countsLine.split()

    # find 'M END'
    MENDi = -1
    MENDi = sdfSourceList.index('M  END')
    # connection table
    if MENDi != -1:
        connectionTable = sdfSourceList[4:MENDi]

    # *** check
    _countsLineListSize = len(countsLineList)
    if _countsLineListSize == 10:
        # atom no
        atomNo = int(countsLineList[0])
        # bond no
        bondNo = int(countsLineList[1])
    elif _countsLineListSize == 9:
        _connectionTableLines = connectionTable
        _connectionTableLinesSize = len(_connectionTableLines)
        # first record
        _firstRecord = _connectionTableLines[0]
        _firstRecordSize = len(_firstRecord)
        _secondRecord = _connectionTableLines[0]
        _secondRecordSize = len(_secondRecord)
        if _firstRecordSize != _secondRecordSize:
            raise Exception('sdf file is not coded correctly.')
        # loop
        for l in range(_connectionTableLinesSize):
            _loopRecordSize = len(_connectionTableLines[l])
            if _loopRecordSize < _firstRecordSize:
                # atom no
                atomNo = int(l)
                # bond no
                bondNo = int(str(countsLineList[0]).split(str(atomNo))[1])
                # break
                break
    else:
        raise Exception('3rd line of the sdf file is not coded correctly.')

    # element rows
    elementRows = connectionTable[0:atomNo]
    # bond rows
    bondRows = connectionTable[atomNo:]

    # other vars
    compoundPropertiesList = MolParser.__var_finder(sdfSource)
    # dict vars
    compoundProperties = MolParser.__var_analyzer(compoundPropertiesList)
    # extract:
    PUBCHEM_COMPOUND_CID = compoundProperties.get('PUBCHEM_COMPOUND_CID')
    PUBCHEM_IUPAC_NAME = compoundProperties.get('PUBCHEM_IUPAC_NAME')
    PUBCHEM_EXACT_MASS = compoundProperties.get('PUBCHEM_EXACT_MASS')
    PUBCHEM_MOLECULAR_FORMULA = compoundProperties.get(
        'PUBCHEM_MOLECULAR_FORMULA')
    PUBCHEM_MOLECULAR_WEIGHT = compoundProperties.get(
        'PUBCHEM_MOLECULAR_WEIGHT')

    atoms = []
    atomList = []
    xyzList = []

    # atoms position
    for i in range(atomNo):
        _atomRow = elementRows[i].split()
        # position
        _x = float(_atomRow[0])
        _y = float(_atomRow[1])
        _z = float(_atomRow[2])
        # name
        _name = _atomRow[3]

        # atom list
        atomList.append(_name)

        # atom info
        atom = {
            'id': i+1,
            'symbol': _name,
            'index': i+1,
            'x': _x,
            'y': _y,
            'z': _z,
            'position': {
                'x': _x,
                'y': _y,
                'z': _z
            },
            'xyz': [_x, _y, _z]
        }
        # save atom info
        atoms.append(atom)
        # xyz
        xyzList.append([_x, _y, _z])

    # set
    xyzList = np.array(xyzList)

    # object base
    objectBaseCoordinate = Structure.CenterPoints(xyzList)
    # print(f"objectBaseCoordinate: {objectBaseCoordinate}")

    # move to the center [0,0,0]
    xyzCenterList, movingCoordinate = Structure.CenterObject(
        xyzList, objectBaseCoordinate)
    # print(f"xyzCenterList: {xyzCenterList} \n movingCoordinate: \n {movingCoordinate}")

    # create mat formula
    matFormula = Structure.create_formula(atomList)
    # calculate molecular mass
    # matMass = 1

    # bond analysis
    bondList = []
    atomBonds = []

    # atomNo: *** atom id in the structure ***
    for i in range(atomNo):
        # name
        _nameAtom1 = str(atomList[i])
        # index [not duplicated element]
        # _nameAtom1Index = atomList.index(_nameAtom1)
        _nameAtom1Index = i+1
        # check bond type
        for j in range(bondNo):
            _bondRow = bondRows[j].split()
            # starts from 1 to ... (not 0)
            _nameAtomBondRow = int(_bondRow[0])
            # check bond exist
            if _nameAtom1Index == _nameAtomBondRow:
                # atom 2 index
                _indexAtom = int(_bondRow[1])
                # atom 2 name
                _nameAtom2 = str(atomList[_indexAtom-1])
                # bound type
                _bondType = int(_bondRow[2])
                # str bond
                _bondName = _nameAtom1 + _nameAtom2
                # atom bond
                atomBonds.append(
                    (_indexAtom, _nameAtom2, _bondName, _bondType))

        if len(atomBonds) > 0:
            # save
            _bondList = {
                'id': _nameAtom1Index,
                'symbol': _nameAtom1,
                'bonds': atomBonds
            }
            bondList.append(_bondList)

        # reset
        atomBonds = []
        _bondList = {}

    # res
    res = {
        'header_block': headerBlock,
        'counts_line': countsLine,
        'atom_numbers': atomNo,
        'mat_cid': PUBCHEM_COMPOUND_CID,
        'mat_name': PUBCHEM_IUPAC_NAME if PUBCHEM_IUPAC_NAME is not None else '',
        'mat_formula': PUBCHEM_MOLECULAR_FORMULA if PUBCHEM_MOLECULAR_FORMULA is not None else matFormula,
        'mat_mass': PUBCHEM_EXACT_MASS if PUBCHEM_EXACT_MASS is not None else PUBCHEM_MOLECULAR_WEIGHT,
        'atom_names': atomList,
        'atom_elements': atomList,
        'bond_numbers': bondNo,
        'atom_block': atoms,
        'bond_block': bondList,
        'xyz_list': xyzList,
        'xyz_center_list': xyzCenterList,
        'compound_properties': compoundProperties
    }

    # return
    return res

observer

Observer

Source code in pyMolinfo/docs/observer.py
class Observer():

    def __init__(self):
        pass

    @staticmethod
    def GenerateCircularObserver(xyzList, obsRadius, dataNo):
        '''
        generate observer points based on circle's track
        '''
        # angular frequency
        freq = 3
        # obs angles
        obsAnglesRes = Structure.PeriodGenerator(dataNo)
        obsAngles = obsAnglesRes[0]
        obsAnglesStep = obsAnglesRes[1]

        # circle loops
        obsAnglesLoopRes = Structure.PeriodGenerator(dataNo, freq)
        obsAnglesLoop = obsAnglesLoopRes[0]
        obsAnglesLoopStep = obsAnglesLoopRes[1]

        # len
        obsAnglesLength = len(obsAngles)
        # observer coordinate
        obsCoordinate = np.zeros((freq, obsAnglesLength, 3))

        # circle 1
        for i in range(obsAnglesLength):
            _x, _y = Structure.CircleCoordinate(obsAngles[i], obsRadius)
            # outside compound
            _obsCoordinate = [_x, _y, 0]
            # inside compound
            # save
            obsCoordinate[0, i, :] = _obsCoordinate
            # reset
            _x, _y = 0, 0

        # circle 2
        for i in range(obsAnglesLength):
            _y, _z = Structure.CircleCoordinate(obsAngles[i], obsRadius)
            # outside compound
            _obsCoordinate = [0, _y, _z]
            # inside compound
            # save
            obsCoordinate[1, i, :] = _obsCoordinate
            # reset
            _y, _z = 0, 0

        # circle 3
        for i in range(obsAnglesLength):
            _x, _z = Structure.CircleCoordinate(obsAngles[i], obsRadius)
            # outside compound
            _obsCoordinate = [_x, 0, _z]
            # inside compound
            # save
            obsCoordinate[2, i, :] = _obsCoordinate
            # reset
            _x, _z = 0, 0

        # res
        return obsCoordinate, obsAngles, obsAnglesLoop, obsAnglesLoopStep

    @staticmethod
    def GeneratorLinearObserver(xyzList, obsDistance, dataNo):
        # number of observers
        obsNo = 3
        # obs length
        obsLengthRes = Structure.LineGenerator(dataNo)
        obsLength = obsLengthRes[0]
        obsLengthStep = obsLengthRes[1]

        # obs total length
        obsLengthLoopRes = Structure.LineGenerator(dataNo, w=obsNo)
        obsLengthLoop = obsLengthLoopRes[0]
        obsLengthLoopStep = obsLengthLoopRes[1]

        # len
        obsLengthSize = len(obsLength)
        # observer cordinate
        obsCoordinate = np.zeros((obsNo, obsLengthSize, 3))

        # line 1 (parallel x)
        for i in range(obsLengthSize):
            _x, _y, _z = obsLength[i], obsDistance, 0
            # outside compound
            _obsCoordinate = [_x, _y, _z]
            # inside compound
            # save
            obsCoordinate[0, i, :] = _obsCoordinate
            # reset
            _x, _y, _z = 0, 0, 0

        # line 2 (parallel y)
        for i in range(obsLengthSize):
            _x, _y, _z = obsDistance, obsLength[i], 0
            # outside compound
            _obsCoordinate = [_x, _y, _z]
            # inside compound
            # save
            obsCoordinate[1, i, :] = _obsCoordinate
            # reset
            _x, _y, _z = 0, 0, 0

        # line 1 (parallel z)
        for i in range(obsLengthSize):
            _x, _y, _z = obsLength[i], 0, obsDistance
            # outside compound
            _obsCoordinate = [_x, _y, _z]
            # inside compound
            # save
            obsCoordinate[2, i, :] = _obsCoordinate
            # reset
            _x, _y, _z = 0, 0, 0

        # res
        return obsCoordinate, obsLength, obsLengthLoop, obsLengthLoopStep

    @staticmethod
    def GeneratorCircleObserver(r, tetaNo, phiNo, limits=[]):
        '''
        Generate circle xyz points in cartesian coordinate
        '''
        # rad [rad]
        tetaRes = Structure.PeriodGenerator(n=tetaNo)
        teta = tetaRes[0]
        tetaStep = tetaRes[1]
        # print(f"teta: {teta.shape}")
        tetaNo = len(teta)
        # phi [rad]
        if len(limits) > 0:
            phiRes = Structure.PeriodLimitGenerator(n=phiNo, limits=limits)
            phi = phiRes[0]
            phiStep = phiRes[1]
        else:
            phiRes = Structure.PeriodGenerator(n=phiNo)
            phi = phiRes[0]
            phiStep = phiRes[1]
        # print(f"phi: {phi.shape}")
        phiNo = len(phi)

        # total rotation
        angFreq = Structure.PeriodGenerator(n=tetaNo, w=tetaNo)[0]
        # print(f"angFreq: {angFreq}")
        # frequency
        freq = np.max(angFreq)/(2*np.pi)
        # print(f"freq: {freq}")
        # period
        period = 1/freq
        # print(f"period: {period}")
        # sample no [in a second]
        sampleNo = len(angFreq)
        # print(f"sampleNo: {sampleNo}")
        # sampling rate [number of samples in a second]
        samplingRate = sampleNo
        # sampling interval
        samplingInterval = 1/samplingRate
        # time span
        timeSpan = np.arange(0, 1, samplingInterval)

        # [number of circles, number of points in a circle, [x,y,z] points]
        xyzPoints = np.zeros((tetaNo, phiNo, 3))

        for i in range(tetaNo):
            for j in range(phiNo):
                _rtpPoint = np.array([r, teta[i], phi[j]])
                _xyzPoint = Structure.SphericalToCartesianCoordinate(_rtpPoint)
                # save
                xyzPoints[i, j, :] = _xyzPoint

        # print(f"xyzPoints: {xyzPoints.shape}")
        # res
        return xyzPoints, teta, phi, tetaStep, phiStep, angFreq, freq, period, sampleNo, samplingRate, samplingInterval, timeSpan

    @staticmethod
    def ObsWatchPathGenerator(r, tetaNo, phiNo, tetaLimits=[], phiLimit=[]):
        '''
        generate a circle path containing xyz points in the cartesian coordinate

        Parameters
        ----------
        r : float
            distance between observer points and element points
        tetaNo : int
            number of circle paths
        phiNo : int
            number of observer points in a circle path
        tetaLimits : list
            angles define the limit of observer, default: [0, pi]
        phiLimit : list
            angles define the limit of observer, default: [0, 2pi]

        Returns
        -------
        xyzPoints : numpy.ndarray
            [number of circles, number of points in a circle, [x,y,z] points]
        teta : numpy.ndarray
            teta angles
        phi : numpy.ndarray
            phi angles
        tetaStep : float
            teta step
        phiStep : float
            phi step
        angFreq : numpy.ndarray
            angular frequency
        freq : float
            frequency
        period : float
            period
        sampleNo : int
            sampling no [1 second]
        samplingRate : int
            sampling rate [number of samples in a second]
        samplingInterval : float
            sampling interval
        timeSpan : numpy.ndarray
            time span [for one loop]
        timeSpanTotal : numpy.ndarray
            total time span

        Notes
        -----
        - The last obs point is the mirror of angle 0, thus it is ignored.
        '''
        # rad [rad]
        if len(tetaLimits) > 0:
            tetaRes = Structure.PeriodLimitGenerator(
                n=tetaNo+1, limits=tetaLimits)
            teta = tetaRes[0]
            tetaStep = tetaRes[1]
        else:
            tetaRes = Structure.PeriodGenerator(n=tetaNo+1)
            teta = tetaRes[0]
            tetaStep = tetaRes[1]

        # phi [rad]
        if len(phiLimit) > 0:
            phiRes = Structure.PeriodLimitGenerator(
                n=phiNo+1, limits=phiLimit)
            phi = phiRes[0]
            phiStep = phiRes[1]
        else:
            phiRes = Structure.PeriodGenerator(n=phiNo+1)
            phi = phiRes[0]
            phiStep = phiRes[1]

        # time span [for one loop]
        timeLoop = 1
        # sampling no [1 second]
        sampleNo = phiNo
        # sampling rate [number of samples in a second]
        samplingRate = sampleNo
        # sampling interval
        samplingInterval = 1/samplingRate
        # time span
        timeSpan = np.arange(0, timeLoop, samplingInterval)
        # frequency set
        freq = 1
        # total rotation [1 second]
        angFreq = 2*np.pi*freq
        # period
        period = 1/freq

        # total sampling time [s]
        samplingTimeTotal = tetaNo
        # total number of samples (total time)
        sampleNoTotal = samplingTimeTotal*sampleNo
        # sampling rate with respect to total time
        samplingIntervalTotal = samplingTimeTotal/sampleNoTotal
        # time span
        timeSpanTotal = np.arange(0, samplingTimeTotal, samplingIntervalTotal)

        # [number of circles, number of points in a circle, [x,y,z] points]
        xyzPoints = np.zeros((tetaNo, phiNo, 3))

        for i in range(tetaNo):
            for j in range(phiNo):
                _rtpPoint = np.array([r, teta[i], phi[j]])
                _xyzPoint = Structure.SphericalToCartesianCoordinate(_rtpPoint)
                # save
                xyzPoints[i, j, :] = _xyzPoint

        # res
        return xyzPoints, teta, phi, tetaStep, phiStep, angFreq, freq, period, sampleNo, samplingRate, samplingInterval, timeSpan, timeSpanTotal

GenerateCircularObserver(xyzList, obsRadius, dataNo) staticmethod

generate observer points based on circle's track

Source code in pyMolinfo/docs/observer.py
@staticmethod
def GenerateCircularObserver(xyzList, obsRadius, dataNo):
    '''
    generate observer points based on circle's track
    '''
    # angular frequency
    freq = 3
    # obs angles
    obsAnglesRes = Structure.PeriodGenerator(dataNo)
    obsAngles = obsAnglesRes[0]
    obsAnglesStep = obsAnglesRes[1]

    # circle loops
    obsAnglesLoopRes = Structure.PeriodGenerator(dataNo, freq)
    obsAnglesLoop = obsAnglesLoopRes[0]
    obsAnglesLoopStep = obsAnglesLoopRes[1]

    # len
    obsAnglesLength = len(obsAngles)
    # observer coordinate
    obsCoordinate = np.zeros((freq, obsAnglesLength, 3))

    # circle 1
    for i in range(obsAnglesLength):
        _x, _y = Structure.CircleCoordinate(obsAngles[i], obsRadius)
        # outside compound
        _obsCoordinate = [_x, _y, 0]
        # inside compound
        # save
        obsCoordinate[0, i, :] = _obsCoordinate
        # reset
        _x, _y = 0, 0

    # circle 2
    for i in range(obsAnglesLength):
        _y, _z = Structure.CircleCoordinate(obsAngles[i], obsRadius)
        # outside compound
        _obsCoordinate = [0, _y, _z]
        # inside compound
        # save
        obsCoordinate[1, i, :] = _obsCoordinate
        # reset
        _y, _z = 0, 0

    # circle 3
    for i in range(obsAnglesLength):
        _x, _z = Structure.CircleCoordinate(obsAngles[i], obsRadius)
        # outside compound
        _obsCoordinate = [_x, 0, _z]
        # inside compound
        # save
        obsCoordinate[2, i, :] = _obsCoordinate
        # reset
        _x, _z = 0, 0

    # res
    return obsCoordinate, obsAngles, obsAnglesLoop, obsAnglesLoopStep

GeneratorCircleObserver(r, tetaNo, phiNo, limits=[]) staticmethod

Generate circle xyz points in cartesian coordinate

Source code in pyMolinfo/docs/observer.py
@staticmethod
def GeneratorCircleObserver(r, tetaNo, phiNo, limits=[]):
    '''
    Generate circle xyz points in cartesian coordinate
    '''
    # rad [rad]
    tetaRes = Structure.PeriodGenerator(n=tetaNo)
    teta = tetaRes[0]
    tetaStep = tetaRes[1]
    # print(f"teta: {teta.shape}")
    tetaNo = len(teta)
    # phi [rad]
    if len(limits) > 0:
        phiRes = Structure.PeriodLimitGenerator(n=phiNo, limits=limits)
        phi = phiRes[0]
        phiStep = phiRes[1]
    else:
        phiRes = Structure.PeriodGenerator(n=phiNo)
        phi = phiRes[0]
        phiStep = phiRes[1]
    # print(f"phi: {phi.shape}")
    phiNo = len(phi)

    # total rotation
    angFreq = Structure.PeriodGenerator(n=tetaNo, w=tetaNo)[0]
    # print(f"angFreq: {angFreq}")
    # frequency
    freq = np.max(angFreq)/(2*np.pi)
    # print(f"freq: {freq}")
    # period
    period = 1/freq
    # print(f"period: {period}")
    # sample no [in a second]
    sampleNo = len(angFreq)
    # print(f"sampleNo: {sampleNo}")
    # sampling rate [number of samples in a second]
    samplingRate = sampleNo
    # sampling interval
    samplingInterval = 1/samplingRate
    # time span
    timeSpan = np.arange(0, 1, samplingInterval)

    # [number of circles, number of points in a circle, [x,y,z] points]
    xyzPoints = np.zeros((tetaNo, phiNo, 3))

    for i in range(tetaNo):
        for j in range(phiNo):
            _rtpPoint = np.array([r, teta[i], phi[j]])
            _xyzPoint = Structure.SphericalToCartesianCoordinate(_rtpPoint)
            # save
            xyzPoints[i, j, :] = _xyzPoint

    # print(f"xyzPoints: {xyzPoints.shape}")
    # res
    return xyzPoints, teta, phi, tetaStep, phiStep, angFreq, freq, period, sampleNo, samplingRate, samplingInterval, timeSpan

ObsWatchPathGenerator(r, tetaNo, phiNo, tetaLimits=[], phiLimit=[]) staticmethod

generate a circle path containing xyz points in the cartesian coordinate

Parameters

r : float distance between observer points and element points tetaNo : int number of circle paths phiNo : int number of observer points in a circle path tetaLimits : list angles define the limit of observer, default: [0, pi] phiLimit : list angles define the limit of observer, default: [0, 2pi]

Returns

xyzPoints : numpy.ndarray [number of circles, number of points in a circle, [x,y,z] points] teta : numpy.ndarray teta angles phi : numpy.ndarray phi angles tetaStep : float teta step phiStep : float phi step angFreq : numpy.ndarray angular frequency freq : float frequency period : float period sampleNo : int sampling no [1 second] samplingRate : int sampling rate [number of samples in a second] samplingInterval : float sampling interval timeSpan : numpy.ndarray time span [for one loop] timeSpanTotal : numpy.ndarray total time span

Notes
  • The last obs point is the mirror of angle 0, thus it is ignored.
Source code in pyMolinfo/docs/observer.py
@staticmethod
def ObsWatchPathGenerator(r, tetaNo, phiNo, tetaLimits=[], phiLimit=[]):
    '''
    generate a circle path containing xyz points in the cartesian coordinate

    Parameters
    ----------
    r : float
        distance between observer points and element points
    tetaNo : int
        number of circle paths
    phiNo : int
        number of observer points in a circle path
    tetaLimits : list
        angles define the limit of observer, default: [0, pi]
    phiLimit : list
        angles define the limit of observer, default: [0, 2pi]

    Returns
    -------
    xyzPoints : numpy.ndarray
        [number of circles, number of points in a circle, [x,y,z] points]
    teta : numpy.ndarray
        teta angles
    phi : numpy.ndarray
        phi angles
    tetaStep : float
        teta step
    phiStep : float
        phi step
    angFreq : numpy.ndarray
        angular frequency
    freq : float
        frequency
    period : float
        period
    sampleNo : int
        sampling no [1 second]
    samplingRate : int
        sampling rate [number of samples in a second]
    samplingInterval : float
        sampling interval
    timeSpan : numpy.ndarray
        time span [for one loop]
    timeSpanTotal : numpy.ndarray
        total time span

    Notes
    -----
    - The last obs point is the mirror of angle 0, thus it is ignored.
    '''
    # rad [rad]
    if len(tetaLimits) > 0:
        tetaRes = Structure.PeriodLimitGenerator(
            n=tetaNo+1, limits=tetaLimits)
        teta = tetaRes[0]
        tetaStep = tetaRes[1]
    else:
        tetaRes = Structure.PeriodGenerator(n=tetaNo+1)
        teta = tetaRes[0]
        tetaStep = tetaRes[1]

    # phi [rad]
    if len(phiLimit) > 0:
        phiRes = Structure.PeriodLimitGenerator(
            n=phiNo+1, limits=phiLimit)
        phi = phiRes[0]
        phiStep = phiRes[1]
    else:
        phiRes = Structure.PeriodGenerator(n=phiNo+1)
        phi = phiRes[0]
        phiStep = phiRes[1]

    # time span [for one loop]
    timeLoop = 1
    # sampling no [1 second]
    sampleNo = phiNo
    # sampling rate [number of samples in a second]
    samplingRate = sampleNo
    # sampling interval
    samplingInterval = 1/samplingRate
    # time span
    timeSpan = np.arange(0, timeLoop, samplingInterval)
    # frequency set
    freq = 1
    # total rotation [1 second]
    angFreq = 2*np.pi*freq
    # period
    period = 1/freq

    # total sampling time [s]
    samplingTimeTotal = tetaNo
    # total number of samples (total time)
    sampleNoTotal = samplingTimeTotal*sampleNo
    # sampling rate with respect to total time
    samplingIntervalTotal = samplingTimeTotal/sampleNoTotal
    # time span
    timeSpanTotal = np.arange(0, samplingTimeTotal, samplingIntervalTotal)

    # [number of circles, number of points in a circle, [x,y,z] points]
    xyzPoints = np.zeros((tetaNo, phiNo, 3))

    for i in range(tetaNo):
        for j in range(phiNo):
            _rtpPoint = np.array([r, teta[i], phi[j]])
            _xyzPoint = Structure.SphericalToCartesianCoordinate(_rtpPoint)
            # save
            xyzPoints[i, j, :] = _xyzPoint

    # res
    return xyzPoints, teta, phi, tetaStep, phiStep, angFreq, freq, period, sampleNo, samplingRate, samplingInterval, timeSpan, timeSpanTotal

structure

Structure

chemical structure methods

Source code in pyMolinfo/docs/structure.py
class Structure():
    '''
    chemical structure methods
    '''

    def __init__(self):
        pass

    @staticmethod
    def CenterPoints(xyzList):
        '''
        Find the center coordination of an object

        Parameters
        ----------
        xyzList : list
            list of xyz points

        Returns
        -------
        objectBaseCoordinate : list
            center coordination of an object
        '''
        # set
        xyzList = np.array(xyzList)
        # find the highest xyz
        # x
        xMax = np.max(xyzList[:, 0])
        xMin = np.min(xyzList[:, 0])
        if np.abs(xMax) != np.abs(xMin):
            xLen = np.abs(xMax - xMin)
            xCenter = xMin + (xLen/2)
        else:
            xLen = 0
            xCenter = 0 + (xLen/2)
        # print(f"X value: {xMin}, {xMax}")

        # y
        yMax = np.max(xyzList[:, 1])
        yMin = np.min(xyzList[:, 1])
        if np.abs(yMax) != np.abs(yMin):
            yLen = np.abs(yMax - yMin)
            yCenter = yMin + (yLen/2)
        else:
            yLen = 0
            yCenter = 0 + (yLen/2)
        # print(f"Y value: {yMin}, {yMax}")

        # z
        zMax = np.max(xyzList[:, 2])
        zMin = np.min(xyzList[:, 2])
        if np.abs(zMax) != np.abs(zMin):
            zLen = np.abs(zMax - zMin)
            zCenter = zMin + (zLen/2)
        else:
            zLen = 0
            zCenter = 0 + (zLen/2)
        # print(f"Z value: {zMin}, {zMax}")

        # object base
        objectBaseCoordinate = np.array([xCenter, yCenter, zCenter])

        return objectBaseCoordinate

    @staticmethod
    def CenterObject(xyzList, centerPoint):
        '''
        move an object to the center of the origin [0,0,0]

        Parameters
        ----------
        xyzList : list
            list of xyz points
        centerPoint : list
            center point of the object

        Returns
        -------
        newCenterPoints : list
            new center points of the object
        movingCoordinate : list
            moving coordinate of the object
        '''
        originPoint = np.array([0, 0, 0])
        movingCoordinate = originPoint - centerPoint
        newCenterPoints = np.array(xyzList) + movingCoordinate

        return newCenterPoints, movingCoordinate

    @staticmethod
    def ObjectDislay(xyzList, xyzCenterList):
        '''
        display object in two places
        '''
        # find the center of object
        xyzCenter = Structure.CenterPoints(xyzList)
        # find the center of object after moving to the origin [0,0,0]
        xyzCenterMoving = Structure.CenterPoints(xyzCenterList)

        # 3d plot
        fig = plt.figure()
        ax = plt.axes(projection='3d')
        ax.scatter3D(xyzList[:, 0], xyzList[:, 1], xyzList[:, 2])
        ax.scatter3D(xyzCenter[0], xyzCenter[1], xyzCenter[2])
        ax.scatter3D(xyzCenterList[:, 0],
                     xyzCenterList[:, 1], xyzCenterList[:, 2])
        ax.scatter3D(xyzCenterMoving[0],
                     xyzCenterMoving[1], xyzCenterMoving[2])
        # line
        ax.plot3D([xyzCenter[0], xyzCenterMoving[0]], [xyzCenter[1],
                  xyzCenterMoving[1]], [xyzCenter[2], xyzCenterMoving[2]])
        plt.show()

    @staticmethod
    def CircleCoordinate(teta, r):
        '''
        circle coordination with teta and r

        Parameters
        ----------
        teta : float
            angle with x-axis
        r : float
            circle radius

        Returns
        -------
        x : float
            x coordinate
        y : float
            y coordinate
        '''
        x = r*np.cos(teta)
        y = r*np.sin(teta)
        return (x, y)

    @staticmethod
    def PeriodGenerator(n=100, w=1):
        '''
        set array of 2pi period
        '''
        recordsNo = w*n
        obsAnglesRes, obsAnglesStep = np.linspace(
            0, 2, recordsNo, retstep=True)
        obsAngles = obsAnglesRes*w*np.pi

        return obsAngles, obsAnglesStep

    @staticmethod
    def PeriodLimitGenerator(n=100, w=1, limits=[0, 0]):
        '''
        set array of 2pi period

        Parameters
        ----------
        n : int
            number of records
        w : int
            number of periods
        limits : list
            list of min, max to be removed from the period

        Returns
        -------
        obsAngles : list
            list of angles
        obsAnglesStep : float
            step of angles
        '''
        # print(f"limits: {limits}")
        point1 = limits[0]
        point2 = limits[1]
        recordsNo = w*n
        obsAnglesRes, obsAnglesStep = np.linspace(
            point1, point2, recordsNo, retstep=True)
        obsAngles = obsAnglesRes

        return obsAngles, obsAnglesStep

    @staticmethod
    def SphericalToCartesianCoordinate(rtpPoint):
        '''
        convert spherical to cartesian points

        Parameters
        ----------
        rtPoint : list
            r, teta, phi of spherical coordinate

        Returns
        -------
        xyzPoint : list
            x, y, z of cartesian coordinate
        '''
        # spherical
        r = rtpPoint[0]
        teta = rtpPoint[1]
        phi = rtpPoint[2]
        # cartesian
        x = r*np.sin(phi)*np.cos(teta)
        y = r*np.sin(phi)*np.sin(teta)
        z = r*np.cos(phi)
        # res
        return np.array([x, y, z])

    @staticmethod
    def LineGenerator(n=100, w=1):
        recordsNo = w*n
        obsLengthRes, obsLengthStep = np.linspace(
            -10, 10, recordsNo, retstep=True)
        obsLength = obsLengthRes*w

        return obsLength, obsLengthStep

    @staticmethod
    def CartesianToSphericalCoordinate(xyzPoint):
        '''
        convert cartesian to spherical coordinate

        Parameters
        ----------
        xyzPoint : list
            x, y, z of cartesian coordinate

        Returns
        -------
        rtpPoint : list
            r, teta, phi of spherical coordinate
        '''
        # cartesian
        x = xyzPoint[0]
        y = xyzPoint[1]
        z = xyzPoint[2]
        # spherical
        r = np.sqrt(x**2 + y**2 + z**2)
        teta = np.arctan(y/x)
        # np.arccos(z/np.sqrt(x**2 + y**2 + z**2))
        phi = np.arctan(np.sqrt(x**2 + y**2)/z)
        # res
        return np.array([r, teta, phi]), np.array([r, np.degrees(teta), np.rad2deg(phi)])

    @staticmethod
    def create_formula(atom_elements):
        '''
        create mat using mat elements
        '''
        try:
            # check
            if len(atom_elements) == 0:
                raise Exception('atom elements list is empty')

            my_dict = {i: atom_elements.count(i) for i in atom_elements}
            # mat name
            elList = ''
            for key, value in my_dict.items():
                if value == 1:
                    _el = str(key)
                else:
                    _el = str(key)+str(value)
                elList += _el
                elList += ''
            # transform
            elList = elList.strip()

            return elList
        except Exception as e:
            raise Exception(f"fail to create formula: {e}")

CartesianToSphericalCoordinate(xyzPoint) staticmethod

convert cartesian to spherical coordinate

Parameters

xyzPoint : list x, y, z of cartesian coordinate

Returns

rtpPoint : list r, teta, phi of spherical coordinate

Source code in pyMolinfo/docs/structure.py
@staticmethod
def CartesianToSphericalCoordinate(xyzPoint):
    '''
    convert cartesian to spherical coordinate

    Parameters
    ----------
    xyzPoint : list
        x, y, z of cartesian coordinate

    Returns
    -------
    rtpPoint : list
        r, teta, phi of spherical coordinate
    '''
    # cartesian
    x = xyzPoint[0]
    y = xyzPoint[1]
    z = xyzPoint[2]
    # spherical
    r = np.sqrt(x**2 + y**2 + z**2)
    teta = np.arctan(y/x)
    # np.arccos(z/np.sqrt(x**2 + y**2 + z**2))
    phi = np.arctan(np.sqrt(x**2 + y**2)/z)
    # res
    return np.array([r, teta, phi]), np.array([r, np.degrees(teta), np.rad2deg(phi)])

CenterObject(xyzList, centerPoint) staticmethod

move an object to the center of the origin [0,0,0]

Parameters

xyzList : list list of xyz points centerPoint : list center point of the object

Returns

newCenterPoints : list new center points of the object movingCoordinate : list moving coordinate of the object

Source code in pyMolinfo/docs/structure.py
@staticmethod
def CenterObject(xyzList, centerPoint):
    '''
    move an object to the center of the origin [0,0,0]

    Parameters
    ----------
    xyzList : list
        list of xyz points
    centerPoint : list
        center point of the object

    Returns
    -------
    newCenterPoints : list
        new center points of the object
    movingCoordinate : list
        moving coordinate of the object
    '''
    originPoint = np.array([0, 0, 0])
    movingCoordinate = originPoint - centerPoint
    newCenterPoints = np.array(xyzList) + movingCoordinate

    return newCenterPoints, movingCoordinate

CenterPoints(xyzList) staticmethod

Find the center coordination of an object

Parameters

xyzList : list list of xyz points

Returns

objectBaseCoordinate : list center coordination of an object

Source code in pyMolinfo/docs/structure.py
@staticmethod
def CenterPoints(xyzList):
    '''
    Find the center coordination of an object

    Parameters
    ----------
    xyzList : list
        list of xyz points

    Returns
    -------
    objectBaseCoordinate : list
        center coordination of an object
    '''
    # set
    xyzList = np.array(xyzList)
    # find the highest xyz
    # x
    xMax = np.max(xyzList[:, 0])
    xMin = np.min(xyzList[:, 0])
    if np.abs(xMax) != np.abs(xMin):
        xLen = np.abs(xMax - xMin)
        xCenter = xMin + (xLen/2)
    else:
        xLen = 0
        xCenter = 0 + (xLen/2)
    # print(f"X value: {xMin}, {xMax}")

    # y
    yMax = np.max(xyzList[:, 1])
    yMin = np.min(xyzList[:, 1])
    if np.abs(yMax) != np.abs(yMin):
        yLen = np.abs(yMax - yMin)
        yCenter = yMin + (yLen/2)
    else:
        yLen = 0
        yCenter = 0 + (yLen/2)
    # print(f"Y value: {yMin}, {yMax}")

    # z
    zMax = np.max(xyzList[:, 2])
    zMin = np.min(xyzList[:, 2])
    if np.abs(zMax) != np.abs(zMin):
        zLen = np.abs(zMax - zMin)
        zCenter = zMin + (zLen/2)
    else:
        zLen = 0
        zCenter = 0 + (zLen/2)
    # print(f"Z value: {zMin}, {zMax}")

    # object base
    objectBaseCoordinate = np.array([xCenter, yCenter, zCenter])

    return objectBaseCoordinate

CircleCoordinate(teta, r) staticmethod

circle coordination with teta and r

Parameters

teta : float angle with x-axis r : float circle radius

Returns

x : float x coordinate y : float y coordinate

Source code in pyMolinfo/docs/structure.py
@staticmethod
def CircleCoordinate(teta, r):
    '''
    circle coordination with teta and r

    Parameters
    ----------
    teta : float
        angle with x-axis
    r : float
        circle radius

    Returns
    -------
    x : float
        x coordinate
    y : float
        y coordinate
    '''
    x = r*np.cos(teta)
    y = r*np.sin(teta)
    return (x, y)

ObjectDislay(xyzList, xyzCenterList) staticmethod

display object in two places

Source code in pyMolinfo/docs/structure.py
@staticmethod
def ObjectDislay(xyzList, xyzCenterList):
    '''
    display object in two places
    '''
    # find the center of object
    xyzCenter = Structure.CenterPoints(xyzList)
    # find the center of object after moving to the origin [0,0,0]
    xyzCenterMoving = Structure.CenterPoints(xyzCenterList)

    # 3d plot
    fig = plt.figure()
    ax = plt.axes(projection='3d')
    ax.scatter3D(xyzList[:, 0], xyzList[:, 1], xyzList[:, 2])
    ax.scatter3D(xyzCenter[0], xyzCenter[1], xyzCenter[2])
    ax.scatter3D(xyzCenterList[:, 0],
                 xyzCenterList[:, 1], xyzCenterList[:, 2])
    ax.scatter3D(xyzCenterMoving[0],
                 xyzCenterMoving[1], xyzCenterMoving[2])
    # line
    ax.plot3D([xyzCenter[0], xyzCenterMoving[0]], [xyzCenter[1],
              xyzCenterMoving[1]], [xyzCenter[2], xyzCenterMoving[2]])
    plt.show()

PeriodGenerator(n=100, w=1) staticmethod

set array of 2pi period

Source code in pyMolinfo/docs/structure.py
@staticmethod
def PeriodGenerator(n=100, w=1):
    '''
    set array of 2pi period
    '''
    recordsNo = w*n
    obsAnglesRes, obsAnglesStep = np.linspace(
        0, 2, recordsNo, retstep=True)
    obsAngles = obsAnglesRes*w*np.pi

    return obsAngles, obsAnglesStep

PeriodLimitGenerator(n=100, w=1, limits=[0, 0]) staticmethod

set array of 2pi period

Parameters

n : int number of records w : int number of periods limits : list list of min, max to be removed from the period

Returns

obsAngles : list list of angles obsAnglesStep : float step of angles

Source code in pyMolinfo/docs/structure.py
@staticmethod
def PeriodLimitGenerator(n=100, w=1, limits=[0, 0]):
    '''
    set array of 2pi period

    Parameters
    ----------
    n : int
        number of records
    w : int
        number of periods
    limits : list
        list of min, max to be removed from the period

    Returns
    -------
    obsAngles : list
        list of angles
    obsAnglesStep : float
        step of angles
    '''
    # print(f"limits: {limits}")
    point1 = limits[0]
    point2 = limits[1]
    recordsNo = w*n
    obsAnglesRes, obsAnglesStep = np.linspace(
        point1, point2, recordsNo, retstep=True)
    obsAngles = obsAnglesRes

    return obsAngles, obsAnglesStep

SphericalToCartesianCoordinate(rtpPoint) staticmethod

convert spherical to cartesian points

Parameters

rtPoint : list r, teta, phi of spherical coordinate

Returns

xyzPoint : list x, y, z of cartesian coordinate

Source code in pyMolinfo/docs/structure.py
@staticmethod
def SphericalToCartesianCoordinate(rtpPoint):
    '''
    convert spherical to cartesian points

    Parameters
    ----------
    rtPoint : list
        r, teta, phi of spherical coordinate

    Returns
    -------
    xyzPoint : list
        x, y, z of cartesian coordinate
    '''
    # spherical
    r = rtpPoint[0]
    teta = rtpPoint[1]
    phi = rtpPoint[2]
    # cartesian
    x = r*np.sin(phi)*np.cos(teta)
    y = r*np.sin(phi)*np.sin(teta)
    z = r*np.cos(phi)
    # res
    return np.array([x, y, z])

create_formula(atom_elements) staticmethod

create mat using mat elements

Source code in pyMolinfo/docs/structure.py
@staticmethod
def create_formula(atom_elements):
    '''
    create mat using mat elements
    '''
    try:
        # check
        if len(atom_elements) == 0:
            raise Exception('atom elements list is empty')

        my_dict = {i: atom_elements.count(i) for i in atom_elements}
        # mat name
        elList = ''
        for key, value in my_dict.items():
            if value == 1:
                _el = str(key)
            else:
                _el = str(key)+str(value)
            elList += _el
            elList += ''
        # transform
        elList = elList.strip()

        return elList
    except Exception as e:
        raise Exception(f"fail to create formula: {e}")

utility

Utility

Source code in pyMolinfo/docs/utility.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
class Utility():

    json_csv_columns = [
        'file_name',
        'image_name_1',
        'image_name_2',
        'image_name_3',
        'mat_structure',
        'atom_numbers',
        'mat_cid',
        'mat_name',
        'mat_formula',
        'mat_smiles',
        'mat_mass',
        'atom_elements',
        'atom_atomic_number',
        'bond_numbers',
        'bond_list',
        'xyz_list',
        'xyz_center_list',
        'atom_weights',
        'mat_inchikey'
    ]

    def __init__(self):
        pass

    @staticmethod
    def CheckFileFormat(filePath):
        '''
        check file format

        Parameters
        ----------
        filePath : str
            file path

        Returns
        -------
        fileDir : str
            file directory
        fileName : str
            file name
        fileFormat : str
            file format
        '''
        # check file exist
        if os.path.isfile(filePath):
            # file analysis
            fileDir = os.path.dirname(filePath)
            fileName = os.path.basename(filePath)
            fileFormat = os.path.splitext(filePath)[1]
            fileFormat = str(fileFormat.split(".")[-1]).lower()
            # res
            return fileDir, fileName, fileFormat
        else:
            raise Exception('file path is not valid.')

    @staticmethod
    def ReadContent(sourceContent):
        '''
        Read (load) file with respect to its format extension

        Parameters
        ----------
        sourceContent : dict
            contentFile: string content of a text file (url request)
            contentFormat: content format (sdf, json, ...)

        Returns
        -------
        file content : str
            content of a text file
        _contentFormat : str
            content format
        '''
        try:
            #
            _contentSource, _contentFormat = sourceContent.values()
            # check
            if _contentSource and _contentFormat:
                # read file
                if _contentFormat == 'sdf':
                    # string
                    fileContent = _contentSource
                elif _contentFormat == 'json':
                    # json convert to dict
                    fileContent = json.loads(_contentSource)
                elif _contentFormat == 'json-string':
                    # json convert to dict
                    fileContent = json.loads(_contentSource)

                # res
                return fileContent, _contentFormat
            else:
                raise Exception("target path is not valid.")
        except Exception as e:
            raise Exception(e)

    @staticmethod
    def OpenFile(filePath):
        '''
        Open file with respect to its format extension

        Parameters
        ----------
        filePath : str
            file path

        Returns
        -------
        file content : str
            content of a text file
        fileDir : str
            file directory
        fileName : str
            file name
        fileFormat : str
            file format
        '''
        try:
            # check
            if os.path.exists(filePath):
                # file info
                fileDir, fileName, fileFormat = Utility.CheckFileFormat(
                    filePath)

                # read a file
                with open(filePath, 'r') as f:
                    if fileFormat == 'sdf':
                        fileContent = f.read()
                    elif fileFormat == 'json':
                        fileContent = json.load(f)

                # res
                return fileContent, fileDir, fileName, fileFormat
            else:
                raise Exception("target path is not valid.")

        except Exception as e:
            raise Exception(e)

    @staticmethod
    def ListFiles(targetPath, fileExtension=''):
        '''
        list files in a target file

        Parameters
        ----------
        targetPath : str
            target path
        fileExtension : str
            file extension, default is empty

        Returns
        -------
        filesFound : list
            list of files in the target path
        '''
        try:
            # check
            if os.path.exists(targetPath):
                if not fileExtension:
                    # files
                    filesFound = os.listdir(targetPath)
                else:
                    # res
                    filesFound = []
                    for f in os.listdir(targetPath):
                        if f.endswith('.'+str(fileExtension).strip()):
                            filesFound.append(f)
                # res
                return filesFound
            else:
                raise Exception("target path is not valid.")

        except Exception as e:
            raise Exception(e)

    @staticmethod
    def SaveFile(fileContent, fileName, fileFormat, fileDir, logMessage=' file is successfully created and saved in'):
        '''
        save a file with respect to a format

        Parameters
        ----------
        fileContent : str
            file content
        fileName : str
            file name
        fileFormat : str
            file format
        fileDir : str
            file directory
        logMessage : str
            log message

        Returns
        -------
        bool
            True if the file is successfully created and saved in
        '''
        try:
            # set
            _fileFormat = str(fileFormat).lower()
            # file full name with format
            _fileFullName = fileName + f'.{_fileFormat}'
            # file full location
            fileLoc = os.path.join(fileDir, _fileFullName)

            # check
            if not os.path.isdir(fileDir):
                raise Exception('file directory is not valid.')

            # open file
            with open(fileLoc, 'w') as f:
                # check
                if _fileFormat == 'sdf':
                    # save
                    f.write(fileContent)
                    f.close()
                # FIXME
                if _fileFormat == 'json-string':
                    # save
                    json.dumps(fileContent, f, indent=5)
                    f.close()
                if _fileFormat == 'json':
                    # save ()
                    json.dump(fileContent, f, indent=5)
                    f.close()

            # log
            print(f"the {_fileFormat + logMessage} `{fileLoc}`")

            return True
        except Exception as e:
            raise Exception(e)

    @staticmethod
    def FindTargetFileInFolder(file_location, file_format):
        '''
        Find target files with respect to a specific format such as json

        Parameters
        ----------
        file_location : str
            file location
        file_format : str
            file format

        Returns
        -------
        listFiles : list
            list of target files
        '''
        try:
            # access directory
            allFiles = os.listdir(file_location)
            # file format
            fileExtension = f"*.{file_format}"
            # list
            listFiles = fnmatch.filter(allFiles, fileExtension)
            # res
            return listFiles
        except Exception as e:
            raise Exception(e)

    @staticmethod
    def SelectFile(list_file_names, file_name_ids, file_name_prefix='cid'):
        '''
        select files from a file list based on a query

        Parameters
        ----------
        list_file_names : list
            list of file names
        file_name_ids : list
            list of file ids
        file_name_prefix : str
            file name prefix

        Returns
        -------
        listFiles : list
            list of selected files
        '''
        try:
            # res
            listFiles = []
            cids = []

            # check
            if file_name_prefix == 'cid':
                # *** load only specific cids ***
                if len(file_name_ids) > 0 and len(list_file_names) > 0:
                    #
                    for i in range(len(list_file_names)):
                        _res = re.search(
                            r'(^cid_([0-9]+)(\.)([a-z0-9]*))', str(list_file_names[i]))
                        # check
                        if _res:
                            # save
                            _cid = _res.group(2)
                            cids.append(_cid)
                            # check
                            if _cid in file_name_ids:
                                _fileName = _res.group(1)
                                # save
                                listFiles.append(_fileName)
            elif file_name_prefix == 'file_name':
                # *** load only specific names ***
                if len(file_name_ids) > 0 and len(list_file_names) > 0:
                    #
                    _fileNames = [
                        item for item in file_name_ids if item in list_file_names]
                    # save
                    listFiles.extend(_fileName)
            # res
            return listFiles

        except Exception as e:
            raise Exception(e)

    @staticmethod
    def CreateCVS(csv_list, file_location, file_name='', file_format='json', save_location=''):
        '''
        Create csv file from dictionary

        Parameters
        ----------
        csv_list : list
            list of dictionary
        file_location : str
            file location
        file_name : str
            file name
        file_format : str
            file format
        save_location : str
            save location

        Returns
        -------
        _save_location : str
            save location
        csvFileName : str
            csv file name
        '''
        try:
            # check
            if len(save_location) == 0:
                _save_location = file_location
            else:
                _save_location = save_location

            # log
            _log = f"csv file is successfully created in {_save_location}"

            # csv file
            csvFileId = str(date.today()) + \
                "-" + str(random.randint(0, 256))

            # # file name
            if len(file_name) > 0:
                csvFileName = f'{file_name}.csv'
            else:
                csvFileName = f'cid_{csvFileId}.csv'

            # create path
            csvFile = os.path.join(_save_location, csvFileName)

            # write
            with open(csvFile, 'w') as f:
                writer = csv.DictWriter(
                    f, fieldnames=Utility.json_csv_columns)
                writer.writeheader()
                writer.writerows(csv_list)
                # log
                # print(_log)

            # res
            return _save_location, csvFileName
        except Exception as e:
            raise Exception(f"Error: {e}")

    @staticmethod
    def ConvertCSVContentToList(csv_content):
        '''
        convert csv content (string) to list

        Parameters
        ----------
        csv_content : str
            csv string content

        Returns
        -------
        list
            list of csv content

        Notes
        -----
        - list[0]: column head
        - list[1]: records

        '''
        try:
            # csv column
            csv_column = []
            csv_rows = []

            #  csv list
            csv_list = csv_content.splitlines()
            # check
            if type(csv_list) is list:
                # len
                csv_list_len = len(csv_list)
                # csv column
                _column = str(csv_list[0]).replace(
                    "'", "").replace('"', '').split(',')
                csv_column.extend(_column)
                # csv rows
                _rows = str(csv_list[1]).replace(
                    "'", "").replace('"', '').split(',')
                csv_rows.extend(_rows)
            else:
                csv_list_len = -1

            # res
            return csv_list, csv_column, csv_rows, csv_list_len

        except Exception as e:
            raise Exception(e)

    @staticmethod
    def SaveNpArray(arr, file_name='', location=''):
        '''
        Save numpy array

        Parameters
        ----------
        arr : numpy array
            numpy array
        file_name : str
            file name
        location : str
            location

        '''
        try:
            # full file name
            if not file_name:
                file_name = "np_array"
            fullFileName = f"{file_name}.npy"
            # location
            fileLoc = os.path.join(location, fullFileName)

            # open
            with open(fileLoc, 'wb') as f:
                np.save(f, arr)

            print("save operation is done.")
        except Exception as e:
            raise Exception(e)

    @staticmethod
    def load_custom_functional_group(file_location) -> List[Dict[str, List[str]]]:
        '''
        load custom functional group

        Parameters
        ----------
        file_location : str
            file location

        Returns
        -------
        custom_functional_group : dict
            custom functional group

        '''
        try:
            if os.path.exists(file_location):
                # check yml file format
                with open(file_location, 'r') as f:
                    _ref = yaml.load(
                        f, Loader=yaml.FullLoader)
                # check not empty
                if _ref:
                    # ref keys
                    # _ref_keys = list(_ref.keys())

                    # custom functional group
                    custom_functional_groups: List[Dict[str, List[str]]] = []
                    for key, value in _ref.items():
                        # check value
                        if not value:
                            raise Exception(
                                "value is not found.")

                        # NOTE: check value format
                        if isinstance(value, list):
                            # save
                            custom_functional_groups.append(
                                {
                                    key: value
                                }
                            )
                        else:
                            raise Exception(
                                "value format is not valid.")

                    return custom_functional_groups
                else:
                    raise Exception("ref file is empty.")
            else:
                raise Exception("file path is not valid.")
        except Exception as e:
            raise Exception(e)

CheckFileFormat(filePath) staticmethod

check file format

Parameters

filePath : str file path

Returns

fileDir : str file directory fileName : str file name fileFormat : str file format

Source code in pyMolinfo/docs/utility.py
@staticmethod
def CheckFileFormat(filePath):
    '''
    check file format

    Parameters
    ----------
    filePath : str
        file path

    Returns
    -------
    fileDir : str
        file directory
    fileName : str
        file name
    fileFormat : str
        file format
    '''
    # check file exist
    if os.path.isfile(filePath):
        # file analysis
        fileDir = os.path.dirname(filePath)
        fileName = os.path.basename(filePath)
        fileFormat = os.path.splitext(filePath)[1]
        fileFormat = str(fileFormat.split(".")[-1]).lower()
        # res
        return fileDir, fileName, fileFormat
    else:
        raise Exception('file path is not valid.')

ConvertCSVContentToList(csv_content) staticmethod

convert csv content (string) to list

Parameters

csv_content : str csv string content

Returns

list list of csv content

Notes
  • list[0]: column head
  • list[1]: records
Source code in pyMolinfo/docs/utility.py
@staticmethod
def ConvertCSVContentToList(csv_content):
    '''
    convert csv content (string) to list

    Parameters
    ----------
    csv_content : str
        csv string content

    Returns
    -------
    list
        list of csv content

    Notes
    -----
    - list[0]: column head
    - list[1]: records

    '''
    try:
        # csv column
        csv_column = []
        csv_rows = []

        #  csv list
        csv_list = csv_content.splitlines()
        # check
        if type(csv_list) is list:
            # len
            csv_list_len = len(csv_list)
            # csv column
            _column = str(csv_list[0]).replace(
                "'", "").replace('"', '').split(',')
            csv_column.extend(_column)
            # csv rows
            _rows = str(csv_list[1]).replace(
                "'", "").replace('"', '').split(',')
            csv_rows.extend(_rows)
        else:
            csv_list_len = -1

        # res
        return csv_list, csv_column, csv_rows, csv_list_len

    except Exception as e:
        raise Exception(e)

CreateCVS(csv_list, file_location, file_name='', file_format='json', save_location='') staticmethod

Create csv file from dictionary

Parameters

csv_list : list list of dictionary file_location : str file location file_name : str file name file_format : str file format save_location : str save location

Returns

_save_location : str save location csvFileName : str csv file name

Source code in pyMolinfo/docs/utility.py
@staticmethod
def CreateCVS(csv_list, file_location, file_name='', file_format='json', save_location=''):
    '''
    Create csv file from dictionary

    Parameters
    ----------
    csv_list : list
        list of dictionary
    file_location : str
        file location
    file_name : str
        file name
    file_format : str
        file format
    save_location : str
        save location

    Returns
    -------
    _save_location : str
        save location
    csvFileName : str
        csv file name
    '''
    try:
        # check
        if len(save_location) == 0:
            _save_location = file_location
        else:
            _save_location = save_location

        # log
        _log = f"csv file is successfully created in {_save_location}"

        # csv file
        csvFileId = str(date.today()) + \
            "-" + str(random.randint(0, 256))

        # # file name
        if len(file_name) > 0:
            csvFileName = f'{file_name}.csv'
        else:
            csvFileName = f'cid_{csvFileId}.csv'

        # create path
        csvFile = os.path.join(_save_location, csvFileName)

        # write
        with open(csvFile, 'w') as f:
            writer = csv.DictWriter(
                f, fieldnames=Utility.json_csv_columns)
            writer.writeheader()
            writer.writerows(csv_list)
            # log
            # print(_log)

        # res
        return _save_location, csvFileName
    except Exception as e:
        raise Exception(f"Error: {e}")

FindTargetFileInFolder(file_location, file_format) staticmethod

Find target files with respect to a specific format such as json

Parameters

file_location : str file location file_format : str file format

Returns

listFiles : list list of target files

Source code in pyMolinfo/docs/utility.py
@staticmethod
def FindTargetFileInFolder(file_location, file_format):
    '''
    Find target files with respect to a specific format such as json

    Parameters
    ----------
    file_location : str
        file location
    file_format : str
        file format

    Returns
    -------
    listFiles : list
        list of target files
    '''
    try:
        # access directory
        allFiles = os.listdir(file_location)
        # file format
        fileExtension = f"*.{file_format}"
        # list
        listFiles = fnmatch.filter(allFiles, fileExtension)
        # res
        return listFiles
    except Exception as e:
        raise Exception(e)

ListFiles(targetPath, fileExtension='') staticmethod

list files in a target file

Parameters

targetPath : str target path fileExtension : str file extension, default is empty

Returns

filesFound : list list of files in the target path

Source code in pyMolinfo/docs/utility.py
@staticmethod
def ListFiles(targetPath, fileExtension=''):
    '''
    list files in a target file

    Parameters
    ----------
    targetPath : str
        target path
    fileExtension : str
        file extension, default is empty

    Returns
    -------
    filesFound : list
        list of files in the target path
    '''
    try:
        # check
        if os.path.exists(targetPath):
            if not fileExtension:
                # files
                filesFound = os.listdir(targetPath)
            else:
                # res
                filesFound = []
                for f in os.listdir(targetPath):
                    if f.endswith('.'+str(fileExtension).strip()):
                        filesFound.append(f)
            # res
            return filesFound
        else:
            raise Exception("target path is not valid.")

    except Exception as e:
        raise Exception(e)

OpenFile(filePath) staticmethod

Open file with respect to its format extension

Parameters

filePath : str file path

Returns

file content : str content of a text file fileDir : str file directory fileName : str file name fileFormat : str file format

Source code in pyMolinfo/docs/utility.py
@staticmethod
def OpenFile(filePath):
    '''
    Open file with respect to its format extension

    Parameters
    ----------
    filePath : str
        file path

    Returns
    -------
    file content : str
        content of a text file
    fileDir : str
        file directory
    fileName : str
        file name
    fileFormat : str
        file format
    '''
    try:
        # check
        if os.path.exists(filePath):
            # file info
            fileDir, fileName, fileFormat = Utility.CheckFileFormat(
                filePath)

            # read a file
            with open(filePath, 'r') as f:
                if fileFormat == 'sdf':
                    fileContent = f.read()
                elif fileFormat == 'json':
                    fileContent = json.load(f)

            # res
            return fileContent, fileDir, fileName, fileFormat
        else:
            raise Exception("target path is not valid.")

    except Exception as e:
        raise Exception(e)

ReadContent(sourceContent) staticmethod

Read (load) file with respect to its format extension

Parameters

sourceContent : dict contentFile: string content of a text file (url request) contentFormat: content format (sdf, json, ...)

Returns

file content : str content of a text file _contentFormat : str content format

Source code in pyMolinfo/docs/utility.py
@staticmethod
def ReadContent(sourceContent):
    '''
    Read (load) file with respect to its format extension

    Parameters
    ----------
    sourceContent : dict
        contentFile: string content of a text file (url request)
        contentFormat: content format (sdf, json, ...)

    Returns
    -------
    file content : str
        content of a text file
    _contentFormat : str
        content format
    '''
    try:
        #
        _contentSource, _contentFormat = sourceContent.values()
        # check
        if _contentSource and _contentFormat:
            # read file
            if _contentFormat == 'sdf':
                # string
                fileContent = _contentSource
            elif _contentFormat == 'json':
                # json convert to dict
                fileContent = json.loads(_contentSource)
            elif _contentFormat == 'json-string':
                # json convert to dict
                fileContent = json.loads(_contentSource)

            # res
            return fileContent, _contentFormat
        else:
            raise Exception("target path is not valid.")
    except Exception as e:
        raise Exception(e)

SaveFile(fileContent, fileName, fileFormat, fileDir, logMessage=' file is successfully created and saved in') staticmethod

save a file with respect to a format

Parameters

fileContent : str file content fileName : str file name fileFormat : str file format fileDir : str file directory logMessage : str log message

Returns

bool True if the file is successfully created and saved in

Source code in pyMolinfo/docs/utility.py
@staticmethod
def SaveFile(fileContent, fileName, fileFormat, fileDir, logMessage=' file is successfully created and saved in'):
    '''
    save a file with respect to a format

    Parameters
    ----------
    fileContent : str
        file content
    fileName : str
        file name
    fileFormat : str
        file format
    fileDir : str
        file directory
    logMessage : str
        log message

    Returns
    -------
    bool
        True if the file is successfully created and saved in
    '''
    try:
        # set
        _fileFormat = str(fileFormat).lower()
        # file full name with format
        _fileFullName = fileName + f'.{_fileFormat}'
        # file full location
        fileLoc = os.path.join(fileDir, _fileFullName)

        # check
        if not os.path.isdir(fileDir):
            raise Exception('file directory is not valid.')

        # open file
        with open(fileLoc, 'w') as f:
            # check
            if _fileFormat == 'sdf':
                # save
                f.write(fileContent)
                f.close()
            # FIXME
            if _fileFormat == 'json-string':
                # save
                json.dumps(fileContent, f, indent=5)
                f.close()
            if _fileFormat == 'json':
                # save ()
                json.dump(fileContent, f, indent=5)
                f.close()

        # log
        print(f"the {_fileFormat + logMessage} `{fileLoc}`")

        return True
    except Exception as e:
        raise Exception(e)

SaveNpArray(arr, file_name='', location='') staticmethod

Save numpy array

Parameters

arr : numpy array numpy array file_name : str file name location : str location

Source code in pyMolinfo/docs/utility.py
@staticmethod
def SaveNpArray(arr, file_name='', location=''):
    '''
    Save numpy array

    Parameters
    ----------
    arr : numpy array
        numpy array
    file_name : str
        file name
    location : str
        location

    '''
    try:
        # full file name
        if not file_name:
            file_name = "np_array"
        fullFileName = f"{file_name}.npy"
        # location
        fileLoc = os.path.join(location, fullFileName)

        # open
        with open(fileLoc, 'wb') as f:
            np.save(f, arr)

        print("save operation is done.")
    except Exception as e:
        raise Exception(e)

SelectFile(list_file_names, file_name_ids, file_name_prefix='cid') staticmethod

select files from a file list based on a query

Parameters

list_file_names : list list of file names file_name_ids : list list of file ids file_name_prefix : str file name prefix

Returns

listFiles : list list of selected files

Source code in pyMolinfo/docs/utility.py
@staticmethod
def SelectFile(list_file_names, file_name_ids, file_name_prefix='cid'):
    '''
    select files from a file list based on a query

    Parameters
    ----------
    list_file_names : list
        list of file names
    file_name_ids : list
        list of file ids
    file_name_prefix : str
        file name prefix

    Returns
    -------
    listFiles : list
        list of selected files
    '''
    try:
        # res
        listFiles = []
        cids = []

        # check
        if file_name_prefix == 'cid':
            # *** load only specific cids ***
            if len(file_name_ids) > 0 and len(list_file_names) > 0:
                #
                for i in range(len(list_file_names)):
                    _res = re.search(
                        r'(^cid_([0-9]+)(\.)([a-z0-9]*))', str(list_file_names[i]))
                    # check
                    if _res:
                        # save
                        _cid = _res.group(2)
                        cids.append(_cid)
                        # check
                        if _cid in file_name_ids:
                            _fileName = _res.group(1)
                            # save
                            listFiles.append(_fileName)
        elif file_name_prefix == 'file_name':
            # *** load only specific names ***
            if len(file_name_ids) > 0 and len(list_file_names) > 0:
                #
                _fileNames = [
                    item for item in file_name_ids if item in list_file_names]
                # save
                listFiles.extend(_fileName)
        # res
        return listFiles

    except Exception as e:
        raise Exception(e)

load_custom_functional_group(file_location) staticmethod

load custom functional group

Parameters

file_location : str file location

Returns

custom_functional_group : dict custom functional group

Source code in pyMolinfo/docs/utility.py
@staticmethod
def load_custom_functional_group(file_location) -> List[Dict[str, List[str]]]:
    '''
    load custom functional group

    Parameters
    ----------
    file_location : str
        file location

    Returns
    -------
    custom_functional_group : dict
        custom functional group

    '''
    try:
        if os.path.exists(file_location):
            # check yml file format
            with open(file_location, 'r') as f:
                _ref = yaml.load(
                    f, Loader=yaml.FullLoader)
            # check not empty
            if _ref:
                # ref keys
                # _ref_keys = list(_ref.keys())

                # custom functional group
                custom_functional_groups: List[Dict[str, List[str]]] = []
                for key, value in _ref.items():
                    # check value
                    if not value:
                        raise Exception(
                            "value is not found.")

                    # NOTE: check value format
                    if isinstance(value, list):
                        # save
                        custom_functional_groups.append(
                            {
                                key: value
                            }
                        )
                    else:
                        raise Exception(
                            "value format is not valid.")

                return custom_functional_groups
            else:
                raise Exception("ref file is empty.")
        else:
            raise Exception("file path is not valid.")
    except Exception as e:
        raise Exception(e)

graph3d

graph3d

3D Visualizer of a compound

hint

xyzList is selected for visualization

Source code in pyMolinfo/docs/graph3d.py
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
class graph3d():
    '''
    3D Visualizer of a compound

    hint:
        xyzList is selected for visualization
    '''

    # properties
    _structure_type = ''
    plotScale = []

    def __init__(self, atomElements, atomBonds, xyzList, xyzCenterList, robs, tetaNo, phiNo, limits, atom_bonds_1d):
        self.atomElements = atomElements
        # bond block (info)
        self.atomBonds = atomBonds
        self.xyzList = xyzList
        self.xyzCenterList = xyzCenterList
        self.robs = robs
        self.tetaNo = tetaNo
        self.phiNo = phiNo
        self.limits = limits
        self.atomBonds_1d = atom_bonds_1d

        # set structure type
        structureType, perpendicularAxis, perpendicularVector, XYZ0 = self.StructureAnalyzer()
        self.structure_type = structureType

    @property
    def structure_type(self):
        return self._structure_type

    @structure_type.setter
    def structure_type(self, value):
        self._structure_type = value

    def StructureAnalyzer(self):
        '''
        Check geometry structure to determine 2D/3D

        Parameters
        ----------
        xyzList: list
            point coordination

        Returns
        -------
        structureType: str
            2D or 3D
        '''
        try:
            # array
            xyzList = np.array(self.xyzList)
            # set
            X = xyzList[:, 0]
            Y = xyzList[:, 1]
            Z = xyzList[:, 2]

            # check plane structure (2D, 3D)
            X0 = np.abs(X).sum()
            Y0 = np.abs(Y).sum()
            Z0 = np.abs(Z).sum()
            XYZ0 = [X0, Y0, Z0]

            # perpendicular vector [x,y,z]
            perpendicularVector = [False, False, False]
            # set
            if X0 == 0:
                perpendicularVector[0] = True

            if Y0 == 0:
                perpendicularVector[1] = True

            if Z0 == 0:
                perpendicularVector[2] = True

            # status
            if True in perpendicularVector:
                structureType = '2D'
            else:
                structureType = '3D'

            # axis selection
            perpendicularAxis = []
            if perpendicularVector[0] is True:
                perpendicularAxis.append('X')
            if perpendicularVector[1] is True:
                perpendicularAxis.append('Y')
            if perpendicularVector[2] is True:
                perpendicularAxis.append('Z')

            return structureType, perpendicularAxis, perpendicularVector, XYZ0
        except Exception as e:
            raise Exception(e)

    def set_color(self, atom_symbol):
        '''
        Set a color for each compound
        taken from https://en.wikipedia.org/wiki/CPK_coloring

        Parameters
        ----------
        atom_symbol: str
            atom symbol

        Returns
        -------
        color: str
            atom color
        '''
        colors = {
            "H": '#ffffff',
            "C": '#BCBCBC',
            "N": '#0586f6',
            "O": '#f6052a',
            "F": '#2dd930',
            "Cl": '#2dd930',
            "Br": '#950e0e',
            "I": '#360e89',
            "He": '#3dbaf1',
            "Ne": '#3dbaf1',
            "Ar": '#3dbaf1',
            "Kr": '#3dbaf1',
            "Xe": '#3dbaf1',
            "P": '#f1a03d',
            "S": '#f1ef3d',
            "B": '#efc867',
            "Li": '#6b3ccb',
            "Na": '#6b3ccb',
            "K": '#6b3ccb',
            "Rb": '#6b3ccb',
            "Cs": '#6b3ccb',
            "Fr": '#6b3ccb',
            "Be": '#1c881e',
            "Mg": '#1c881e',
            "Ca": '#1c881e',
            "Sr": '#1c881e',
            "Ba": '#1c881e',
            "Ra": '#1c881e',
            "Ti": '#3d3e40',
            "Fe": '#a48620',
            "other": '#a729ba'
        }

        # check
        _color = colors.get(str(atom_symbol))
        if _color is None:
            return colors.get(str('other'))
        else:
            return _color

    def set_size(self, symbol, _sy=300, _s=1):
        '''
        Set atom size (spherical shape)

        Parameters
        ----------
        symbol: str
            atom symbol
        _s: int
            size

        Returns
        -------
        size: int
            size
        '''
        _sy = 300
        _sx = 0.50*_sy

        sizes = {
            "H": _s*_sx,
            "C": _s*_sy,
            "N": _s*_sy,
            "O": _s*_sy,
            "F": _s*_sy,
            "Cl": _s*_sx,
            "Br": _s*_sy,
            "I": _s*_sy,
            "He": _s*_sy,
            "Ne": _s*_sy,
            "Ar": _s*_sy,
            "Kr": _s*_sy,
            "Xe": _s*_sy,
            "P": _s*_sy,
            "S": _s*_sy,
            "B": _s*_sy,
            "Li": _s*_sy,
            "Na": _s*_sy,
            "K": _s*_sy,
            "Rb": _s*_sy,
            "Cs": _s*_sy,
            "Fr": _s*_sy,
            "Be": _s*_sy,
            "Mg": _s*_sy,
            "Ca": _s*_sy,
            "Sr": _s*_sy,
            "Ba": _s*_sy,
            "Ra": _s*_sy,
            "Ti": _s*_sy,
            "Fe": _s*_sy,
            "other": _s*_sy,
        }

        _size = sizes.get(symbol)
        if _size is None:
            _sizeSet = int(sizes.get('other'))
        else:
            _sizeSet = int(_size)

        return _sizeSet

    def create_3dframe(self):
        '''
        Create 3d frame dimension
        '''
        # max length
        # x
        xMin = np.min(self.xyzList[:, 0])
        xMax = np.max(self.xyzList[:, 0])
        xLen = np.abs(np.abs(xMax) - np.abs(xMin))
        # y
        yMin = np.min(self.xyzList[:, 1])
        yMax = np.max(self.xyzList[:, 1])
        yLen = np.abs(np.abs(yMax) - np.abs(yMin))
        # z
        zMin = np.min(self.xyzList[:, 2])
        zMax = np.max(self.xyzList[:, 2])
        zLen = np.abs(np.abs(zMax) - np.abs(zMin))
        # max
        xyzLenMax = np.max([xMax, yMax, zMax])
        xyzLenMin = np.min([xMin, yMin, zMin])
        xyzR = 1/xyzLenMax

        # res
        return xyzLenMax, xyzLenMin, xyzR, xLen, yLen, zLen

    def create_bond_line(self, xyz1, xyz2, bond_type, xyzL=[1, 1, 1], xyzR=0.15):
        '''
        Create bond line (single, double, tipple)

        Parameters
        ----------
        xyz1: list
            (x,y,z) point 1
        xyz2: list
            (x,y,z) point 2
        bond_type: int
            bond type (1,2,3)
        xyzL: list
            (x,y,z) length
        xyzR: int
            (x,y,z) radius

        Returns
        -------
        bondLines: list
            list of bond lines
        '''
        # bond lines
        bondLines = []
        # bond length
        bondLength = []

        xL, yL, zL = xyzR*np.array(xyzL)

        # Calculate center-to-center vector
        center_vector = np.array(xyz2) - np.array(xyz1)

        # Calculate center-to-center line
        center_line = [[xyz1[0], xyz2[0]], [
            xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]

        if bond_type == 1:
            # bond line
            bondLines.append(center_line)
            # bond length
            _bond_length_res = self.calculate_distance(xyz1, xyz2)
            bondLength.append(_bond_length_res)

        elif bond_type == 2:
            # parallel line
            # y increase
            # center to center line
            # Calculate perpendicular vector to center vector
            perp_vector = np.array([center_vector[1], - center_vector[0], 0])
            perp_vector /= np.linalg.norm(perp_vector)

            # Calculate offset vectors
            offset_vector1 = 0.15 * perp_vector
            offset_vector2 = -0.15 * perp_vector

            # Calculate parallel lines
            _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
                xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
            _l2 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
                xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

            # lines
            _l1_xyz1 = [xyz1[0]+offset_vector1[0], xyz1[1] +
                        offset_vector1[1], xyz1[2]+offset_vector1[2]]
            _l1_xyz2 = [xyz2[0]+offset_vector1[0], xyz2[1] +
                        offset_vector1[1], xyz2[2]+offset_vector1[2]]

            _l2_xyz1 = [xyz1[0]+offset_vector2[0], xyz1[1] +
                        offset_vector2[1], xyz1[2]+offset_vector2[2]]
            _l2_xyz2 = [xyz2[0]+offset_vector2[0], xyz2[1] +
                        offset_vector2[1], xyz2[2]+offset_vector2[2]]

            # bond lines
            bondLines.append(_l1)
            bondLines.append(_l2)
            # bond length
            _bond_length_res = self.calculate_distance(_l1_xyz1, _l1_xyz2)
            bondLength.append(_bond_length_res)
            _bond_length_res = self.calculate_distance(_l2_xyz1, _l2_xyz2)
            bondLength.append(_bond_length_res)

        elif bond_type == 3:
            # parallel line
            # y increase
            # line
            # Calculate perpendicular vector to center vector
            perp_vector = np.array([center_vector[1], -center_vector[0], 0])
            perp_vector /= np.linalg.norm(perp_vector)

            # Calculate offset vectors
            offset_vector1 = 0.125 * perp_vector
            offset_vector2 = -0.125 * perp_vector

            # Calculate parallel lines
            _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
                xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
            _l2 = [[xyz1[0], xyz2[0]], [
                xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]

            _l3 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
                xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

            # lines
            _l1_xyz1 = [xyz1[0]+offset_vector1[0], xyz1[1] +
                        offset_vector1[1], xyz1[2]+offset_vector1[2]]
            _l1_xyz2 = [xyz2[0]+offset_vector1[0], xyz2[1] +
                        offset_vector1[1], xyz2[2]+offset_vector1[2]]
            _l2_xyz1 = xyz1
            _l2_xyz2 = xyz2
            _l3_xyz1 = [xyz1[0]+offset_vector2[0], xyz1[1] +
                        offset_vector2[1], xyz1[2]+offset_vector2[2]]
            _l3_xyz2 = [xyz2[0]+offset_vector2[0], xyz2[1] +
                        offset_vector2[1], xyz2[2]+offset_vector2[2]]

            # bond lines
            bondLines.append(_l1)
            bondLines.append(_l2)
            bondLines.append(_l3)
            # bond length
            # bond length
            _bond_length_res = self.calculate_distance(_l1_xyz1, _l1_xyz2)
            bondLength.append(_bond_length_res)
            _bond_length_res = self.calculate_distance(_l2_xyz1, _l2_xyz2)
            bondLength.append(_bond_length_res)
            _bond_length_res = self.calculate_distance(_l3_xyz1, _l3_xyz2)
            bondLength.append(_bond_length_res)

        return bondLines, bond_type, bondLength

    def create_bond_line_V2(self, xyz1, xyz2, bond_type, xyzL=[1, 1, 1], xyzR=0.15):
        '''
        Create bond line (single, double, triple)

        Parameters
        ----------
        xyz1: list
            (x,y,z) point 1
        xyz2: list
            (x,y,z) point 2
        bond_type: int
            bond type (1,2,3)
        xyzL: list
            (x,y,z) length
        xyzR: int
            (x,y,z) radius

        Returns
        -------
        bondLines: list
            list of bond lines
        '''
        bondLines = []

        xL, yL, zL = xyzR*np.array(xyzL)

        # Calculate center-to-center vector
        center_vector = np.array(xyz2) - np.array(xyz1)

        # Calculate center-to-center line
        center_line = [[xyz1[0], xyz2[0]], [
            xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]

        # Calculate midpoint
        midpoint = [(xyz1[0] + xyz2[0]) / 2, (xyz1[1] +
                                              xyz2[1]) / 2, (xyz1[2] + xyz2[2]) / 2]

        if bond_type == 1:
            # Break single bond into two lines
            bondLines.append(
                [[xyz1[0], midpoint[0]], [xyz1[1], midpoint[1]], [xyz1[2], midpoint[2]]])
            bondLines.append(
                [[midpoint[0], xyz2[0]], [midpoint[1], xyz2[1]], [midpoint[2], xyz2[2]]])

        elif bond_type == 2:
            # parallel line
            # y increase
            # center to center line
            # Calculate perpendicular vector to center vector
            perp_vector = np.array([center_vector[1], - center_vector[0], 0])
            perp_vector /= np.linalg.norm(perp_vector)

            # Calculate offset vectors
            offset_vector1 = 0.15 * perp_vector
            offset_vector2 = -0.15 * perp_vector

            # Calculate parallel lines
            _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
                xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
            _l2 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
                xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

            # Break each parallel line into two lines
            bondLines.append([[xyz1[0]+offset_vector1[0], midpoint[0]+offset_vector1[0]], [
                xyz1[1]+offset_vector1[1], midpoint[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], midpoint[2]+offset_vector1[2]]])
            bondLines.append([[midpoint[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
                midpoint[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [midpoint[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]])
            bondLines.append([[xyz1[0]+offset_vector2[0], midpoint[0]+offset_vector2[0]], [
                xyz1[1]+offset_vector2[1], midpoint[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], midpoint[2]+offset_vector2[2]]])
            bondLines.append([[midpoint[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
                midpoint[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [midpoint[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]])

        elif bond_type == 3:
            # parallel line
            # y increase
            # line
            # Calculate perpendicular vector to center vector
            perp_vector = np.array([center_vector[1], -center_vector[0], 0])
            perp_vector /= np.linalg.norm(perp_vector)

            # Calculate offset vectors
            offset_vector1 = 0.125 * perp_vector
            offset_vector2 = -0.125 * perp_vector

            # Calculate parallel lines
            _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
                xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
            _l2 = [[xyz1[0], xyz2[0]], [
                xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]
            _l3 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
                xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

            # Break each parallel line into two lines
            bondLines.append([[xyz1[0]+offset_vector1[0], midpoint[0]+offset_vector1[0]], [
                xyz1[1]+offset_vector1[1], midpoint[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], midpoint[2]+offset_vector1[2]]])
            bondLines.append([[midpoint[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
                midpoint[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [midpoint[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]])
            bondLines.append([[xyz1[0], midpoint[0]], [
                xyz1[1], midpoint[1]], [xyz1[2], midpoint[2]]])
            bondLines.append([[midpoint[0], xyz2[0]], [
                midpoint[1], xyz2[1]], [midpoint[2], xyz2[2]]])
            bondLines.append([[xyz1[0]+offset_vector2[0], midpoint[0]+offset_vector2[0]], [
                xyz1[1]+offset_vector2[1], midpoint[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], midpoint[2]+offset_vector2[2]]])
            bondLines.append([[midpoint[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
                midpoint[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [midpoint[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]])

        return bondLines, bond_type

    def line_mid_points(self, xyz1, xyz2):
        '''
        Divide a line in two equal parts

        Parameters
        ----------
        xyz1: list
            (x,y,z) point 1
        xyz2: list
            (x,y,z) point 2

        Returns
        -------
        midPoints: list
            list of mid points
        '''
        # Calculate midpoints
        midpoint_x = (xyz1[0] + xyz2[0]) / 2
        midpoint_y = (xyz1[1] + xyz2[1]) / 2
        midpoint_z = (xyz1[2] + xyz2[2]) / 2

        # Divide center_line into two parts
        part1 = [[xyz1[0], midpoint_x], [
            xyz1[1], midpoint_y], [xyz1[2], midpoint_z]]
        part2 = [[midpoint_x, xyz2[0]], [
            midpoint_y, xyz2[1]], [midpoint_z, xyz2[2]]]

        # Get four points
        point1 = [xyz1[0], xyz1[1], xyz1[2]]
        point2 = [midpoint_x, midpoint_y, midpoint_z]
        point3 = [midpoint_x, midpoint_y, midpoint_z]
        point4 = [xyz2[0], xyz2[1], xyz2[2]]

        # res
        return point1, point2, point3, point4, part1, part2

    def line_property(self, xyz1, xyz2):
        '''
        Check a line property with which plane is parallel
        when res contains two True, it means the False coordination contains all elements.

        Parameters
        ----------
        xyz1: list
            (x,y,z) point 1
        xyz2: list
            (x,y,z) point 2

        Returns
        -------
        res: bool
            res
        '''
        try:
            # mean value
            Xm = np.mean([xyz1[0], xyz2[0]])
            Ym = np.mean([xyz1[1], xyz2[1]])
            Zm = np.mean([xyz1[2], xyz2[2]])
            xyzMean = [Xm, Ym, Zm]

            # check plane
            isSubtractZero = np.array([False, False, False])

            # points in one line
            X = xyz1[0] - xyz2[0]
            Y = xyz1[1] - xyz2[1]
            Z = xyz1[2] - xyz2[2]
            xyzPlane = [X, Y, Z]

            # axis vector
            xyzL = np.array([0, 0, 0])

            # set
            # axis selection
            perpendicularAxis = np.array([False, False, False])
            if X == 0:
                isSubtractZero[0] = True
                perpendicularAxis[0] = True
                # xyzL = xyzL + np.array(1, 1, 0)

            if Y == 0:
                isSubtractZero[1] = True
                perpendicularAxis[1] = True
                # xyzL = xyzL + np.array(1, 1, 0)

            if Z == 0:
                isSubtractZero[2] = True
                perpendicularAxis[2] = True
                # xyzL = xyzL + np.array(1, 0, 1)

            # set xyzL
            xyzL = isSubtractZero.astype(int)

            return xyzMean, xyzPlane, isSubtractZero, xyzL, perpendicularAxis

        except Exception as e:
            raise Exception(e)

    def set_plot_scale(self):
        '''
        Set plot scale
        '''
        # atom no
        atomNo = len(self.xyzList)
        # bond no
        bondNo = len(self.atomBonds)

        # bond length list
        bondLengthList = []

        # *** using bond block
        for i in range(bondNo):
            # atom id
            _atom1Id = int(self.atomBonds[i]['id']) - 1
            # atom symbol
            _atom1Symbol = self.atomBonds[i]['symbol']
            # atom color
            _atom1Color = self.set_color(_atom1Symbol)
            # atom bond list
            _atom1BondList = self.atomBonds[i]['bonds']
            atom1BondSize = len(_atom1BondList)

            _atom1X = self.xyzList[_atom1Id, 0]
            _atom1Y = self.xyzList[_atom1Id, 1]
            _atom1Z = self.xyzList[_atom1Id, 2]
            _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

            # draw bond
            if atom1BondSize > 0:
                for j in range(atom1BondSize):
                    # atom [2] id
                    _atom2Id = int(_atom1BondList[j][0]) - 1
                    # atom [2] symbol
                    _atom2Symbol = _atom1BondList[j][1]
                    # atom color
                    _atom2Color = self.set_color(_atom2Symbol)
                    # atom [1] - atom [2] bond type
                    _bondType = int(_atom1BondList[j][3])

                    # xyz
                    _atom2X = self.xyzList[_atom2Id, 0]
                    _atom2Y = self.xyzList[_atom2Id, 1]
                    _atom2Z = self.xyzList[_atom2Id, 2]
                    _atom2XYZ = [_atom2X, _atom2Y, _atom2Z]

                    # bond connection (points)
                    _bondConnection, _bondTypeLog, _bondLengths = self.create_bond_line(
                        _atom1XYZ, _atom2XYZ, _bondType)

                    # save bond length
                    bondLengthList.append(_bondLengths)

        # check bond length list
        if len(bondLengthList) > 0:
            # flatten list
            bondLengthList_flatten = sum(bondLengthList, [])

            # max bond length
            maxBondLength = max(bondLengthList_flatten)
            # min bond length
            minBondLength = min(bondLengthList_flatten)
            # mean bond length
            meanBondLength = np.mean(bondLengthList_flatten)
            # median bond length
            medianBondLength = np.median(bondLengthList_flatten)

            # set plot scale
            self.plotScale = [minBondLength, maxBondLength,
                              meanBondLength, medianBondLength]

    @staticmethod
    def view_graph3d(G, subgraphs=None, **kwargs):
        '''
        Draw a compound in 3D from a graph representation

        Parameters
        ----------
        G : networkx.Graph
            Graph with nodes having 'symbol' and 'xyz' attributes and edges having 'symbol' and 'type' attributes
        subgraphs : list, optional
            List of subgraphs to highlight
        **kwargs : dict
            Additional parameters for visualization
            figSize: tuple - figure size
            bg_color: str - background color
            display_legend: bool - display legend
            theme: str - theme for visualization ('light' or 'dark')
            display_atom_id: bool - display atom IDs
            display_bond_length: bool - display bond lengths
            bond_type_color: list - colors for different bond types

        Returns
        -------
        plot_summary : list
            Summary of plotted elements
        '''
        figSize = kwargs.get('figSize', [])
        bg_color = kwargs.get('bg_color', '#ffffff')
        display_legend = kwargs.get('display_legend', False)
        theme = kwargs.get('theme', 'light')
        display_atom_id = kwargs.get('display_atom_id', True)
        display_bond_length = kwargs.get('display_bond_length', False)
        bond_type_color = kwargs.get(
            'bond_type_color', ['#1B263B', '#EF476F', '#4361EE'])

        # Plot summary
        plot_summary = []

        # Create the figure
        fig = go.Figure()

        # Extract node data from the graph
        nodes = G.nodes(data=True)
        edges = G.edges(data=True)

        # Create list of atom positions from nodes
        xyzList = []
        atomElements = []
        node_ids = []

        for node_id, node_data in nodes:
            if 'xyz' in node_data:
                xyzList.append(node_data['xyz'])
                atomElements.append(node_data['symbol'])
                node_ids.append(node_id)

        # Convert to numpy array for easier manipulation
        xyzList = np.array(xyzList)

        # Create 3D frame dimensions
        xyzLenMax = np.max(np.abs(xyzList))

        # Helper functions
        def set_color(atom_symbol):
            '''Set a color for each atom based on element'''
            colors = {
                "H": '#ffffff',
                "C": '#BCBCBC',
                "N": '#0586f6',
                "O": '#f6052a',
                "F": '#2dd930',
                "Cl": '#2dd930',
                "Br": '#950e0e',
                "I": '#360e89',
                "He": '#3dbaf1',
                "Ne": '#3dbaf1',
                "Ar": '#3dbaf1',
                "Kr": '#3dbaf1',
                "Xe": '#3dbaf1',
                "P": '#f1a03d',
                "S": '#f1ef3d',
                "B": '#efc867',
                "Li": '#6b3ccb',
                "Na": '#6b3ccb',
                "K": '#6b3ccb',
                "Rb": '#6b3ccb',
                "Cs": '#6b3ccb',
                "Fr": '#6b3ccb',
                "Be": '#1c881e',
                "Mg": '#1c881e',
                "Ca": '#1c881e',
                "Sr": '#1c881e',
                "Ba": '#1c881e',
                "Ra": '#1c881e',
                "Ti": '#3d3e40',
                "Fe": '#a48620',
                "other": '#a729ba'
            }

            _color = colors.get(str(atom_symbol))
            if _color is None:
                return colors.get(str('other'))
            else:
                return _color

        def calculate_distance(xyz1, xyz2):
            """Calculate the Euclidean distance between two points."""
            return math.sqrt((xyz2[0] - xyz1[0])**2 +
                             (xyz2[1] - xyz1[1])**2 +
                             (xyz2[2] - xyz1[2])**2)

        # Node visualization (atoms)
        for i, node_id in enumerate(node_ids):
            # Get node data
            node_data = G.nodes[node_id]
            atom_xyz = node_data['xyz']
            atom_symbol = node_data['symbol']

            # Atom label
            atomMark = f"{atom_symbol}{node_id}"

            # Color based on element
            atom_color = set_color(atom_symbol)

            # Text display based on setting
            if display_atom_id:
                text_to_display = [atomMark]
            else:
                text_to_display = ['']

            # Add atom as a 3D scatter point
            fig.add_trace(go.Scatter3d(
                x=[atom_xyz[0]],
                y=[atom_xyz[1]],
                z=[atom_xyz[2]],
                mode='markers+text',
                marker=dict(
                    color=atom_color, 
                    size=10, 
                    sizemode='area', 
                    sizeref=1, 
                    line=dict(width=2, color='black')
                ),
                hoverinfo='all',
                hoverlabel=dict(bgcolor=bg_color),
                hovertext=[f'Element: {atomMark}'],
                text=text_to_display
            ))

        # Edge visualization (bonds)
        for edge in edges:
            # Get nodes connected by this edge
            node1_id, node2_id = edge[0], edge[1]
            edge_data = G.edges[node1_id, node2_id]

            # Get node data
            node1_data = G.nodes[node1_id]
            node2_data = G.nodes[node2_id]

            # Get atom symbols
            atom1_symbol = node1_data['symbol']
            atom2_symbol = node2_data['symbol']

            # Get positions
            atom1_xyz = node1_data['xyz']
            atom2_xyz = node2_data['xyz']

            # Get bond type (default to 1 if missing)
            bond_type = edge_data.get('type', 1) - 1  # Adjust to 0-based index for the color list
            bond_type = min(max(0, bond_type), 2)  # Ensure it's between 0-2

            # Calculate bond length (distance)
            distance = calculate_distance(atom1_xyz, atom2_xyz)

            # Add to plot summary
            plot_summary.append({
                'atom1Id': node1_id,
                'atom2Id': node2_id,
                'atom1Symbol': f"{atom1_symbol}{node1_id}",
                'atom2Symbol': f"{atom2_symbol}{node2_id}",
                'distance': distance
            })

            # Draw the bond as a line
            fig.add_trace(go.Scatter3d(
                x=[atom1_xyz[0], atom2_xyz[0]],
                y=[atom1_xyz[1], atom2_xyz[1]],
                z=[atom1_xyz[2], atom2_xyz[2]],
                mode='lines',
                line=dict(color=bond_type_color[bond_type], width=3),
                hoverinfo='none',
                name=edge_data.get('symbol', f"Bond {node1_id}-{node2_id}"),
                showlegend=True
            ))

            # Calculate midpoint for bond length label
            midX = [(atom1_xyz[0] + atom2_xyz[0]) / 2]
            midY = [(atom1_xyz[1] + atom2_xyz[1]) / 2]
            midZ = [(atom1_xyz[2] + atom2_xyz[2]) / 2]

            # Bond type text
            bond_type_names = ['single bond', 'double bond', 'triple bond']
            bond_type_label = bond_type_names[bond_type]

            # Display bond length if requested
            if display_bond_length:
                text_to_display = [f'{distance:.3f}']
            else:
                text_to_display = ['']

            # Add bond length text
            fig.add_trace(go.Scatter3d(
                x=midX,
                y=midY,
                z=midZ,
                mode='text',
                text=text_to_display,
                hoverinfo='text',
                hoverlabel=dict(bgcolor=bg_color, namelength=-1),
                hovertext=[f'A {bond_type_label} with a length of {distance:.3f}']
            ))

        # Visualize subgraphs if provided
        if subgraphs is not None:
            for subgraph in subgraphs:
                # Get nodes from the subgraph
                subgraph_nodes = list(subgraph.nodes())

                # Highlight nodes
                for node_id in subgraph_nodes:
                    if node_id in G.nodes:
                        node_data = G.nodes[node_id]
                        atom_xyz = node_data['xyz']
                        atom_symbol = node_data['symbol']
                        atom_color = set_color(atom_symbol)

                        # Add highlighted atom
                        fig.add_trace(go.Scatter3d(
                            x=[atom_xyz[0]],
                            y=[atom_xyz[1]],
                            z=[atom_xyz[2]],
                            mode='markers',
                            marker=dict(
                                color=atom_color,
                                size=20,
                                opacity=0.5,
                                sizemode='area',
                                sizeref=1,
                                line=dict(width=2, color='black')
                            )
                        ))

                # Highlight edges
                for edge in subgraph.edges():
                    node1_id, node2_id = edge

                    # Only highlight edges that exist in the main graph
                    if G.has_edge(node1_id, node2_id):
                        node1_data = G.nodes[node1_id]
                        node2_data = G.nodes[node2_id]

                        atom1_xyz = node1_data['xyz']
                        atom2_xyz = node2_data['xyz']

                        # Add highlighted bond
                        fig.add_trace(go.Scatter3d(
                            x=[atom1_xyz[0], atom2_xyz[0]],
                            y=[atom1_xyz[1], atom2_xyz[1]],
                            z=[atom1_xyz[2], atom2_xyz[2]],
                            mode='lines',
                            line=dict(color='blue', width=10, dash='dashdot'),
                            hoverinfo='none'
                        ))

        # Set the limits of the axes
        xyzLenMax = xyzLenMax + 1.5
        fig.update_layout(scene=dict(
            xaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
            yaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
            zaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax])
        ))

        # Set figure size
        if len(figSize) != 0:
            fig.update_layout(width=figSize[0], height=figSize[1])
        else:
            fig.update_layout(autosize=True)

        # Configure legend
        fig.update_layout(legend=dict(
            orientation="h",
            yanchor="bottom",
            y=1.02,
            xanchor="right",
            x=1
        ))

        # Set font color based on theme
        font_color = 'lightgray' if theme == 'black' else 'black'
        fig.update_traces(textfont=dict(color=font_color))

        # Set background color
        fig.update_layout(
            paper_bgcolor=bg_color,
            plot_bgcolor=bg_color,
            scene=dict(
                xaxis=dict(showbackground=True, backgroundcolor=bg_color),
                yaxis=dict(showbackground=True, backgroundcolor=bg_color),
                zaxis=dict(showbackground=True, backgroundcolor=bg_color)
            )
        )

        # Remove axes and other elements for cleaner visualization
        fig.update_layout(
            scene=dict(
                xaxis=dict(showticklabels=False, showgrid=False, zeroline=False, showspikes=False, title=''),
                yaxis=dict(showticklabels=False, showgrid=False, zeroline=False, showspikes=False, title=''),
                zaxis=dict(showticklabels=False, showgrid=False, zeroline=False, showspikes=False, title=''),
                aspectmode='manual',
                aspectratio=dict(x=1, y=1, z=1),
            ),
            showlegend=display_legend,
            margin=dict(l=0, r=0, b=0, t=0)
        )

        # Show the plot
        fig.show(config={
            'scrollZoom': True,
            'displayModeBar': True,
            'displaylogo': True,
            'modeBarButtonsToRemove': ['zoom2d', 'zoomIn2d', 'zoomOut2d', 'pan2d']
        })

        return plot_summary

    @staticmethod
    def view_graph3d_mpl(G, subgraphs=None, **kwargs):
        '''
        Draw a compound in 3D from a graph representation using matplotlib

        Parameters
        ----------
        G : networkx.Graph
            Graph with nodes having 'symbol' and 'xyz' attributes and edges having 'symbol' and 'type' attributes
        subgraphs : list, optional
            List of subgraphs to highlight
        **kwargs : dict
            Additional parameters for visualization
            figSize: tuple - figure size (default: (10, 10))
            elev: float - elevation angle for the 3D view
            azim: float - azimuth angle for the 3D view
            display_legend: bool - display legend (default: True)
            theme: str - theme for visualization ('light' or 'dark')
            display_atom_id: bool - display atom IDs (default: True)
            display_bond_length: bool - display bond lengths (default: False)
            bond_type_color: list - colors for different bond types

        Returns
        -------
        plot_summary : list
            Summary of plotted elements
        fig : matplotlib.figure.Figure
            Matplotlib figure object
        ax : matplotlib.axes.Axes
            Matplotlib axes object
        '''
        # Extract parameters from kwargs with defaults
        figSize = kwargs.get('figSize', (10, 10))
        elev = kwargs.get('elev', 30)
        azim = kwargs.get('azim', 30)
        display_legend = kwargs.get('display_legend', True)
        theme = kwargs.get('theme', 'light')
        display_atom_id = kwargs.get('display_atom_id', True)
        display_bond_length = kwargs.get('display_bond_length', False)
        bond_type_color = kwargs.get('bond_type_color', ['k', 'b', 'r'])  # Black, blue, red

        # Plot summary list to return
        plot_summary = []

        # Create figure and 3D axes
        fig = plt.figure(figsize=figSize)
        ax = fig.add_subplot(111, projection='3d')

        # Extract node data from the graph
        nodes = G.nodes(data=True)
        edges = G.edges(data=True)

        # Create lists for atom positions and elements
        xyzList = []
        atomElements = []
        node_ids = []

        # Extract node data
        for node_id, node_data in nodes:
            if 'xyz' in node_data:
                xyzList.append(node_data['xyz'])
                atomElements.append(node_data['symbol'])
                node_ids.append(node_id)

        # Convert to numpy array for easier manipulation
        xyzList = np.array(xyzList)

        # Helper function to set colors based on atom symbol
        def set_color(atom_symbol):
            colors = {
                "H": '#ffffff',
                "C": '#BCBCBC',
                "N": '#0586f6',
                "O": '#f6052a',
                "F": '#2dd930',
                "Cl": '#2dd930',
                "Br": '#950e0e',
                "I": '#360e89',
                "He": '#3dbaf1',
                "Ne": '#3dbaf1',
                "Ar": '#3dbaf1',
                "Kr": '#3dbaf1',
                "Xe": '#3dbaf1',
                "P": '#f1a03d',
                "S": '#f1ef3d',
                "B": '#efc867',
                "Li": '#6b3ccb',
                "Na": '#6b3ccb',
                "K": '#6b3ccb',
                "Rb": '#6b3ccb',
                "Cs": '#6b3ccb',
                "Fr": '#6b3ccb',
                "Be": '#1c881e',
                "Mg": '#1c881e',
                "Ca": '#1c881e',
                "Sr": '#1c881e',
                "Ba": '#1c881e',
                "Ra": '#1c881e',
                "Ti": '#3d3e40',
                "Fe": '#a48620',
                "other": '#a729ba'
            }

            _color = colors.get(str(atom_symbol))
            if _color is None:
                return colors.get(str('other'))
            else:
                return _color

        # Helper function to calculate distance between atoms
        def calculate_distance(xyz1, xyz2):
            return math.sqrt((xyz2[0] - xyz1[0])**2 +
                             (xyz2[1] - xyz1[1])**2 +
                             (xyz2[2] - xyz1[2])**2)

        # Calculate limits for the plot
        if len(xyzList) > 0:
            xyzLenMax = np.max(np.abs(xyzList)) + 1.5
        else:
            xyzLenMax = 5.0  # Default if no atoms

        # Node visualization (atoms)
        for i, node_id in enumerate(node_ids):
            # Get node data
            node_data = G.nodes[node_id]
            atom_xyz = node_data['xyz']
            atom_symbol = node_data['symbol']

            # Atom label
            atomMark = f"{atom_symbol}{node_id}"

            # Set color based on atom type
            atom_color = set_color(atom_symbol)

            # Plot the atom as a 3D point
            ax.scatter(atom_xyz[0], atom_xyz[1], atom_xyz[2], 
                       color=atom_color, s=100, edgecolors='black')

            # Add atom label if requested
            if display_atom_id:
                ax.text(atom_xyz[0], atom_xyz[1], atom_xyz[2], 
                        atomMark, size=8, zorder=1, ha='center', va='center')

        # Edge visualization (bonds)
        for edge in edges:
            # Get nodes connected by this edge
            node1_id, node2_id = edge[0], edge[1]
            edge_data = G.edges[node1_id, node2_id]

            # Get node data
            node1_data = G.nodes[node1_id]
            node2_data = G.nodes[node2_id]

            # Get atom symbols
            atom1_symbol = node1_data['symbol']
            atom2_symbol = node2_data['symbol']

            # Get positions
            atom1_xyz = node1_data['xyz']
            atom2_xyz = node2_data['xyz']

            # Get bond type (default to 1 if missing)
            bond_type = edge_data.get('type', 1)
            bond_type = min(max(1, bond_type), 3)  # Ensure it's between 1-3

            # Calculate bond length (distance)
            distance = calculate_distance(atom1_xyz, atom2_xyz)

            # Add to plot summary
            plot_summary.append({
                'atom1Id': node1_id,
                'atom2Id': node2_id,
                'atom1Symbol': f"{atom1_symbol}{node1_id}",
                'atom2Symbol': f"{atom2_symbol}{node2_id}",
                'distance': distance
            })

            # Draw the bond line
            bond_linewidth = bond_type * 1.5  # Increase line width based on bond type
            ax.plot([atom1_xyz[0], atom2_xyz[0]],
                    [atom1_xyz[1], atom2_xyz[1]],
                    [atom1_xyz[2], atom2_xyz[2]],
                    color=bond_type_color[bond_type-1], 
                    linewidth=bond_linewidth,
                    alpha=0.8)

            # Display bond length if requested
            if display_bond_length:
                # Calculate midpoint for placing text
                mid_x = (atom1_xyz[0] + atom2_xyz[0]) / 2
                mid_y = (atom1_xyz[1] + atom2_xyz[1]) / 2
                mid_z = (atom1_xyz[2] + atom2_xyz[2]) / 2

                # Add bond length label
                ax.text(mid_x, mid_y, mid_z, f"{distance:.2f}", 
                        size=6, ha='center', va='center', 
                        bbox=dict(facecolor='white', alpha=0.7))

        # Visualize subgraphs if provided
        if subgraphs is not None:
            for subgraph in subgraphs:
                # Handle different possible formats of subgraphs
                if hasattr(subgraph, 'nodes'):
                    # If subgraph is a NetworkX graph
                    subgraph_nodes = list(subgraph.nodes())
                    subgraph_edges = list(subgraph.edges())
                elif isinstance(subgraph, dict) and 'subgraph_pattern' in subgraph:
                    # If subgraph is a dict with 'subgraph_pattern' key (as in view3d function)
                    subgraph_pattern = subgraph['subgraph_pattern']
                    subgraph_nodes = list(subgraph_pattern.nodes())
                    subgraph_edges = list(subgraph_pattern.edges())
                else:
                    continue  # Skip if format not recognized

                # Highlight nodes
                for node_id in subgraph_nodes:
                    if node_id in G.nodes:
                        node_data = G.nodes[node_id]
                        if 'xyz' in node_data:
                            atom_xyz = node_data['xyz']
                            atom_symbol = node_data['symbol']
                            atom_color = set_color(atom_symbol)

                            # Add highlighted atom (larger, translucent)
                            ax.scatter(atom_xyz[0], atom_xyz[1], atom_xyz[2],
                                      color=atom_color, s=200, alpha=0.3,
                                      edgecolors='blue', linewidths=2)

                # Highlight edges
                for edge in subgraph_edges:
                    node1_id, node2_id = edge

                    # Only highlight edges that exist in the main graph
                    if G.has_edge(node1_id, node2_id):
                        node1_data = G.nodes[node1_id]
                        node2_data = G.nodes[node2_id]

                        if 'xyz' in node1_data and 'xyz' in node2_data:
                            atom1_xyz = node1_data['xyz']
                            atom2_xyz = node2_data['xyz']

                            # Add highlighted bond (thicker, blue, dashed)
                            ax.plot([atom1_xyz[0], atom2_xyz[0]],
                                    [atom1_xyz[1], atom2_xyz[1]],
                                    [atom1_xyz[2], atom2_xyz[2]],
                                    color='blue', linewidth=4, linestyle='--',
                                    alpha=0.7)

        # Set axis limits and remove ticks for cleaner visualization
        ax.set_xlim(-xyzLenMax, xyzLenMax)
        ax.set_ylim(-xyzLenMax, xyzLenMax)
        ax.set_zlim(-xyzLenMax, xyzLenMax)

        # Set equal aspect ratio for all axes
        ax.set_box_aspect([1, 1, 1])

        # Remove tick labels for cleaner visualization
        ax.set_xticklabels([])
        ax.set_yticklabels([])
        ax.set_zticklabels([])

        # Remove tick marks and background grid
        ax.xaxis.set_ticks([])
        ax.yaxis.set_ticks([])
        ax.zaxis.set_ticks([])
        ax.xaxis._axinfo['grid'].update({'visible': False})
        ax.yaxis._axinfo['grid'].update({'visible': False})
        ax.zaxis._axinfo['grid'].update({'visible': False})

        # Set axis labels
        ax.set_xlabel('X')
        ax.set_ylabel('Y')
        ax.set_zlabel('Z')

        # Set background color and styling based on theme
        if theme == 'dark':
            fig.patch.set_facecolor('black')
            ax.set_facecolor('black')
            ax.xaxis.label.set_color('white')
            ax.yaxis.label.set_color('white')
            ax.zaxis.label.set_color('white')
        else:
            fig.patch.set_facecolor('white')
            ax.set_facecolor('white')

        # Set the view angle
        ax.view_init(elev=elev, azim=azim)

        # Add a legend if requested
        if display_legend and len(atomElements) > 0:
            # Create a legend for unique atom types
            unique_elements = set(atomElements)
            legend_elements = []

            for element in unique_elements:
                legend_elements.append(plt.Line2D([0], [0], marker='o', color='w', 
                                                  label=element,
                                                  markerfacecolor=set_color(element), 
                                                  markersize=10))

            # Add a legend for bond types
            for i, bond_name in enumerate(['Single', 'Double', 'Triple']):
                if i < len(bond_type_color):
                    legend_elements.append(plt.Line2D([0], [0], color=bond_type_color[i], 
                                                     lw=(i+1)*1.5, label=f'{bond_name} bond'))

            ax.legend(handles=legend_elements, loc='upper right', frameon=True)

        # Adjust layout to make room for the legend
        plt.tight_layout()

        # Display the plot
        plt.show()

        # Return summary and figure objects for further manipulation
        return plot_summary, fig, ax

    def view3d(self, subgraphs=None, **kwargs):
        '''
        Draw a compound in the cartesian coordinate
        atomElements atom symbol
        atomBonds atom bonds (bond blocks)
        xyzList atom position in the cartesian coordinate
        figSize=(10, 10) plt 3d setting
        obsOption=[False, 0] display center point [0,0,0]

        Parameters
        ----------
        figSize: tuple
            figure size
        bg_color: str
            background color
        display_legend: bool
            display legend

        Returns
        -------
        fig: figure
            figure
        '''
        figSize = kwargs.get('figSize', [])
        bg_color = kwargs.get('bg_color', '#ffffff')
        display_legend = kwargs.get('display_legend', True)
        theme = kwargs.get('theme', 'light')
        display_atom_id = kwargs.get('display_atom_id', True)
        display_bond_length = kwargs.get('display_bond_length', False)
        bond_type_color = kwargs.get(
            'bond_type_color', ['#1B263B', '#EF476F', '#4361EE'])

        # plot summary
        plot_summary = []

        # Create the figure
        fig = go.Figure()

        # legend
        legend_list = []

        # marker label
        marker_labels = []

        # atom no
        atomNo = len(self.xyzList)
        # bond no
        bondNo = len(self.atomBonds_1d)

        # create 3d frame
        xyzLenMax, xyzLenMin, xyzR, xLen, yLen, zLen = self.create_3dframe()

        # *** plot scale
        plot_scale_res = self.set_plot_scale()
        # min bond length
        min_bond_length = self.plotScale[0]
        # max bond length
        max_bond_length = self.plotScale[1]
        # set marker size
        marker_size_0 = self.set_marker_size(min_bond_length, max_bond_length)

        # *** atom visualization
        for i in range(atomNo):
            # xyz
            _atom1X = self.xyzList[i, 0]
            _atom1Y = self.xyzList[i, 1]
            _atom1Z = self.xyzList[i, 2]
            _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

            # color
            # atom id
            _atomId = int(i+1)
            # symbol
            _atomSymbol = str(self.atomElements[i]).strip()
            # size
            _atomSize = self.set_size(_atomSymbol, _sy=marker_size_0)
            # color
            _atomColor = self.set_color(_atomSymbol)

            # atom mark
            atomMark = str(_atomSymbol) + str(_atomId)

            # atom label
            marker_labels.append(atomMark)

            if display_atom_id:  # Replace with your condition
                text_to_display = [atomMark]
            else:
                text_to_display = ['']  # Empty string to hide text

            # scatter
            fig.add_trace(go.Scatter3d(x=[_atom1X],
                                       y=[_atom1Y],
                                       z=[_atom1Z],
                                       mode='markers+text',
                                       marker=dict(
                                           color=_atomColor, size=10, sizemode='area', sizeref=1, line=dict(width=2, color='black')),
                                       hoverinfo='all',  # Display all available information
                                       hoverlabel=dict(bgcolor=bg_color),
                                       # Set custom hover text
                                       hovertext=[f'Element: {atomMark}'],
                                       text=text_to_display))

            # textfont = dict(weight='normal')

        # *** bond visualization
        # *** using bond block
        for i in range(bondNo):
            # id 1
            _atom1Id = int(self.atomBonds_1d[i]['id1']) - 1
            # id 2
            _atom2Id = int(self.atomBonds_1d[i]['id2']) - 1
            # symbol 1
            _atom1Symbol = self.atomBonds_1d[i]['symbol1']
            # symbol 2
            _atom2Symbol = self.atomBonds_1d[i]['symbol2']
            # color 1
            _atom1Color = self.set_color(_atom1Symbol)
            # color 2
            _atom2Color = self.set_color(_atom2Symbol)

            # bond symbol
            _bondSymbol = self.atomBonds_1d[i]['bond_symbol']
            # bond type
            _bondType = int(self.atomBonds_1d[i]['bond_type']-1)
            # set bond type
            _bondTypeLabel = ''
            if _bondType == 1:
                _bondTypeLabel = 'single bond'
            elif _bondType == 2:
                _bondTypeLabel = 'double bond'
            else:
                _bondTypeLabel = 'triple bond'

            # xyz atom 1
            _atom1X = self.xyzList[_atom1Id, 0]
            _atom1Y = self.xyzList[_atom1Id, 1]
            _atom1Z = self.xyzList[_atom1Id, 2]
            _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

            # xyz atom 2
            _atom2X = self.xyzList[_atom2Id, 0]
            _atom2Y = self.xyzList[_atom2Id, 1]
            _atom2Z = self.xyzList[_atom2Id, 2]
            _atom2XYZ = [_atom2X, _atom2Y, _atom2Z]

            # distance
            _distance = self.calculate_distance(_atom1XYZ, _atom2XYZ)

            # plot summary
            plot_summary.append(
                {
                    'atom1Id': _atom1Id+1,
                    'atom2Id': _atom2Id+1,
                    'atom1Symbol': str(_atom1Symbol) + str(_atom1Id+1),
                    'atom2Symbol': str(_atom2Symbol) + str(_atom2Id+1),
                    'distance': _distance
                }
            )

            # Calculate midpoint coordinates
            midX = [(_atom1X + _atom2X) / 2]
            midY = [(_atom1Y + _atom2Y) / 2]
            midZ = [(_atom1Z + _atom2Z) / 2]

            # add line
            fig.add_trace(go.Scatter3d(x=[_atom1X, _atom2X],
                                       y=[_atom1Y, _atom2Y],
                                       z=[_atom1Z, _atom2Z],
                                       mode='lines',
                                       line=dict(color=bond_type_color[_bondType], width=3), hoverinfo='none', name=_bondSymbol, showlegend=True))

            if display_bond_length is True:  # Condition to show text
                text_to_display = [f'{_distance:.3f}']
            else:
                text_to_display = ['']  # Empty string to hide text

            # Add text at midpoint
            fig.add_trace(go.Scatter3d(x=midX,
                                       y=midY,
                                       z=midZ,
                                       mode='text',
                                       # Replace with your desired text
                                       text=text_to_display,
                                       hoverinfo='text',  # Display all available information
                                       hoverlabel=dict(
                                           bgcolor=bg_color, namelength=-1),
                                       # Set custom hover text
                                       hovertext=[f'A {_bondTypeLabel} with a length of {_distance:.3f}']))

        # *** visualize subgraph
        if subgraphs is not None:
            for subgraph in subgraphs:
                # add subgraph
                subgraph_pattern = subgraph['subgraph_pattern']
                _node_list = list(subgraph_pattern.nodes())
                for i in _node_list:
                    # xyz
                    _atom1X = self.xyzList[i-1, 0]
                    _atom1Y = self.xyzList[i-1, 1]
                    _atom1Z = self.xyzList[i-1, 2]
                    _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

                    # color
                    # atom id
                    _atomId = i
                    # symbol
                    _atomSymbol = str(self.atomElements[i-1]).strip()
                    # color
                    _atomColor = self.set_color(_atomSymbol)

                    # scatter
                    fig.add_trace(go.Scatter3d(x=[_atom1X],
                                               y=[_atom1Y],
                                               z=[_atom1Z],
                                               mode='markers',
                                               marker=dict(
                                                    color=_atomColor, size=2*10, opacity=0.5,
                                                    sizemode='area', sizeref=1,
                                                    line=dict(width=2, color='black')),
                                               ))

                # edge list
                _edge_list = list(subgraph_pattern.edges())
                for i in _edge_list:
                    # xyz
                    _atom1X = self.xyzList[i[0]-1, 0]
                    _atom1Y = self.xyzList[i[0]-1, 1]
                    _atom1Z = self.xyzList[i[0]-1, 2]
                    _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

                    # xyz
                    _atom2X = self.xyzList[i[1]-1, 0]
                    _atom2Y = self.xyzList[i[1]-1, 1]
                    _atom2Z = self.xyzList[i[1]-1, 2]
                    _atom2XYZ = [_atom2X, _atom2Y, _atom2Z]

                    # add line
                    fig.add_trace(go.Scatter3d(x=[_atom1X, _atom2X],
                                               y=[_atom1Y, _atom2Y],
                                               z=[_atom1Z, _atom2Z],
                                               mode='lines',
                                               line=dict(
                                                   color='blue', width=2*5, dash='dashdot'),
                                               hoverinfo='none'))

        # Set the limits of the axes
        # set max and offset
        xyzLenMax = xyzLenMax + 1.5
        fig.update_layout(scene=dict(
            xaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
            yaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
            zaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax])
        ))

        # Set figure size to a square
        if len(figSize) != 0:
            fig.update_layout(width=figSize[0], height=figSize[1])
        else:
            fig.update_layout(
                autosize=True
            )

        # Show legend
        # Update layout to display legend
        fig.update_layout(legend=dict(
            orientation="h",  # Horizontal legend
            yanchor="bottom",  # Anchor at bottom
            y=1.02,  # Move legend up slightly
            xanchor="right",  # Anchor at right
            x=1  # Move legend to right
        ))

        if theme == 'black':
            font_color = 'lightgray'
        else:
            font_color = 'black'

        # Update text font color
        fig.update_traces(textfont=dict(color=font_color))

        # Set background color to dark
        fig.update_layout(
            paper_bgcolor=bg_color,
            plot_bgcolor=bg_color,
            scene=dict(
                xaxis=dict(showbackground=True,
                           backgroundcolor=bg_color),
                yaxis=dict(showbackground=True,
                           backgroundcolor=bg_color),
                zaxis=dict(showbackground=True,
                           backgroundcolor=bg_color)
            )
        )

        # Remove axes and other elements
        fig.update_layout(
            scene=dict(
                xaxis=dict(showticklabels=False, showgrid=False,
                           zeroline=False, showspikes=False, title=''),
                yaxis=dict(showticklabels=False, showgrid=False,
                           zeroline=False, showspikes=False, title=''),
                zaxis=dict(showticklabels=False, showgrid=False,
                           zeroline=False, showspikes=False, title=''),
                aspectmode='manual',
                aspectratio=dict(x=1, y=1, z=1),
            ),
            showlegend=False,
            margin=dict(l=0, r=0, b=0, t=0)
        )

        # Show the plot with zoom disabled
        fig.show(config={
            'scrollZoom': True,  # Disable zoom with scroll
            'displayModeBar': True,
            'displaylogo': True,
            'modeBarButtonsToRemove': ['zoom2d', 'zoomIn2d', 'zoomOut2d', 'pan2d']
        })

        # res
        return plot_summary

    def view3dobs(self, elev=None, azim=None, figSize=(10, 10), obsOption=[True, 0]):
        '''
        Draw a compound in the cartesian coordinate with observer

        Parameters
        ----------
        elev : float
            elevation angle
        azim : float
            azimuth angle
        figSize : tuple
            figure size
        obsOption : list
            [True, 0] --> show observer, 0 --> observer radius
        '''
        # 3d plot
        fig = plt.figure(figsize=figSize)
        ax = plt.axes(projection='3d')

        # atom no
        atomNo = len(self.xyzList)
        # bond no
        bondNo = len(self.atomBonds)

        # atom visualization
        for i in range(atomNo):
            # xyz
            _atomX = self.xyzList[i, 0]
            _atomY = self.xyzList[i, 1]
            _atomZ = self.xyzList[i, 2]
            # color
            # size

            # draw atom 1
            ax.scatter3D(_atomX, _atomY, _atomZ, s=40)

        # bond visualization
        for i in range(bondNo):
            # atom id
            _atom1Id = int(self.atomBonds[i]['id']) - 1
            # atom symbol
            _atom1Symbol = self.atomBonds[i]['symbol']
            # atom bond list
            _atom1BondList = self.atomBonds[i]['bonds']
            atom1BondSize = len(_atom1BondList)

            _atom1X = self.xyzList[:, 0]
            _atom1Y = self.xyzList[:, 1]
            _atom1Z = self.xyzList[:, 2]

            # draw bond
            if atom1BondSize > 0:
                for j in range(atom1BondSize):
                    # atom [2] id
                    _atom2Id = int(_atom1BondList[j][0]) - 1
                    # atom [2] symbol
                    _atom2Symbol = _atom1BondList[j][1]
                    # atom [1] - atom [2] bond type
                    _bondType = int(_atom1BondList[j][3])

                    # set color
                    lineColor = ['k', 'b', 'c']
                    lineWidth = [1, 2, 3]

                    # xyz
                    _atom2X = self.xyzList[_atom2Id, 0]
                    _atom2Y = self.xyzList[_atom2Id, 1]
                    _atom2Z = self.xyzList[_atom2Id, 2]

                    # bond connection
                    _bondConnection = self.xyzList[[_atom1Id, _atom2Id]]
                    # draw line
                    ax.plot3D(_bondConnection[:, 0], _bondConnection[:, 1], _bondConnection[:, 2],
                              color=lineColor[_bondType-1], linewidth=lineWidth[_bondType-1])

        # obs visualization
        xyzObsList = Observer.GeneratorCircleObserver(
            self._robs, self.tetaNo, self.phiNo, self.limits['teta'])[0]
        # obs size
        xyzObsSize = len(xyzObsList)
        for i in range(xyzObsSize):
            ax.scatter3D(xyzObsList[i, :, 0],
                         xyzObsList[i, :, 1], xyzObsList[i, :, 2])
            ax.plot3D(xyzObsList[i, :, 0],
                      xyzObsList[i, :, 1], xyzObsList[i, :, 2])

        # obs show
        if obsOption[0]:
            ax.scatter3D(obsOption[1], 0, 0, s=40)

        ax.view_init(elev=elev, azim=azim)
        plt.show()

    def create_line(self, xyzList1, xyzList2, t=1):
        '''
        Create a line equation and its parallel lines

        Parameters
        ----------
        xyzList1 : list
            [x1,y1,z1]
        xyzList2 : list
            [x2,y2,z2]
        t : float
            ratio between xyzList1 and xyzList2

        Returns
        -------
        r : list
            [[x1,y1,z1], [x2,y2,z2]]
        _l1 : list
            [[x1,y1,z1], [x2,y2,z2]]
        _l2 : list
            [[x1,y1,z1], [x2,y2,z2]]
        '''
        try:
            r = np.array(xyzList2) - np.array(xyzList1)

            # matrix
            rMat = np.array([xyzList1, xyzList1])

            # x
            x = r[0]*t + xyzList1[0]
            # y
            y = r[1]*t + xyzList1[1]
            # z
            z = r[2]*t + xyzList1[2]

            xL, yL, zL = 0.1*np.ones(3)
            _l1 = [[xyzList1[0]+xL, xyzList2[0]+xL], [xyzList1[1] +
                                                      yL, xyzList2[1]+yL], [xyzList1[2]+zL, xyzList2[2]+zL]]
            _l2 = [[xyzList1[0]-xL, xyzList2[0]-xL], [xyzList1[1] -
                                                      yL, xyzList2[1]-yL], [xyzList1[2]-zL, xyzList2[2]-zL]]

            # res
            return r, _l1, _l2
        except Exception as e:
            raise Exception(e)

    def calculate_distance(self, xyz1, xyz2):
        """
        Calculate the Euclidean distance between two points.

        Parameters:
        ----------
        xyz1 : list
            Coordinates of the first point [x, y, z]
        xyz2 : list
            Coordinates of the second point [x, y, z]

        Returns:
        -------
        float
            Distance between the two points
        """
        return math.sqrt((xyz2[0] - xyz1[0])**2 +
                         (xyz2[1] - xyz1[1])**2 +
                         (xyz2[2] - xyz1[2])**2)

    def set_marker_size(self, min_distance, ref_length=1, min_marker_size=200, max_marker_size=500):
        '''
        Set the plot view based on the minimum distance.

        Parameters:
        ----------
        min_distance : float
            Minimum distance between points
        ref_length : float
            Reference length for scaling
        min_marker_size : float
            Minimum marker size
        max_marker_size : float
            Maximum

        Returns:
        -------
        float
            Marker size
        '''
        scaling_factor = min_marker_size / ref_length
        marker_size = min_distance * scaling_factor

        # Ensure the marker size is within the desired range
        marker_size = min(max_marker_size, max(min_marker_size, marker_size))

        return marker_size

StructureAnalyzer()

Check geometry structure to determine 2D/3D

Parameters

xyzList: list point coordination

Returns

structureType: str 2D or 3D

Source code in pyMolinfo/docs/graph3d.py
def StructureAnalyzer(self):
    '''
    Check geometry structure to determine 2D/3D

    Parameters
    ----------
    xyzList: list
        point coordination

    Returns
    -------
    structureType: str
        2D or 3D
    '''
    try:
        # array
        xyzList = np.array(self.xyzList)
        # set
        X = xyzList[:, 0]
        Y = xyzList[:, 1]
        Z = xyzList[:, 2]

        # check plane structure (2D, 3D)
        X0 = np.abs(X).sum()
        Y0 = np.abs(Y).sum()
        Z0 = np.abs(Z).sum()
        XYZ0 = [X0, Y0, Z0]

        # perpendicular vector [x,y,z]
        perpendicularVector = [False, False, False]
        # set
        if X0 == 0:
            perpendicularVector[0] = True

        if Y0 == 0:
            perpendicularVector[1] = True

        if Z0 == 0:
            perpendicularVector[2] = True

        # status
        if True in perpendicularVector:
            structureType = '2D'
        else:
            structureType = '3D'

        # axis selection
        perpendicularAxis = []
        if perpendicularVector[0] is True:
            perpendicularAxis.append('X')
        if perpendicularVector[1] is True:
            perpendicularAxis.append('Y')
        if perpendicularVector[2] is True:
            perpendicularAxis.append('Z')

        return structureType, perpendicularAxis, perpendicularVector, XYZ0
    except Exception as e:
        raise Exception(e)

calculate_distance(xyz1, xyz2)

Calculate the Euclidean distance between two points.

Parameters:

xyz1 : list Coordinates of the first point [x, y, z] xyz2 : list Coordinates of the second point [x, y, z]

Returns:

float Distance between the two points

Source code in pyMolinfo/docs/graph3d.py
def calculate_distance(self, xyz1, xyz2):
    """
    Calculate the Euclidean distance between two points.

    Parameters:
    ----------
    xyz1 : list
        Coordinates of the first point [x, y, z]
    xyz2 : list
        Coordinates of the second point [x, y, z]

    Returns:
    -------
    float
        Distance between the two points
    """
    return math.sqrt((xyz2[0] - xyz1[0])**2 +
                     (xyz2[1] - xyz1[1])**2 +
                     (xyz2[2] - xyz1[2])**2)

create_3dframe()

Create 3d frame dimension

Source code in pyMolinfo/docs/graph3d.py
def create_3dframe(self):
    '''
    Create 3d frame dimension
    '''
    # max length
    # x
    xMin = np.min(self.xyzList[:, 0])
    xMax = np.max(self.xyzList[:, 0])
    xLen = np.abs(np.abs(xMax) - np.abs(xMin))
    # y
    yMin = np.min(self.xyzList[:, 1])
    yMax = np.max(self.xyzList[:, 1])
    yLen = np.abs(np.abs(yMax) - np.abs(yMin))
    # z
    zMin = np.min(self.xyzList[:, 2])
    zMax = np.max(self.xyzList[:, 2])
    zLen = np.abs(np.abs(zMax) - np.abs(zMin))
    # max
    xyzLenMax = np.max([xMax, yMax, zMax])
    xyzLenMin = np.min([xMin, yMin, zMin])
    xyzR = 1/xyzLenMax

    # res
    return xyzLenMax, xyzLenMin, xyzR, xLen, yLen, zLen

create_bond_line(xyz1, xyz2, bond_type, xyzL=[1, 1, 1], xyzR=0.15)

Create bond line (single, double, tipple)

Parameters

xyz1: list (x,y,z) point 1 xyz2: list (x,y,z) point 2 bond_type: int bond type (1,2,3) xyzL: list (x,y,z) length xyzR: int (x,y,z) radius

Returns

bondLines: list list of bond lines

Source code in pyMolinfo/docs/graph3d.py
def create_bond_line(self, xyz1, xyz2, bond_type, xyzL=[1, 1, 1], xyzR=0.15):
    '''
    Create bond line (single, double, tipple)

    Parameters
    ----------
    xyz1: list
        (x,y,z) point 1
    xyz2: list
        (x,y,z) point 2
    bond_type: int
        bond type (1,2,3)
    xyzL: list
        (x,y,z) length
    xyzR: int
        (x,y,z) radius

    Returns
    -------
    bondLines: list
        list of bond lines
    '''
    # bond lines
    bondLines = []
    # bond length
    bondLength = []

    xL, yL, zL = xyzR*np.array(xyzL)

    # Calculate center-to-center vector
    center_vector = np.array(xyz2) - np.array(xyz1)

    # Calculate center-to-center line
    center_line = [[xyz1[0], xyz2[0]], [
        xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]

    if bond_type == 1:
        # bond line
        bondLines.append(center_line)
        # bond length
        _bond_length_res = self.calculate_distance(xyz1, xyz2)
        bondLength.append(_bond_length_res)

    elif bond_type == 2:
        # parallel line
        # y increase
        # center to center line
        # Calculate perpendicular vector to center vector
        perp_vector = np.array([center_vector[1], - center_vector[0], 0])
        perp_vector /= np.linalg.norm(perp_vector)

        # Calculate offset vectors
        offset_vector1 = 0.15 * perp_vector
        offset_vector2 = -0.15 * perp_vector

        # Calculate parallel lines
        _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
            xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
        _l2 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
            xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

        # lines
        _l1_xyz1 = [xyz1[0]+offset_vector1[0], xyz1[1] +
                    offset_vector1[1], xyz1[2]+offset_vector1[2]]
        _l1_xyz2 = [xyz2[0]+offset_vector1[0], xyz2[1] +
                    offset_vector1[1], xyz2[2]+offset_vector1[2]]

        _l2_xyz1 = [xyz1[0]+offset_vector2[0], xyz1[1] +
                    offset_vector2[1], xyz1[2]+offset_vector2[2]]
        _l2_xyz2 = [xyz2[0]+offset_vector2[0], xyz2[1] +
                    offset_vector2[1], xyz2[2]+offset_vector2[2]]

        # bond lines
        bondLines.append(_l1)
        bondLines.append(_l2)
        # bond length
        _bond_length_res = self.calculate_distance(_l1_xyz1, _l1_xyz2)
        bondLength.append(_bond_length_res)
        _bond_length_res = self.calculate_distance(_l2_xyz1, _l2_xyz2)
        bondLength.append(_bond_length_res)

    elif bond_type == 3:
        # parallel line
        # y increase
        # line
        # Calculate perpendicular vector to center vector
        perp_vector = np.array([center_vector[1], -center_vector[0], 0])
        perp_vector /= np.linalg.norm(perp_vector)

        # Calculate offset vectors
        offset_vector1 = 0.125 * perp_vector
        offset_vector2 = -0.125 * perp_vector

        # Calculate parallel lines
        _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
            xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
        _l2 = [[xyz1[0], xyz2[0]], [
            xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]

        _l3 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
            xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

        # lines
        _l1_xyz1 = [xyz1[0]+offset_vector1[0], xyz1[1] +
                    offset_vector1[1], xyz1[2]+offset_vector1[2]]
        _l1_xyz2 = [xyz2[0]+offset_vector1[0], xyz2[1] +
                    offset_vector1[1], xyz2[2]+offset_vector1[2]]
        _l2_xyz1 = xyz1
        _l2_xyz2 = xyz2
        _l3_xyz1 = [xyz1[0]+offset_vector2[0], xyz1[1] +
                    offset_vector2[1], xyz1[2]+offset_vector2[2]]
        _l3_xyz2 = [xyz2[0]+offset_vector2[0], xyz2[1] +
                    offset_vector2[1], xyz2[2]+offset_vector2[2]]

        # bond lines
        bondLines.append(_l1)
        bondLines.append(_l2)
        bondLines.append(_l3)
        # bond length
        # bond length
        _bond_length_res = self.calculate_distance(_l1_xyz1, _l1_xyz2)
        bondLength.append(_bond_length_res)
        _bond_length_res = self.calculate_distance(_l2_xyz1, _l2_xyz2)
        bondLength.append(_bond_length_res)
        _bond_length_res = self.calculate_distance(_l3_xyz1, _l3_xyz2)
        bondLength.append(_bond_length_res)

    return bondLines, bond_type, bondLength

create_bond_line_V2(xyz1, xyz2, bond_type, xyzL=[1, 1, 1], xyzR=0.15)

Create bond line (single, double, triple)

Parameters

xyz1: list (x,y,z) point 1 xyz2: list (x,y,z) point 2 bond_type: int bond type (1,2,3) xyzL: list (x,y,z) length xyzR: int (x,y,z) radius

Returns

bondLines: list list of bond lines

Source code in pyMolinfo/docs/graph3d.py
def create_bond_line_V2(self, xyz1, xyz2, bond_type, xyzL=[1, 1, 1], xyzR=0.15):
    '''
    Create bond line (single, double, triple)

    Parameters
    ----------
    xyz1: list
        (x,y,z) point 1
    xyz2: list
        (x,y,z) point 2
    bond_type: int
        bond type (1,2,3)
    xyzL: list
        (x,y,z) length
    xyzR: int
        (x,y,z) radius

    Returns
    -------
    bondLines: list
        list of bond lines
    '''
    bondLines = []

    xL, yL, zL = xyzR*np.array(xyzL)

    # Calculate center-to-center vector
    center_vector = np.array(xyz2) - np.array(xyz1)

    # Calculate center-to-center line
    center_line = [[xyz1[0], xyz2[0]], [
        xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]

    # Calculate midpoint
    midpoint = [(xyz1[0] + xyz2[0]) / 2, (xyz1[1] +
                                          xyz2[1]) / 2, (xyz1[2] + xyz2[2]) / 2]

    if bond_type == 1:
        # Break single bond into two lines
        bondLines.append(
            [[xyz1[0], midpoint[0]], [xyz1[1], midpoint[1]], [xyz1[2], midpoint[2]]])
        bondLines.append(
            [[midpoint[0], xyz2[0]], [midpoint[1], xyz2[1]], [midpoint[2], xyz2[2]]])

    elif bond_type == 2:
        # parallel line
        # y increase
        # center to center line
        # Calculate perpendicular vector to center vector
        perp_vector = np.array([center_vector[1], - center_vector[0], 0])
        perp_vector /= np.linalg.norm(perp_vector)

        # Calculate offset vectors
        offset_vector1 = 0.15 * perp_vector
        offset_vector2 = -0.15 * perp_vector

        # Calculate parallel lines
        _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
            xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
        _l2 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
            xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

        # Break each parallel line into two lines
        bondLines.append([[xyz1[0]+offset_vector1[0], midpoint[0]+offset_vector1[0]], [
            xyz1[1]+offset_vector1[1], midpoint[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], midpoint[2]+offset_vector1[2]]])
        bondLines.append([[midpoint[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
            midpoint[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [midpoint[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]])
        bondLines.append([[xyz1[0]+offset_vector2[0], midpoint[0]+offset_vector2[0]], [
            xyz1[1]+offset_vector2[1], midpoint[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], midpoint[2]+offset_vector2[2]]])
        bondLines.append([[midpoint[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
            midpoint[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [midpoint[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]])

    elif bond_type == 3:
        # parallel line
        # y increase
        # line
        # Calculate perpendicular vector to center vector
        perp_vector = np.array([center_vector[1], -center_vector[0], 0])
        perp_vector /= np.linalg.norm(perp_vector)

        # Calculate offset vectors
        offset_vector1 = 0.125 * perp_vector
        offset_vector2 = -0.125 * perp_vector

        # Calculate parallel lines
        _l1 = [[xyz1[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
            xyz1[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]]
        _l2 = [[xyz1[0], xyz2[0]], [
            xyz1[1], xyz2[1]], [xyz1[2], xyz2[2]]]
        _l3 = [[xyz1[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
            xyz1[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]]

        # Break each parallel line into two lines
        bondLines.append([[xyz1[0]+offset_vector1[0], midpoint[0]+offset_vector1[0]], [
            xyz1[1]+offset_vector1[1], midpoint[1]+offset_vector1[1]], [xyz1[2]+offset_vector1[2], midpoint[2]+offset_vector1[2]]])
        bondLines.append([[midpoint[0]+offset_vector1[0], xyz2[0]+offset_vector1[0]], [
            midpoint[1]+offset_vector1[1], xyz2[1]+offset_vector1[1]], [midpoint[2]+offset_vector1[2], xyz2[2]+offset_vector1[2]]])
        bondLines.append([[xyz1[0], midpoint[0]], [
            xyz1[1], midpoint[1]], [xyz1[2], midpoint[2]]])
        bondLines.append([[midpoint[0], xyz2[0]], [
            midpoint[1], xyz2[1]], [midpoint[2], xyz2[2]]])
        bondLines.append([[xyz1[0]+offset_vector2[0], midpoint[0]+offset_vector2[0]], [
            xyz1[1]+offset_vector2[1], midpoint[1]+offset_vector2[1]], [xyz1[2]+offset_vector2[2], midpoint[2]+offset_vector2[2]]])
        bondLines.append([[midpoint[0]+offset_vector2[0], xyz2[0]+offset_vector2[0]], [
            midpoint[1]+offset_vector2[1], xyz2[1]+offset_vector2[1]], [midpoint[2]+offset_vector2[2], xyz2[2]+offset_vector2[2]]])

    return bondLines, bond_type

create_line(xyzList1, xyzList2, t=1)

Create a line equation and its parallel lines

Parameters

xyzList1 : list [x1,y1,z1] xyzList2 : list [x2,y2,z2] t : float ratio between xyzList1 and xyzList2

Returns

r : list [[x1,y1,z1], [x2,y2,z2]] _l1 : list [[x1,y1,z1], [x2,y2,z2]] _l2 : list [[x1,y1,z1], [x2,y2,z2]]

Source code in pyMolinfo/docs/graph3d.py
def create_line(self, xyzList1, xyzList2, t=1):
    '''
    Create a line equation and its parallel lines

    Parameters
    ----------
    xyzList1 : list
        [x1,y1,z1]
    xyzList2 : list
        [x2,y2,z2]
    t : float
        ratio between xyzList1 and xyzList2

    Returns
    -------
    r : list
        [[x1,y1,z1], [x2,y2,z2]]
    _l1 : list
        [[x1,y1,z1], [x2,y2,z2]]
    _l2 : list
        [[x1,y1,z1], [x2,y2,z2]]
    '''
    try:
        r = np.array(xyzList2) - np.array(xyzList1)

        # matrix
        rMat = np.array([xyzList1, xyzList1])

        # x
        x = r[0]*t + xyzList1[0]
        # y
        y = r[1]*t + xyzList1[1]
        # z
        z = r[2]*t + xyzList1[2]

        xL, yL, zL = 0.1*np.ones(3)
        _l1 = [[xyzList1[0]+xL, xyzList2[0]+xL], [xyzList1[1] +
                                                  yL, xyzList2[1]+yL], [xyzList1[2]+zL, xyzList2[2]+zL]]
        _l2 = [[xyzList1[0]-xL, xyzList2[0]-xL], [xyzList1[1] -
                                                  yL, xyzList2[1]-yL], [xyzList1[2]-zL, xyzList2[2]-zL]]

        # res
        return r, _l1, _l2
    except Exception as e:
        raise Exception(e)

line_mid_points(xyz1, xyz2)

Divide a line in two equal parts

Parameters

xyz1: list (x,y,z) point 1 xyz2: list (x,y,z) point 2

Returns

midPoints: list list of mid points

Source code in pyMolinfo/docs/graph3d.py
def line_mid_points(self, xyz1, xyz2):
    '''
    Divide a line in two equal parts

    Parameters
    ----------
    xyz1: list
        (x,y,z) point 1
    xyz2: list
        (x,y,z) point 2

    Returns
    -------
    midPoints: list
        list of mid points
    '''
    # Calculate midpoints
    midpoint_x = (xyz1[0] + xyz2[0]) / 2
    midpoint_y = (xyz1[1] + xyz2[1]) / 2
    midpoint_z = (xyz1[2] + xyz2[2]) / 2

    # Divide center_line into two parts
    part1 = [[xyz1[0], midpoint_x], [
        xyz1[1], midpoint_y], [xyz1[2], midpoint_z]]
    part2 = [[midpoint_x, xyz2[0]], [
        midpoint_y, xyz2[1]], [midpoint_z, xyz2[2]]]

    # Get four points
    point1 = [xyz1[0], xyz1[1], xyz1[2]]
    point2 = [midpoint_x, midpoint_y, midpoint_z]
    point3 = [midpoint_x, midpoint_y, midpoint_z]
    point4 = [xyz2[0], xyz2[1], xyz2[2]]

    # res
    return point1, point2, point3, point4, part1, part2

line_property(xyz1, xyz2)

Check a line property with which plane is parallel when res contains two True, it means the False coordination contains all elements.

Parameters

xyz1: list (x,y,z) point 1 xyz2: list (x,y,z) point 2

Returns

res: bool res

Source code in pyMolinfo/docs/graph3d.py
def line_property(self, xyz1, xyz2):
    '''
    Check a line property with which plane is parallel
    when res contains two True, it means the False coordination contains all elements.

    Parameters
    ----------
    xyz1: list
        (x,y,z) point 1
    xyz2: list
        (x,y,z) point 2

    Returns
    -------
    res: bool
        res
    '''
    try:
        # mean value
        Xm = np.mean([xyz1[0], xyz2[0]])
        Ym = np.mean([xyz1[1], xyz2[1]])
        Zm = np.mean([xyz1[2], xyz2[2]])
        xyzMean = [Xm, Ym, Zm]

        # check plane
        isSubtractZero = np.array([False, False, False])

        # points in one line
        X = xyz1[0] - xyz2[0]
        Y = xyz1[1] - xyz2[1]
        Z = xyz1[2] - xyz2[2]
        xyzPlane = [X, Y, Z]

        # axis vector
        xyzL = np.array([0, 0, 0])

        # set
        # axis selection
        perpendicularAxis = np.array([False, False, False])
        if X == 0:
            isSubtractZero[0] = True
            perpendicularAxis[0] = True
            # xyzL = xyzL + np.array(1, 1, 0)

        if Y == 0:
            isSubtractZero[1] = True
            perpendicularAxis[1] = True
            # xyzL = xyzL + np.array(1, 1, 0)

        if Z == 0:
            isSubtractZero[2] = True
            perpendicularAxis[2] = True
            # xyzL = xyzL + np.array(1, 0, 1)

        # set xyzL
        xyzL = isSubtractZero.astype(int)

        return xyzMean, xyzPlane, isSubtractZero, xyzL, perpendicularAxis

    except Exception as e:
        raise Exception(e)

set_color(atom_symbol)

Set a color for each compound taken from https://en.wikipedia.org/wiki/CPK_coloring

Parameters

atom_symbol: str atom symbol

Returns

color: str atom color

Source code in pyMolinfo/docs/graph3d.py
def set_color(self, atom_symbol):
    '''
    Set a color for each compound
    taken from https://en.wikipedia.org/wiki/CPK_coloring

    Parameters
    ----------
    atom_symbol: str
        atom symbol

    Returns
    -------
    color: str
        atom color
    '''
    colors = {
        "H": '#ffffff',
        "C": '#BCBCBC',
        "N": '#0586f6',
        "O": '#f6052a',
        "F": '#2dd930',
        "Cl": '#2dd930',
        "Br": '#950e0e',
        "I": '#360e89',
        "He": '#3dbaf1',
        "Ne": '#3dbaf1',
        "Ar": '#3dbaf1',
        "Kr": '#3dbaf1',
        "Xe": '#3dbaf1',
        "P": '#f1a03d',
        "S": '#f1ef3d',
        "B": '#efc867',
        "Li": '#6b3ccb',
        "Na": '#6b3ccb',
        "K": '#6b3ccb',
        "Rb": '#6b3ccb',
        "Cs": '#6b3ccb',
        "Fr": '#6b3ccb',
        "Be": '#1c881e',
        "Mg": '#1c881e',
        "Ca": '#1c881e',
        "Sr": '#1c881e',
        "Ba": '#1c881e',
        "Ra": '#1c881e',
        "Ti": '#3d3e40',
        "Fe": '#a48620',
        "other": '#a729ba'
    }

    # check
    _color = colors.get(str(atom_symbol))
    if _color is None:
        return colors.get(str('other'))
    else:
        return _color

set_marker_size(min_distance, ref_length=1, min_marker_size=200, max_marker_size=500)

Set the plot view based on the minimum distance.

Parameters:

min_distance : float Minimum distance between points ref_length : float Reference length for scaling min_marker_size : float Minimum marker size max_marker_size : float Maximum

Returns:

float Marker size

Source code in pyMolinfo/docs/graph3d.py
def set_marker_size(self, min_distance, ref_length=1, min_marker_size=200, max_marker_size=500):
    '''
    Set the plot view based on the minimum distance.

    Parameters:
    ----------
    min_distance : float
        Minimum distance between points
    ref_length : float
        Reference length for scaling
    min_marker_size : float
        Minimum marker size
    max_marker_size : float
        Maximum

    Returns:
    -------
    float
        Marker size
    '''
    scaling_factor = min_marker_size / ref_length
    marker_size = min_distance * scaling_factor

    # Ensure the marker size is within the desired range
    marker_size = min(max_marker_size, max(min_marker_size, marker_size))

    return marker_size

set_plot_scale()

Set plot scale

Source code in pyMolinfo/docs/graph3d.py
def set_plot_scale(self):
    '''
    Set plot scale
    '''
    # atom no
    atomNo = len(self.xyzList)
    # bond no
    bondNo = len(self.atomBonds)

    # bond length list
    bondLengthList = []

    # *** using bond block
    for i in range(bondNo):
        # atom id
        _atom1Id = int(self.atomBonds[i]['id']) - 1
        # atom symbol
        _atom1Symbol = self.atomBonds[i]['symbol']
        # atom color
        _atom1Color = self.set_color(_atom1Symbol)
        # atom bond list
        _atom1BondList = self.atomBonds[i]['bonds']
        atom1BondSize = len(_atom1BondList)

        _atom1X = self.xyzList[_atom1Id, 0]
        _atom1Y = self.xyzList[_atom1Id, 1]
        _atom1Z = self.xyzList[_atom1Id, 2]
        _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

        # draw bond
        if atom1BondSize > 0:
            for j in range(atom1BondSize):
                # atom [2] id
                _atom2Id = int(_atom1BondList[j][0]) - 1
                # atom [2] symbol
                _atom2Symbol = _atom1BondList[j][1]
                # atom color
                _atom2Color = self.set_color(_atom2Symbol)
                # atom [1] - atom [2] bond type
                _bondType = int(_atom1BondList[j][3])

                # xyz
                _atom2X = self.xyzList[_atom2Id, 0]
                _atom2Y = self.xyzList[_atom2Id, 1]
                _atom2Z = self.xyzList[_atom2Id, 2]
                _atom2XYZ = [_atom2X, _atom2Y, _atom2Z]

                # bond connection (points)
                _bondConnection, _bondTypeLog, _bondLengths = self.create_bond_line(
                    _atom1XYZ, _atom2XYZ, _bondType)

                # save bond length
                bondLengthList.append(_bondLengths)

    # check bond length list
    if len(bondLengthList) > 0:
        # flatten list
        bondLengthList_flatten = sum(bondLengthList, [])

        # max bond length
        maxBondLength = max(bondLengthList_flatten)
        # min bond length
        minBondLength = min(bondLengthList_flatten)
        # mean bond length
        meanBondLength = np.mean(bondLengthList_flatten)
        # median bond length
        medianBondLength = np.median(bondLengthList_flatten)

        # set plot scale
        self.plotScale = [minBondLength, maxBondLength,
                          meanBondLength, medianBondLength]

set_size(symbol, _sy=300, _s=1)

Set atom size (spherical shape)

Parameters

symbol: str atom symbol _s: int size

Returns

size: int size

Source code in pyMolinfo/docs/graph3d.py
def set_size(self, symbol, _sy=300, _s=1):
    '''
    Set atom size (spherical shape)

    Parameters
    ----------
    symbol: str
        atom symbol
    _s: int
        size

    Returns
    -------
    size: int
        size
    '''
    _sy = 300
    _sx = 0.50*_sy

    sizes = {
        "H": _s*_sx,
        "C": _s*_sy,
        "N": _s*_sy,
        "O": _s*_sy,
        "F": _s*_sy,
        "Cl": _s*_sx,
        "Br": _s*_sy,
        "I": _s*_sy,
        "He": _s*_sy,
        "Ne": _s*_sy,
        "Ar": _s*_sy,
        "Kr": _s*_sy,
        "Xe": _s*_sy,
        "P": _s*_sy,
        "S": _s*_sy,
        "B": _s*_sy,
        "Li": _s*_sy,
        "Na": _s*_sy,
        "K": _s*_sy,
        "Rb": _s*_sy,
        "Cs": _s*_sy,
        "Fr": _s*_sy,
        "Be": _s*_sy,
        "Mg": _s*_sy,
        "Ca": _s*_sy,
        "Sr": _s*_sy,
        "Ba": _s*_sy,
        "Ra": _s*_sy,
        "Ti": _s*_sy,
        "Fe": _s*_sy,
        "other": _s*_sy,
    }

    _size = sizes.get(symbol)
    if _size is None:
        _sizeSet = int(sizes.get('other'))
    else:
        _sizeSet = int(_size)

    return _sizeSet

view3d(subgraphs=None, **kwargs)

Draw a compound in the cartesian coordinate atomElements atom symbol atomBonds atom bonds (bond blocks) xyzList atom position in the cartesian coordinate figSize=(10, 10) plt 3d setting obsOption=[False, 0] display center point [0,0,0]

Parameters

figSize: tuple figure size bg_color: str background color display_legend: bool display legend

Returns

fig: figure figure

Source code in pyMolinfo/docs/graph3d.py
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
def view3d(self, subgraphs=None, **kwargs):
    '''
    Draw a compound in the cartesian coordinate
    atomElements atom symbol
    atomBonds atom bonds (bond blocks)
    xyzList atom position in the cartesian coordinate
    figSize=(10, 10) plt 3d setting
    obsOption=[False, 0] display center point [0,0,0]

    Parameters
    ----------
    figSize: tuple
        figure size
    bg_color: str
        background color
    display_legend: bool
        display legend

    Returns
    -------
    fig: figure
        figure
    '''
    figSize = kwargs.get('figSize', [])
    bg_color = kwargs.get('bg_color', '#ffffff')
    display_legend = kwargs.get('display_legend', True)
    theme = kwargs.get('theme', 'light')
    display_atom_id = kwargs.get('display_atom_id', True)
    display_bond_length = kwargs.get('display_bond_length', False)
    bond_type_color = kwargs.get(
        'bond_type_color', ['#1B263B', '#EF476F', '#4361EE'])

    # plot summary
    plot_summary = []

    # Create the figure
    fig = go.Figure()

    # legend
    legend_list = []

    # marker label
    marker_labels = []

    # atom no
    atomNo = len(self.xyzList)
    # bond no
    bondNo = len(self.atomBonds_1d)

    # create 3d frame
    xyzLenMax, xyzLenMin, xyzR, xLen, yLen, zLen = self.create_3dframe()

    # *** plot scale
    plot_scale_res = self.set_plot_scale()
    # min bond length
    min_bond_length = self.plotScale[0]
    # max bond length
    max_bond_length = self.plotScale[1]
    # set marker size
    marker_size_0 = self.set_marker_size(min_bond_length, max_bond_length)

    # *** atom visualization
    for i in range(atomNo):
        # xyz
        _atom1X = self.xyzList[i, 0]
        _atom1Y = self.xyzList[i, 1]
        _atom1Z = self.xyzList[i, 2]
        _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

        # color
        # atom id
        _atomId = int(i+1)
        # symbol
        _atomSymbol = str(self.atomElements[i]).strip()
        # size
        _atomSize = self.set_size(_atomSymbol, _sy=marker_size_0)
        # color
        _atomColor = self.set_color(_atomSymbol)

        # atom mark
        atomMark = str(_atomSymbol) + str(_atomId)

        # atom label
        marker_labels.append(atomMark)

        if display_atom_id:  # Replace with your condition
            text_to_display = [atomMark]
        else:
            text_to_display = ['']  # Empty string to hide text

        # scatter
        fig.add_trace(go.Scatter3d(x=[_atom1X],
                                   y=[_atom1Y],
                                   z=[_atom1Z],
                                   mode='markers+text',
                                   marker=dict(
                                       color=_atomColor, size=10, sizemode='area', sizeref=1, line=dict(width=2, color='black')),
                                   hoverinfo='all',  # Display all available information
                                   hoverlabel=dict(bgcolor=bg_color),
                                   # Set custom hover text
                                   hovertext=[f'Element: {atomMark}'],
                                   text=text_to_display))

        # textfont = dict(weight='normal')

    # *** bond visualization
    # *** using bond block
    for i in range(bondNo):
        # id 1
        _atom1Id = int(self.atomBonds_1d[i]['id1']) - 1
        # id 2
        _atom2Id = int(self.atomBonds_1d[i]['id2']) - 1
        # symbol 1
        _atom1Symbol = self.atomBonds_1d[i]['symbol1']
        # symbol 2
        _atom2Symbol = self.atomBonds_1d[i]['symbol2']
        # color 1
        _atom1Color = self.set_color(_atom1Symbol)
        # color 2
        _atom2Color = self.set_color(_atom2Symbol)

        # bond symbol
        _bondSymbol = self.atomBonds_1d[i]['bond_symbol']
        # bond type
        _bondType = int(self.atomBonds_1d[i]['bond_type']-1)
        # set bond type
        _bondTypeLabel = ''
        if _bondType == 1:
            _bondTypeLabel = 'single bond'
        elif _bondType == 2:
            _bondTypeLabel = 'double bond'
        else:
            _bondTypeLabel = 'triple bond'

        # xyz atom 1
        _atom1X = self.xyzList[_atom1Id, 0]
        _atom1Y = self.xyzList[_atom1Id, 1]
        _atom1Z = self.xyzList[_atom1Id, 2]
        _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

        # xyz atom 2
        _atom2X = self.xyzList[_atom2Id, 0]
        _atom2Y = self.xyzList[_atom2Id, 1]
        _atom2Z = self.xyzList[_atom2Id, 2]
        _atom2XYZ = [_atom2X, _atom2Y, _atom2Z]

        # distance
        _distance = self.calculate_distance(_atom1XYZ, _atom2XYZ)

        # plot summary
        plot_summary.append(
            {
                'atom1Id': _atom1Id+1,
                'atom2Id': _atom2Id+1,
                'atom1Symbol': str(_atom1Symbol) + str(_atom1Id+1),
                'atom2Symbol': str(_atom2Symbol) + str(_atom2Id+1),
                'distance': _distance
            }
        )

        # Calculate midpoint coordinates
        midX = [(_atom1X + _atom2X) / 2]
        midY = [(_atom1Y + _atom2Y) / 2]
        midZ = [(_atom1Z + _atom2Z) / 2]

        # add line
        fig.add_trace(go.Scatter3d(x=[_atom1X, _atom2X],
                                   y=[_atom1Y, _atom2Y],
                                   z=[_atom1Z, _atom2Z],
                                   mode='lines',
                                   line=dict(color=bond_type_color[_bondType], width=3), hoverinfo='none', name=_bondSymbol, showlegend=True))

        if display_bond_length is True:  # Condition to show text
            text_to_display = [f'{_distance:.3f}']
        else:
            text_to_display = ['']  # Empty string to hide text

        # Add text at midpoint
        fig.add_trace(go.Scatter3d(x=midX,
                                   y=midY,
                                   z=midZ,
                                   mode='text',
                                   # Replace with your desired text
                                   text=text_to_display,
                                   hoverinfo='text',  # Display all available information
                                   hoverlabel=dict(
                                       bgcolor=bg_color, namelength=-1),
                                   # Set custom hover text
                                   hovertext=[f'A {_bondTypeLabel} with a length of {_distance:.3f}']))

    # *** visualize subgraph
    if subgraphs is not None:
        for subgraph in subgraphs:
            # add subgraph
            subgraph_pattern = subgraph['subgraph_pattern']
            _node_list = list(subgraph_pattern.nodes())
            for i in _node_list:
                # xyz
                _atom1X = self.xyzList[i-1, 0]
                _atom1Y = self.xyzList[i-1, 1]
                _atom1Z = self.xyzList[i-1, 2]
                _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

                # color
                # atom id
                _atomId = i
                # symbol
                _atomSymbol = str(self.atomElements[i-1]).strip()
                # color
                _atomColor = self.set_color(_atomSymbol)

                # scatter
                fig.add_trace(go.Scatter3d(x=[_atom1X],
                                           y=[_atom1Y],
                                           z=[_atom1Z],
                                           mode='markers',
                                           marker=dict(
                                                color=_atomColor, size=2*10, opacity=0.5,
                                                sizemode='area', sizeref=1,
                                                line=dict(width=2, color='black')),
                                           ))

            # edge list
            _edge_list = list(subgraph_pattern.edges())
            for i in _edge_list:
                # xyz
                _atom1X = self.xyzList[i[0]-1, 0]
                _atom1Y = self.xyzList[i[0]-1, 1]
                _atom1Z = self.xyzList[i[0]-1, 2]
                _atom1XYZ = [_atom1X, _atom1Y, _atom1Z]

                # xyz
                _atom2X = self.xyzList[i[1]-1, 0]
                _atom2Y = self.xyzList[i[1]-1, 1]
                _atom2Z = self.xyzList[i[1]-1, 2]
                _atom2XYZ = [_atom2X, _atom2Y, _atom2Z]

                # add line
                fig.add_trace(go.Scatter3d(x=[_atom1X, _atom2X],
                                           y=[_atom1Y, _atom2Y],
                                           z=[_atom1Z, _atom2Z],
                                           mode='lines',
                                           line=dict(
                                               color='blue', width=2*5, dash='dashdot'),
                                           hoverinfo='none'))

    # Set the limits of the axes
    # set max and offset
    xyzLenMax = xyzLenMax + 1.5
    fig.update_layout(scene=dict(
        xaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
        yaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
        zaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax])
    ))

    # Set figure size to a square
    if len(figSize) != 0:
        fig.update_layout(width=figSize[0], height=figSize[1])
    else:
        fig.update_layout(
            autosize=True
        )

    # Show legend
    # Update layout to display legend
    fig.update_layout(legend=dict(
        orientation="h",  # Horizontal legend
        yanchor="bottom",  # Anchor at bottom
        y=1.02,  # Move legend up slightly
        xanchor="right",  # Anchor at right
        x=1  # Move legend to right
    ))

    if theme == 'black':
        font_color = 'lightgray'
    else:
        font_color = 'black'

    # Update text font color
    fig.update_traces(textfont=dict(color=font_color))

    # Set background color to dark
    fig.update_layout(
        paper_bgcolor=bg_color,
        plot_bgcolor=bg_color,
        scene=dict(
            xaxis=dict(showbackground=True,
                       backgroundcolor=bg_color),
            yaxis=dict(showbackground=True,
                       backgroundcolor=bg_color),
            zaxis=dict(showbackground=True,
                       backgroundcolor=bg_color)
        )
    )

    # Remove axes and other elements
    fig.update_layout(
        scene=dict(
            xaxis=dict(showticklabels=False, showgrid=False,
                       zeroline=False, showspikes=False, title=''),
            yaxis=dict(showticklabels=False, showgrid=False,
                       zeroline=False, showspikes=False, title=''),
            zaxis=dict(showticklabels=False, showgrid=False,
                       zeroline=False, showspikes=False, title=''),
            aspectmode='manual',
            aspectratio=dict(x=1, y=1, z=1),
        ),
        showlegend=False,
        margin=dict(l=0, r=0, b=0, t=0)
    )

    # Show the plot with zoom disabled
    fig.show(config={
        'scrollZoom': True,  # Disable zoom with scroll
        'displayModeBar': True,
        'displaylogo': True,
        'modeBarButtonsToRemove': ['zoom2d', 'zoomIn2d', 'zoomOut2d', 'pan2d']
    })

    # res
    return plot_summary

view3dobs(elev=None, azim=None, figSize=(10, 10), obsOption=[True, 0])

Draw a compound in the cartesian coordinate with observer

Parameters

elev : float elevation angle azim : float azimuth angle figSize : tuple figure size obsOption : list [True, 0] --> show observer, 0 --> observer radius

Source code in pyMolinfo/docs/graph3d.py
def view3dobs(self, elev=None, azim=None, figSize=(10, 10), obsOption=[True, 0]):
    '''
    Draw a compound in the cartesian coordinate with observer

    Parameters
    ----------
    elev : float
        elevation angle
    azim : float
        azimuth angle
    figSize : tuple
        figure size
    obsOption : list
        [True, 0] --> show observer, 0 --> observer radius
    '''
    # 3d plot
    fig = plt.figure(figsize=figSize)
    ax = plt.axes(projection='3d')

    # atom no
    atomNo = len(self.xyzList)
    # bond no
    bondNo = len(self.atomBonds)

    # atom visualization
    for i in range(atomNo):
        # xyz
        _atomX = self.xyzList[i, 0]
        _atomY = self.xyzList[i, 1]
        _atomZ = self.xyzList[i, 2]
        # color
        # size

        # draw atom 1
        ax.scatter3D(_atomX, _atomY, _atomZ, s=40)

    # bond visualization
    for i in range(bondNo):
        # atom id
        _atom1Id = int(self.atomBonds[i]['id']) - 1
        # atom symbol
        _atom1Symbol = self.atomBonds[i]['symbol']
        # atom bond list
        _atom1BondList = self.atomBonds[i]['bonds']
        atom1BondSize = len(_atom1BondList)

        _atom1X = self.xyzList[:, 0]
        _atom1Y = self.xyzList[:, 1]
        _atom1Z = self.xyzList[:, 2]

        # draw bond
        if atom1BondSize > 0:
            for j in range(atom1BondSize):
                # atom [2] id
                _atom2Id = int(_atom1BondList[j][0]) - 1
                # atom [2] symbol
                _atom2Symbol = _atom1BondList[j][1]
                # atom [1] - atom [2] bond type
                _bondType = int(_atom1BondList[j][3])

                # set color
                lineColor = ['k', 'b', 'c']
                lineWidth = [1, 2, 3]

                # xyz
                _atom2X = self.xyzList[_atom2Id, 0]
                _atom2Y = self.xyzList[_atom2Id, 1]
                _atom2Z = self.xyzList[_atom2Id, 2]

                # bond connection
                _bondConnection = self.xyzList[[_atom1Id, _atom2Id]]
                # draw line
                ax.plot3D(_bondConnection[:, 0], _bondConnection[:, 1], _bondConnection[:, 2],
                          color=lineColor[_bondType-1], linewidth=lineWidth[_bondType-1])

    # obs visualization
    xyzObsList = Observer.GeneratorCircleObserver(
        self._robs, self.tetaNo, self.phiNo, self.limits['teta'])[0]
    # obs size
    xyzObsSize = len(xyzObsList)
    for i in range(xyzObsSize):
        ax.scatter3D(xyzObsList[i, :, 0],
                     xyzObsList[i, :, 1], xyzObsList[i, :, 2])
        ax.plot3D(xyzObsList[i, :, 0],
                  xyzObsList[i, :, 1], xyzObsList[i, :, 2])

    # obs show
    if obsOption[0]:
        ax.scatter3D(obsOption[1], 0, 0, s=40)

    ax.view_init(elev=elev, azim=azim)
    plt.show()

view_graph3d(G, subgraphs=None, **kwargs) staticmethod

Draw a compound in 3D from a graph representation

Parameters

G : networkx.Graph Graph with nodes having 'symbol' and 'xyz' attributes and edges having 'symbol' and 'type' attributes subgraphs : list, optional List of subgraphs to highlight **kwargs : dict Additional parameters for visualization figSize: tuple - figure size bg_color: str - background color display_legend: bool - display legend theme: str - theme for visualization ('light' or 'dark') display_atom_id: bool - display atom IDs display_bond_length: bool - display bond lengths bond_type_color: list - colors for different bond types

Returns

plot_summary : list Summary of plotted elements

Source code in pyMolinfo/docs/graph3d.py
@staticmethod
def view_graph3d(G, subgraphs=None, **kwargs):
    '''
    Draw a compound in 3D from a graph representation

    Parameters
    ----------
    G : networkx.Graph
        Graph with nodes having 'symbol' and 'xyz' attributes and edges having 'symbol' and 'type' attributes
    subgraphs : list, optional
        List of subgraphs to highlight
    **kwargs : dict
        Additional parameters for visualization
        figSize: tuple - figure size
        bg_color: str - background color
        display_legend: bool - display legend
        theme: str - theme for visualization ('light' or 'dark')
        display_atom_id: bool - display atom IDs
        display_bond_length: bool - display bond lengths
        bond_type_color: list - colors for different bond types

    Returns
    -------
    plot_summary : list
        Summary of plotted elements
    '''
    figSize = kwargs.get('figSize', [])
    bg_color = kwargs.get('bg_color', '#ffffff')
    display_legend = kwargs.get('display_legend', False)
    theme = kwargs.get('theme', 'light')
    display_atom_id = kwargs.get('display_atom_id', True)
    display_bond_length = kwargs.get('display_bond_length', False)
    bond_type_color = kwargs.get(
        'bond_type_color', ['#1B263B', '#EF476F', '#4361EE'])

    # Plot summary
    plot_summary = []

    # Create the figure
    fig = go.Figure()

    # Extract node data from the graph
    nodes = G.nodes(data=True)
    edges = G.edges(data=True)

    # Create list of atom positions from nodes
    xyzList = []
    atomElements = []
    node_ids = []

    for node_id, node_data in nodes:
        if 'xyz' in node_data:
            xyzList.append(node_data['xyz'])
            atomElements.append(node_data['symbol'])
            node_ids.append(node_id)

    # Convert to numpy array for easier manipulation
    xyzList = np.array(xyzList)

    # Create 3D frame dimensions
    xyzLenMax = np.max(np.abs(xyzList))

    # Helper functions
    def set_color(atom_symbol):
        '''Set a color for each atom based on element'''
        colors = {
            "H": '#ffffff',
            "C": '#BCBCBC',
            "N": '#0586f6',
            "O": '#f6052a',
            "F": '#2dd930',
            "Cl": '#2dd930',
            "Br": '#950e0e',
            "I": '#360e89',
            "He": '#3dbaf1',
            "Ne": '#3dbaf1',
            "Ar": '#3dbaf1',
            "Kr": '#3dbaf1',
            "Xe": '#3dbaf1',
            "P": '#f1a03d',
            "S": '#f1ef3d',
            "B": '#efc867',
            "Li": '#6b3ccb',
            "Na": '#6b3ccb',
            "K": '#6b3ccb',
            "Rb": '#6b3ccb',
            "Cs": '#6b3ccb',
            "Fr": '#6b3ccb',
            "Be": '#1c881e',
            "Mg": '#1c881e',
            "Ca": '#1c881e',
            "Sr": '#1c881e',
            "Ba": '#1c881e',
            "Ra": '#1c881e',
            "Ti": '#3d3e40',
            "Fe": '#a48620',
            "other": '#a729ba'
        }

        _color = colors.get(str(atom_symbol))
        if _color is None:
            return colors.get(str('other'))
        else:
            return _color

    def calculate_distance(xyz1, xyz2):
        """Calculate the Euclidean distance between two points."""
        return math.sqrt((xyz2[0] - xyz1[0])**2 +
                         (xyz2[1] - xyz1[1])**2 +
                         (xyz2[2] - xyz1[2])**2)

    # Node visualization (atoms)
    for i, node_id in enumerate(node_ids):
        # Get node data
        node_data = G.nodes[node_id]
        atom_xyz = node_data['xyz']
        atom_symbol = node_data['symbol']

        # Atom label
        atomMark = f"{atom_symbol}{node_id}"

        # Color based on element
        atom_color = set_color(atom_symbol)

        # Text display based on setting
        if display_atom_id:
            text_to_display = [atomMark]
        else:
            text_to_display = ['']

        # Add atom as a 3D scatter point
        fig.add_trace(go.Scatter3d(
            x=[atom_xyz[0]],
            y=[atom_xyz[1]],
            z=[atom_xyz[2]],
            mode='markers+text',
            marker=dict(
                color=atom_color, 
                size=10, 
                sizemode='area', 
                sizeref=1, 
                line=dict(width=2, color='black')
            ),
            hoverinfo='all',
            hoverlabel=dict(bgcolor=bg_color),
            hovertext=[f'Element: {atomMark}'],
            text=text_to_display
        ))

    # Edge visualization (bonds)
    for edge in edges:
        # Get nodes connected by this edge
        node1_id, node2_id = edge[0], edge[1]
        edge_data = G.edges[node1_id, node2_id]

        # Get node data
        node1_data = G.nodes[node1_id]
        node2_data = G.nodes[node2_id]

        # Get atom symbols
        atom1_symbol = node1_data['symbol']
        atom2_symbol = node2_data['symbol']

        # Get positions
        atom1_xyz = node1_data['xyz']
        atom2_xyz = node2_data['xyz']

        # Get bond type (default to 1 if missing)
        bond_type = edge_data.get('type', 1) - 1  # Adjust to 0-based index for the color list
        bond_type = min(max(0, bond_type), 2)  # Ensure it's between 0-2

        # Calculate bond length (distance)
        distance = calculate_distance(atom1_xyz, atom2_xyz)

        # Add to plot summary
        plot_summary.append({
            'atom1Id': node1_id,
            'atom2Id': node2_id,
            'atom1Symbol': f"{atom1_symbol}{node1_id}",
            'atom2Symbol': f"{atom2_symbol}{node2_id}",
            'distance': distance
        })

        # Draw the bond as a line
        fig.add_trace(go.Scatter3d(
            x=[atom1_xyz[0], atom2_xyz[0]],
            y=[atom1_xyz[1], atom2_xyz[1]],
            z=[atom1_xyz[2], atom2_xyz[2]],
            mode='lines',
            line=dict(color=bond_type_color[bond_type], width=3),
            hoverinfo='none',
            name=edge_data.get('symbol', f"Bond {node1_id}-{node2_id}"),
            showlegend=True
        ))

        # Calculate midpoint for bond length label
        midX = [(atom1_xyz[0] + atom2_xyz[0]) / 2]
        midY = [(atom1_xyz[1] + atom2_xyz[1]) / 2]
        midZ = [(atom1_xyz[2] + atom2_xyz[2]) / 2]

        # Bond type text
        bond_type_names = ['single bond', 'double bond', 'triple bond']
        bond_type_label = bond_type_names[bond_type]

        # Display bond length if requested
        if display_bond_length:
            text_to_display = [f'{distance:.3f}']
        else:
            text_to_display = ['']

        # Add bond length text
        fig.add_trace(go.Scatter3d(
            x=midX,
            y=midY,
            z=midZ,
            mode='text',
            text=text_to_display,
            hoverinfo='text',
            hoverlabel=dict(bgcolor=bg_color, namelength=-1),
            hovertext=[f'A {bond_type_label} with a length of {distance:.3f}']
        ))

    # Visualize subgraphs if provided
    if subgraphs is not None:
        for subgraph in subgraphs:
            # Get nodes from the subgraph
            subgraph_nodes = list(subgraph.nodes())

            # Highlight nodes
            for node_id in subgraph_nodes:
                if node_id in G.nodes:
                    node_data = G.nodes[node_id]
                    atom_xyz = node_data['xyz']
                    atom_symbol = node_data['symbol']
                    atom_color = set_color(atom_symbol)

                    # Add highlighted atom
                    fig.add_trace(go.Scatter3d(
                        x=[atom_xyz[0]],
                        y=[atom_xyz[1]],
                        z=[atom_xyz[2]],
                        mode='markers',
                        marker=dict(
                            color=atom_color,
                            size=20,
                            opacity=0.5,
                            sizemode='area',
                            sizeref=1,
                            line=dict(width=2, color='black')
                        )
                    ))

            # Highlight edges
            for edge in subgraph.edges():
                node1_id, node2_id = edge

                # Only highlight edges that exist in the main graph
                if G.has_edge(node1_id, node2_id):
                    node1_data = G.nodes[node1_id]
                    node2_data = G.nodes[node2_id]

                    atom1_xyz = node1_data['xyz']
                    atom2_xyz = node2_data['xyz']

                    # Add highlighted bond
                    fig.add_trace(go.Scatter3d(
                        x=[atom1_xyz[0], atom2_xyz[0]],
                        y=[atom1_xyz[1], atom2_xyz[1]],
                        z=[atom1_xyz[2], atom2_xyz[2]],
                        mode='lines',
                        line=dict(color='blue', width=10, dash='dashdot'),
                        hoverinfo='none'
                    ))

    # Set the limits of the axes
    xyzLenMax = xyzLenMax + 1.5
    fig.update_layout(scene=dict(
        xaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
        yaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax]),
        zaxis=dict(nticks=4, range=[-xyzLenMax, xyzLenMax])
    ))

    # Set figure size
    if len(figSize) != 0:
        fig.update_layout(width=figSize[0], height=figSize[1])
    else:
        fig.update_layout(autosize=True)

    # Configure legend
    fig.update_layout(legend=dict(
        orientation="h",
        yanchor="bottom",
        y=1.02,
        xanchor="right",
        x=1
    ))

    # Set font color based on theme
    font_color = 'lightgray' if theme == 'black' else 'black'
    fig.update_traces(textfont=dict(color=font_color))

    # Set background color
    fig.update_layout(
        paper_bgcolor=bg_color,
        plot_bgcolor=bg_color,
        scene=dict(
            xaxis=dict(showbackground=True, backgroundcolor=bg_color),
            yaxis=dict(showbackground=True, backgroundcolor=bg_color),
            zaxis=dict(showbackground=True, backgroundcolor=bg_color)
        )
    )

    # Remove axes and other elements for cleaner visualization
    fig.update_layout(
        scene=dict(
            xaxis=dict(showticklabels=False, showgrid=False, zeroline=False, showspikes=False, title=''),
            yaxis=dict(showticklabels=False, showgrid=False, zeroline=False, showspikes=False, title=''),
            zaxis=dict(showticklabels=False, showgrid=False, zeroline=False, showspikes=False, title=''),
            aspectmode='manual',
            aspectratio=dict(x=1, y=1, z=1),
        ),
        showlegend=display_legend,
        margin=dict(l=0, r=0, b=0, t=0)
    )

    # Show the plot
    fig.show(config={
        'scrollZoom': True,
        'displayModeBar': True,
        'displaylogo': True,
        'modeBarButtonsToRemove': ['zoom2d', 'zoomIn2d', 'zoomOut2d', 'pan2d']
    })

    return plot_summary

view_graph3d_mpl(G, subgraphs=None, **kwargs) staticmethod

Draw a compound in 3D from a graph representation using matplotlib

Parameters

G : networkx.Graph Graph with nodes having 'symbol' and 'xyz' attributes and edges having 'symbol' and 'type' attributes subgraphs : list, optional List of subgraphs to highlight **kwargs : dict Additional parameters for visualization figSize: tuple - figure size (default: (10, 10)) elev: float - elevation angle for the 3D view azim: float - azimuth angle for the 3D view display_legend: bool - display legend (default: True) theme: str - theme for visualization ('light' or 'dark') display_atom_id: bool - display atom IDs (default: True) display_bond_length: bool - display bond lengths (default: False) bond_type_color: list - colors for different bond types

Returns

plot_summary : list Summary of plotted elements fig : matplotlib.figure.Figure Matplotlib figure object ax : matplotlib.axes.Axes Matplotlib axes object

Source code in pyMolinfo/docs/graph3d.py
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
@staticmethod
def view_graph3d_mpl(G, subgraphs=None, **kwargs):
    '''
    Draw a compound in 3D from a graph representation using matplotlib

    Parameters
    ----------
    G : networkx.Graph
        Graph with nodes having 'symbol' and 'xyz' attributes and edges having 'symbol' and 'type' attributes
    subgraphs : list, optional
        List of subgraphs to highlight
    **kwargs : dict
        Additional parameters for visualization
        figSize: tuple - figure size (default: (10, 10))
        elev: float - elevation angle for the 3D view
        azim: float - azimuth angle for the 3D view
        display_legend: bool - display legend (default: True)
        theme: str - theme for visualization ('light' or 'dark')
        display_atom_id: bool - display atom IDs (default: True)
        display_bond_length: bool - display bond lengths (default: False)
        bond_type_color: list - colors for different bond types

    Returns
    -------
    plot_summary : list
        Summary of plotted elements
    fig : matplotlib.figure.Figure
        Matplotlib figure object
    ax : matplotlib.axes.Axes
        Matplotlib axes object
    '''
    # Extract parameters from kwargs with defaults
    figSize = kwargs.get('figSize', (10, 10))
    elev = kwargs.get('elev', 30)
    azim = kwargs.get('azim', 30)
    display_legend = kwargs.get('display_legend', True)
    theme = kwargs.get('theme', 'light')
    display_atom_id = kwargs.get('display_atom_id', True)
    display_bond_length = kwargs.get('display_bond_length', False)
    bond_type_color = kwargs.get('bond_type_color', ['k', 'b', 'r'])  # Black, blue, red

    # Plot summary list to return
    plot_summary = []

    # Create figure and 3D axes
    fig = plt.figure(figsize=figSize)
    ax = fig.add_subplot(111, projection='3d')

    # Extract node data from the graph
    nodes = G.nodes(data=True)
    edges = G.edges(data=True)

    # Create lists for atom positions and elements
    xyzList = []
    atomElements = []
    node_ids = []

    # Extract node data
    for node_id, node_data in nodes:
        if 'xyz' in node_data:
            xyzList.append(node_data['xyz'])
            atomElements.append(node_data['symbol'])
            node_ids.append(node_id)

    # Convert to numpy array for easier manipulation
    xyzList = np.array(xyzList)

    # Helper function to set colors based on atom symbol
    def set_color(atom_symbol):
        colors = {
            "H": '#ffffff',
            "C": '#BCBCBC',
            "N": '#0586f6',
            "O": '#f6052a',
            "F": '#2dd930',
            "Cl": '#2dd930',
            "Br": '#950e0e',
            "I": '#360e89',
            "He": '#3dbaf1',
            "Ne": '#3dbaf1',
            "Ar": '#3dbaf1',
            "Kr": '#3dbaf1',
            "Xe": '#3dbaf1',
            "P": '#f1a03d',
            "S": '#f1ef3d',
            "B": '#efc867',
            "Li": '#6b3ccb',
            "Na": '#6b3ccb',
            "K": '#6b3ccb',
            "Rb": '#6b3ccb',
            "Cs": '#6b3ccb',
            "Fr": '#6b3ccb',
            "Be": '#1c881e',
            "Mg": '#1c881e',
            "Ca": '#1c881e',
            "Sr": '#1c881e',
            "Ba": '#1c881e',
            "Ra": '#1c881e',
            "Ti": '#3d3e40',
            "Fe": '#a48620',
            "other": '#a729ba'
        }

        _color = colors.get(str(atom_symbol))
        if _color is None:
            return colors.get(str('other'))
        else:
            return _color

    # Helper function to calculate distance between atoms
    def calculate_distance(xyz1, xyz2):
        return math.sqrt((xyz2[0] - xyz1[0])**2 +
                         (xyz2[1] - xyz1[1])**2 +
                         (xyz2[2] - xyz1[2])**2)

    # Calculate limits for the plot
    if len(xyzList) > 0:
        xyzLenMax = np.max(np.abs(xyzList)) + 1.5
    else:
        xyzLenMax = 5.0  # Default if no atoms

    # Node visualization (atoms)
    for i, node_id in enumerate(node_ids):
        # Get node data
        node_data = G.nodes[node_id]
        atom_xyz = node_data['xyz']
        atom_symbol = node_data['symbol']

        # Atom label
        atomMark = f"{atom_symbol}{node_id}"

        # Set color based on atom type
        atom_color = set_color(atom_symbol)

        # Plot the atom as a 3D point
        ax.scatter(atom_xyz[0], atom_xyz[1], atom_xyz[2], 
                   color=atom_color, s=100, edgecolors='black')

        # Add atom label if requested
        if display_atom_id:
            ax.text(atom_xyz[0], atom_xyz[1], atom_xyz[2], 
                    atomMark, size=8, zorder=1, ha='center', va='center')

    # Edge visualization (bonds)
    for edge in edges:
        # Get nodes connected by this edge
        node1_id, node2_id = edge[0], edge[1]
        edge_data = G.edges[node1_id, node2_id]

        # Get node data
        node1_data = G.nodes[node1_id]
        node2_data = G.nodes[node2_id]

        # Get atom symbols
        atom1_symbol = node1_data['symbol']
        atom2_symbol = node2_data['symbol']

        # Get positions
        atom1_xyz = node1_data['xyz']
        atom2_xyz = node2_data['xyz']

        # Get bond type (default to 1 if missing)
        bond_type = edge_data.get('type', 1)
        bond_type = min(max(1, bond_type), 3)  # Ensure it's between 1-3

        # Calculate bond length (distance)
        distance = calculate_distance(atom1_xyz, atom2_xyz)

        # Add to plot summary
        plot_summary.append({
            'atom1Id': node1_id,
            'atom2Id': node2_id,
            'atom1Symbol': f"{atom1_symbol}{node1_id}",
            'atom2Symbol': f"{atom2_symbol}{node2_id}",
            'distance': distance
        })

        # Draw the bond line
        bond_linewidth = bond_type * 1.5  # Increase line width based on bond type
        ax.plot([atom1_xyz[0], atom2_xyz[0]],
                [atom1_xyz[1], atom2_xyz[1]],
                [atom1_xyz[2], atom2_xyz[2]],
                color=bond_type_color[bond_type-1], 
                linewidth=bond_linewidth,
                alpha=0.8)

        # Display bond length if requested
        if display_bond_length:
            # Calculate midpoint for placing text
            mid_x = (atom1_xyz[0] + atom2_xyz[0]) / 2
            mid_y = (atom1_xyz[1] + atom2_xyz[1]) / 2
            mid_z = (atom1_xyz[2] + atom2_xyz[2]) / 2

            # Add bond length label
            ax.text(mid_x, mid_y, mid_z, f"{distance:.2f}", 
                    size=6, ha='center', va='center', 
                    bbox=dict(facecolor='white', alpha=0.7))

    # Visualize subgraphs if provided
    if subgraphs is not None:
        for subgraph in subgraphs:
            # Handle different possible formats of subgraphs
            if hasattr(subgraph, 'nodes'):
                # If subgraph is a NetworkX graph
                subgraph_nodes = list(subgraph.nodes())
                subgraph_edges = list(subgraph.edges())
            elif isinstance(subgraph, dict) and 'subgraph_pattern' in subgraph:
                # If subgraph is a dict with 'subgraph_pattern' key (as in view3d function)
                subgraph_pattern = subgraph['subgraph_pattern']
                subgraph_nodes = list(subgraph_pattern.nodes())
                subgraph_edges = list(subgraph_pattern.edges())
            else:
                continue  # Skip if format not recognized

            # Highlight nodes
            for node_id in subgraph_nodes:
                if node_id in G.nodes:
                    node_data = G.nodes[node_id]
                    if 'xyz' in node_data:
                        atom_xyz = node_data['xyz']
                        atom_symbol = node_data['symbol']
                        atom_color = set_color(atom_symbol)

                        # Add highlighted atom (larger, translucent)
                        ax.scatter(atom_xyz[0], atom_xyz[1], atom_xyz[2],
                                  color=atom_color, s=200, alpha=0.3,
                                  edgecolors='blue', linewidths=2)

            # Highlight edges
            for edge in subgraph_edges:
                node1_id, node2_id = edge

                # Only highlight edges that exist in the main graph
                if G.has_edge(node1_id, node2_id):
                    node1_data = G.nodes[node1_id]
                    node2_data = G.nodes[node2_id]

                    if 'xyz' in node1_data and 'xyz' in node2_data:
                        atom1_xyz = node1_data['xyz']
                        atom2_xyz = node2_data['xyz']

                        # Add highlighted bond (thicker, blue, dashed)
                        ax.plot([atom1_xyz[0], atom2_xyz[0]],
                                [atom1_xyz[1], atom2_xyz[1]],
                                [atom1_xyz[2], atom2_xyz[2]],
                                color='blue', linewidth=4, linestyle='--',
                                alpha=0.7)

    # Set axis limits and remove ticks for cleaner visualization
    ax.set_xlim(-xyzLenMax, xyzLenMax)
    ax.set_ylim(-xyzLenMax, xyzLenMax)
    ax.set_zlim(-xyzLenMax, xyzLenMax)

    # Set equal aspect ratio for all axes
    ax.set_box_aspect([1, 1, 1])

    # Remove tick labels for cleaner visualization
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.set_zticklabels([])

    # Remove tick marks and background grid
    ax.xaxis.set_ticks([])
    ax.yaxis.set_ticks([])
    ax.zaxis.set_ticks([])
    ax.xaxis._axinfo['grid'].update({'visible': False})
    ax.yaxis._axinfo['grid'].update({'visible': False})
    ax.zaxis._axinfo['grid'].update({'visible': False})

    # Set axis labels
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')

    # Set background color and styling based on theme
    if theme == 'dark':
        fig.patch.set_facecolor('black')
        ax.set_facecolor('black')
        ax.xaxis.label.set_color('white')
        ax.yaxis.label.set_color('white')
        ax.zaxis.label.set_color('white')
    else:
        fig.patch.set_facecolor('white')
        ax.set_facecolor('white')

    # Set the view angle
    ax.view_init(elev=elev, azim=azim)

    # Add a legend if requested
    if display_legend and len(atomElements) > 0:
        # Create a legend for unique atom types
        unique_elements = set(atomElements)
        legend_elements = []

        for element in unique_elements:
            legend_elements.append(plt.Line2D([0], [0], marker='o', color='w', 
                                              label=element,
                                              markerfacecolor=set_color(element), 
                                              markersize=10))

        # Add a legend for bond types
        for i, bond_name in enumerate(['Single', 'Double', 'Triple']):
            if i < len(bond_type_color):
                legend_elements.append(plt.Line2D([0], [0], color=bond_type_color[i], 
                                                 lw=(i+1)*1.5, label=f'{bond_name} bond'))

        ax.legend(handles=legend_elements, loc='upper right', frameon=True)

    # Adjust layout to make room for the legend
    plt.tight_layout()

    # Display the plot
    plt.show()

    # Return summary and figure objects for further manipulation
    return plot_summary, fig, ax