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

non-deterministic matrix assembly for multiple domains #1002

Closed
stevengj opened this issue Apr 25, 2024 · 6 comments · Fixed by #1004
Closed

non-deterministic matrix assembly for multiple domains #1002

stevengj opened this issue Apr 25, 2024 · 6 comments · Fixed by #1004

Comments

@stevengj
Copy link
Contributor

stevengj commented Apr 25, 2024

@mochen4 pointed out via email that Gridap's matrix assembly yields non-deterministic roundoff errors, e.g. the matrices from this tutorial change on the order of machine precision from run to run. This makes debugging harder, and is especially inconvenient if the Gridap outputs are fed into some calculation exhibiting chaos or instability (e.g. we noticed it in optimization problems exhibiting spontaneous symmetry breaking, which converge to a local optimum that is sensitively affected by tiny discrepancies).

@fverdugo wrote to explain:

we use a Dict to store the contributions of several domains to an integral. This can definitely be a cause of operations being done in non deterministic order.

and indeed the issue arises in problems involving multiple domains. Essentially, Dict iteration depends on the hashing algorithm, and Julia's default hashing algorithm for a struct uses the pointer address (objectid), which is effectively non-deterministic. Changing iteration order can change the floating-point roundoff errors of a sum.

The solution is, in principle, simple — just implement a custom hash function (and ==) that is deterministic for any object that is used as a Dict key. Question: which data structures in Gridap need this?

@JordiManyer
Copy link
Member

This would only affect the DomainContribution structure, which stores a dictionary of Triangulation objects. So the hash would be on the triangulations.

@fverdugo
Copy link
Member

Another option is to use another dict type that preserves insertion order, e.g.:

https://juliacollections.github.io/DataStructures.jl/stable/ordered_containers/#OrderedDicts-and-OrderedSets-1

@mochen4 can you try to use an OrderedDict here?

dict::IdDict{Triangulation,AbstractArray}

@stevengj
Copy link
Contributor Author

Using an OrderedDict makes a lot of sense here.

Otherwise, you'd have to switch to an ordinary Dict (not IdDict, which always hashes based on the pointer), and implement a hash for the triangulation, which would make lookup more expensive (because it would have to look at the whole triangulation to hash it). Does performance matter here? Of course, a simple workaround would be to store a hash of the Triangulation object in the object itself, so that you'd only pay the hash cost once when the object is constructed.

@mochen4
Copy link
Contributor

mochen4 commented Apr 28, 2024

@mochen4 can you try to use an OrderedDict here?

dict::IdDict{Triangulation,AbstractArray}

Using OrderedDict fixes the issue for me.

@stevengj
Copy link
Contributor Author

@mochen4, can you make a pull request that makes this 1-line change (also changing the Project.toml to add the DataStructures dependency)?

@mochen4
Copy link
Contributor

mochen4 commented Apr 30, 2024

@mochen4, can you make a pull request that makes this 1-line change (also changing the Project.toml to add the DataStructures dependency)?

The DataStructures dependency is already specified in Project.toml

DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"

So I simply added using dataStrutures in DomainContributions.jl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants