<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://turbogap.fi/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Miguel+Caro</id>
	<title>TurboGAP - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://turbogap.fi/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Miguel+Caro"/>
	<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php/Special:Contributions/Miguel_Caro"/>
	<updated>2026-06-13T15:50:14Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.34.2</generator>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=689</id>
		<title>TurboGAP</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=689"/>
		<updated>2026-04-03T04:53:03Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;float:right; margin-left: 10px; width: 45%; margin-top: 0px;&amp;quot;&lt;br /&gt;
| style=&amp;quot;text-align: center;&amp;quot;|&lt;br /&gt;
[[File:turbogap_logo.png|320px]]&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
'''What's new?'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;div style = &amp;quot;height: 320px; overflow-y: scroll;&amp;quot; &amp;gt;&lt;br /&gt;
'''02/04/2026''': On 23-24 March 2026, the '''TurboGAP''' team organized the [https://ocamm.fi/event/turbogap-school-2026 first TurboGAP School] at Aalto University.&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
[[File:group_pic_turbogap_school.jpg|240px]]&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
-----&lt;br /&gt;
'''11/06/2025''': There are new [[potentials]] available for P, CH, CO and AuPt:H.&lt;br /&gt;
-----&lt;br /&gt;
'''10/06/2023''': There are new [[potentials]] available for Fe and Pt.&lt;br /&gt;
-----&lt;br /&gt;
'''01/04/2022''': There is a new [[tutorials|tutorial]] available: [[Simulating icosahedral gold clusters]].&lt;br /&gt;
-----&lt;br /&gt;
'''28/02/2022''': We have added a [[potential]] for elemental gold. Potentials for C, Si and Au are now publicly available for use with TurboGAP, and many more are under development.&lt;br /&gt;
-----&lt;br /&gt;
'''28/11/2021''': We have added a [[tutorials|tutorial]] on how to use barostating and van der Waals corrections in '''TurboGAP'''.&lt;br /&gt;
-----&lt;br /&gt;
'''10/05/2021''': The '''TurboGAP''' repo has been made public. Expansion of this website will now take place rapidly.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Welcome to the official '''TurboGAP''' website!&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is a Fortran code that offers different levels of functionality for machine-learning driven atomistic simulation. The main functionality is provided via the [http://github.com/libAtoms/soap_turbo soap_turbo] library for efficient construction of accurate SOAP-type many-body atomic descriptors. These can also be used in combination with the [http://libatoms.github.io QUIP/GAP code] or with '''TurboGAP''''s native interface. Thanks to QUIP/GAP, '''TurboGAP''''s &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; descriptors can be used to run MD simulations with LAMMPS. Together with the libraries and the native interface, we also provide a list of interatomic [[potentials]] to be used with the code.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is developed in [https://miguelcaro.org Miguel Caro's group] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is free to use for non-commercial purposes, provided that you adhere to its [https://github.com/mcaroba/turbogap/blob/master/LICENSE.md license]. If you want to use '''TurboGAP''' for commercial purposes, or purposes not covered by the '''TurboGAP''' non-commercial license, please contact the [[TurboGAP team]].&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width: 80%&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
| [[File:Aalto_logo.png|200px]]&lt;br /&gt;
| [[File:AKA_logo.jpg|200px]]&lt;br /&gt;
| [[File:CSC_logo.png|150px]]&lt;br /&gt;
| [[File:eu_funded_en.jpg|250px]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=688</id>
		<title>TurboGAP</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=688"/>
		<updated>2026-04-02T13:01:14Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;float:right; margin-left: 10px; width: 45%; margin-top: 0px;&amp;quot;&lt;br /&gt;
| style=&amp;quot;text-align: center;&amp;quot;|&lt;br /&gt;
[[File:turbogap_logo.png|320px]]&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
'''What's new?'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;div style = &amp;quot;height: 200px; overflow-y: scroll;&amp;quot; &amp;gt;&lt;br /&gt;
'''02/04/2026''': On 23-24 March 2026, the '''TurboGAP''' team organized the [https://ocamm.fi/event/turbogap-school-2026 first TurboGAP School] at Aalto University.&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
[[File:group_pic_turbogap_school.jpg|240px]]&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
-----&lt;br /&gt;
'''11/06/2025''': There are new [[potentials]] available for P, CH, CO and AuPt:H.&lt;br /&gt;
-----&lt;br /&gt;
'''10/06/2023''': There are new [[potentials]] available for Fe and Pt.&lt;br /&gt;
-----&lt;br /&gt;
'''01/04/2022''': There is a new [[tutorials|tutorial]] available: [[Simulating icosahedral gold clusters]].&lt;br /&gt;
-----&lt;br /&gt;
'''28/02/2022''': We have added a [[potential]] for elemental gold. Potentials for C, Si and Au are now publicly available for use with TurboGAP, and many more are under development.&lt;br /&gt;
-----&lt;br /&gt;
'''28/11/2021''': We have added a [[tutorials|tutorial]] on how to use barostating and van der Waals corrections in '''TurboGAP'''.&lt;br /&gt;
-----&lt;br /&gt;
'''10/05/2021''': The '''TurboGAP''' repo has been made public. Expansion of this website will now take place rapidly.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Welcome to the official '''TurboGAP''' website!&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is a Fortran code that offers different levels of functionality for machine-learning driven atomistic simulation. The main functionality is provided via the [http://github.com/libAtoms/soap_turbo soap_turbo] library for efficient construction of accurate SOAP-type many-body atomic descriptors. These can also be used in combination with the [http://libatoms.github.io QUIP/GAP code] or with '''TurboGAP''''s native interface. Thanks to QUIP/GAP, '''TurboGAP''''s &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; descriptors can be used to run MD simulations with LAMMPS. Together with the libraries and the native interface, we also provide a list of interatomic [[potentials]] to be used with the code.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is developed in [https://miguelcaro.org Miguel Caro's group] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is free to use for non-commercial purposes, provided that you adhere to its [https://github.com/mcaroba/turbogap/blob/master/LICENSE.md license]. If you want to use '''TurboGAP''' for commercial purposes, or purposes not covered by the '''TurboGAP''' non-commercial license, please contact the [[TurboGAP team]].&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width: 80%&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
| [[File:Aalto_logo.png|200px]]&lt;br /&gt;
| [[File:AKA_logo.jpg|200px]]&lt;br /&gt;
| [[File:CSC_logo.png|150px]]&lt;br /&gt;
| [[File:eu_funded_en.jpg|250px]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=687</id>
		<title>TurboGAP</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=687"/>
		<updated>2026-04-02T13:00:46Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;float:right; margin-left: 10px; width: 45%; margin-top: 0px;&amp;quot;&lt;br /&gt;
| style=&amp;quot;text-align: center;&amp;quot;|&lt;br /&gt;
[[File:turbogap_logo.png|320px]]&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
'''What's new?'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;div style = &amp;quot;height: 200px; overflow-y: scroll;&amp;quot; &amp;gt;&lt;br /&gt;
'''02/04/2026''': On 23-24 March 2026, the '''TurboGAP''' team organized the [https://ocamm.fi/event/turbogap-school-2026 first TurboGAP School] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
[[File:group_pic_turbogap_school.jpg|240px]]&lt;br /&gt;
-----&lt;br /&gt;
'''11/06/2025''': There are new [[potentials]] available for P, CH, CO and AuPt:H.&lt;br /&gt;
-----&lt;br /&gt;
'''10/06/2023''': There are new [[potentials]] available for Fe and Pt.&lt;br /&gt;
-----&lt;br /&gt;
'''01/04/2022''': There is a new [[tutorials|tutorial]] available: [[Simulating icosahedral gold clusters]].&lt;br /&gt;
-----&lt;br /&gt;
'''28/02/2022''': We have added a [[potential]] for elemental gold. Potentials for C, Si and Au are now publicly available for use with TurboGAP, and many more are under development.&lt;br /&gt;
-----&lt;br /&gt;
'''28/11/2021''': We have added a [[tutorials|tutorial]] on how to use barostating and van der Waals corrections in '''TurboGAP'''.&lt;br /&gt;
-----&lt;br /&gt;
'''10/05/2021''': The '''TurboGAP''' repo has been made public. Expansion of this website will now take place rapidly.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Welcome to the official '''TurboGAP''' website!&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is a Fortran code that offers different levels of functionality for machine-learning driven atomistic simulation. The main functionality is provided via the [http://github.com/libAtoms/soap_turbo soap_turbo] library for efficient construction of accurate SOAP-type many-body atomic descriptors. These can also be used in combination with the [http://libatoms.github.io QUIP/GAP code] or with '''TurboGAP''''s native interface. Thanks to QUIP/GAP, '''TurboGAP''''s &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; descriptors can be used to run MD simulations with LAMMPS. Together with the libraries and the native interface, we also provide a list of interatomic [[potentials]] to be used with the code.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is developed in [https://miguelcaro.org Miguel Caro's group] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is free to use for non-commercial purposes, provided that you adhere to its [https://github.com/mcaroba/turbogap/blob/master/LICENSE.md license]. If you want to use '''TurboGAP''' for commercial purposes, or purposes not covered by the '''TurboGAP''' non-commercial license, please contact the [[TurboGAP team]].&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width: 80%&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
| [[File:Aalto_logo.png|200px]]&lt;br /&gt;
| [[File:AKA_logo.jpg|200px]]&lt;br /&gt;
| [[File:CSC_logo.png|150px]]&lt;br /&gt;
| [[File:eu_funded_en.jpg|250px]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:Group_pic_turbogap_school.jpg&amp;diff=686</id>
		<title>File:Group pic turbogap school.jpg</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:Group_pic_turbogap_school.jpg&amp;diff=686"/>
		<updated>2026-04-02T12:59:56Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=685</id>
		<title>TurboGAP</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=685"/>
		<updated>2026-04-02T12:59:37Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;float:right; margin-left: 10px; width: 45%; margin-top: 0px;&amp;quot;&lt;br /&gt;
| style=&amp;quot;text-align: center;&amp;quot;|&lt;br /&gt;
[[File:turbogap_logo.png|320px]]&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
'''What's new?'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;div style = &amp;quot;height: 200px; overflow-y: scroll;&amp;quot; &amp;gt;&lt;br /&gt;
'''02/04/2026''': The '''TurboGAP''' team organized the [https://ocamm.fi/event/turbogap-school-2026 first TurboGAP School] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
[[File:group_pic_turbogap_school.jpg|240px]]&lt;br /&gt;
-----&lt;br /&gt;
'''11/06/2025''': There are new [[potentials]] available for P, CH, CO and AuPt:H.&lt;br /&gt;
-----&lt;br /&gt;
'''10/06/2023''': There are new [[potentials]] available for Fe and Pt.&lt;br /&gt;
-----&lt;br /&gt;
'''01/04/2022''': There is a new [[tutorials|tutorial]] available: [[Simulating icosahedral gold clusters]].&lt;br /&gt;
-----&lt;br /&gt;
'''28/02/2022''': We have added a [[potential]] for elemental gold. Potentials for C, Si and Au are now publicly available for use with TurboGAP, and many more are under development.&lt;br /&gt;
-----&lt;br /&gt;
'''28/11/2021''': We have added a [[tutorials|tutorial]] on how to use barostating and van der Waals corrections in '''TurboGAP'''.&lt;br /&gt;
-----&lt;br /&gt;
'''10/05/2021''': The '''TurboGAP''' repo has been made public. Expansion of this website will now take place rapidly.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Welcome to the official '''TurboGAP''' website!&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is a Fortran code that offers different levels of functionality for machine-learning driven atomistic simulation. The main functionality is provided via the [http://github.com/libAtoms/soap_turbo soap_turbo] library for efficient construction of accurate SOAP-type many-body atomic descriptors. These can also be used in combination with the [http://libatoms.github.io QUIP/GAP code] or with '''TurboGAP''''s native interface. Thanks to QUIP/GAP, '''TurboGAP''''s &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; descriptors can be used to run MD simulations with LAMMPS. Together with the libraries and the native interface, we also provide a list of interatomic [[potentials]] to be used with the code.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is developed in [https://miguelcaro.org Miguel Caro's group] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is free to use for non-commercial purposes, provided that you adhere to its [https://github.com/mcaroba/turbogap/blob/master/LICENSE.md license]. If you want to use '''TurboGAP''' for commercial purposes, or purposes not covered by the '''TurboGAP''' non-commercial license, please contact the [[TurboGAP team]].&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width: 80%&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
| [[File:Aalto_logo.png|200px]]&lt;br /&gt;
| [[File:AKA_logo.jpg|200px]]&lt;br /&gt;
| [[File:CSC_logo.png|150px]]&lt;br /&gt;
| [[File:eu_funded_en.jpg|250px]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=670</id>
		<title>TurboGAP</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=670"/>
		<updated>2025-10-29T07:37:51Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;float:right; margin-left: 10px; width: 45%; margin-top: 0px;&amp;quot;&lt;br /&gt;
| style=&amp;quot;text-align: center;&amp;quot;|&lt;br /&gt;
[[File:turbogap_logo.png|320px]]&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
'''What's new?'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;div style = &amp;quot;height: 200px; overflow-y: scroll;&amp;quot; &amp;gt;&lt;br /&gt;
'''11/06/2025''': There are new [[potentials]] available for P, CH, CO and AuPt:H.&lt;br /&gt;
-----&lt;br /&gt;
'''10/06/2023''': There are new [[potentials]] available for Fe and Pt.&lt;br /&gt;
-----&lt;br /&gt;
'''01/04/2022''': There is a new [[tutorials|tutorial]] available: [[Simulating icosahedral gold clusters]].&lt;br /&gt;
-----&lt;br /&gt;
'''28/02/2022''': We have added a [[potential]] for elemental gold. Potentials for C, Si and Au are now publicly available for use with TurboGAP, and many more are under development.&lt;br /&gt;
-----&lt;br /&gt;
'''28/11/2021''': We have added a [[tutorials|tutorial]] on how to use barostating and van der Waals corrections in '''TurboGAP'''.&lt;br /&gt;
-----&lt;br /&gt;
'''10/05/2021''': The '''TurboGAP''' repo has been made public. Expansion of this website will now take place rapidly.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Welcome to the official '''TurboGAP''' website!&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is a Fortran code that offers different levels of functionality for machine-learning driven atomistic simulation. The main functionality is provided via the [http://github.com/libAtoms/soap_turbo soap_turbo] library for efficient construction of accurate SOAP-type many-body atomic descriptors. These can also be used in combination with the [http://libatoms.github.io QUIP/GAP code] or with '''TurboGAP''''s native interface. Thanks to QUIP/GAP, '''TurboGAP''''s &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; descriptors can be used to run MD simulations with LAMMPS. Together with the libraries and the native interface, we also provide a list of interatomic [[potentials]] to be used with the code.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is developed in [https://miguelcaro.org Miguel Caro's group] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is free to use for non-commercial purposes, provided that you adhere to its [https://github.com/mcaroba/turbogap/blob/master/LICENSE.md license]. If you want to use '''TurboGAP''' for commercial purposes, or purposes not covered by the '''TurboGAP''' non-commercial license, please contact the [[TurboGAP team]].&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width: 80%&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
| [[File:Aalto_logo.png|200px]]&lt;br /&gt;
| [[File:AKA_logo.jpg|200px]]&lt;br /&gt;
| [[File:CSC_logo.png|150px]]&lt;br /&gt;
| [[File:eu_funded_en.jpg|250px]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=669</id>
		<title>TurboGAP</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=669"/>
		<updated>2025-10-29T07:37:41Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;float:right; margin-left: 10px; width: 45%; margin-top: 0px;&amp;quot;&lt;br /&gt;
| style=&amp;quot;text-align: center;&amp;quot;|&lt;br /&gt;
[[File:turbogap_logo.png|320px]]&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
'''What's new?'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;div style = &amp;quot;height: 200px; overflow-y: scroll;&amp;quot; &amp;gt;&lt;br /&gt;
'''11/06/2025''': There are new [[potentials]] available for P, CH, CO and AuPt:H.&lt;br /&gt;
-----&lt;br /&gt;
'''10/06/2023''': There are new [[potentials]] available for Fe and Pt.&lt;br /&gt;
-----&lt;br /&gt;
'''01/04/2022''': There is a new [[tutorials|tutorial]] available: [[Simulating icosahedral gold clusters]].&lt;br /&gt;
-----&lt;br /&gt;
'''28/02/2022''': We have added a [[potential]] for elemental gold. Potentials for C, Si and Au are now publicly available for use with TurboGAP, and many more are under development.&lt;br /&gt;
-----&lt;br /&gt;
'''28/11/2021''': We have added a [[tutorials|tutorial]] on how to use barostating and van der Waals corrections in '''TurboGAP'''.&lt;br /&gt;
-----&lt;br /&gt;
'''10/05/2021''': The '''TurboGAP''' repo has been made public. Expansion of this website will now take place rapidly.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Welcome to the official '''TurboGAP''' website!&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is a Fortran code that offers different levels of functionality for machine-learning driven atomistic simulation. The main functionality is provided via the [http://github.com/libAtoms/soap_turbo soap_turbo] library for efficient construction of accurate SOAP-type many-body atomic descriptors. These can also be used in combination with the [http://libatoms.github.io QUIP/GAP code] or with '''TurboGAP''''s native interface. Thanks to QUIP/GAP, '''TurboGAP''''s &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; descriptors can be used to run MD simulations with LAMMPS. Together with the libraries and the native interface, we also provide a list of interatomic [[potentials]] to be used with the code.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is developed in [https://miguelcaro.org Miguel Caro's group] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is free to use for non-commercial purposes, provided that you adhere to its [https://github.com/mcaroba/turbogap/blob/master/LICENSE.md license]. If you want to use '''TurboGAP''' for commercial purposes, or purposes not covered by the '''TurboGAP''' non-commercial license, please contact the [[TurboGAP team]].&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width: 80%&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
| [[File:Aalto_logo.png|200px]]&lt;br /&gt;
| [[File:AKA_logo.jpg|200px]]&lt;br /&gt;
| [[File:CSC_logo.png|1500px]]&lt;br /&gt;
| [[File:eu_funded_en.jpg|250px]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:Eu_funded_en.jpg&amp;diff=668</id>
		<title>File:Eu funded en.jpg</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:Eu_funded_en.jpg&amp;diff=668"/>
		<updated>2025-10-29T07:36:09Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=667</id>
		<title>TurboGAP</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=TurboGAP&amp;diff=667"/>
		<updated>2025-06-11T12:48:05Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;float:right; margin-left: 10px; width: 45%; margin-top: 0px;&amp;quot;&lt;br /&gt;
| style=&amp;quot;text-align: center;&amp;quot;|&lt;br /&gt;
[[File:turbogap_logo.png|320px]]&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
'''What's new?'''&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;div style = &amp;quot;height: 200px; overflow-y: scroll;&amp;quot; &amp;gt;&lt;br /&gt;
'''11/06/2025''': There are new [[potentials]] available for P, CH, CO and AuPt:H.&lt;br /&gt;
-----&lt;br /&gt;
'''10/06/2023''': There are new [[potentials]] available for Fe and Pt.&lt;br /&gt;
-----&lt;br /&gt;
'''01/04/2022''': There is a new [[tutorials|tutorial]] available: [[Simulating icosahedral gold clusters]].&lt;br /&gt;
-----&lt;br /&gt;
'''28/02/2022''': We have added a [[potential]] for elemental gold. Potentials for C, Si and Au are now publicly available for use with TurboGAP, and many more are under development.&lt;br /&gt;
-----&lt;br /&gt;
'''28/11/2021''': We have added a [[tutorials|tutorial]] on how to use barostating and van der Waals corrections in '''TurboGAP'''.&lt;br /&gt;
-----&lt;br /&gt;
'''10/05/2021''': The '''TurboGAP''' repo has been made public. Expansion of this website will now take place rapidly.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Welcome to the official '''TurboGAP''' website!&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is a Fortran code that offers different levels of functionality for machine-learning driven atomistic simulation. The main functionality is provided via the [http://github.com/libAtoms/soap_turbo soap_turbo] library for efficient construction of accurate SOAP-type many-body atomic descriptors. These can also be used in combination with the [http://libatoms.github.io QUIP/GAP code] or with '''TurboGAP''''s native interface. Thanks to QUIP/GAP, '''TurboGAP''''s &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; descriptors can be used to run MD simulations with LAMMPS. Together with the libraries and the native interface, we also provide a list of interatomic [[potentials]] to be used with the code.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is developed in [https://miguelcaro.org Miguel Caro's group] at Aalto University.&lt;br /&gt;
&lt;br /&gt;
'''TurboGAP''' is free to use for non-commercial purposes, provided that you adhere to its [https://github.com/mcaroba/turbogap/blob/master/LICENSE.md license]. If you want to use '''TurboGAP''' for commercial purposes, or purposes not covered by the '''TurboGAP''' non-commercial license, please contact the [[TurboGAP team]].&lt;br /&gt;
&lt;br /&gt;
{| style=&amp;quot;width: 80%&amp;quot; align=&amp;quot;center&amp;quot;&lt;br /&gt;
| [[File:Aalto_logo.png|250px]]&lt;br /&gt;
| [[File:AKA_logo.jpg|200px]]&lt;br /&gt;
| [[File:CSC_logo.png|200px]]&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Core_pot_buffer&amp;diff=666</id>
		<title>Core pot buffer</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Core_pot_buffer&amp;diff=666"/>
		<updated>2025-04-20T07:21:59Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: Created page with &amp;quot;&amp;lt;code&amp;gt;core_pot_buffer&amp;lt;/code&amp;gt; defines the buffer zone of the cosine cutoff function, in Angstrom, for use with &amp;lt;code&amp;gt;core_pot_cutoff&amp;lt;/code&amp;gt;. The value of &amp;lt;code&amp;gt;core_p...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;code&amp;gt;[[core_pot_buffer]]&amp;lt;/code&amp;gt; defines the buffer zone of the cosine cutoff function, in Angstrom, for use with &amp;lt;code&amp;gt;[[core_pot_cutoff]]&amp;lt;/code&amp;gt;. The value of &amp;lt;code&amp;gt;[[core_pot_buffer]]&amp;lt;/code&amp;gt; is only used if the value of &amp;lt;code&amp;gt;[[core_pot_cutoff]]&amp;lt;/code&amp;gt; is smaller than the default cutoff for the tabulated [[core potential]].&lt;br /&gt;
&lt;br /&gt;
=== Summary ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Summary for &amp;lt;code&amp;gt;core_pot_buffer&amp;lt;/code&amp;gt; keyword&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Required/optional&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Type&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Accepted values&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Default&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| See also&lt;br /&gt;
|-&lt;br /&gt;
| Optional&lt;br /&gt;
| Real&lt;br /&gt;
| Any positive real&lt;br /&gt;
| 1. (only used if [[core_pot_cutoff]] is smaller than the tabulated [[core potential]] cutoff)&lt;br /&gt;
| &amp;lt;code&amp;gt;[[core potential]]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[[core_pot_cutoff]]&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
 [[core_pot_cutoff]] = 10.&lt;br /&gt;
 [[core_pot_buffer]] = 1.&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Core_pot_cutoff&amp;diff=664</id>
		<title>Core pot cutoff</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Core_pot_cutoff&amp;diff=664"/>
		<updated>2025-04-20T07:20:32Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;code&amp;gt;[[core_pot_cutoff]]&amp;lt;/code&amp;gt; defines an outer cutoff, in Angstrom, for the tabulated [[core potential]]. This is useful when the tabulated [[core potential]] has a long tail (20 or 25 Angstrom) and the user wants to reduce this value to avoid computing large neighbor lists, which may slow down the calculations. &amp;lt;code&amp;gt;core_pot_cutoff&amp;lt;/code&amp;gt; multiplies the tabulated potential(s) by a cosine cutoff function that goes to zero at &amp;lt;code&amp;gt;core_pot_cutoff&amp;lt;/code&amp;gt;, switching from 1 to 0 over a distance defined by &amp;lt;code&amp;gt;[[core_pot_buffer]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Summary ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Summary for &amp;lt;code&amp;gt;core_pot_cutoff&amp;lt;/code&amp;gt; keyword&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Required/optional&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Type&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Accepted values&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| Default&lt;br /&gt;
! style=&amp;quot;text-align:left;&amp;quot;| See also&lt;br /&gt;
|-&lt;br /&gt;
| Optional&lt;br /&gt;
| Real&lt;br /&gt;
| Any positive real&lt;br /&gt;
| None (the default [[core potential]] cutoff is used if [[core_pot_cutoff]] is not defined)&lt;br /&gt;
| &amp;lt;code&amp;gt;[[core potential]]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;[[core_pot_buffer]]&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
 [[core_pot_cutoff]] = 10.&lt;br /&gt;
 [[core_pot_buffer]] = 1.&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=630</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=630"/>
		<updated>2024-05-15T07:17:34Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': to be able to generate the nanoparticles with '''TurboGAP''', you'll need to download the gap_files from [https://zenodo.org/records/11184039 Zenodo]. If you want to skip this step (e.g., to speed up the process of going through the tutorial), you can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found in Ref.&amp;lt;ref name=&amp;quot;kloppenburg_2023&amp;quot; /&amp;gt;), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities). The initial database &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; has been updated with information related to the clustering and embedding. You can download a sample updated file &amp;lt;code&amp;gt;db1.xyz&amp;lt;/code&amp;gt; from [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db1.xyz here]. In the &amp;quot;info&amp;quot; line, the tags &amp;quot;clmds_coordinates&amp;quot;, &amp;quot;clmds_cluster&amp;quot; and &amp;quot;clmds_medoid&amp;quot; will appear, respectively indicating the 2D MDS embedding coordinates, cluster it belongs to and whether it's a medoid or not of the corresponding NP.&lt;br /&gt;
&lt;br /&gt;
Now, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the cartoons for the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities at a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are plotting the similarities on the XY coordinates and choose three different color codings to display further information: to which data cluster the NP corresponds to, what is the composition of each NP, and what is the energy (per atom) of each NP. Unsurprisingly, there is a very strong correlation between these three quantities, because for these NP the most differentiating property is their chemical composition (i.e., their Pt fraction).&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
After using the global SOAP kernel &amp;lt;math&amp;gt;K(I,J)&amp;lt;/math&amp;gt; to compare and analyze the structure of whole NPs, we are going to use the regular (atom- or site-wise) SOAP kernel &amp;lt;math&amp;gt;k(i,j)&amp;lt;/math&amp;gt; to gain insight into the site distribution in our database. We are further going to use some of the &amp;lt;code&amp;gt;ase_tools&amp;lt;/code&amp;gt; functionality to identify the surface atoms. This might be relevant, e.g., for applications in catalysis. We now use the sparsification features of &amp;lt;code&amp;gt;cl-MDS&amp;lt;/code&amp;gt; because the number of atomic sites is too large compared to the number of NPs, and this would significantly slow down the clustering+embedding procedure if we were using the entire set of data points. The following script takes care of all the different steps:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have again updated the database, this time with per-atom information. The new &amp;lt;code&amp;gt;db2.xyz&amp;lt;/code&amp;gt; file (an example of which you can download [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db2.xyz here]) now contains extra columns with the cluster and embedding information for all atoms, as well as a column indicating whether the atom is a surface atom or not. Let us now create a new directory, &amp;lt;code&amp;gt;plot_sites&amp;lt;/code&amp;gt;, to run the plotting script within, again aiming to keep our top directory as clean as possible.&lt;br /&gt;
&lt;br /&gt;
Note how our Ovito code is now significantly more complex, since we are doing two things: 1) we are aligning the line of view with the center of mass of the NP and the position of the medoids; 2) when the medoid is not a surface atom, we are adding transparency to all the atoms between the medoid and the viewer. Besides, we are doing the less sophisticated operation of mixing the color of the Pt/Au atom (gray/yellow, respectively) with red, to highlight the medoid.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
cluster = []&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
            cluster.append(atoms2.arrays[&amp;quot;clmds_cluster&amp;quot;][i])&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (cluster[k]+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is what the Ovito rendering of the medoid sites looks like:&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Finally, let's plot the MDS maps and color code according to useful information:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have now chosen to color code that data points (of which we have many more than before!) by cluster, species+bulk/surface character, and local GAP energy. We can see that the clustering and embedding algorithms separate preferentially according to species, but also by surface/bulk character. It is also interesting to see how the GAP local energy clearly establishes bulk Pt atoms to be more stable than surface Pt atoms. However, take this with a grain of salt: due to the very small size of these NPs, there is no proper bulk. Nevertheless, the stabilizing effect of the increased atomic coordination is quite evident going from the interior sites to the surface sites.&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=397</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=397"/>
		<updated>2023-08-15T12:08:19Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found in Ref.&amp;lt;ref name=&amp;quot;kloppenburg_2023&amp;quot; /&amp;gt;), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities). The initial database &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; has been updated with information related to the clustering and embedding. You can download a sample updated file &amp;lt;code&amp;gt;db1.xyz&amp;lt;/code&amp;gt; from [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db1.xyz here]. In the &amp;quot;info&amp;quot; line, the tags &amp;quot;clmds_coordinates&amp;quot;, &amp;quot;clmds_cluster&amp;quot; and &amp;quot;clmds_medoid&amp;quot; will appear, respectively indicating the 2D MDS embedding coordinates, cluster it belongs to and whether it's a medoid or not of the corresponding NP.&lt;br /&gt;
&lt;br /&gt;
Now, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the cartoons for the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities at a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are plotting the similarities on the XY coordinates and choose three different color codings to display further information: to which data cluster the NP corresponds to, what is the composition of each NP, and what is the energy (per atom) of each NP. Unsurprisingly, there is a very strong correlation between these three quantities, because for these NP the most differentiating property is their chemical composition (i.e., their Pt fraction).&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
After using the global SOAP kernel &amp;lt;math&amp;gt;K(I,J)&amp;lt;/math&amp;gt; to compare and analyze the structure of whole NPs, we are going to use the regular (atom- or site-wise) SOAP kernel &amp;lt;math&amp;gt;k(i,j)&amp;lt;/math&amp;gt; to gain insight into the site distribution in our database. We are further going to use some of the &amp;lt;code&amp;gt;ase_tools&amp;lt;/code&amp;gt; functionality to identify the surface atoms. This might be relevant, e.g., for applications in catalysis. We now use the sparsification features of &amp;lt;code&amp;gt;cl-MDS&amp;lt;/code&amp;gt; because the number of atomic sites is too large compared to the number of NPs, and this would significantly slow down the clustering+embedding procedure if we were using the entire set of data points. The following script takes care of all the different steps:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have again updated the database, this time with per-atom information. The new &amp;lt;code&amp;gt;db2.xyz&amp;lt;/code&amp;gt; file (an example of which you can download [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db2.xyz here]) now contains extra columns with the cluster and embedding information for all atoms, as well as a column indicating whether the atom is a surface atom or not. Let us now create a new directory, &amp;lt;code&amp;gt;plot_sites&amp;lt;/code&amp;gt;, to run the plotting script within, again aiming to keep our top directory as clean as possible.&lt;br /&gt;
&lt;br /&gt;
Note how our Ovito code is now significantly more complex, since we are doing two things: 1) we are aligning the line of view with the center of mass of the NP and the position of the medoids; 2) when the medoid is not a surface atom, we are adding transparency to all the atoms between the medoid and the viewer. Besides, we are doing the less sophisticated operation of mixing the color of the Pt/Au atom (gray/yellow, respectively) with red, to highlight the medoid.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
cluster = []&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
            cluster.append(atoms2.arrays[&amp;quot;clmds_cluster&amp;quot;][i])&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (cluster[k]+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is what the Ovito rendering of the medoid sites looks like:&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Finally, let's plot the MDS maps and color code according to useful information:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have now chosen to color code that data points (of which we have many more than before!) by cluster, species+bulk/surface character, and local GAP energy. We can see that the clustering and embedding algorithms separate preferentially according to species, but also by surface/bulk character. It is also interesting to see how the GAP local energy clearly establishes bulk Pt atoms to be more stable than surface Pt atoms. However, take this with a grain of salt: due to the very small size of these NPs, there is no proper bulk. Nevertheless, the stabilizing effect of the increased atomic coordination is quite evident going from the interior sites to the surface sites.&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=396</id>
		<title>Template:Reference list</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=396"/>
		<updated>2023-08-15T12:08:05Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Reference list */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Reference list ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references&amp;gt;&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2010&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, M.C. Payne, R. Kondor, and G. Csányi'''. ''Gaussian approximation potentials: The accuracy of quantum mechanics, without the electrons''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.104.136403 Phys. Rev. Lett. '''104''', 136403 (2010)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli and M.A. Caro'''. ''GAP interatomic potential for C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://doi.org/10.5281/zenodo.4616343 Zenodo: 10.5281/zenodo.4616343 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021b&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli, X. Chen, A.P. Bartók, P. Hernández-León, G. Csányi, T. Ala-Nissila, and M.A. Caro'''. ''Machine learning force fields based on local parametrization of dispersion interactions: Application to the phase diagram of C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.104.054106 Phys. Rev. B '''104''', 054106 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2013&amp;quot;&amp;gt;&lt;br /&gt;
'''AP Bartók, R Kondor, G Csányi'''. ''On representing chemical environments''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.87.184115 Phys. Rev. B '''87''', 184115 (2013)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2015&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók and G. Csányi'''. ''Gaussian Approximation Potentials: a brief tutorial introduction''. [https://doi.org/10.1002/qua.24927 Int. J. Quantum Chem. '''115''', 1051 (2015)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;caro_2019&amp;quot;&amp;gt;&lt;br /&gt;
'''M.A. Caro'''. ''Optimizing many-body atomic descriptors for enhanced computational performance of machine learning based interatomic potentials''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.100.024112 Phys. Rev. B '''100''', 024112 (2019)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2009&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko and M. Scheffler'''. ''Accurate Molecular Van Der Waals Interactions from Ground-State Electron Density and Free-Atom Reference Data''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.102.073005 Phys. Rev. Lett. '''102''', 073005 (2009)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2012&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko, R.A. DiStasio Jr, R. Car, and M. Scheffler'''. ''Accurate and efficient method for many-body van der Waals interactions''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.108.236402 Phys. Rev. Lett. '''108''', 236402 (2012)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2018&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, J. Kermode, N. Bernstein, and G. Csányi'''. ''Machine learning a general-purpose interatomic potential for silicon''. [https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.041048 Phys. Rev. X '''8''', 041048 (2018)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, S. De, C. Poelking, N. Bernstein, J.R. Kermode, G. Csányi, and M. Ceriotti'''. ''Machine learning unifies the modeling of materials and molecules''. [https://www.science.org/doi/10.1126/sciadv.1701816 Sci. Adv. '''3''', e1701816 (2017)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;de_2016&amp;quot;&amp;gt;&lt;br /&gt;
'''S. De, A.P. Bartók, G. Csányi, and M. Ceriotti'''. ''Comparing molecules and solids across structural and alchemical space''. [https://pubs.rsc.org/en/content/articlelanding/2016/cp/c6cp00415f Phys. Chem. Chem. Phys. '''18''', 13754 (2016)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot;&amp;gt;&lt;br /&gt;
'''P. Hernández-León and M.A. Caro'''. ''Cluster-based multidimensional scaling embedding tool for data visualization''. [https://arxiv.org/abs/2209.06614 arXiv:2209.06614 (2023)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;kloppenburg_2023&amp;quot;&amp;gt;&lt;br /&gt;
'''J. Kloppenburg, L.B. Pártay, H. Jónsson, and M.A. Caro'''. ''A general-purpose machine learning Pt interatomic potential for an accurate description of bulk, surfaces, and nanoparticles''. [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 J. Chem. Phys. '''158''', 134704 (2023)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/references&amp;gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=395</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=395"/>
		<updated>2023-08-15T12:07:53Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found in Ref.&amp;lt;ref name=&amp;quot;kloppenburg_2023&amp;quot; /&amp;gt;), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities). The initial database &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; has been updated with information related to the clustering and embedding. You can download a sample updated file &amp;lt;code&amp;gt;db1.xyz&amp;lt;/code&amp;gt; from [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db1.xyz here]. In the &amp;quot;info&amp;quot; line, the tags &amp;quot;clmds_coordinates&amp;quot;, &amp;quot;clmds_cluster&amp;quot; and &amp;quot;clmds_medoid&amp;quot; will appear, respectively indicating the 2D MDS embedding coordinates, cluster it belongs to and whether it's a medoid or not of the corresponding NP.&lt;br /&gt;
&lt;br /&gt;
Now, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the cartoons for the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities at a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are plotting the similarities on the XY coordinates and choose three different color codings to display further information: to which data cluster the NP corresponds to, what is the composition of each NP, and what is the energy (per atom) of each NP. Unsurprisingly, there is a very strong correlation between these three quantities, because for these NP the most differentiating property is their chemical composition (i.e., their Pt fraction).&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
After using the global SOAP kernel &amp;lt;math&amp;gt;K(I,J)&amp;lt;/math&amp;gt; to compare and analyze the structure of whole NPs, we are going to use the regular (atom- or site-wise) SOAP kernel &amp;lt;math&amp;gt;k(i,j)&amp;lt;/math&amp;gt; to gain insight into the site distribution in our database. We are further going to use some of the &amp;lt;code&amp;gt;ase_tools&amp;lt;/code&amp;gt; functionality to identify the surface atoms. This might be relevant, e.g., for applications in catalysis. We now use the sparsification features of &amp;lt;code&amp;gt;cl-MDS&amp;lt;/code&amp;gt; because the number of atomic sites is too large compared to the number of NPs, and this would significantly slow down the clustering+embedding procedure if we were using the entire set of data points. The following script takes care of all the different steps:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have again updated the database, this time with per-atom information. The new &amp;lt;code&amp;gt;db2.xyz&amp;lt;/code&amp;gt; file (an example of which you can download [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db2.xyz here]) now contains extra columns with the cluster and embedding information for all atoms, as well as a column indicating whether the atom is a surface atom or not. Let us now create a new directory, &amp;lt;code&amp;gt;plot_sites&amp;lt;/code&amp;gt;, to run the plotting script within, again aiming to keep our top directory as clean as possible.&lt;br /&gt;
&lt;br /&gt;
Note how our Ovito code is now significantly more complex, since we are doing two things: 1) we are aligning the line of view with the center of mass of the NP and the position of the medoids; 2) when the medoid is not a surface atom, we are adding transparency to all the atoms between the medoid and the viewer. Besides, we are doing the less sophisticated operation of mixing the color of the Pt/Au atom (gray/yellow, respectively) with red, to highlight the medoid.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
cluster = []&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
            cluster.append(atoms2.arrays[&amp;quot;clmds_cluster&amp;quot;][i])&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (cluster[k]+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is what the Ovito rendering of the medoid sites looks like:&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Finally, let's plot the MDS maps and color code according to useful information:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have now chosen to color code that data points (of which we have many more than before!) by cluster, species+bulk/surface character, and local GAP energy. We can see that the clustering and embedding algorithms separate preferentially according to species, but also by surface/bulk character. It is also interesting to see how the GAP local energy clearly establishes bulk Pt atoms to be more stable than surface Pt atoms. However, take this with a grain of salt: due to the very small size of these NPs, there is no proper bulk. Nevertheless, the stabilizing effect of the increased atomic coordination is quite evident going from the interior sites to the surface sites.&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=394</id>
		<title>Template:Reference list</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=394"/>
		<updated>2023-08-15T12:07:15Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Reference list */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Reference list ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references&amp;gt;&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2010&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, M.C. Payne, R. Kondor, and G. Csányi'''. ''Gaussian approximation potentials: The accuracy of quantum mechanics, without the electrons''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.104.136403 Phys. Rev. Lett. '''104''', 136403 (2010)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli and M.A. Caro'''. ''GAP interatomic potential for C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://doi.org/10.5281/zenodo.4616343 Zenodo: 10.5281/zenodo.4616343 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021b&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli, X. Chen, A.P. Bartók, P. Hernández-León, G. Csányi, T. Ala-Nissila, and M.A. Caro'''. ''Machine learning force fields based on local parametrization of dispersion interactions: Application to the phase diagram of C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.104.054106 Phys. Rev. B '''104''', 054106 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2013&amp;quot;&amp;gt;&lt;br /&gt;
'''AP Bartók, R Kondor, G Csányi'''. ''On representing chemical environments''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.87.184115 Phys. Rev. B '''87''', 184115 (2013)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2015&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók and G. Csányi'''. ''Gaussian Approximation Potentials: a brief tutorial introduction''. [https://doi.org/10.1002/qua.24927 Int. J. Quantum Chem. '''115''', 1051 (2015)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;caro_2019&amp;quot;&amp;gt;&lt;br /&gt;
'''M.A. Caro'''. ''Optimizing many-body atomic descriptors for enhanced computational performance of machine learning based interatomic potentials''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.100.024112 Phys. Rev. B '''100''', 024112 (2019)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2009&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko and M. Scheffler'''. ''Accurate Molecular Van Der Waals Interactions from Ground-State Electron Density and Free-Atom Reference Data''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.102.073005 Phys. Rev. Lett. '''102''', 073005 (2009)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2012&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko, R.A. DiStasio Jr, R. Car, and M. Scheffler'''. ''Accurate and efficient method for many-body van der Waals interactions''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.108.236402 Phys. Rev. Lett. '''108''', 236402 (2012)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2018&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, J. Kermode, N. Bernstein, and G. Csányi'''. ''Machine learning a general-purpose interatomic potential for silicon''. [https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.041048 Phys. Rev. X '''8''', 041048 (2018)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, S. De, C. Poelking, N. Bernstein, J.R. Kermode, G. Csányi, and M. Ceriotti'''. ''Machine learning unifies the modeling of materials and molecules''. [https://www.science.org/doi/10.1126/sciadv.1701816 Sci. Adv. '''3''', e1701816 (2017)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;de_2016&amp;quot;&amp;gt;&lt;br /&gt;
'''S. De, A.P. Bartók, G. Csányi, and M. Ceriotti'''. ''Comparing molecules and solids across structural and alchemical space''. [https://pubs.rsc.org/en/content/articlelanding/2016/cp/c6cp00415f Phys. Chem. Chem. Phys. '''18''', 13754 (2016)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot;&amp;gt;&lt;br /&gt;
'''P. Hernández-León and M.A. Caro'''. ''Cluster-based multidimensional scaling embedding tool for data visualization''. [https://arxiv.org/abs/2209.06614 arXiv:2209.06614 (2023)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;kloppenburg_2023&amp;quot;&amp;gt;&lt;br /&gt;
'''J. Kloppenburg, L.B. Pártay, H. Jónsson, and M.A. Caro'''. ''A general-purpose machine learning Pt interatomic potential for an accurate description of bulk, surfaces, and nanoparticles''. J. Chem. Phys. '''158''', 134704 (2023).&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/references&amp;gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=393</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=393"/>
		<updated>2023-08-15T12:03:10Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyzing the individual atomic sites */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities). The initial database &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; has been updated with information related to the clustering and embedding. You can download a sample updated file &amp;lt;code&amp;gt;db1.xyz&amp;lt;/code&amp;gt; from [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db1.xyz here]. In the &amp;quot;info&amp;quot; line, the tags &amp;quot;clmds_coordinates&amp;quot;, &amp;quot;clmds_cluster&amp;quot; and &amp;quot;clmds_medoid&amp;quot; will appear, respectively indicating the 2D MDS embedding coordinates, cluster it belongs to and whether it's a medoid or not of the corresponding NP.&lt;br /&gt;
&lt;br /&gt;
Now, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the cartoons for the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities at a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are plotting the similarities on the XY coordinates and choose three different color codings to display further information: to which data cluster the NP corresponds to, what is the composition of each NP, and what is the energy (per atom) of each NP. Unsurprisingly, there is a very strong correlation between these three quantities, because for these NP the most differentiating property is their chemical composition (i.e., their Pt fraction).&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
After using the global SOAP kernel &amp;lt;math&amp;gt;K(I,J)&amp;lt;/math&amp;gt; to compare and analyze the structure of whole NPs, we are going to use the regular (atom- or site-wise) SOAP kernel &amp;lt;math&amp;gt;k(i,j)&amp;lt;/math&amp;gt; to gain insight into the site distribution in our database. We are further going to use some of the &amp;lt;code&amp;gt;ase_tools&amp;lt;/code&amp;gt; functionality to identify the surface atoms. This might be relevant, e.g., for applications in catalysis. We now use the sparsification features of &amp;lt;code&amp;gt;cl-MDS&amp;lt;/code&amp;gt; because the number of atomic sites is too large compared to the number of NPs, and this would significantly slow down the clustering+embedding procedure if we were using the entire set of data points. The following script takes care of all the different steps:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have again updated the database, this time with per-atom information. The new &amp;lt;code&amp;gt;db2.xyz&amp;lt;/code&amp;gt; file (an example of which you can download [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db2.xyz here]) now contains extra columns with the cluster and embedding information for all atoms, as well as a column indicating whether the atom is a surface atom or not. Let us now create a new directory, &amp;lt;code&amp;gt;plot_sites&amp;lt;/code&amp;gt;, to run the plotting script within, again aiming to keep our top directory as clean as possible.&lt;br /&gt;
&lt;br /&gt;
Note how our Ovito code is now significantly more complex, since we are doing two things: 1) we are aligning the line of view with the center of mass of the NP and the position of the medoids; 2) when the medoid is not a surface atom, we are adding transparency to all the atoms between the medoid and the viewer. Besides, we are doing the less sophisticated operation of mixing the color of the Pt/Au atom (gray/yellow, respectively) with red, to highlight the medoid.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
cluster = []&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
            cluster.append(atoms2.arrays[&amp;quot;clmds_cluster&amp;quot;][i])&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (cluster[k]+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is what the Ovito rendering of the medoid sites looks like:&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Finally, let's plot the MDS maps and color code according to useful information:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have now chosen to color code that data points (of which we have many more than before!) by cluster, species+bulk/surface character, and local GAP energy. We can see that the clustering and embedding algorithms separate preferentially according to species, but also by surface/bulk character. It is also interesting to see how the GAP local energy clearly establishes bulk Pt atoms to be more stable than surface Pt atoms. However, take this with a grain of salt: due to the very small size of these NPs, there is no proper bulk. Nevertheless, the stabilizing effect of the increased atomic coordination is quite evident going from the interior sites to the surface sites.&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=392</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=392"/>
		<updated>2023-08-15T11:46:35Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyzing the individual atomic sites */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities). The initial database &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; has been updated with information related to the clustering and embedding. You can download a sample updated file &amp;lt;code&amp;gt;db1.xyz&amp;lt;/code&amp;gt; from [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db1.xyz here]. In the &amp;quot;info&amp;quot; line, the tags &amp;quot;clmds_coordinates&amp;quot;, &amp;quot;clmds_cluster&amp;quot; and &amp;quot;clmds_medoid&amp;quot; will appear, respectively indicating the 2D MDS embedding coordinates, cluster it belongs to and whether it's a medoid or not of the corresponding NP.&lt;br /&gt;
&lt;br /&gt;
Now, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the cartoons for the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities at a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are plotting the similarities on the XY coordinates and choose three different color codings to display further information: to which data cluster the NP corresponds to, what is the composition of each NP, and what is the energy (per atom) of each NP. Unsurprisingly, there is a very strong correlation between these three quantities, because for these NP the most differentiating property is their chemical composition (i.e., their Pt fraction).&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
After using the global SOAP kernel &amp;lt;math&amp;gt;K(I,J)&amp;lt;/math&amp;gt; to compare and analyze the structure of whole NPs, we are going to use the regular (atom- or site-wise) SOAP kernel &amp;lt;math&amp;gt;k(i,j)&amp;lt;/math&amp;gt; to gain insight into the site distribution in our database. We are further going to use some of the &amp;lt;code&amp;gt;ase_tools&amp;lt;/code&amp;gt; functionality to identify the surface atoms. This might be relevant, e.g., for applications in catalysis. We now use the sparsification features of &amp;lt;code&amp;gt;cl-MDS&amp;lt;/code&amp;gt; because the number of atomic sites is too large compared to the number of NPs, and this would significantly slow down the clustering+embedding procedure if we were using the entire set of data points. The following script takes care of all the different steps:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have again updated the database, this time with per-atom information. The new &amp;lt;code&amp;gt;db2.xyz&amp;lt;/code&amp;gt; file (an example of which you can download [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db2.xyz here]) now contains extra columns with the cluster and embedding information for all atoms, as well as a column indicating whether the atom is a surface atom or not. Let us now create a new directory, &amp;lt;code&amp;gt;plot_sites&amp;lt;/code&amp;gt;, to run the plotting script within, again aiming to keep our top directory as clean as possible.&lt;br /&gt;
&lt;br /&gt;
Note how our Ovito code is now significantly more complex, since we are doing two things: 1) we are aligning the line of view with the center of mass of the NP and the position of the medoids; 2) when the medoid is not a surface atom, we are adding transparency to all the atoms between the medoid and the viewer. Besides, we are doing the less sophisticated operation of mixing the color of the Pt/Au atom (gray/yellow, respectively) with red, to highlight the medoid.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is what the Ovito rendering of the medoid sites looks like:&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Finally, let's plot the MDS maps and color code according to useful information:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We have now chosen to color code that data points (of which we have many more than before!) by cluster, species+bulk/surface character, and local GAP energy. We can see that the clustering and embedding algorithms separate preferentially according to species, but also by surface/bulk character. It is also interesting to see how the GAP local energy clearly establishes bulk Pt atoms to be more stable than surface Pt atoms. However, take this with a grain of salt: due to the very small size of these NPs, there is no proper bulk. Nevertheless, the stabilizing effect of the increased atomic coordination is quite evident going from the interior sites to the surface sites.&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png|800px]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=391</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=391"/>
		<updated>2023-08-15T08:00:11Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyze the structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities). The initial database &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; has been updated with information related to the clustering and embedding. You can download a sample updated file &amp;lt;code&amp;gt;db1.xyz&amp;lt;/code&amp;gt; from [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db1.xyz here]. In the &amp;quot;info&amp;quot; line, the tags &amp;quot;clmds_coordinates&amp;quot;, &amp;quot;clmds_cluster&amp;quot; and &amp;quot;clmds_medoid&amp;quot; will appear, respectively indicating the 2D MDS embedding coordinates, cluster it belongs to and whether it's a medoid or not of the corresponding NP.&lt;br /&gt;
&lt;br /&gt;
Now, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the cartoons for the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities at a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are plotting the similarities on the XY coordinates and choose three different color codings to display further information: to which data cluster the NP corresponds to, what is the composition of each NP, and what is the energy (per atom) of each NP. Unsurprisingly, there is a very strong correlation between these three quantities, because for these NP the most differentiating property is their chemical composition (i.e., their Pt fraction).&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Gap_files&amp;diff=390</id>
		<title>Gap files</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Gap_files&amp;diff=390"/>
		<updated>2023-08-15T06:08:09Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: Redirected page to Documentation#Potential directory (gap files/)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Documentation#Potential directory (gap_files/)]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=XYZ_file&amp;diff=389</id>
		<title>XYZ file</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=XYZ_file&amp;diff=389"/>
		<updated>2023-08-15T06:06:46Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: Redirected page to Documentation#Atoms file (*.xyz)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Documentation#Atoms file (*.xyz)]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=388</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=388"/>
		<updated>2023-08-15T05:17:43Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyze the structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities). The initial database &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; has been updated with information related to the clustering and embedding. In the &amp;quot;info&amp;quot; line, the tags &amp;quot;clmds_coordinates&amp;quot;, &amp;quot;clmds_cluster&amp;quot; and &amp;quot;clmds_medoid&amp;quot; will appear, respectively indicating the 2D MDS embedding coordinates, cluster it belongs to and whether it's a medoid or not of the corresponding NP.&lt;br /&gt;
&lt;br /&gt;
Now, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the cartoons for the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities at a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we are plotting the similarities on the XY coordinates and choose three different color codings to display further information: to which data cluster the NP corresponds to, what is the composition of each NP, and what is the energy (per atom) of each NP. Unsurprisingly, there is a very strong correlation between these three quantities, because for these NP the most differentiating property is their chemical composition (i.e., their Pt fraction).&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=387</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=387"/>
		<updated>2023-08-14T18:36:15Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2016&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities).&lt;br /&gt;
&lt;br /&gt;
First, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities are a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:Mds_sites.png&amp;diff=386</id>
		<title>File:Mds sites.png</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:Mds_sites.png&amp;diff=386"/>
		<updated>2023-08-14T18:34:18Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:Mds_np.png&amp;diff=385</id>
		<title>File:Mds np.png</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:Mds_np.png&amp;diff=385"/>
		<updated>2023-08-14T18:33:46Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=382</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=382"/>
		<updated>2023-08-14T18:26:10Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyze the structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2019&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^\zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities).&lt;br /&gt;
&lt;br /&gt;
First, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities are a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=381</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=381"/>
		<updated>2023-08-14T18:25:42Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyze the structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2019&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \textbf{q}_j)^zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = \sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities).&lt;br /&gt;
&lt;br /&gt;
First, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities are a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=380</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=380"/>
		<updated>2023-08-14T18:24:51Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyze the structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2019&amp;quot; /&amp;gt;. First, we use Quippy to compute the SOAP descriptors (using the &amp;lt;code&amp;gt;soap_turbo&amp;lt;/code&amp;gt; implementation) of our NPs. With these descriptors, which are a set of vectors (one per atom) for each NP, &amp;lt;math&amp;gt;\{ \textbf{q}_i \}&amp;lt;/math&amp;gt;, we construct the kernels. These kernels constitute a measure of similarity between individual atomic environments, and are given as dot products, &amp;lt;math&amp;gt;k(i,j) = (\textbf{q}_i \cdot \texbf{q}_j)^zeta&amp;lt;/math&amp;gt;, where &amp;lt;math&amp;gt;k(i,j) \in [0,1]&amp;lt;/math&amp;gt; (0 means nothing alike and 1 identical up to symmetry operations). With these individual kernels we compute the global kernels, which allow us to compare whole NPs, &amp;lt;math&amp;gt;K(I,J) = (\sum_{i \in I} \sum_{j \in J} k(i,j)) / \sqrt{K(I,I) K(J,J)}&amp;lt;/math&amp;gt;. From the kernels, we construct a distance metric, &amp;lt;math&amp;gt;d(I,J) = sqrt{1-K(I,J)}&amp;lt;/math&amp;gt;. FInally, we use this distance to build a dissimilarity matrix which is passed to the [https://github.com/mcaroba/cl-MDS cl-MDS] code, which performs k-medoids clustering and hierarchical MDS-based embedding of the data points.&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f \&lt;br /&gt;
               atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} \&lt;br /&gt;
               radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} \&lt;br /&gt;
               n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The code will select a series of &amp;quot;medoids&amp;quot; or representative NPs, together with the embedding in two dimensions of the data. Each point in the MDS map represents a NP, and distances on the plot represent how similar two NPs are: the closer they are on the plot the more similar they are in the significantly higher-dimensional configuration space (as goven by the kernel similarities).&lt;br /&gt;
&lt;br /&gt;
First, let's make a &amp;lt;code&amp;gt;plot_nps&amp;lt;/code&amp;gt; directory and run this to get the medoid NPs:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can see how the most representative NPs span all alloy compositions encountered in the database. Here we chose 22 clusters/medoids for the k-medoids algorithm. In k-medoids this number is chosen by the user, althugh one can also sweep over different values and monitor the evolution of some &amp;quot;incoherence&amp;quot; measure, such as average intracluster distances, to select the optimal number of clusters.&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Now, we can also plot the MDS maps to visualize the global dissimilarities are a glance:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset xtics&lt;br /&gt;
unset ytics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=379</id>
		<title>Template:Reference list</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=379"/>
		<updated>2023-08-14T18:10:18Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Reference list */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Reference list ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references&amp;gt;&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2010&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, M.C. Payne, R. Kondor, and G. Csányi'''. ''Gaussian approximation potentials: The accuracy of quantum mechanics, without the electrons''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.104.136403 Phys. Rev. Lett. '''104''', 136403 (2010)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli and M.A. Caro'''. ''GAP interatomic potential for C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://doi.org/10.5281/zenodo.4616343 Zenodo: 10.5281/zenodo.4616343 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021b&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli, X. Chen, A.P. Bartók, P. Hernández-León, G. Csányi, T. Ala-Nissila, and M.A. Caro'''. ''Machine learning force fields based on local parametrization of dispersion interactions: Application to the phase diagram of C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.104.054106 Phys. Rev. B '''104''', 054106 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2013&amp;quot;&amp;gt;&lt;br /&gt;
'''AP Bartók, R Kondor, G Csányi'''. ''On representing chemical environments''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.87.184115 Phys. Rev. B '''87''', 184115 (2013)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2015&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók and G. Csányi'''. ''Gaussian Approximation Potentials: a brief tutorial introduction''. [https://doi.org/10.1002/qua.24927 Int. J. Quantum Chem. '''115''', 1051 (2015)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;caro_2019&amp;quot;&amp;gt;&lt;br /&gt;
'''M.A. Caro'''. ''Optimizing many-body atomic descriptors for enhanced computational performance of machine learning based interatomic potentials''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.100.024112 Phys. Rev. B '''100''', 024112 (2019)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2009&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko and M. Scheffler'''. ''Accurate Molecular Van Der Waals Interactions from Ground-State Electron Density and Free-Atom Reference Data''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.102.073005 Phys. Rev. Lett. '''102''', 073005 (2009)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2012&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko, R.A. DiStasio Jr, R. Car, and M. Scheffler'''. ''Accurate and efficient method for many-body van der Waals interactions''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.108.236402 Phys. Rev. Lett. '''108''', 236402 (2012)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2018&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, J. Kermode, N. Bernstein, and G. Csányi'''. ''Machine learning a general-purpose interatomic potential for silicon''. [https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.041048 Phys. Rev. X '''8''', 041048 (2018)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, S. De, C. Poelking, N. Bernstein, J.R. Kermode, G. Csányi, and M. Ceriotti'''. ''Machine learning unifies the modeling of materials and molecules''. [https://www.science.org/doi/10.1126/sciadv.1701816 Sci. Adv. '''3''', e1701816 (2017)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;de_2016&amp;quot;&amp;gt;&lt;br /&gt;
'''S. De, A.P. Bartók, G. Csányi, and M. Ceriotti'''. ''Comparing molecules and solids across structural and alchemical space''. [https://pubs.rsc.org/en/content/articlelanding/2016/cp/c6cp00415f Phys. Chem. Chem. Phys. '''18''', 13754 (2016)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;hernandez-leon_2023&amp;quot;&amp;gt;&lt;br /&gt;
'''P. Hernández-León and M.A. Caro'''. ''Cluster-based multidimensional scaling embedding tool for data visualization''. [https://arxiv.org/abs/2209.06614 arXiv:2209.06614 (2023)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/references&amp;gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=378</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=378"/>
		<updated>2023-08-14T16:14:59Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2019&amp;quot; /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;br /&gt;
&lt;br /&gt;
{{Reference list}}&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=377</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=377"/>
		<updated>2023-08-14T16:14:25Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Analyze the structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
To grasp the overall structural features of our NPs, we are going to ''global'' similarity kernels based on SOAP descriptors, as described in Ref.&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot; /&amp;gt; and Ref.&amp;lt;ref name=&amp;quot;de_2019&amp;quot; /&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=376</id>
		<title>Template:Reference list</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=376"/>
		<updated>2023-08-14T16:10:43Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Reference list */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Reference list ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references&amp;gt;&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2010&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, M.C. Payne, R. Kondor, and G. Csányi'''. ''Gaussian approximation potentials: The accuracy of quantum mechanics, without the electrons''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.104.136403 Phys. Rev. Lett. '''104''', 136403 (2010)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli and M.A. Caro'''. ''GAP interatomic potential for C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://doi.org/10.5281/zenodo.4616343 Zenodo: 10.5281/zenodo.4616343 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021b&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli, X. Chen, A.P. Bartók, P. Hernández-León, G. Csányi, T. Ala-Nissila, and M.A. Caro'''. ''Machine learning force fields based on local parametrization of dispersion interactions: Application to the phase diagram of C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.104.054106 Phys. Rev. B '''104''', 054106 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2013&amp;quot;&amp;gt;&lt;br /&gt;
'''AP Bartók, R Kondor, G Csányi'''. ''On representing chemical environments''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.87.184115 Phys. Rev. B '''87''', 184115 (2013)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2015&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók and G. Csányi'''. ''Gaussian Approximation Potentials: a brief tutorial introduction''. [https://doi.org/10.1002/qua.24927 Int. J. Quantum Chem. '''115''', 1051 (2015)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;caro_2019&amp;quot;&amp;gt;&lt;br /&gt;
'''M.A. Caro'''. ''Optimizing many-body atomic descriptors for enhanced computational performance of machine learning based interatomic potentials''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.100.024112 Phys. Rev. B '''100''', 024112 (2019)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2009&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko and M. Scheffler'''. ''Accurate Molecular Van Der Waals Interactions from Ground-State Electron Density and Free-Atom Reference Data''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.102.073005 Phys. Rev. Lett. '''102''', 073005 (2009)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2012&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko, R.A. DiStasio Jr, R. Car, and M. Scheffler'''. ''Accurate and efficient method for many-body van der Waals interactions''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.108.236402 Phys. Rev. Lett. '''108''', 236402 (2012)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2018&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, J. Kermode, N. Bernstein, and G. Csányi'''. ''Machine learning a general-purpose interatomic potential for silicon''. [https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.041048 Phys. Rev. X '''8''', 041048 (2018)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, S. De, C. Poelking, N. Bernstein, J.R. Kermode, G. Csányi, and M. Ceriotti'''. ''Machine learning unifies the modeling of materials and molecules''. [https://www.science.org/doi/10.1126/sciadv.1701816 Sci. Adv. '''3''', e1701816 (2017)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;de_2016&amp;quot;&amp;gt;&lt;br /&gt;
'''S. De, A.P. Bartók, G. Csányi, and M. Ceriotti'''. ''Comparing molecules and solids across structural and alchemical space''. Phys. Chem. Chem. Phys. '''18''', 13754 (2016).&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/references&amp;gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=375</id>
		<title>Template:Reference list</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Template:Reference_list&amp;diff=375"/>
		<updated>2023-08-14T15:52:38Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Reference list */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Reference list ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references&amp;gt;&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2010&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, M.C. Payne, R. Kondor, and G. Csányi'''. ''Gaussian approximation potentials: The accuracy of quantum mechanics, without the electrons''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.104.136403 Phys. Rev. Lett. '''104''', 136403 (2010)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli and M.A. Caro'''. ''GAP interatomic potential for C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://doi.org/10.5281/zenodo.4616343 Zenodo: 10.5281/zenodo.4616343 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;muhli_2021b&amp;quot;&amp;gt;&lt;br /&gt;
'''H. Muhli, X. Chen, A.P. Bartók, P. Hernández-León, G. Csányi, T. Ala-Nissila, and M.A. Caro'''. ''Machine learning force fields based on local parametrization of dispersion interactions: Application to the phase diagram of C&amp;lt;sub&amp;gt;60&amp;lt;/sub&amp;gt;''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.104.054106 Phys. Rev. B '''104''', 054106 (2021)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2013&amp;quot;&amp;gt;&lt;br /&gt;
'''AP Bartók, R Kondor, G Csányi'''. ''On representing chemical environments''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.87.184115 Phys. Rev. B '''87''', 184115 (2013)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2015&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók and G. Csányi'''. ''Gaussian Approximation Potentials: a brief tutorial introduction''. [https://doi.org/10.1002/qua.24927 Int. J. Quantum Chem. '''115''', 1051 (2015)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;caro_2019&amp;quot;&amp;gt;&lt;br /&gt;
'''M.A. Caro'''. ''Optimizing many-body atomic descriptors for enhanced computational performance of machine learning based interatomic potentials''. [https://journals.aps.org/prb/abstract/10.1103/PhysRevB.100.024112 Phys. Rev. B '''100''', 024112 (2019)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2009&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko and M. Scheffler'''. ''Accurate Molecular Van Der Waals Interactions from Ground-State Electron Density and Free-Atom Reference Data''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.102.073005 Phys. Rev. Lett. '''102''', 073005 (2009)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;tkatchenko_2012&amp;quot;&amp;gt;&lt;br /&gt;
'''A. Tkatchenko, R.A. DiStasio Jr, R. Car, and M. Scheffler'''. ''Accurate and efficient method for many-body van der Waals interactions''.&lt;br /&gt;
[https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.108.236402 Phys. Rev. Lett. '''108''', 236402 (2012)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2018&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, J. Kermode, N. Bernstein, and G. Csányi'''. ''Machine learning a general-purpose interatomic potential for silicon''. [https://journals.aps.org/prx/abstract/10.1103/PhysRevX.8.041048 Phys. Rev. X '''8''', 041048 (2018)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref name=&amp;quot;bartok_2017&amp;quot;&amp;gt;&lt;br /&gt;
'''A.P. Bartók, S. De, C. Poelking, N. Bernstein, J.R. Kermode, G. Csányi, and M. Ceriotti'''. ''Machine learning unifies the modeling of materials and molecules''. [https://www.science.org/doi/10.1126/sciadv.1701816 Sci. Adv. '''3''', e1701816 (2017)].&lt;br /&gt;
&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/references&amp;gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=374</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=374"/>
		<updated>2023-08-14T15:26:11Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Construct the convex hull of NP stability */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While we upload the PtAu GAP to a public repository (needed to run the workflow above), you can download a sample &amp;lt;code&amp;gt;db0.xyz&amp;lt;/code&amp;gt; file [https://github.com/mcaroba/turbogap/blob/master/tutorials/PtAu_NPs_MDS/db0.xyz here].&lt;br /&gt;
&lt;br /&gt;
Now, let's plot the convex hulls for the different NP sizes. First, create a directory called &amp;lt;code&amp;gt;hulls&amp;lt;/code&amp;gt;, to keep the top directory a bit cleaner, and run the following code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After extracting and collecting the NP energies in a suitable format, you can easily plot the results. This is a sample gnuplot code that can do the job.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the figure below you can see how most of the alloy data (purple dots at &amp;lt;math&amp;gt;x \neq 0,1&amp;lt;/math&amp;gt;) are higher in energy than the linearly interpolated values for the pure NP of the same size. The only exception are the NPs for &amp;lt;math&amp;gt;N = 30&amp;lt;/math&amp;gt;. If the simulations had been carried out carefully, this would mean that alloying is energetically unfavorable since, for a given composition, a mixture of pure NPs is lower in energy than the alloyed NPs with the same average composition. In reality, 1) our annealing time was way too short to properly sample low minima of the alloy potential energy surface and 2) we are omitting the configurational entropy of the alloy which lowers its ''free'' energy of formation at finite temperature (we are only looking at the ''potential'' energy).&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png|800px]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=373</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=373"/>
		<updated>2023-08-14T14:54:42Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Construct the convex hull of NP stability */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with &amp;lt;code&amp;gt;.xyz&amp;lt;/code&amp;gt; extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=372</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=372"/>
		<updated>2023-08-14T14:54:09Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Construct the convex hull of NP stability */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
We are now going to collect all the 660 relaxed structures and put them into an ASE &amp;quot;database&amp;quot; XYZ file: this is simply a file with `.xyz` extension with all the individual XYZ files (containing only the final snapshot) concatenated. The following Python script will collect the data:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=371</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=371"/>
		<updated>2023-08-14T06:19:06Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png|800px]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=370</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=370"/>
		<updated>2023-08-11T14:00:08Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here]), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This value scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=369</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=369"/>
		<updated>2023-08-11T13:59:26Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
#            print(num)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here), and the PtAu and Au temperatures are estimated from that one scaling by the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). All temperatures are further scaled by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below; you can try changing this number to check if it improves the structures). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This values scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=368</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=368"/>
		<updated>2023-08-11T13:56:41Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the &amp;lt;code&amp;gt;init_xyz&amp;lt;/code&amp;gt; directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
#            print(num)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here), and the PtAu and Au temperatures are estimated from that one scaling by a global parameter (&amp;lt;code&amp;gt;f = 1.1&amp;lt;/code&amp;gt; below) and the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This values scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the &amp;lt;code&amp;gt;trajs/traj_1.1&amp;lt;/code&amp;gt; directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=367</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=367"/>
		<updated>2023-08-11T13:55:17Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particular, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the `init_xyz` directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
#            print(num)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here), and the PtAu and Au temperatures are estimated from that one scaling by a global parameter (`f = 1.1` below) and the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This values scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the `trajs/traj_1.1` directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=366</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=366"/>
		<updated>2023-08-11T13:54:40Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Generating the nanoparticles */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 '''WARNING1''': the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are ''most definitely'' too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
 '''WARNING2''': because the PtAu GAP is still not published, you will not be able to run this step (yet). You can still try to follow the scripts and simulation logic, and download the database of relaxed NPs from the next section.&lt;br /&gt;
&lt;br /&gt;
We are going to generate a database of PtAu NPs with variable size and composition. In particularly, we will generate 10 NPs per size and composition, where the Pt and Au contents will be varied at 10% increments and the size will be varied at 5 atoms increments from 25 to 50. This will lead to 660 unique PtAu NPs (including pure Pt and Au NPs). The first step is to generate a set of random initial structures with the desired properties. The following Python code will take care of generating random distributions of atoms with roughly spherical shapes where the interatomic distances are not too small to avoid highly energetic starting configurations that may be prone to &amp;quot;blowing up&amp;quot;. Make sure to create the `init_xyz` directory before proceeding.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
#            print(num)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The resulting structures will look something like this:&lt;br /&gt;
&lt;br /&gt;
[[File:Init_all.png]]&lt;br /&gt;
&lt;br /&gt;
Next, we will run some '''TurboGAP''' workflows consisting of high-temperature annealing, where the annealing temperature (1150 K) is the optimal temperature for Pt (as found [https://pubs.aip.org/aip/jcp/article/158/13/134704/2883270 here), and the PtAu and Au temperatures are estimated from that one scaling by a global parameter (`f = 1.1` below) and the ratio of experimental melting temperatures of pure Au and Pt (and interpolating linearly). High-&amp;lt;math&amp;gt;T&amp;lt;/math&amp;gt; annealing is followed by a rapid quench down to 100 K, and this is again followed by [[gradient descent]] static relaxation. The whole workflow can be reproduced with the following Bash script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This values scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 1 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trajectory files with exactly three snapshots (system after annealing, quench and relaxation) will be save into the `trajs/traj_1.1` directory. As mentioned at the top of this section, the chosen parameters are way too lax to make properly relaxed NPs.&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:Init_all.png&amp;diff=365</id>
		<title>File:Init all.png</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:Init_all.png&amp;diff=365"/>
		<updated>2023-08-11T13:35:12Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=364</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=364"/>
		<updated>2023-08-11T09:45:09Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 WARNING: the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are (quite probably) too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
#            print(num)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This values scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 12 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_np.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_np.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:All_sites.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Mds_sites.png]]&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=363</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=363"/>
		<updated>2023-08-11T09:42:33Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: /* Construct the convex hull of NP stability */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 WARNING: the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are (quite probably) too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
#            print(num)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This values scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 12 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Hulls.png]]&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:All_sites.png&amp;diff=360</id>
		<title>File:All sites.png</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:All_sites.png&amp;diff=360"/>
		<updated>2023-08-11T09:41:00Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:All_np.png&amp;diff=359</id>
		<title>File:All np.png</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:All_np.png&amp;diff=359"/>
		<updated>2023-08-11T09:40:50Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=File:Hulls.png&amp;diff=358</id>
		<title>File:Hulls.png</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=File:Hulls.png&amp;diff=358"/>
		<updated>2023-08-11T09:40:20Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=357</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=357"/>
		<updated>2023-08-11T09:38:40Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;br /&gt;
&lt;br /&gt;
== Generating the nanoparticles ==&lt;br /&gt;
&lt;br /&gt;
 WARNING: the options below are just enough to generate example NPs for this tutorial. The annealing and quenching times are (quite probably) too short to generate stable &amp;quot;publication-quality&amp;quot; NPs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import write&lt;br /&gt;
from ase import Atoms&lt;br /&gt;
&lt;br /&gt;
a = 3.9668&lt;br /&gt;
d_min = 2.&lt;br /&gt;
&lt;br /&gt;
append = False&lt;br /&gt;
num = 1&lt;br /&gt;
&lt;br /&gt;
nrand = 10&lt;br /&gt;
for n in range(25,50+1,5):&lt;br /&gt;
    for f_Au in np.arange(0.,1.+1.e-10,0.1):&lt;br /&gt;
        for i in range(0, nrand):&lt;br /&gt;
            R = (a**3 / 4. * n * 3./4./np.pi)**(1./3.)&lt;br /&gt;
&lt;br /&gt;
            pos = []&lt;br /&gt;
            while len(pos) &amp;lt; n:&lt;br /&gt;
                this_pos = (2.*np.random.sample([3]) -1) * R&lt;br /&gt;
                d = np.dot(this_pos, this_pos)**0.5&lt;br /&gt;
                if d &amp;gt; R:&lt;br /&gt;
                    continue&lt;br /&gt;
                if len(pos) == 0:&lt;br /&gt;
                    pos.append(this_pos)&lt;br /&gt;
                else:&lt;br /&gt;
                    too_close = False&lt;br /&gt;
                    for other_pos in pos:&lt;br /&gt;
                        dv = this_pos - other_pos&lt;br /&gt;
                        d = np.dot(dv, dv)**0.5&lt;br /&gt;
                        if d &amp;lt; d_min:&lt;br /&gt;
                            too_close = True&lt;br /&gt;
                            break&lt;br /&gt;
                    if not too_close:&lt;br /&gt;
                        pos.append(this_pos)&lt;br /&gt;
&lt;br /&gt;
            n_Au = int(n*f_Au)&lt;br /&gt;
            n_Pt = n - n_Au&lt;br /&gt;
&lt;br /&gt;
            atoms = Atoms(&amp;quot;Pt%iAu%i&amp;quot; % (n_Pt, n_Au), positions = pos, pbc=True)&lt;br /&gt;
            write(&amp;quot;init_xyz/all.xyz&amp;quot;, atoms, append = append)&lt;br /&gt;
            atoms.center(vacuum = 10.)&lt;br /&gt;
            write(&amp;quot;init_xyz/%i.xyz&amp;quot; % num, atoms)&lt;br /&gt;
            num += 1&lt;br /&gt;
            append = True&lt;br /&gt;
#            print(num)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
# This values scales the annealing temperature&lt;br /&gt;
f=1.1&lt;br /&gt;
&lt;br /&gt;
mkdir -p trajs/&lt;br /&gt;
mkdir -p logs/&lt;br /&gt;
mkdir -p trajs/trajs_$f&lt;br /&gt;
mkdir -p logs/logs_$f&lt;br /&gt;
&lt;br /&gt;
for i in $(seq 12 1 660); do&lt;br /&gt;
&lt;br /&gt;
SECONDS=0&lt;br /&gt;
&lt;br /&gt;
n_Au=$(awk '{if($1==&amp;quot;Au&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
n_Pt=$(awk '{if($1==&amp;quot;Pt&amp;quot;){n+=1} else {n+=0}} END {print n}' init_xyz/$i.xyz)&lt;br /&gt;
&lt;br /&gt;
T=$(echo $n_Au $n_Pt | awk -v f=$f '{print f*($1/($1+$2)*750.+$2/($1+$2)*1150.)}')&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;Doing $i/660...&amp;quot;&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Anneal at $T for 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'init_xyz/$i.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = $T&lt;br /&gt;
tau_t = 10.&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/anneal_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Quench to 100K over 1 ps&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 250&lt;br /&gt;
md_step = 4.&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;vv&amp;quot;&lt;br /&gt;
thermostat = &amp;quot;bussi&amp;quot;&lt;br /&gt;
t_beg = $T&lt;br /&gt;
t_end = 100.&lt;br /&gt;
tau_t = 100.&lt;br /&gt;
&lt;br /&gt;
write_xyz = 250&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt; atoms.xyz&lt;br /&gt;
mv trajectory_out.xyz trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/quench_$i.log&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
##########################################&lt;br /&gt;
# Relax&lt;br /&gt;
cat&amp;gt;input&amp;lt;&amp;lt;eof&lt;br /&gt;
atoms_file = 'atoms.xyz'&lt;br /&gt;
pot_file = 'gap_files/PtAuH.gap'&lt;br /&gt;
&lt;br /&gt;
n_species = 3&lt;br /&gt;
species = Pt Au H&lt;br /&gt;
&lt;br /&gt;
md_nsteps = 1000&lt;br /&gt;
&lt;br /&gt;
optimize = &amp;quot;gd&amp;quot;&lt;br /&gt;
eof&lt;br /&gt;
&lt;br /&gt;
mpirun -np 4 turbogap md &amp;amp;&amp;gt; /dev/null&lt;br /&gt;
n=$(head -1 trajectory_out.xyz | awk '{print $1+2}')&lt;br /&gt;
tail -$n trajectory_out.xyz &amp;gt;&amp;gt; trajs/trajs_$f/$i.xyz&lt;br /&gt;
mv thermo.log logs/logs_$f/relax_$i.log&lt;br /&gt;
rm trajectory_out.xyz&lt;br /&gt;
rm input&lt;br /&gt;
##########################################&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
echo &amp;quot;    ... in $SECONDS s&amp;quot;&lt;br /&gt;
&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Construct the convex hull of NP stability ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = []&lt;br /&gt;
for i in range(1,660+1):&lt;br /&gt;
    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db0.xyz&amp;quot;, db)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase.io import read&lt;br /&gt;
import numpy as np&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;../db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
all = {}&lt;br /&gt;
Ns = []&lt;br /&gt;
xs = {}&lt;br /&gt;
&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    N = len(atoms)&lt;br /&gt;
    x = np.round(atoms.symbols.count(&amp;quot;Pt&amp;quot;)/N, decimals = 8)&lt;br /&gt;
    if (N,x) not in all:&lt;br /&gt;
        all[N,x] = [atoms.info[&amp;quot;energy&amp;quot;]/N]&lt;br /&gt;
    else:&lt;br /&gt;
        all[N,x].append(atoms.info[&amp;quot;energy&amp;quot;]/N)&lt;br /&gt;
    if N not in Ns:&lt;br /&gt;
        Ns.append(N)&lt;br /&gt;
    if N not in xs:&lt;br /&gt;
        xs[N] = [x]&lt;br /&gt;
    elif x not in xs[N]:&lt;br /&gt;
        xs[N].append(x)&lt;br /&gt;
&lt;br /&gt;
for N in Ns:&lt;br /&gt;
    x_vals = []&lt;br /&gt;
    e_vals = []&lt;br /&gt;
    for x in xs[N]:&lt;br /&gt;
        for e in all[N,x]:&lt;br /&gt;
            if x not in x_vals:&lt;br /&gt;
                x_vals.append(x)&lt;br /&gt;
                e_vals.append(e)&lt;br /&gt;
            else:&lt;br /&gt;
                for i in range(0, len(x_vals)):&lt;br /&gt;
                    if x == x_vals[i] and e &amp;lt; e_vals[i]:&lt;br /&gt;
                        e_vals[i] = e&lt;br /&gt;
    if 0. in x_vals and 1. in x_vals:&lt;br /&gt;
        i0 = np.where([x == 0. for x in x_vals])[0][0]&lt;br /&gt;
        i1 = np.where([x == 1. for x in x_vals])[0][0]&lt;br /&gt;
        e0 = e_vals[i0]&lt;br /&gt;
        e1 = e_vals[i1]&lt;br /&gt;
        idx = np.argsort(x_vals)&lt;br /&gt;
        x_vals = np.array(x_vals)[idx]&lt;br /&gt;
        e_vals = np.array(e_vals)[idx]&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            x = x_vals[i]&lt;br /&gt;
            e_vals[i] -= e1*x + e0*(1.-x)&lt;br /&gt;
        f = open(&amp;quot;%i.dat&amp;quot; % N, &amp;quot;w&amp;quot;)&lt;br /&gt;
        for i in range(0, len(x_vals)):&lt;br /&gt;
            if i == 0 or i == len(x_vals) or e_vals[i] &amp;lt;= 0.:&lt;br /&gt;
                print(x_vals[i], e_vals[i], file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        print(&amp;quot;&amp;quot;, file=f)&lt;br /&gt;
        for x in xs[N]:&lt;br /&gt;
            for e in all[N,x]:&lt;br /&gt;
                print(x, e-e1*x-e0*(1.-x), file=f)&lt;br /&gt;
        f.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;hulls.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,6&lt;br /&gt;
&lt;br /&gt;
set xlabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
set ylabel &amp;quot;Energy above hull (eV/atom)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set yrange [-0.02:0.12]&lt;br /&gt;
set xtics 0.5&lt;br /&gt;
set mxtics 1&lt;br /&gt;
&lt;br /&gt;
do for [i=25:50:5] {&lt;br /&gt;
set title &amp;quot;N = &amp;quot;.i&lt;br /&gt;
plot &amp;quot;&amp;quot;.i.&amp;quot;.dat&amp;quot; index 1 pt 7 not, &amp;quot;&amp;quot; index 0 pt 6 ps 1.2 lw 4 w lp not&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Analyze the structure&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
from ase import cluster&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
#db = []&lt;br /&gt;
#for i in range(1,660+1):&lt;br /&gt;
#    atoms = read(&amp;quot;trajs/trajs_1.1/%i.xyz&amp;quot; % i, index=-1)&lt;br /&gt;
#    db.append(atoms)&lt;br /&gt;
&lt;br /&gt;
db = read(&amp;quot;db0.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the global kernel for the NP-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d1 = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d2 = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    q1 = d1.calc_descriptor(at)&lt;br /&gt;
    q2 = d2.calc_descriptor(at)&lt;br /&gt;
    if q1.shape == (1,0):&lt;br /&gt;
        q = q2&lt;br /&gt;
    elif q2.shape == (1,0):&lt;br /&gt;
        q = q1&lt;br /&gt;
    else:&lt;br /&gt;
        q = np.concatenate((q1, q2))&lt;br /&gt;
    qs.append(q)&lt;br /&gt;
&lt;br /&gt;
kern = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(i,len(qs)):&lt;br /&gt;
        k = 0.&lt;br /&gt;
        for i2 in range(0, len(qs[i])):&lt;br /&gt;
            for j2 in range(0, len(qs[j])):&lt;br /&gt;
                k += np.dot(qs[i][i2], qs[j][j2])**zeta&lt;br /&gt;
        kern[i,j] = k&lt;br /&gt;
        kern[j,i] = k&lt;br /&gt;
&lt;br /&gt;
kern_n = np.zeros([len(qs), len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    for j in range(0,len(qs)):&lt;br /&gt;
        kern_n[i,j] = kern[i,j] / np.sqrt(kern[i,i] * kern[j,j])&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    dist[i, i] = 0.&lt;br /&gt;
    for j in range(i+1,len(qs)):&lt;br /&gt;
        dist[i, j] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
        dist[j, i] = np.sqrt(1.-kern_n[i,j])&lt;br /&gt;
&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, N, E, x in Pt_{x}Au_{1-x}&amp;quot;, file=f)&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    db[i].info[&amp;quot;clmds_coordinates&amp;quot;] = XY_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_cluster&amp;quot;] = C_sparse[i]&lt;br /&gt;
    db[i].info[&amp;quot;clmds_medoid&amp;quot;] = i in M_sparse&lt;br /&gt;
    print(*XY_sparse[i], C_sparse[i], i in M_sparse, len(db[i]), db[i].info[&amp;quot;energy&amp;quot;], db[i].symbols.count(&amp;quot;Pt&amp;quot;)/len(db[i]), file=f)&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
write(&amp;quot;db1.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
cl = []&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for atoms in db0:&lt;br /&gt;
    if atoms.info[&amp;quot;clmds_medoid&amp;quot;]:&lt;br /&gt;
        db.append(atoms)&lt;br /&gt;
        cl.append(atoms.info[&amp;quot;clmds_cluster&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
dbt = np.array(db, dtype=object)&lt;br /&gt;
db = dbt[cl]&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([0.8,0.8,0.8])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = np.array([1,1,1])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() - direction / np.dot(direction,direction)**0.5 * 30.&lt;br /&gt;
    vp.render_image(filename = &amp;quot;np_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_np.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to composition&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;x in Pt_{x}Au_{1-x}&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:7 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;PtAu NP according to energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Total energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds.dat&amp;quot; u 1:2:($6/$5) palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Analyzing the individual atomic sites ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from quippy.descriptors import Descriptor&lt;br /&gt;
from quippy.convert import ase_to_quip&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
import cluster_mds as clmds&lt;br /&gt;
import kmedoids&lt;br /&gt;
from ase_tools import surface_list&lt;br /&gt;
&lt;br /&gt;
# Read in the database&lt;br /&gt;
db = read(&amp;quot;db1.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Create mappings&lt;br /&gt;
which_structure = []&lt;br /&gt;
which_atom = []&lt;br /&gt;
map_back = {}&lt;br /&gt;
local_energy = []&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    le = db[i].get_array(&amp;quot;local_energy&amp;quot;)&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        which_structure.append(i)&lt;br /&gt;
        which_atom.append(j)&lt;br /&gt;
        map_back[i,j] = k&lt;br /&gt;
        local_energy.append(le[j])&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Rolling sphere algorithm parameters&lt;br /&gt;
r_min = 4.&lt;br /&gt;
r_max = 4.5&lt;br /&gt;
n_tries = 1000000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Build a list of surface atoms&lt;br /&gt;
surf_list_all = []&lt;br /&gt;
is_surface = np.full(len(which_atom), False, dtype=bool)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    surf_list = surface_list(db[i], r_min, r_max, n_tries, cluster=True)&lt;br /&gt;
    surf_list_all.append(surf_list)&lt;br /&gt;
    for j in range(0,len(db[i])):&lt;br /&gt;
        if j in surf_list:&lt;br /&gt;
            is_surface[k] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    if True:&lt;br /&gt;
        sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(db)) )&lt;br /&gt;
        sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###############################################################################################&lt;br /&gt;
# This builds the kernel for the site-wise cl-MDS map&lt;br /&gt;
n_cl = 22 # number of clusters&lt;br /&gt;
cutoff = 5.7; dc = 0.5; sigma = 0.5&lt;br /&gt;
zeta = 6&lt;br /&gt;
soap = {&amp;quot;Au&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=2 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma])),&lt;br /&gt;
        &amp;quot;Pt&amp;quot;: 'soap_turbo alpha_max={8 8} l_max=8 rcut_soft=%.4f rcut_hard=%.4f atom_sigma_r={%.4f %.4f} atom_sigma_t={%.4f %.4f} \&lt;br /&gt;
               atom_sigma_r_scaling={0. 0.} atom_sigma_t_scaling={0. 0.} radial_enhancement=1 amplitude_scaling={1. 1.} \&lt;br /&gt;
               basis=&amp;quot;poly3gauss&amp;quot; scaling_mode=&amp;quot;polynomial&amp;quot; species_Z={78 79} n_species=2 central_index=1 central_weight={1. 1.} \&lt;br /&gt;
               compress_mode=trivial' % (cutoff-dc, cutoff, *(4*[sigma]))}&lt;br /&gt;
&lt;br /&gt;
d = {}&lt;br /&gt;
&lt;br /&gt;
d[&amp;quot;Pt&amp;quot;] = Descriptor(soap[&amp;quot;Pt&amp;quot;])&lt;br /&gt;
d[&amp;quot;Au&amp;quot;] = Descriptor(soap[&amp;quot;Au&amp;quot;])&lt;br /&gt;
&lt;br /&gt;
qs = []&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    at = ase_to_quip(atoms)&lt;br /&gt;
    N = {}&lt;br /&gt;
    q = {}&lt;br /&gt;
    for i in range(0,len(atoms)):&lt;br /&gt;
        symb = atoms.symbols[i]&lt;br /&gt;
        if symb not in N:&lt;br /&gt;
            N[symb] = 0&lt;br /&gt;
            q[symb] = d[symb].calc_descriptor(at)&lt;br /&gt;
        this_q = q[symb][N[symb]]&lt;br /&gt;
        qs.append(this_q)&lt;br /&gt;
        N[symb] += 1&lt;br /&gt;
&lt;br /&gt;
dist = np.zeros([len(qs),len(qs)])&lt;br /&gt;
n = 0&lt;br /&gt;
for i in range(0,len(qs)):&lt;br /&gt;
    arg = 1. - np.dot(qs[:], qs[i])**zeta&lt;br /&gt;
    arg2 = np.clip(arg, 0, 1)&lt;br /&gt;
    dist[i,:] = np.sqrt(arg2)&lt;br /&gt;
#    sys.stdout.write('\rBuilding list of surface atoms:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.write('\rBuilding distance matrix:%6.1f%%' % (float(i)*100./len(qs)) )&lt;br /&gt;
    sys.stdout.flush()&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;    MBytes: &amp;quot;, dist.nbytes/1024/1024)&lt;br /&gt;
print(&amp;quot;&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
# Embedding&lt;br /&gt;
M, C = kmedoids.kMedoids(dist, n_cl, n_iso=n_cl//2, init_Ms=&amp;quot;isolated&amp;quot;, n_inits=10)&lt;br /&gt;
data = clmds.clMDS(dist_matrix = dist, sparsify=&amp;quot;cur&amp;quot;, n_sparse=500)&lt;br /&gt;
data.cluster_MDS([n_cl,1], weight_cluster_mds=2., weight_anchor_mds=10., init_medoids=M)&lt;br /&gt;
list_sparse = data.sparse_list&lt;br /&gt;
XY_sparse = data.sparse_coordinates&lt;br /&gt;
C_sparse = data.sparse_cluster_indices&lt;br /&gt;
M_sparse = data.sparse_medoids&lt;br /&gt;
M = []&lt;br /&gt;
for k in M_sparse:&lt;br /&gt;
    M.append(list_sparse[k])&lt;br /&gt;
&lt;br /&gt;
M = np.array( M, dtype=int )&lt;br /&gt;
indices = np.array( list(range(0,len(dist))), dtype=int )&lt;br /&gt;
data.compute_pts_estim_coordinates( indices=indices )&lt;br /&gt;
XY = data.estim_coordinates&lt;br /&gt;
C = data.estim_cluster_indices&lt;br /&gt;
# Transform to usual cluster array&lt;br /&gt;
C_usual = []&lt;br /&gt;
for i in range(0, n_cl):&lt;br /&gt;
    C_usual.append([])&lt;br /&gt;
&lt;br /&gt;
for i in range(0, len(C)):&lt;br /&gt;
    C_usual[C[i]].append(i)&lt;br /&gt;
&lt;br /&gt;
C_list = C&lt;br /&gt;
C = C_usual&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_cluster&amp;quot;, C_list[k:k+len(atoms)])&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_coordinates&amp;quot;, XY[k:k+len(atoms)])&lt;br /&gt;
    k += len(atoms)&lt;br /&gt;
&lt;br /&gt;
k = 0&lt;br /&gt;
for atoms in db:&lt;br /&gt;
    is_medoid = np.full(len(atoms), False)&lt;br /&gt;
    is_surface2 = np.full(len(atoms), False)&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if is_surface[k]:&lt;br /&gt;
            is_surface2[i] = True&lt;br /&gt;
        if k in M:&lt;br /&gt;
            is_medoid[i] = True&lt;br /&gt;
        k += 1&lt;br /&gt;
    atoms.set_array(&amp;quot;clmds_medoid&amp;quot;, is_medoid)&lt;br /&gt;
    atoms.set_array(&amp;quot;surface&amp;quot;, is_surface2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
f = open(&amp;quot;mds_sites.dat&amp;quot;, &amp;quot;w&amp;quot;)&lt;br /&gt;
print(&amp;quot;# X, Y, cluster, is_medoid, is_surface, le, species, N of NP&amp;quot;, file=f)&lt;br /&gt;
k = 0&lt;br /&gt;
for i in range(0, len(db)):&lt;br /&gt;
    for j in range(0, len(db[i])):&lt;br /&gt;
        print(*XY[k], C_list[k], k in M, is_surface[k], local_energy[k], db[i].symbols[j], len(db[i]), file=f)&lt;br /&gt;
        k += 1&lt;br /&gt;
&lt;br /&gt;
f.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
write(&amp;quot;db2.xyz&amp;quot;, db)&lt;br /&gt;
###############################################################################################&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
import sys&lt;br /&gt;
from ase.io import read,write&lt;br /&gt;
from ase.visualize import view&lt;br /&gt;
import numpy as np&lt;br /&gt;
from ovito.io.ase import ase_to_ovito&lt;br /&gt;
from ovito.pipeline import StaticSource, Pipeline&lt;br /&gt;
from ovito.vis import Viewport, BondsVis, ParticlesVis, TachyonRenderer&lt;br /&gt;
from ovito.io import import_file&lt;br /&gt;
from ovito.modifiers import CreateBondsModifier, ColorCodingModifier&lt;br /&gt;
from scipy.special import erf&lt;br /&gt;
&lt;br /&gt;
db0 = read(&amp;quot;../db2.xyz&amp;quot;, index=&amp;quot;:&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
db = []&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db0)):&lt;br /&gt;
    atoms = db0[k]&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            this_medoid = np.full(len(atoms), False)&lt;br /&gt;
            this_medoid[i] = True&lt;br /&gt;
            atoms2 = atoms.copy()&lt;br /&gt;
            atoms2.set_array(&amp;quot;clmds_medoid&amp;quot;, this_medoid)&lt;br /&gt;
            db.append(atoms2)&lt;br /&gt;
&lt;br /&gt;
for k in range(0, len(db)):&lt;br /&gt;
&lt;br /&gt;
    atoms = db[k].copy()&lt;br /&gt;
&lt;br /&gt;
#    atoms.center(vacuum=0.)&lt;br /&gt;
#    atoms.pbc=False&lt;br /&gt;
&lt;br /&gt;
    medoid = atoms.get_array(&amp;quot;clmds_medoid&amp;quot;)&lt;br /&gt;
    surface = atoms.get_array(&amp;quot;surface&amp;quot;)&lt;br /&gt;
    slice = False&lt;br /&gt;
    transparency = np.zeros(len(atoms))&lt;br /&gt;
    cm = atoms.get_center_of_mass()&lt;br /&gt;
    atoms.positions -= cm&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i] and not surface[i]:&lt;br /&gt;
            v = atoms[i].position&lt;br /&gt;
            for j in range(0, len(atoms)):&lt;br /&gt;
                u = atoms[j].position&lt;br /&gt;
                if np.dot(v,u) &amp;gt; np.dot(v,v) and j != i:&lt;br /&gt;
                    transparency[j] = 0.9&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 30.&lt;br /&gt;
        elif medoid[i]:&lt;br /&gt;
            direction = atoms.positions[i]&lt;br /&gt;
            dist = 25.&lt;br /&gt;
&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;fix_atoms&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;medoid&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;surface&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
    try:&lt;br /&gt;
        del atoms.arrays[&amp;quot;clmds_cluster&amp;quot;]&lt;br /&gt;
    except:&lt;br /&gt;
        pass&lt;br /&gt;
&lt;br /&gt;
    atoms_data = ase_to_ovito(atoms)&lt;br /&gt;
    pipeline = Pipeline(source = StaticSource(data = atoms_data))&lt;br /&gt;
    pipeline.add_to_scene()&lt;br /&gt;
&lt;br /&gt;
    n_Pt = atoms.symbols.count(&amp;quot;Pt&amp;quot;)&lt;br /&gt;
    n_Au = atoms.symbols.count(&amp;quot;Au&amp;quot;)&lt;br /&gt;
    n_H = atoms.symbols.count(&amp;quot;H&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    types = pipeline.source.data.particles.particle_types_&lt;br /&gt;
&lt;br /&gt;
    radius = {&amp;quot;Au&amp;quot;: 1.8, &amp;quot;Pt&amp;quot;: 1.8, &amp;quot;H&amp;quot;: 0.5}&lt;br /&gt;
    for n in range(1,4):&lt;br /&gt;
        try:&lt;br /&gt;
            el = types.type_by_id_(n).name&lt;br /&gt;
            types.type_by_id_(n).radius = radius[el]&lt;br /&gt;
        except:&lt;br /&gt;
            pass&lt;br /&gt;
&lt;br /&gt;
    colors = np.zeros([len(atoms),3])&lt;br /&gt;
    for i in range(0, len(atoms)):&lt;br /&gt;
        if medoid[i]:&lt;br /&gt;
            k1 = 0.7; k2 = 0.3&lt;br /&gt;
        else:&lt;br /&gt;
            k1 = 1.; k2 = 0.&lt;br /&gt;
        if atoms[i].symbol == &amp;quot;Pt&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([0.8,0.8,0.8]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;Au&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,0]) + k2*np.array([1,0,0])&lt;br /&gt;
        elif atoms[i].symbol == &amp;quot;H&amp;quot;:&lt;br /&gt;
            colors[i] = k1*np.array([1,1,1]) + k2*np.array([1,0,0])&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Color&amp;quot;, data=colors)&lt;br /&gt;
    pipeline.source.data.particles_.create_property(&amp;quot;Transparency&amp;quot;, data=transparency)&lt;br /&gt;
&lt;br /&gt;
    tachyon = TachyonRenderer(shadows=False, direct_light_intensity=1.1)&lt;br /&gt;
&lt;br /&gt;
    pipeline.source.data.cell_.vis.render_cell = False&lt;br /&gt;
&lt;br /&gt;
    vp = Viewport()&lt;br /&gt;
    vp.type = Viewport.Type.Perspective&lt;br /&gt;
#    vp.camera_dir = (2, 3, -1)&lt;br /&gt;
#    vp.zoom_all()&lt;br /&gt;
#    direction = (2, 3, -1)&lt;br /&gt;
    vp.camera_dir = -direction&lt;br /&gt;
    vp.camera_pos = atoms.get_center_of_mass() + direction + direction / np.dot(direction,direction)**0.5 * dist&lt;br /&gt;
    vp.render_image(filename = &amp;quot;sites_png/%i.png&amp;quot; % (k+1), size=(120,120), alpha=False, renderer=tachyon)&lt;br /&gt;
    pipeline.remove_from_scene()&lt;br /&gt;
&lt;br /&gt;
#   Print progress&lt;br /&gt;
#    sys.stdout.write('\rProgress:%6.1f%%' % ((k + 1)*100./len(db)) )&lt;br /&gt;
#    sys.stdout.flush()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot; line=&amp;quot;line&amp;quot;&amp;gt;&lt;br /&gt;
set term pngcairo size 1200,480&lt;br /&gt;
&lt;br /&gt;
set output &amp;quot;mds_sites.png&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set multiplot layout 1,3&lt;br /&gt;
&lt;br /&gt;
set size ratio -1&lt;br /&gt;
set format x &amp;quot;&amp;quot;&lt;br /&gt;
set format y &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
unset tics&lt;br /&gt;
&lt;br /&gt;
set key right box opaque&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Atomic sites according to cluster&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:3 lc var pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 7 ps 3 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2:(sprintf(&amp;quot;%i&amp;quot;, $3+1)) w labels not&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Sites according to composition and surface/bulk&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Pt&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Pt (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;True&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (surface)&amp;quot;, \&lt;br /&gt;
     &amp;quot;../mds_sites.dat&amp;quot; u 1:(stringcolumn(7) eq &amp;quot;Au&amp;quot; &amp;amp;&amp;amp; stringcolumn(5) eq &amp;quot;False&amp;quot; ? $2 : 1/0) pt 7 t &amp;quot;Au (bulk)&amp;quot;, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&lt;br /&gt;
set tics&lt;br /&gt;
&lt;br /&gt;
set title &amp;quot;Local site energy&amp;quot;&lt;br /&gt;
set cblabel &amp;quot;Local GAP energy (eV/atom)&amp;quot;&lt;br /&gt;
plot &amp;quot;../mds_sites.dat&amp;quot; u 1:2:6 palette pt 7 not, \&lt;br /&gt;
     &amp;quot;&amp;quot; u (stringcolumn(4) eq &amp;quot;True&amp;quot; ? $1 : 1/0):2 pt 6 ps 1.4 lw 4 lc &amp;quot;green&amp;quot; t &amp;quot;Medoids&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
	<entry>
		<id>https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=356</id>
		<title>Energetic and structural analysis of a database of PtAu nanoclusters</title>
		<link rel="alternate" type="text/html" href="https://turbogap.fi/wiki/index.php?title=Energetic_and_structural_analysis_of_a_database_of_PtAu_nanoclusters&amp;diff=356"/>
		<updated>2023-08-11T04:37:33Z</updated>

		<summary type="html">&lt;p&gt;Miguel Caro: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;'''WARNING: Tutorial under construction!!!!'''&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to go beyond the core functionality of '''TurboGAP''' and focus instead of the analysis and interpretation of the results that can be obtained with '''TurboGAP'''. We will look at the energetics and structural characteristics of small PtAu alloy nanoparticles (NPs). Note: at the time of writing this tutorial the PtAu GAP used to generate the NPs is not publicly available. For this reason you will need to skip the step on NP generation, but can still download the pregenerated database so that you can run the rest of the tutorial.&lt;br /&gt;
&lt;br /&gt;
'''Tutorial difficulty: &amp;lt;span style=&amp;quot;color:white;background:red&amp;quot;&amp;gt;HARD&amp;lt;/span&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
== Prerequisites for this tutorial ==&lt;br /&gt;
&lt;br /&gt;
* A '''TurboGAP''' installation, only for the NP database generation step, which you can skip by downloading the database;&lt;br /&gt;
* An [https://wiki.fysik.dtu.dk/ase/ ASE] installation;&lt;br /&gt;
* Numpy;&lt;br /&gt;
* A plotting program. I will use gnuplot and provide the corresponding code, but it can be replaced by something else;&lt;br /&gt;
* An [https://github.com/mcaroba/ase_tools ase_tools] installation;&lt;br /&gt;
* A [https://github.com/libatoms/quip quippy] installation (Python interface for the QUIP code);&lt;br /&gt;
* A program for ball-and-stick visualization of atomistic structures. I will use the Python version of [https://www.ovito.org/python-downloads/ Ovito].&lt;/div&gt;</summary>
		<author><name>Miguel Caro</name></author>
		
	</entry>
</feed>