Skip to content

Conversation

cphyc
Copy link
Member

@cphyc cphyc commented Jul 16, 2025

PR Summary

This adds a simple syntax to define a new derived field.
This allows to write:

ds = yt.load(...)

ds.fields.gas.density_square = ds.fields.gas.density * ds.fields.gas.density
ds.add_field(("gas", "double_density"), ds.fields.gas.density * 2)

ds.r["gas", "density_square"]
ds.r["gas", "double_density"]

Open for discussion before I write the documentation (or we kill that PR :) )

PR Checklist

  • New features are documented, with docstrings and narrative docs
  • Adds a test for any bugs fixed. Adds tests for new features.

@cphyc cphyc added enhancement Making something better new feature Something fun and new! labels Jul 16, 2025
@matthewturk
Copy link
Member

I genuinely love this and I find it to be an incredibly nice, ergonomic way to add fields.

@matthewturk
Copy link
Member

I still think this is great! Sorry for the noise, but I am demonstrating commenting from the command line.

@cphyc cphyc force-pushed the simple-add-field branch from c8e0cfc to 5b07296 Compare July 17, 2025 15:24
This is clunky, but this works
ds = yt.load(output_00080)
my_filter = ds.fields.io.particle_position_x < ds.quan(0.5, unitary)
ds.add_particle_filter(my_filter)

print(np.max(ds.r[my_filter.name, particle_position_x]).to(unitary))
@cphyc
Copy link
Member Author

cphyc commented Jul 17, 2025

Quick update: we can now do:

import yt
ds = yt.load("output_00080")

# Quickly define new fields
ds.fields.gas.double_density = ds.fields.gas.density * 2

# Quickly filter on the grid
ad = ds.all_data()
left = ad[ds.fields.gas.x < (0.5, "unitary")]

# Quickly filter particles
left_particles = ds.fields.io.particle_position_x < ds.quan(0.5, "unitary")
ds.add_particle_filter(left_particles)
ad[left_particles.name, "particle_position_x"]

@cphyc
Copy link
Member Author

cphyc commented Aug 5, 2025

Note to reviewers: I'm waiting to get approval (if any :D) before writing the documentation. If you'd like to see more examples, I can write the doc before getting approval though.

@@ -1717,7 +1728,7 @@ def quan(self):
return self._quan

def add_field(
self, name, function, sampling_type, *, force_override=False, **kwargs
self, name, function, *, sampling_type=None, force_override=False, **kwargs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is a slightly significant change in behavior, isn't it? We've previously mandated that the sampling_type be specified. Any concerns about this? I don't have any, but I wondering if anyone else has any ideas about potential downstream effects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, I see how you handle this below now.

jzuhone
jzuhone previously approved these changes Aug 21, 2025
def __gt__(self, other) -> "DerivedFieldCombination":
return DerivedFieldCombination([self, other], op=operator.gt)

# def __eq__(self, other) -> "DerivedFieldCombination":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you leave a comment as to why this is commented out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point! I went ahead and implemented this feature.

The annoying point is that defining an __eq__ method forces you to also implement a __hash__ method (which I did).
But this is worth it, since now we can write something like

ad = ds.all_data()
ds.fields.index.is_level_0 = ds.fields.index.grid_level == 0
ad["index", "is_level_0"]

low_res_only = ad.cut_region(ds.fields.index.grid_level == 0)

@cphyc cphyc force-pushed the simple-add-field branch from b7b59d9 to 0ba92ab Compare August 25, 2025 09:58
@cphyc cphyc requested a review from jzuhone August 25, 2025 10:27
@chrishavlin
Copy link
Contributor

For what it's worth at this point, I like the new method! I did check out the branch and quickly played around with defining fields and it all worked as expected but I haven't had time to actually review the code (and probably won't in the short term...). So +1 conceptual approval from me, and don't hold this up on my behalf if anyone else has time to get to it.

P.S., The most recent test failure does look related to the most recent changes.

This implements bool(field1 == field2), so that it returns True iff field1 is field2. For all other operators, it returns True (since the truthness of an object is True). It raises an error if we have more than two terms to the equality.
cphyc added 2 commits August 27, 2025 11:21
Mypy expects them to return a boolean. We return instead a combination of fields which evaluate to a boolean (through __bool__).
@cphyc
Copy link
Member Author

cphyc commented Aug 27, 2025

I found the source of the failures @chrishavlin was talking about. Here's the log of my little adventure:

  • Defining the __eq__ forces us also to define a __hash__ function for DerivedFields to be hashable (e.g., to make sets of them, or use them as an entry to dictionaries),
  • Since we support the syntax ds.fields.ftype1.fname1 == ds.fields.ftype2.fname2, which returns a DerivedFieldCombination, we need to evaluate the truthness of this expression, i.e. implement a __bool__ method on DerivedFieldCombination,
  • That boolean should be True in all cases (since an object in Python is considered as True), unless we're comparing a field to another i.e.
# The following two evaluate to the same boolean
bool(ds.fields.ftype1.fname1 == ds.fields.ftype2.fname2)
ds.fields.ftype1.fname1 is ds.fields.ftype2.fname2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Making something better new feature Something fun and new! workshop-2025
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants