Skip to content

Commit

Permalink
Feature/mutable helper class (#25)
Browse files Browse the repository at this point in the history
* Introducing the kind checks.
In principle, we can also not define the kinds, in that case it is up to the plugin
to use the get_kinds() method of the StructureData to define kinds.

* Starting to migrate to mkdocs

* StructureData developer guide,
missing: PropertyCollector and properties.

* First profiling for the atomistic vs orm StructureData generation.
Need more profiling for low-atoms systems, where it seems orm is better

* Comment on profiling

* Additional notes on profilings

* Adding a last scaling test, to be commented and inserted in the github issue

* Adding the automatic kind generation.
Missing: deactivate this automatism.

* Adding the `allow_kinds` input in the StructureData init.
used to deactivate the automatic kind generation.

* Adding the `to_legacy_structuredata` method in atomistc.StructureData

This is provided in temporary way in order to make easier to
migrate the plugin gradually to this new Data.

In a first step, we should use this method anytime the plugin uses
the StructureData. Then add the properties setting.

* - Adding the developer docs
- Adding some bugfixing for the empty StructureData init.

* Added user guide with examples

* Fixing #16

* This solves issue #10

This is implemented following the pydantic PR for aiida-core: aiidateam/aiida-core#6255

* New implementation, following the pydantic PR.

adding the `charges` properties, in the same way as we have the cell and
pbc: hard-coding the getter and setter methods.

For now, no data validation is there.

* Added the support to read charges also from ASE Atoms object.
Added the map_kinds method, which return the mapping for the kinds as
contained in the sites attribute.

* Reverting back to non-pydantic implementation (Just removing the Model
attribute from the StructureData class, nothing else changed)

Added also the charges in the get_ase method.

* Attaching the properties to the Sites.

- code refactored: now `StructureData` and `Site` classes are in separated files, as well as the utils; `Kind` class is removed
- `Site` class now contains the properties: `symbol`, `position`, `mass`, `charge`, `magnetization`, `kind_name`; `kind_name` should be removed. The properties are defined via the `property` decorator
- `from_ase`, `to_ase` methods are added
- `from_pymatgen`, `to_pymatgen` methods are added (**TOBE fixed to add also the magnetization**)
- `from_file`, `to_file` methods are added. They rely on ASE `io.read` and `io.write` functions.
- the `orm.StructureData` (the old one) now has a method `to_atomistic`
- the `utils.py` file contains also the `to_kinds` and `get_kinds` functions, to automatically generate kinds. The user should only use the `get_kinds` function. **TOBE refined: kind names, and remove the other kinds methods from the StructureData class.**
- the `StructureData` constructor now works in this way: we provide the pbc, cell. Then we should use the `append_atom` method afterwards. **TOBE discussed, I guess we cann add lists contaning site-wise value for properties**
- slicing of structure data: defined the __getitem__ method, to obtain a sliced structuredata instance from the initial one.
- TOBE added: `to_supercell` method.

* adding the possiblity to provide lists in the constructor BUT we are
gonna change this by providing the possibility to provide list of sites
(each of them is a dictionary), in the same format as they are
represented in the database

* Moving the get_kinds and _to_kinds (now a private method) into the
StructureData class.

* Removed kind related methods which now are no more needed in the
StructureData

* Utilities for cell and sites update

* New StructureData and StructureDataMutable

both inheriting from StructureDataCore.
Added properties:
- charge
- magmom

Relevant added methods:

- from/to ASE/Pymatgen
- to_legacy
- to_structuredata
- to_mutable_structuredata

Added single page for API tutorial.

Added tests for basic functionalities of both Structure Classes.

* Allowing also magmom to be provided as floats: they
are then stored as [magmom,0,0]

* Improving mutability and minor changes.

Mutability is improved by removing all the setter methods, both in
StructureDataCore and Site classes. Moreover, lists and tuples are
returned as np.array with flags.writeable=False, meaning that we cannot
even modify the internal arrays/lists.

Other minor changes:

- Now in StructureDataCore we have to define the pbc, cell and sites
keywords to initialise it (and so the other subclasses), this is
helpful to then document the inputs.
- `get_kinds` routine fixed to work also with magmoms (arrays)
- docu and tests updated with respect to these changes.

* Adding type hinting and minor fixes

removing mutable attribute in Site class
adding a get_global_properties method in StructureDataCore,
not used now but in the future to have a list of the properties in the
database (not only attached to the single sites), to make easy to query
wrt a property.

* Improving flexibility, automatic get_kinds and pymatgen fixings.

Providing full flexibility in the StructureDataMutable while preserving same data structure of StructureData.

- adding the parent and index when we initialise the Site class (i.e. when we access structure.sites). This is helpful to be able to change directly properties in the StructureDataMutable class.
- in the same direction, we implemented the ObservedArray class which basically allows to trigger the setter methods of arrays/lists even if we modify only one element (for example: structure.sites[0].position[0] = 5). In this way, changes are written into the structure._data, which represents the real data stored in the instance.

`get_kinds` method now is stable.

Fixing pymatgen problems with oxi_state and magmom.

* Adding in the tutorial markdown also the get_kinds method, and minimal
update of the pytests.
  • Loading branch information
mikibonacci authored Jul 18, 2024
1 parent 86af31b commit 2b51e99
Show file tree
Hide file tree
Showing 17 changed files with 825 additions and 255 deletions.
26 changes: 25 additions & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,33 @@ def example_structure_dict():
"position": [0.0, 0.0, 0.0],
"mass": 63.546,
"charge": 1.0,
"magmom": 0.0,
"magmom": [0,0,0],
}
],
}

