Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rob/mdanse trajectory filter #615

Open
wants to merge 128 commits into
base: protos
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
b71e31a
initial filter class
RobBuchananCompPhys Sep 16, 2024
ae5dd6c
updated filter classes for better design, initial filter gui widget
RobBuchananCompPhys Sep 17, 2024
01c7e46
update filter widget and configurator
RobBuchananCompPhys Sep 17, 2024
23890e2
futher work on widget and configurator
RobBuchananCompPhys Sep 17, 2024
e582838
change method name
RobBuchananCompPhys Sep 17, 2024
c532584
filter designer widget initial commit
RobBuchananCompPhys Sep 24, 2024
7d9607b
input widget, job
RobBuchananCompPhys Nov 28, 2024
207ded6
add basic unit tests for functioning of filter class, with methane (h…
RobBuchananCompPhys Dec 9, 2024
db423fa
added tooltips from filter input descriptions, string+json representa…
RobBuchananCompPhys Dec 9, 2024
897dd16
run job - apply filter and write output trajectory
RobBuchananCompPhys Dec 9, 2024
e1c293a
get timestep and n steps if not defined
RobBuchananCompPhys Dec 13, 2024
277d3c0
fix filter designer crash when returning to input page
RobBuchananCompPhys Dec 13, 2024
689270a
fix array not json serializable when dumping bandpass filter cutoffs
RobBuchananCompPhys Dec 13, 2024
8b55b35
get filtered trajectories into new array
RobBuchananCompPhys Dec 13, 2024
d5a957f
remove unused method
RobBuchananCompPhys Dec 13, 2024
cf7f5a9
actually use number of simulation steps, not number of atoms (!)
RobBuchananCompPhys Dec 18, 2024
9a07024
standalone method to compute power spectrum for trajectory
RobBuchananCompPhys Dec 18, 2024
5d9ef30
cleaner array initialisation
RobBuchananCompPhys Dec 18, 2024
1497a2a
build out support for superimposed plot of trajectory power spectral …
RobBuchananCompPhys Dec 18, 2024
c007d34
compute attenuation graph
RobBuchananCompPhys Dec 19, 2024
d995824
compute attenuation graph part 2 - ensure proper conversion between f…
RobBuchananCompPhys Dec 19, 2024
a204069
remove unused arg
RobBuchananCompPhys Dec 19, 2024
5d72387
set x axis limits
RobBuchananCompPhys Dec 20, 2024
468b884
write filter configuration to metadata
RobBuchananCompPhys Dec 23, 2024
5c24640
move to trajectory category
RobBuchananCompPhys Dec 23, 2024
530e44e
frames configurator
RobBuchananCompPhys Dec 23, 2024
1f1357d
remove rough script
RobBuchananCompPhys Dec 23, 2024
255db97
temporarily move trajectory filter job back to original categories
RobBuchananCompPhys Jan 2, 2025
e7d17ee
complete docstrings for signal methods
RobBuchananCompPhys Jan 3, 2025
caabc24
numpy docstrings for ui
RobBuchananCompPhys Jan 6, 2025
83aae39
Change category
RobBuchananCompPhys Jan 6, 2025
41bcb1a
correct form of z-transform polynomial string
RobBuchananCompPhys Jan 6, 2025
b39be9e
fix key, data path
RobBuchananCompPhys Jan 13, 2025
e67fc89
unit test data path
RobBuchananCompPhys Jan 13, 2025
7f79594
fix key for power spectrum
RobBuchananCompPhys Jan 13, 2025
732d6a7
add error checking to configurator
RobBuchananCompPhys Jan 13, 2025
8fafca6
rmeove unused settings
RobBuchananCompPhys Jan 13, 2025
721c01e
keep some settings required for power spectrum calc
RobBuchananCompPhys Jan 13, 2025
a9ef6f3
restore configurator
RobBuchananCompPhys Jan 13, 2025
68e315c
dynamic x axis limits
RobBuchananCompPhys Jan 13, 2025
f5739de
further work on power spectrum calc
RobBuchananCompPhys Jan 13, 2025
da40a0a
x limit value
RobBuchananCompPhys Jan 13, 2025
14ce575
add factor to frequency axis
RobBuchananCompPhys Jan 16, 2025
8187bbc
add back initial positions to atomic trajectories when filter is not …
RobBuchananCompPhys Jan 16, 2025
b95c171
don't store atom initial positions in big array, get them on the fly
RobBuchananCompPhys Jan 17, 2025
2a190ce
basic unit tests
RobBuchananCompPhys Jan 17, 2025
511743c
get attribute in case undefined on filter instance
RobBuchananCompPhys Jan 27, 2025
aca968e
digital filter overrides
RobBuchananCompPhys Jan 27, 2025
9a0ca82
incorporate digital filters in ui
RobBuchananCompPhys Jan 27, 2025
8a373a6
preferences tooltips
RobBuchananCompPhys Jan 29, 2025
ee6a4a2
inline preference tooltips
RobBuchananCompPhys Jan 29, 2025
9352f46
apply black formatting
RobBuchananCompPhys Feb 3, 2025
53c6234
black format 2
RobBuchananCompPhys Feb 3, 2025
7489410
fix typo in filter name
RobBuchananCompPhys Feb 3, 2025
93c6f44
refactor 1
RobBuchananCompPhys Feb 4, 2025
4604c00
refactor 2
RobBuchananCompPhys Feb 4, 2025
afabb72
refactor 3
RobBuchananCompPhys Feb 4, 2025
09bf415
black reformat
RobBuchananCompPhys Feb 4, 2025
be222d0
use single quote to access property inside f string
RobBuchananCompPhys Feb 4, 2025
928aed7
refactor
RobBuchananCompPhys Feb 4, 2025
73cef58
remove unsupported operator
RobBuchananCompPhys Feb 4, 2025
8c9002a
fix not callable error
RobBuchananCompPhys Feb 4, 2025
6c57490
add in missing job
RobBuchananCompPhys Feb 4, 2025
77b4e50
defaults.keys() -> defaults
RobBuchananCompPhys Feb 10, 2025
1718c07
remove f string for empty string
RobBuchananCompPhys Feb 10, 2025
8635b00
reduce to enumerate
RobBuchananCompPhys Feb 10, 2025
f835d17
unpack trajectories on read
RobBuchananCompPhys Feb 10, 2025
bf1c4a7
faster return dictionary
RobBuchananCompPhys Feb 10, 2025
5c4f92f
set constructor args
RobBuchananCompPhys Feb 10, 2025
94a5ee7
enum correct style
RobBuchananCompPhys Feb 10, 2025
86ab4dc
remove default None from get
RobBuchananCompPhys Feb 10, 2025
f52818b
dict() -> {}
RobBuchananCompPhys Feb 10, 2025
d7827b9
use f strings
RobBuchananCompPhys Feb 10, 2025
185a40e
assign dict rather than initialising
RobBuchananCompPhys Feb 10, 2025
11e05ca
upper snake case
RobBuchananCompPhys Feb 10, 2025
68b6147
refactor
RobBuchananCompPhys Feb 10, 2025
223296e
simplify
RobBuchananCompPhys Feb 10, 2025
39dd648
remove redundant iterator
RobBuchananCompPhys Feb 10, 2025
5af14dc
refactor setattr
RobBuchananCompPhys Feb 10, 2025
2ac8601
remove unused import
RobBuchananCompPhys Feb 10, 2025
bcdb90c
fix dict iterator
RobBuchananCompPhys Feb 10, 2025
7f3cc7e
fix enum
RobBuchananCompPhys Feb 10, 2025
06ed240
fix analysis test
RobBuchananCompPhys Feb 10, 2025
df9b262
return dict as string
RobBuchananCompPhys Feb 10, 2025
644f0f7
list comprehension with flattened array
RobBuchananCompPhys Feb 10, 2025
2675968
redundant set cast
RobBuchananCompPhys Feb 10, 2025
4041409
fix configurator json string decoding
RobBuchananCompPhys Feb 10, 2025
e08b5f2
error checking configurator dict value
RobBuchananCompPhys Feb 10, 2025
a347f53
fix conditional
RobBuchananCompPhys Feb 14, 2025
54669a8
get periodic configuration
RobBuchananCompPhys Feb 14, 2025
47d5b7d
filter abstract base class
RobBuchananCompPhys Feb 14, 2025
7cb6a62
default settings set as class attribute
RobBuchananCompPhys Feb 14, 2025
e3fa4ea
replace lambda assignment with def statement
RobBuchananCompPhys Feb 14, 2025
5cca19e
rename variable due to conflict
RobBuchananCompPhys Feb 14, 2025
9afca7e
call Filter classmethod on cls
RobBuchananCompPhys Feb 17, 2025
fa24522
make method an instance method
RobBuchananCompPhys Feb 17, 2025
97d20dd
fix numpy doc
RobBuchananCompPhys Feb 17, 2025
ced29e2
triple quote strings for filter description
RobBuchananCompPhys Feb 17, 2025
0629d16
removed non-existent method
RobBuchananCompPhys Feb 17, 2025
917a4a3
use tmp_path fixture
RobBuchananCompPhys Feb 17, 2025
123bee1
refactor array initialisation
RobBuchananCompPhys Feb 21, 2025
fd75a29
list comprehension for atom trajectory components
RobBuchananCompPhys Feb 21, 2025
6bbdf1e
refactor
RobBuchananCompPhys Feb 21, 2025
56ba4d2
properties as attributes
RobBuchananCompPhys Feb 24, 2025
8e598e3
fix missing import
RobBuchananCompPhys Feb 24, 2025
76341e4
black
RobBuchananCompPhys Feb 24, 2025
17cec74
ruff format
RobBuchananCompPhys Feb 24, 2025
9ca167e
add widget and configurator
RobBuchananCompPhys Feb 25, 2025
e7aa07f
fix output data in power spectrum calc
RobBuchananCompPhys Feb 26, 2025
c4856fc
remove unnecessary f-string
RobBuchananCompPhys Feb 26, 2025
e6030b4
removed unused variable
RobBuchananCompPhys Feb 26, 2025
5fd8fb7
removed unused variable
RobBuchananCompPhys Feb 26, 2025
17ed8db
fix trajectory filter summary string
RobBuchananCompPhys Feb 27, 2025
4946bf8
not mixing class attributes
RobBuchananCompPhys Feb 27, 2025
31bb453
remove unnecessary '\n' from docstrings
RobBuchananCompPhys Feb 27, 2025
8cc9854
numpy docstrings
RobBuchananCompPhys Feb 27, 2025
754ef88
ruff
RobBuchananCompPhys Feb 27, 2025
e0a4a28
comment
RobBuchananCompPhys Feb 27, 2025
bde9ab8
more ruff
RobBuchananCompPhys Feb 27, 2025
da45475
frequencies should be in terahertz!
RobBuchananCompPhys Feb 27, 2025
556b0e4
output chemical system
RobBuchananCompPhys Mar 3, 2025
0652b41
get new chemical system name
RobBuchananCompPhys Mar 3, 2025
879b46c
toolbar
RobBuchananCompPhys Mar 3, 2025
e2c4162
filter desginer stacked group boxes
RobBuchananCompPhys Mar 4, 2025
76e8b85
remove commented out line
RobBuchananCompPhys Mar 4, 2025
ea0c60b
selected atoms
RobBuchananCompPhys Mar 4, 2025
5eb92c2
trajectory filter test with selected atoms
RobBuchananCompPhys Mar 5, 2025
68377e1
ruff format
RobBuchananCompPhys Mar 5, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# This file is part of MDANSE.
#
# MDANSE is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
import json
from MDANSE.Framework.Configurators.IConfigurator import IConfigurator
from MDANSE.Mathematics.Signal import (
DEFAULT_FILTER,
filter_default_attributes,
filter_description_string,
)


class TrajectoryFilterConfigurator(IConfigurator):
"""This configurator allows the application of a filter to the trajectory of atoms in the simulation.

Attributes
----------
_default : str
The defaults selection setting.
"""

_default_filter = DEFAULT_FILTER

_settings = filter_default_attributes()

@classmethod
def get_default(cls) -> str:
"""Return the default filter string.

Returns
-------

A string representation of the default filter settings dictionary
"""
return cls._default

_default = filter_description_string()

def configure(self, value: str):
"""Configure an input value.

Parameters
----------
value : str
The selection setting in a json readable format.
"""
self._settings = value

try:
dict_value = json.loads(value)

if not {"filter", "attributes"} <= dict_value.keys():
self.error_status = f"The dictionary \n{dict_value}\n does not contain the expected keys"

except (TypeError, ValueError):
self.error_status = f"Value \n{value}\n in {self} is not of correct format (expected JSON string)"

self.error_status = "OK"
self["value"] = self._settings
279 changes: 279 additions & 0 deletions MDANSE/Src/MDANSE/Framework/Jobs/TrajectoryFilter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
# This file is part of MDANSE.
#
# MDANSE is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
import collections
import copy
import json

import numpy as np

import h5py
from MDANSE.Framework.Formats import HDFFormat
from MDANSE.Chemistry.ChemicalSystem import ChemicalSystem
from MDANSE.Framework.Jobs.IJob import IJob
from MDANSE.Mathematics.Signal import FILTER_MAP
from MDANSE.MolecularDynamics.Configuration import (
RealConfiguration,
PeriodicRealConfiguration,
)
from MDANSE.MolecularDynamics.Trajectory import TrajectoryWriter
from MDANSE.MLogging import LOG


class TrajectoryFilter(IJob):
"""
Design and apply a filter for the atomic trajectories.
"""

label = "Trajectory Filter"

category = (
"Analysis",
"Trajectory",
)

ancestor = ["hdf_trajectory", "molecular_viewer"]

settings = collections.OrderedDict()
settings["trajectory"] = ("HDFTrajectoryConfigurator", {})
settings["frames"] = (
"CorrelationFramesConfigurator",
{"dependencies": {"trajectory": "trajectory"}},
)
settings["instrument_resolution"] = (
"InstrumentResolutionConfigurator",
{"dependencies": {"trajectory": "trajectory", "frames": "frames"}},
)
settings["projection"] = (
"ProjectionConfigurator",
{"label": "project coordinates"},
)
settings["trajectory_filter"] = (
"TrajectoryFilterConfigurator",
{"dependencies": {"trajectory": "trajectory"}},
)
settings["atom_selection"] = (
"AtomSelectionConfigurator",
{"dependencies": {"trajectory": "trajectory"}},
)
settings["weights"] = (
"WeightsConfigurator",
{
"default": "atomic_weight",
"dependencies": {
"trajectory": "trajectory",
"atom_selection": "atom_selection",
},
},
)
settings["output_files"] = (
"OutputTrajectoryConfigurator",
{"format": "MDTFormat"},
)
settings["running_mode"] = ("RunningModeConfigurator", {})

def initialize(self):
"""
Initialize the input parameters and analysis self variables
"""
super().initialize()

self.numberOfSteps = self.configuration["atom_selection"]["selection_length"]

self._atoms = self.configuration["trajectory"][
"instance"
].chemical_system.atom_list

self._selected_atoms = [
self._atoms[i]
for i in np.array(self.configuration["atom_selection"]["indices"]).flatten()
]

# This stores the trajectory (position array) of atoms by x, y, z component, to be filtered
self.atomic_trajectory_array = np.zeros(
(len(self._selected_atoms), 3, len(self.configuration["frames"]["value"]))
)

def run_step(self, index):
"""
Runs a single step of the job.

:Parameters:
#. index (int): The index of the step.
"""
LOG.debug(f"Running step: {index}")
trajectory = self.configuration["trajectory"]["instance"]

# get atom index
indexes = self.configuration["atom_selection"]["indices"][index]

series = trajectory.read_com_trajectory(
indexes,
first=self.configuration["frames"]["first"],
last=self.configuration["frames"]["last"] + 1,
step=self.configuration["frames"]["step"],
)

self.atomic_trajectory_array[index] = series.T

return index, None

def combine(self, index, x):
"""
Combines returned results of run_step.
:Parameters:
#. index (int): The index of the step.
#. x (any): The returned result(s) of run_step
"""
pass

def finalize(self):
"""
Finalizes the calculations (e.g. averaging the total term, output files creations ...).
"""

# Get filter class and instantiate filter object
filter_config = json.loads(self.configuration["trajectory_filter"]["value"])

filter_class, filter_attributes = (
FILTER_MAP[filter_config["filter"]],
filter_config["attributes"],
)

if {"n_steps", "time_step_ps"} not in set(filter_attributes.keys()):
filter_attributes.update(
{
"n_steps": self.configuration["trajectory"]["length"],
"time_step_ps": self.configuration["trajectory"]["md_time_step"],
}
)

filter = filter_class(**filter_attributes)

trajectories = copy.deepcopy(self.atomic_trajectory_array)

# Apply filter (only apply initial position offset to atoms if filter is not lowpass and setting has been applied)
filtered_coords = apply(
filter,
trajectories,
apply_offsets=filter_attributes.get("attenuation_type", "bandpass")
!= "lowpass",
)

# Create new chemical system for output trajectory
name = self.configuration["output_files"]["file"].name.split(".").pop(0)
if not isinstance(name, str):
name = "filtered_traj_chemical_system"
output_chemical_system = ChemicalSystem(name)
output_chemical_system.initialise_atoms(self._selected_atoms)

# Create trajectory writer object
self._output_trajectory = TrajectoryWriter(
self.configuration["output_files"]["file"],
output_chemical_system,
filter_attributes["n_steps"],
None,
positions_dtype=self.configuration["output_files"]["dtype"],
compression=self.configuration["output_files"]["compression"],
)

# Write trajectory
write_filtered_trajectory(
parent_configuration=self.configuration,
nsteps=filter_attributes["n_steps"],
filtered_coordinates=filtered_coords,
output_trajectory=self._output_trajectory,
)

# The input trajectory is closed.
self.configuration["trajectory"]["instance"].close()

# The output trajectory is closed.
self._output_trajectory.close()

# Write the filter metadata to output
outputFile = h5py.File(self.configuration["output_files"]["file"], "r+")
outputFile.create_group("metadata").create_dataset(
"trajectory_filter",
(1,),
data=filter.__str__(),
dtype=h5py.string_dtype(),
)

outputFile.close()

super().finalize()


def apply(filter, trajectories, apply_offsets: bool) -> np.ndarray:
""" """
output_trajectory_array = np.zeros(trajectories.shape)

for at, (x, y, z) in enumerate(trajectories):
# Store initial positions
offsets = np.array([x[0], y[0], z[0]])

for i, component in enumerate((x, y, z)):
output = filter.apply(component)

if apply_offsets:
output += offsets[i]
output_trajectory_array[at][i] = output

return output_trajectory_array


def write_filtered_trajectory(
parent_configuration,
nsteps: int,
filtered_coordinates: np.ndarray,
output_trajectory: TrajectoryWriter,
) -> None:
""" """
time = parent_configuration["frames"]["time"]
dt = time[1] - time[0]
for index in range(nsteps):
frame_coordinates = [
(x[index], y[index], z[index]) for (x, y, z) in filtered_coordinates
]

# The filtered configuration coordinates at the current frame index
filtered_configuration_coordinates = np.array(frame_coordinates)

filtered_configuration = get_output_configuration(
parent=parent_configuration["trajectory"]["instance"].configuration(
parent_configuration["frames"]["value"][0]
),
output_chemical_system=output_trajectory.chemical_system,
output_coordinates=filtered_configuration_coordinates,
)

output_trajectory.chemical_system.configuration = filtered_configuration

output_trajectory.dump_configuration(
filtered_configuration,
dt * index,
units={"time": "ps", "unit_cell": "nm", "coordinates": "nm"},
)


def get_output_configuration(parent, output_chemical_system, output_coordinates):
""" """
if parent.is_periodic:
return PeriodicRealConfiguration(
output_chemical_system, output_coordinates, parent.unit_cell
)

return RealConfiguration(output_chemical_system, output_coordinates)
Loading