feat: Add Lorenz attractor example#26
Conversation
|
Let me know if you'd prefer to have visible axes, I removed them because I didn't like the look. Also wasn't sure how/if you wanted this added to the README.md, and your .gitignore doesn't allow mp4s so I wasn't sure if you wanted me to upload the result as well. |
ali-ramadhan
left a comment
There was a problem hiding this comment.
Hey @denehoffman! Thank you for contributing and closing an open issue. And congrats on being the first contributor to matplotloom!
This looks great and makes for a great example! I just left a couple of comments.
Also wasn't sure how/if you wanted this added to the README.md, and your .gitignore doesn't allow mp4s so I wasn't sure if you wanted me to upload the result as well.
Ah yes I would add it to the README.md like how the others have been added. I do have *.mp4 in .gitignore so I don't accidently commit output animations, but for the example animations I git add --force.
PS: Apologies for the very slow review. I was not honestly not expecting any PRs but will be watching this repo more closely from now on!
| @dataclass | ||
| class LorenzPlotter: | ||
| plot_speed: int = 20 | ||
| attractor = Lorenz() | ||
| points: list[tuple[float, float, float]] = field(default_factory=list) | ||
|
|
||
| def initialize(self, steps: int): | ||
| self.points = [self.attractor.position] | ||
| for _ in range(steps): | ||
| self.attractor.step() | ||
| self.points.append(self.attractor.position) | ||
|
|
||
| @property | ||
| def frames(self) -> list[int]: | ||
| return list(range(1, len(self.points) // self.plot_speed)) | ||
|
|
||
| def get_frame(self, i: int, loom: Loom): | ||
| fig, ax = plt.subplots(figsize=(12, 8), subplot_kw={'projection': '3d'}) | ||
| points = np.array(self.points[: i * self.plot_speed]) | ||
| xs, ys, zs = points.T | ||
| segments = np.array([points[:-1], points[1:]]).transpose(1, 0, 2) | ||
| norm = Normalize(vmin=0, vmax=len(xs)) | ||
| colors = plt.get_cmap('inferno')(norm(np.arange(len(xs) - 1))) | ||
| lc = Line3DCollection(segments, colors=colors, linewidth=0.5) | ||
| ax.add_collection3d(lc) | ||
| ax.set_xlim(-30, 30) | ||
| ax.set_ylim(-30, 30) | ||
| ax.set_zlim(0, 50) | ||
| ax.view_init( | ||
| azim=(np.pi * 1.7 + 0.8 * np.sin(2.0 * np.pi * i * self.plot_speed / len(self.frames) / 10)) * 180.0 / np.pi | ||
| ) | ||
| ax.set_axis_off() | ||
| ax.grid(visible=False) | ||
| loom.save_frame(fig, i - 1) |
There was a problem hiding this comment.
I think it makes sense to make Lorenz a dataclass but LorenzPlotter feels like it shouldn't be. I would suggest just defining a plot_frame function but curious what you think.
There was a problem hiding this comment.
Actually, now that I look back at the code, I can see why I did it this way. The attractor is a set of points, but the animation steps through them at each update, so to plot a specific frame, you'd have to know all the previous points. You could just generate all the points up to that frame on every frame, but that seems unnecessary, especially if we can just calculate them ahead of time and let the animation part just choose which ones to display. I could rewrite it to be a function which takes a list as an input, and add a function to Lorenz which spits out the desired number of points, if you'd like that let me know. Until then, I'll just add the animation to the README!
There was a problem hiding this comment.
That's fair! I usually run the full simulation then make plots and animations. Having thought about it a bit more I like your approach as it also shows a different way of using matplotloom compared to other examples. So let's keep it like this!
examples/lorenz.py
Outdated
|
|
||
| @dataclass | ||
| class LorenzPlotter: | ||
| plot_speed: int = 20 |
There was a problem hiding this comment.
I think plot_speed can be given a more intuitive name. It sounds like when plot_speed = 1 then every simulation iteration is plotted, and plot_speed = 20 means that every 20th iteration is plotted.
Would steps_per_frame or frame_skip be better names?
There was a problem hiding this comment.
I think my original thought was that a higher value makes the plot spin faster, but I could probably think of a better way to phrase it, I think steps_per_frame makes sense.
|
I've added the MP4 to the repo but I need to figure out how to link it in the README. I haven't used assets on a repo I don't own before, if you have any hints let me know! |
Ah sorry I had to remind myself how I did it, but basically I wanted to host the animations on GitHub and have them show up in the README. But directly linking didn't work. So the workaround I found was to upload/attach them to an issue then the direct link from that could be pasted into the README. So I've been doing this in #11 It's debatable whether want to also include the animations in the |
If I get the link by right-clicking the video there, it looks like |
|
PS: Hope I'm not overstepping but I went ahead and just modified the README a little bit to move the Lorenz example into the section with the other examples. And I forgot to mention that we should add it to the docs so I did that too. |
|
Not overstepping at all, thanks for handling that! |
|
Awesome, I think this is good to merge now! I'll just open a new PR to tag v0.9.1. Thank you again for your contributions! |

Adds a Lorenz attractor example similar to https://docs.makie.org/stable/. This is intended to close #22.