return structure_dict

@pytest.fixture
def example_structure_dict_for_kinds():
"""
Return the dictionary of properties as to be used in the standards tests.
"""
structure_dict = {'pbc': (True, True, True),
'cell': [[2.8403, 0.0, 1.7391821518091137e-16],
[-1.7391821518091137e-16, 2.8403, 1.7391821518091137e-16],
[0.0, 0.0, 2.8403]],
'sites': [{'symbol': 'Fe',
'weights': 55.845,
'position': [0.0, 0.0, 0.0],
'charge': 0.0,
'magmom': [2.5, 0.1, 0.1],
'kind_name': 'Fe'},
{'symbol': 'Fe',
'weights': 55.845,
'position': [1.42015, 1.42015, 1.4201500000000002],
'charge': 0.0,
'magmom': [2.4, 0.1, 0.1],
'kind_name': 'Fe'}]}

return structure_dict
90 changes: 82 additions & 8 deletions docs/docs/source/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ structure_dict = {
{
'symbol':'Si',
'position':[3/4, 3/4, 3/4],
'charge': 0,
},
{
'symbol':'Si',
Expand All @@ -32,8 +33,8 @@ structure_dict = {
],
}
mutable_structure = StructureDataMutable(data=structure_dict)
structure = StructureData(data=structure_dict)
mutable_structure = StructureDataMutable(**structure_dict)
structure = StructureData(**structure_dict)
```

When this dictionary is provided to the constructor, validation check for each of the provided property is done (**for now, only pbc and cell**).
Expand Down Expand Up @@ -74,7 +75,7 @@ print(structure.sites[0].symbol,structure.sites[0].position) # output: Si [0.75
All the properties can be accessed via tab completion, and a list of the supported properties can be accessed via `structure.get_property_names()`.

For now, other supported properties are `charge` (not yet `tot_charge`), `kind_name`, `mass`.
For example, we can initialize a charged structure in this way:
We can initialize a charged structure in this way:


```python=
Expand All @@ -96,12 +97,14 @@ structure_dict = {
],
}
mutable_structure = StructureDataMutable(data=structure_dict)
structure = StructureData(data=structure_dict)
mutable_structure = StructureDataMutable(**structure_dict)
structure = StructureData(**structure_dict)
```

then, `structure.sites[0].charge` will be equal to 1. When the plugins will be adapted, with this information we can build the correct input file for the corresponding quantum engine.

To access the properties summarized for all the sites, you can use methods like `get_charges`, `get_magmoms`, `get_kind_names`. You can also use the more general `get_site_property` method (in this case, you should provide the name of the property as input: 'charge', 'magmom', 'kind_name').

### Initialization from ASE or Pymatgen

If we already have an ASE Atoms or a Pymatgen Structure object, we can use the `from_ase` and `from_pymatgen` methods:
Expand All @@ -115,7 +118,7 @@ atoms.set_tags(["2"])
mutable_structure = StructureDataMutable.from_ase(atoms)
structure = StructureData.from_ase(atoms)
structure.to_dict()
structure.to_dict(detect_kinds=False)
```

This should have as output:
Expand All @@ -131,6 +134,8 @@ This should have as output:
'magmom': 0.0}]}
```

The `detect_kinds`parameter, if `True`, provides automatically detected kind_names (and corresponding properties). For more control on the automatic kinds generation, see the corresponding section below.

This support also the properties like charges (coming soon: magmoms and so on). In the same way, for pymatgen we can proceed as follows:

```python=
Expand Down Expand Up @@ -169,7 +174,7 @@ the output being:

Moreover, we also provide `to_ase` and `to_pymatgen` methods to obtain the corresponding instances. Also this methods for now only support charges, among the new properties.

## Mutation of a structure
## Mutation of a `StructureDataMutable` instance

Let's suppose you want to update some property in the `StructureData` before to use it in a calculation. You cannot. The way to go is either to use ASE or Pymatgen to modify you object and store it back into `StructureData`, or to use the `StructureDataMutable` and its mutation methods, and then convert it into `StructureData`.
The latter method is the preferred one, as you then have support also for additional properties (to be implemented) like hubbard, which is not supported by the former.
Expand Down Expand Up @@ -208,15 +213,84 @@ mutable_structure.add_atom({
})
```

It is also possible to directly access the single properties and modify them, but we strongly suggest to use the dedicated `set_*`methods or the `add_atom`, `pop_atom`, `update_site`. `pbc` and `cell` can be modified only via the corresponding `set_pbc` and `set_cell` methods.

## Slicing a structure

It is possible to *slice* a structure, i.e. returning only a part of it (in terms of sites). Let's that you have an heterostructure and you want to obtain only the first layer, composed of the first 4 atoms over 10 total. This works for both `StructureDataMutable` and `StructureData` (we return a new `StructureData` instance).
It is possible to *slice* a structure, i.e. returning only a part of it (in terms of sites). Let's suppose that you have an heterostructure and you want to obtain only the first layer, composed of the first 4 atoms over 10 total. This works for both `StructureDataMutable` and `StructureData` (we return a new `StructureData` instance).

```python=
sliced_structure = structure[:4]
```

## Passing from StructureData to StructureDataMutable and viceversa

```python=
mutable_structure.to_structuredata() # returns an instance of StructureData
structure.to_mutable_structuredata() # returns an instance of StructureDataMutable
```

## Automatic kinds generation

It is possible to generate the kind_names and the corresponding mapped properties for a given structure.
You can do it by using the `get_kinds` method.

```python=
Fe_BCC_dictionary = {'pbc': (True, True, True),
'cell': [[2.8403, 0.0, 1.7391821518091137e-16],
[-1.7391821518091137e-16, 2.8403, 1.7391821518091137e-16],
[0.0, 0.0, 2.8403]],
'sites': [{'symbol': 'Fe',
'weights': 55.845,
'position': [0.0, 0.0, 0.0],
'charge': 0.0,
'magmom': [2.5, 0.1, 0.1],
'kind_name': 'Fe'},
{'symbol': 'Fe',
'weights': 55.845,
'position': [1.42015, 1.42015, 1.4201500000000002],
'charge': 0.0,
'magmom': [2.4, 0.1, 0.1],
'kind_name': 'Fe'}]}
mutable_structure = StructureDataMutable(**Fe_BCC_dictionary)
new_sites = mutable_structure.get_kinds(ready_to_use=True)
```

By setting `ready_to_use`to True, we provide a list of sites ready to be used in our structure.
We then obtain:

```shell=
[{'kind_name': 'Fe0',
'mass': 55.845,
'charge': 0.0,
'magmom': [2.5, 0.1, 0.1],
'symbol': 'Fe',
'position': [0.0, 0.0, 0.0]},
{'kind_name': 'Fe1',
'mass': 55.845,
'charge': 0.0,
'magmom': [2.4, 0.1, 0.1],
'symbol': 'Fe',
'position': [1.42015, 1.42015, 1.4201500000000002]}]
```

so we can set the new sites:

```python=
mutable_structure.clear_sites()
new_sites = mutable_structure.get_kinds(ready_to_use=True)
for site in new_sites:
mutable_structure.add_atom(site)
```


It is possible to provide custom thresholds, exclude properties from this detection and also to provide already some kinds (kind_tags) to be blocked. *Explanation TOBE extended.*

## Backward compatibility support

We can use the `to_legacy` method to return the corresponding `orm.StructureData` instance, in case a given plugin does not yet support the new `StructureData`.

## How to Query StructureData using properties

TOBE added.
34 changes: 34 additions & 0 deletions examples/structuredata/generation_and_kinds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from aiida import orm, load_profile
load_profile()

from aiida_atomistic.data.structure import StructureData

unit_cell = [[3.5, 0.0, 0.0], [0.0, 3.5, 0.0], [0.0, 0.0, 3.5]]
atomic_positions = [[0.0, 0.0, 0.0],[1.5, 1.5, 1.5]]
symbols = ["Li"]*2
mass = [6.941,6.941]
charge = [1,0]

properties = {
"cell":{"value":unit_cell},
"pbc":{"value":[True,True,True]},
"positions":{"value":atomic_positions,},
"symbols":{"value":symbols},
"mass":{"value":mass,},
"charge":{"value":charge}
}

structure = StructureData(
properties=properties
)
kinds = structure.get_kinds()

print("Kinds: ", kinds)

print("Charge default threshold: ",structure.properties.charge.default_kind_threshold)

kinds = structure.get_kinds(custom_thr={"charge":2})
print("New kinds with updated charge threshold (2): ", kinds)

kinds = structure.get_kinds(exclude=["charge"])
print("New kinds excluding the charge property: ", kinds)
Loading

0 comments on commit 2b51e99

Please sign in to comment.