Trying to add alloy / alchemy command

Hello,
I am currently working on this issue #795 . Can you please state what would be the format for the dialog gui and input-output

Also state should I create 2 separate classes for selection and replacement? But when I refered to insertdna there was one class for one dialog hence here also I think there would be just one class with multiple methods inside it. Am I right?

avogadro_plugin(NucleicInput
  "Insert DNA/RNA sequences."
  ExtensionPlugin
  insertdna.h
  InsertDna
  "insertdna.cpp"
  "insertdnadialog.ui"
)

Here NucleicInput is just for representing the plugin or does it mean certain kind of input?
Also how can I see the output after I make any changes in the code? Do I have to build the target or just run something.

I am a newbie hence need some help
Thanks

Actually, I have an alternate suggestion.

You can implement this in a Python script, which I think will be easier for you.

Take a look, for example at: https://github.com/OpenChemistry/avogadro-commands/blob/000690a74633e5f300e5933e4645e3f3ec6758b5/scale.py

The script sends out some JSON describing the dialog.

You might want, for example a label “Replace Element” and a list of common elements and “New Element” as another label and list of elements.

In the script I linked, it goes through the CJSON from Avogadro and changes the 3d coordinates (e.g., the loop over mol['atoms']['coords']['3d'])

In your case, you can iterate over mol['atoms']['elements']['number'] which is a list of the atomic numbers.

So you’d have a loop, check for the element selected (e.g., lead = 82) and change the number to the target (e.g., gold = 79).

You can test these scripts by dragging the Python script to the Avogadro window - it will install it. If everything is working, you can create a GitHub repository to add as an official plugin. (You’ll want a few extra files like a README and a plugin.json file.)

If you want to try in C++, let me know and I can give some pointers there too.

Hii Sir,
Thank you for your help. I almost understood what you said, Can you please just help me how to run the sample script to change 3d coordinates using the Avogadro interface. Unfortunately I am not able to figure out, after installing how can I provide inputs to the script (in CJSON format probably). Actually I wanted to print the mol['atoms']['coords']['3d'] (and also mol[‘atoms’][‘elements’][‘number’] afterwards) lists. I even tried using terminal for the same
echo '{"X Scale": 2.0, "Y Scale": 2.0, "Z Scale": 2.0}' | python scale.py --run-command
but I think CJSON inputs will only work

In your case, you didn’t add a cjson key with the molecule. (I’ll go edit the docs to be a bit more specific on this).

The stdin will contain keys with the user options as well as the molecule itself, for example in cjson format.

Take a look at this script: Avogadro Script Command Tester · GitHub

An example input for scale might look like:

{"X Scale": 1.0, 
"Y Scale": 1.0, 
"Z Scale": 1.0, 
"cjson": {"chemicalJson": 1, 
          "atoms": {"coords": {"3d": [0.617532, -0.027246, 0.481009, 0.156663, 0.031752, -0.362419, -0.774185, -0.004516, -0.11859]}, 
          "elements": {"number": [1, 8, 1]}}, 
          "bonds": {"connections": {"index": [1, 2, 1, 0]}, 
                    "order": [1, 1]}}
}

Okay thanks for the help…Is this okay?

def getOptions():
    userOptions = {}

    userOptions['Find'] = {}
    userOptions['Find']['label'] = 'Find Element'
    userOptions['Find']['type'] = 'int'
    userOptions['Find']['default'] = 0

    userOptions['Percent'] = {}
    userOptions['Percent']['label'] = 'Percentage to be replaced'
    userOptions['Percent']['type'] = 'float'
    userOptions['Percent']['default'] = 100

    userOptions['Replace'] = {}
    userOptions['Replace']['label'] = 'Replace with'
    userOptions['Replace']['type'] = 'int'
    userOptions['Replace']['default'] = 0

    opts = {'userOptions': userOptions}

    return opts


def replace_element(opts, mol):
    original = int(opts['Find'])
    percent = float(opts['Percent'])
    replace = int(opts['Replace'])

    atomic_numbers = mol['atoms']['elements']['number']
    occurences = atomic_numbers.count(original)
    number_of_atoms = int((percent*occurences)/100)
    count = 0
    for i in range(0, len(atomic_numbers)):
        if (count < number_of_atoms):
            if (atomic_numbers[i] == original):
                atomic_numbers[i] = replace
                count = count+1
        else:
            break

    return mol

Output:

