Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.4
rev: v0.16.5
hooks:
- id: ruff
name: ruff lint
Expand Down
172 changes: 85 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,93 +51,91 @@ implementation of the [quantum espresso](https://www.quantum-espresso.org) densi
code in the `pyiron_base` workflow manager is chosen as example. Still the same steps apply for any kind of simulation
code:
```python
import os
import matplotlib.pyplot as plt
import numpy as np
from ase.build import bulk
from ase.calculators.espresso import Espresso
from ase.io import write
from pwtools import io


def write_input(input_dict, working_directory="."):
filename = os.path.join(working_directory, 'input.pwi')
os.makedirs(working_directory, exist_ok=True)
write(
filename=filename,
images=input_dict["structure"],
Crystal=True,
kpts=input_dict["kpts"],
input_data={"calculation": input_dict["calculation"]},
pseudopotentials=input_dict["pseudopotentials"],
tstress=True,
tprnfor=True
)


def collect_output(working_directory="."):
filename = os.path.join(working_directory, 'output.pwo')
try:
return {"structure": io.read_pw_md(filename)[-1].get_ase_atoms()}
except TypeError:
out = io.read_pw_scf(filename)
return {
"energy": out.etot,
"volume": out.volume,
}


def workflow(project, structure):
# Structure optimization
job_qe_minimize = pr.create.job.QEJob(job_name="qe_relax")
job_qe_minimize.input["calculation"] = "vc-relax"
job_qe_minimize.input.structure = structure
job_qe_minimize.run()
structure_opt = job_qe_minimize.output.structure

# Energy Volume Curve
energy_lst, volume_lst = [], []
for i, strain in enumerate(np.linspace(0.9, 1.1, 5)):
structure_strain = structure_opt.copy()
structure_strain = structure.copy()
structure_strain.set_cell(
structure_strain.cell * strain**(1/3),
scale_atoms=True
)
job_strain = pr.create.job.QEJob(
job_name="job_strain_" + str(i)
)
job_strain.input.structure = structure_strain
job_strain.run(delete_existing_job=True)
energy_lst.append(job_strain.output.energy)
volume_lst.append(job_strain.output.volume)

return {"volume": volume_lst, "energy": energy_lst}


from pyiron_base import Project
pr = Project("test")
pr.create_job_class(
class_name="QEJob",
write_input_funct=write_input,
collect_output_funct=collect_output,
default_input_dict={ # Default Parameter
"structure": None,
"pseudopotentials": {"Al": "Al.pbe-n-kjpaw_psl.1.0.0.UPF"},
"kpts": (3, 3, 3),
"calculation": "scf",
},
executable_str="mpirun -np 1 pw.x -in input.pwi > output.pwo",
)

job_workflow = pr.wrap_python_function(workflow)
job_workflow.input.project = pr
job_workflow.input.structure = bulk('Al', a=4.15, cubic=True)
job_workflow.run()

plt.plot(job_workflow.output.result["volume"], job_workflow.output.result["energy"])
plt.xlabel("Volume")
plt.ylabel("Energy")
import os
import matplotlib.pyplot as plt
import numpy as np
from ase.build import bulk
from ase.calculators.espresso import Espresso
from ase.io import write
from pwtools import io


def write_input(input_dict, working_directory="."):
filename = os.path.join(working_directory, "input.pwi")
os.makedirs(working_directory, exist_ok=True)
write(
filename=filename,
images=input_dict["structure"],
Crystal=True,
kpts=input_dict["kpts"],
input_data={"calculation": input_dict["calculation"]},
pseudopotentials=input_dict["pseudopotentials"],
tstress=True,
tprnfor=True,
)


def collect_output(working_directory="."):
filename = os.path.join(working_directory, "output.pwo")
try:
return {"structure": io.read_pw_md(filename)[-1].get_ase_atoms()}
except TypeError:
out = io.read_pw_scf(filename)
return {
"energy": out.etot,
"volume": out.volume,
}


def workflow(project, structure):
# Structure optimization
job_qe_minimize = pr.create.job.QEJob(job_name="qe_relax")
job_qe_minimize.input["calculation"] = "vc-relax"
job_qe_minimize.input.structure = structure
job_qe_minimize.run()
structure_opt = job_qe_minimize.output.structure

# Energy Volume Curve
energy_lst, volume_lst = [], []
for i, strain in enumerate(np.linspace(0.9, 1.1, 5)):
structure_strain = structure_opt.copy()
structure_strain = structure.copy()
structure_strain.set_cell(
structure_strain.cell * strain ** (1 / 3), scale_atoms=True
)
job_strain = pr.create.job.QEJob(job_name="job_strain_" + str(i))
job_strain.input.structure = structure_strain
job_strain.run(delete_existing_job=True)
energy_lst.append(job_strain.output.energy)
volume_lst.append(job_strain.output.volume)

return {"volume": volume_lst, "energy": energy_lst}


from pyiron_base import Project

pr = Project("test")
pr.create_job_class(
class_name="QEJob",
write_input_funct=write_input,
collect_output_funct=collect_output,
default_input_dict={ # Default Parameter
"structure": None,
"pseudopotentials": {"Al": "Al.pbe-n-kjpaw_psl.1.0.0.UPF"},
"kpts": (3, 3, 3),
"calculation": "scf",
},
executable_str="mpirun -np 1 pw.x -in input.pwi > output.pwo",
)

job_workflow = pr.wrap_python_function(workflow)
job_workflow.input.project = pr
job_workflow.input.structure = bulk("Al", a=4.15, cubic=True)
job_workflow.run()

plt.plot(job_workflow.output.result["volume"], job_workflow.output.result["energy"])
plt.xlabel("Volume")
plt.ylabel("Energy")
```

After the definition of the `write_input()` and `collect_output()` function for the quantum espresso DFT simulation code
Expand Down
10 changes: 5 additions & 5 deletions docs/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ and submits this into the queue. I.e. the command running will be

```python
command = (
"python -m pyiron_base.cli wrapper -p "
+ job.working_directory
+ " -j "
+ str(job.job_id)
)
"python -m pyiron_base.cli wrapper -p "
+ job.working_directory
+ " -j "
+ str(job.job_id)
)
```
which essentially does a `job.load()` and a `job.run()` on the compute node.

Expand Down
43 changes: 20 additions & 23 deletions docs/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ from ase.io import write


def write_input(input_dict, working_directory="."):
filename = os.path.join(working_directory, 'input.pwi')
filename = os.path.join(working_directory, "input.pwi")
os.makedirs(working_directory, exist_ok=True)
write(
filename=filename,
images=input_dict["structure"],
Crystal=True,
kpts=input_dict["kpts"],
input_data={"calculation": input_dict["calculation"]},
filename=filename,
images=input_dict["structure"],
Crystal=True,
kpts=input_dict["kpts"],
input_data={"calculation": input_dict["calculation"]},
pseudopotentials=input_dict["pseudopotentials"],
tstress=True,
tprnfor=True
tstress=True,
tprnfor=True,
)
```
The `write_input()` function takes a dictionary `input_dict` and the path to the working directory `working_directory`
Expand All @@ -33,7 +33,7 @@ from pwtools import io


def collect_output(working_directory="."):
filename = os.path.join(working_directory, 'output.pwo')
filename = os.path.join(working_directory, "output.pwo")
try:
return {"structure": io.read_pw_md(filename)[-1].get_ase_atoms()}
except TypeError:
Expand All @@ -54,30 +54,27 @@ of five different strains ranging from 90% to 110%.
import numpy as np


def workflow(project, structure):
# Structure optimization
def workflow(project, structure):
# Structure optimization
job_qe_minimize = project.create.job.QEJob(job_name="qe_relax")
job_qe_minimize.input["calculation"] = "vc-relax"
job_qe_minimize.input.structure = structure
job_qe_minimize.run()
structure_opt = job_qe_minimize.output.structure

# Energy Volume Curve
# Energy Volume Curve
energy_lst, volume_lst = [], []
for i, strain in enumerate(np.linspace(0.9, 1.1, 5)):
structure_strain = structure_opt.copy()
structure_strain.set_cell(
structure_strain.cell * strain**(1/3),
scale_atoms=True
)
job_strain = project.create.job.QEJob(
job_name="job_strain_" + str(i)
structure_strain.cell * strain ** (1 / 3), scale_atoms=True
)
job_strain = project.create.job.QEJob(job_name="job_strain_" + str(i))
job_strain.input.structure = structure_strain
job_strain.run(delete_existing_job=True)
energy_lst.append(job_strain.output.energy)
volume_lst.append(job_strain.output.volume)

return {"volume": volume_lst, "energy": energy_lst}
```
After the definition of the individual functions it is time to put the different parts together. This part again starts
Expand All @@ -93,18 +90,18 @@ pr.create_job_class(
class_name="QEJob",
write_input_funct=write_input,
collect_output_funct=collect_output,
default_input_dict={ # Default Parameter
"structure": None,
"pseudopotentials": {"Al": "Al.pbe-n-kjpaw_psl.1.0.0.UPF"},
default_input_dict={ # Default Parameter
"structure": None,
"pseudopotentials": {"Al": "Al.pbe-n-kjpaw_psl.1.0.0.UPF"},
"kpts": (3, 3, 3),
"calculation": "scf",
"calculation": "scf",
},
executable_str="mpirun -np 1 pw.x -in input.pwi > output.pwo",
)

job_workflow = pr.wrap_python_function(workflow)
job_workflow.input.project = pr
job_workflow.input.structure = bulk('Al', a=4.15, cubic=True)
job_workflow.input.structure = bulk("Al", a=4.15, cubic=True)
job_workflow.run()

plt.plot(job_workflow.output.result["volume"], job_workflow.output.result["energy"])
Expand Down
2 changes: 2 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ on the [python simple queuing system documentation](https://pysqa.readthedocs.io
To test your queuing system configuration use the [python simple queuing system](https://pysqa.readthedocs.io) interface:
```python
from pysqa import QueueAdapter

qa = QueueAdapter(directory="/home/<username>/resources/queues")
print(qa.queue_list)
```
Expand All @@ -157,6 +158,7 @@ code above is executed.
To validate the `~/.pyiron` configuration file, after the `queue.yaml` file is correctly configured you can use:
```python
from pyiron_base import state

print(state.settings._configuration)
```
The `state` object is commonly used to represent the `~/.pyiron` configuration on the python side.
Expand Down
12 changes: 7 additions & 5 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ distribution over multiple compute nodes and handling the submission to the queu
In this example a very simple python function is used:
```python
def test_function(a, b=8):
return a+b
return a + b
```
The run time for this function is far below a millisecond so is it not reasonable to submit it to a remote computing cluster.
This is primarily a demonstration to highlight the capabilities of the `pyiron_base` workflow manager.
Expand All @@ -124,7 +124,7 @@ To submit the function call to a remote HPC cluster the server object of the job
```python
job.server.queue = "slurm"
job.server.cores = 120
job.server.run_time = 3600 # in seconds
job.server.run_time = 3600 # in seconds
```
In this example the `slurm` queue was selected, a total of 120 CPU cores were selected and a run time of one hour was
selected. Still it is important to mention, that assigning 120 CPU cores does not enable parallel execution of the python
Expand All @@ -142,7 +142,8 @@ components, a `write_input()` function, a `collect_output()` function and finall
after the input files were written. The `write_input()` function takes a `input_dict` dictionary and a `working_directory`
as input parameters and writes the input files into the working directory.
```python
import os
import os


def write_input(input_dict, working_directory="."):
with open(os.path.join(working_directory, "input_file"), "w") as f:
Expand Down Expand Up @@ -201,10 +202,11 @@ python commands some users prefer to submit a whole Jupyter notebook at once. So
introduces the `ScriptJob` job type:
```python
from pyiron_base import Project

pr = Project(path="demo")
script = pr.create.job.ScriptJob(job_name="script")
script.script_path = "demo.ipynb"
script.input['my_variable'] = 5
script.input["my_variable"] = 5
script.run()
```
The `ScriptJob` jobs take a jupyter notebook as an input `script.script_path` as well as a series of input parameters
Expand All @@ -227,7 +229,7 @@ The pyiron table object takes three kinds of inputs. The first is a filter funct
the following functions are applied on. The filter function is not mandatory still it is very helpful in particular when
a large number of jobs are created in a given project.
```python
def myfilter(job):
def myfilter(job):
return "test_function" in job.job_name
```
The second part are the selection of functions which are applied on all job objects in a given project. Again it takes
Expand Down
9 changes: 5 additions & 4 deletions specs/job_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
In pyiron jobs can be very conveniantly be initialized from the `Project` instance:
```python
from pyiron import Project
pr = Project('test')

job = pr.create.job.MyJob('job_name')
pr = Project("test")

job = pr.create.job.MyJob("job_name")
```

### Job registration
Expand All @@ -15,7 +16,7 @@ To enable this feature without the burden to `import` all available `JobClasses`
```python
from pyiron_base import JobType

JobType.register('pyiron_module.submodule.my_job_class_module', 'MyJobClass')
JobType.register("pyiron_module.submodule.my_job_class_module", "MyJobClass")
```

The register `@classmethod` performs some (minor) sanity checks, in particular raises an error whenever there is already such a `MyJobClass` registered with a different class. To replace an already existing job class, an `overwrite` argument is available.
Expand All @@ -38,6 +39,6 @@ class MyBaseJobClass(GenericJob):

It is under consideration to enhance the job registration by providing a scope for the job a la
```python
JobType.register('module.path', 'JobClass', branch='atomistics', tag='DFT')
JobType.register("module.path", "JobClass", branch="atomistics", tag="DFT")
```
which would allow to filter the available job types by these extra inforation.
Loading