PS C:\Users\dhruv\Desktop\SYNAPSE ML> echo '{"Find": 1, 
>> "Percent": 50,  
>> "Replace": 4,
>> "cjson": {"chemicalJson": 1,
>>           "atoms": {"coords": {"3d": [0.617532, -0.027246, 0.481009, 0.156663, 0.031752, -0.362419, -0.774185, -0.004516, -0.11859]},
>>           "elements": {"number": [1, 8, 1]}},
>>           "bonds": {"connections": {"index": [1, 2, 1, 0]},
>>                     "order": [1, 1]}}
>> }'| python scale.py --run-command
{"cjson": {"chemicalJson": 1, "atoms": {"coords": {"3d": [0.617532, -0.027246, 0.481009, 0.156663, 0.031752, -0.362419, 
-0.774185, -0.004516, -0.11859]}, "elements": {"number": [4, 8, 1]}}, "bonds": {"connections": {"index": [1, 2, 1, 0]}, 

So, just few questions more:

  1. How should be the input like in the form of elements or just atomic numbers is sufficient?
  2. How to handle cases where the quantity of the atoms is less for eg atoms having occurences 0 (no atom of the element to be replaced in the molecule) or less than 1
    (eg half atom to be replaced).

Thanks

Well, you won’t want the default to be 0, since there’s no element zero.

A few other notes:

  • You’ll want to set min / max values, e.g.
      "minimum": 1,
      "maximum": 118,
  • You’ll want to do the replacement on a random basis. If I have a mixture of two elements, it won’t have the first 50 atoms of one element and then the rest. It’s a 50/50 random mixture.

  • Personally, I’d probably list elements by name or symbol. I don’t always remember that Cobalt is element 27.

Okay, Thanks for your feedback,
So can I set default to hydrogen?

    userOptions['Find'] = {}
    userOptions['Find']['label'] = 'Find Element'
    userOptions['Find']['type'] = 'stringList'
    userOptions['Find']['values'] = list(element_dict.keys)
    userOptions['Find']['default'] = "Hydrogen"

Just to be more clear ,
Suppose there is a molecule XYXAXBX and the user wants to replace 25 percent of X atoms present in the molecule with atom Z
(so according to my code the output would be ZYZAXBX ) what should be the correct output …like it must be random ie ZYXAZBX or XYZAZBX everytime changing?

Hard to know the best default, but I’d probably set it to Carbon or Iron.

Yes, the output should be random. If I make an alloy of bronze, it’s copper with 12% tin. (Sometimes there are other elements). But it’s a random mixture.

Can you please have a look if its apt


def getOptions():
    userOptions = {}

    userOptions['Find'] = {}
    userOptions['Find']['label'] = 'Find Element'
    userOptions['Find']['type'] = 'stringList'
    userOptions['Find']['values'] = list(element_dict.keys)
    userOptions['Find']['default'] = "Carbon"

    userOptions['Percent'] = {}
    userOptions['Percent']['label'] = 'Percentage to be replaced'
    userOptions['Percent']['type'] = 'float'
    userOptions['Percent']['default'] = 100

    userOptions['Replace'] = {}
    userOptions['Replace']['label'] = 'Replace with'
    userOptions['Replace']['type'] = 'stringList'
    userOptions['Replace']['values'] = list(element_dict.keys)
    userOptions['Replace']['default'] = "Carbon"

    opts = {'userOptions': userOptions}

    return opts


def replace_element(opts, mol):
    original= element_dict.get(opts['Find'])
    percent = float(opts['Percent'])
    replace= element_dict.get(opts['Replace'])


    atomic_numbers = mol['atoms']['elements']['number']
    occurences = atomic_numbers.count(original)
    number_of_atoms = int((percent*occurences)/100)
    indices = []

    for i, num in enumerate(atomic_numbers):
        if num == original:
            indices.append(i)

    selected_indices = random.sample(indices, number_of_atoms)

    for index in selected_indices:
        atomic_numbers[index] = replace

    return mol

Output for 2 different instances for input [1, 1, 8, 1, 9, 1] 25% of hydrogen atoms to be replaced randomly with carbon

{"cjson": {"chemicalJson": 1, "atoms": {"coords": {"3d": [0.617532, -0.027246, 0.481009, 0.156663, 0.031752, -0.362419, -0.774185, -0.004516, -0.11859]}, "elements": {"number": [1, 6, 8, 1, 9, 6]}}, "bonds": {"connections": {"index": [1, 2, 1, 0]}, "order": [1, 1]}}}
{"cjson": {"chemicalJson": 1, "atoms": {"coords": {"3d": [0.617532, -0.027246, 0.481009, 0.156663, 0.031752, -0.362419, -0.774185, -0.004516, -0.11859]}, "elements": {"number": [1, 1, 8, 6, 9, 6]}}, "bonds": {"connections": {"index": [1, 2, 1, 0]}, "order": [1, 1]}}}

Also , shouldn’t this line be elif args['run_command']:

Yes. Those scripts are very old - “run_command” used to be “run_workflow” but no one liked “workflow.”

Are these files okay


Apart from these are there any other files required?
Also tell me to which repo of avogadro should I send a pr to?

If you like, you can send a PR to the avogadro-commands repo. Or you can create your own.

But I think you probably want to add yourself as an author?

Hello,
Will I have to make changes in the README.md file and plugin.json file of the avogadro-commands repo copy the replace.py file and then send a PR?
And if I create my own how can I send or link it to avogadro’s official repo

It would be a privilege to do so. But since you are the project maintainer So, I thought that you would be the author and also without your help I could not have proceeded with the plugin :pray:.

Or can I create a separate folder for the alloy command in the Avogadro commands repo which would contain separate README.md and plugin. json files?

Yes, you’d add your script to the main directory, then edit the plugin.json file.

{
  "author": ["Geoffrey Hutchison", "Dhruv J"],
  "version": 1.0,
  "url": "https://github.com/openchemistry/avogadro-commands",
  "name": "avogadro-commands",
  "description": "Example command scripts",
  "type": "commands",
  "commands": [
    { "name": "Centroid", "command": "centroid.py" },
    { "name": "Flatten", "command": "flatten.py" },
    { "name": "Scale", "command": "scale.py" },
    { "name": "Alloy", "command": "replace.py" },
  ]
}

Or something similar…

1 Like

Okay sir, I have created a pr. Kindly have a look at it, let me know if any other changes need to be made. Merge the pr and close the issue if everything is proper.

Thanks so much for the contribution - it’s much appreciated :tada:

I’ll take a look this evening (Pittsburgh time).

Thank you very much sir :pray:, It is just a small contribution from my side …many more contributions to come…

So, can I change it to run command along with the minor required changes in the pr?

Yes, Made the changes required :+1:
Kindly go through the same
Thanks