Latest Posts (15 found)
Langur Monkey 1 months ago

Google *unkills* JPEG XL?

I’ve written about JPEG XL in the past. First, I noted Google’s move to kill the format in Chromium in favor of the homegrown and inferior AVIF. 1 2 Then, I had a deeper look at the format, and visually compared JPEG XL with AVIF on a handful of images. The latter post started with a quick support test: “If you are browsing this page around 2023, chances are that your browser supports AVIF but does not support JPEG XL.” Well, here we are at the end of 2025, and this very sentence still holds true. Unless you are one of the 17% of users using Safari 3 , or are adventurous enough to use a niche browser like Thorium or LibreWolf , chances are you see the AVIF banner in green and the JPEG XL image in black/red. The good news is, this will change soon. In a dramatic turn of events, the Chromium team has reversed its tag, and has decided to support the format in Blink (the engine behind Chrome/Chromium/Edge). Given Chrome’s position in the browser market share, I predict the format will become a de factor standard for images in the near future. I’ve been following JPEG XL since its experimental support in Blink. What started as a promising feature was quickly axed by the team in a bizarre and ridiculous manner. First, they asked the community for feedback on the format. Then, the community responded very positively. And I don’t only mean a couple of guys in their basement. Meta , Intel , Cloudinary , Adobe , , , Krita , and many more. After that came the infamous comment: [email protected] [email protected] #85 Oct 31, 2022 12:34AM Thank you everyone for your comments and feedback regarding JPEG XL. We will be removing the JPEG XL code and flag from Chromium for the following reasons: Yes, right, “ not enough interest from the entire ecosystem ”. Sure. Anyway, following this comment, a steady stream of messages pointed out how wrong that was, from all the organizations mentioned above and many more. People were noticing in blog posts, videos, and social media interactions. Strangely, the following few years have been pretty calm for JPEG XL. However, a few notable events did take place. First, the Firefox team showed interest in a JPEG XL Rust decoder , after describing their stance on the matter as “neutral”. They were concerned about the increased attack surface resulting from including the current 100K+ lines C++ reference decoder, even though most of those lines are testing code. In any case, they kind of requested a “memory-safe” decoder. This seems to have kick-started the Rust implementation, jxl-rs , from Google Research. To top it off, a couple of weeks ago, the PDF Association announced their intent to adopt JPEG XL as a preferred image format in their PDF specification. The CTO of the PDF Association, Peter Wyatt, expressed their desire to include JPEG XL as the preferred format for HDR content in PDF files. 4 All of this pressure exerted steadily over time made the Chromium team reconsider the format. They tried to kill it in favor of AVIF, but that hasn’t worked out. Rick Byers, on behalf of Chromium, made a comment in the Blink developers Google group about the team welcoming a performant and memory-safe JPEG XL decoder in Chromium. He stated that the change of stance was in light of the positive signs from the community we have exposed above (Safari support, Firefox updating their position, PDF, etc.). Quickly after that, the Chromium issue state was changed from to . This is great news for the format, and I believe it will give it the final push for mass adoption. The format is excellent for all kinds of purposes, and I’ll be adopting it pretty much instantly for this and the Gaia Sky website when support is shipped. Some of the features that make it superior to the competition are: For a full codec feature breakdown, see Battle of the Codecs . JPEG XL is the future of image formats. It checks all the right boxes, and it checks them well. Support in the overwhelmingly most popular browser engine is probably going to be a crucial stepping stone in the format’s path to stardom. I’m happy that the Chromium team reconsidered their inclusion, but I am sad that it took so long and so much pressure from the community to achieve it. https://aomediacodec.github.io/av1-avif/   ↩︎ https://jpegxl.info/resources/battle-of-codecs.html   ↩︎ https://radar.cloudflare.com/reports/browser-market-share-2025-q1   ↩︎ https://www.youtube.com/watch?v=DjUPSfirHek&t=2284s   ↩︎ https://youtu.be/qc2DvJpXh-A   ↩︎ Experimental flags and code should not remain indefinitely There is not enough interest from the entire ecosystem to continue experimenting with JPEG XL The new image format does not bring sufficient incremental benefits over existing formats to warrant enabling it by default By removing the flag and the code in M110, it reduces the maintenance burden and allows us to focus on improving existing formats in Chrome Lossless re-compression of JPEG images. This means you can re-compress your current JPEG library without losing information and benefit from a ~30% reduction in file size for free. This is a killer feature that no other format has. Support for wide gamut and HDR. Support for image sizes of up to 1,073,741,823x1,073,741,824. You won’t run out of image space anytime soon. AVIF is ridiculous in this aspect, capping at 8,193x4,320. WebP goes up to 16K 2 , while the original 1992 JPEG supports 64K 2 . Maximum of 32 bits per channel. No other format (except for the defunct JPEG 2000) offers this. Maximum of 4,099 channels. Most other formats support 4 or 5, with the exception of JPEG 2000, which supports 16,384. JXL is super resilient to generation loss. 5 JXL supports progressive decoding, which is essential for web delivery, IMO. WebP or HEIC have no such feature. Progressive decoding in AVIF was added a few years back. Support for animation. Support for alpha transparency. Depth map support. https://aomediacodec.github.io/av1-avif/   ↩︎ https://jpegxl.info/resources/battle-of-codecs.html   ↩︎ https://radar.cloudflare.com/reports/browser-market-share-2025-q1   ↩︎ https://www.youtube.com/watch?v=DjUPSfirHek&t=2284s   ↩︎ https://youtu.be/qc2DvJpXh-A   ↩︎

0 views
Langur Monkey 7 months ago

Motion trails in Gaia Sky

Astronomical scenes, especially those rendered interactively, often feature supraluminal camera motion over immense distances. Sometimes, these motions are rendered by applying trail effects to light-emitting objects to enhance the faster-than-light velocity sensation. Gaia Sky will get an implementation of motion trails in the next version (3.6.9). Motion trails are a visual effect that stretches stars, galaxies, and other light-emitting particles in the direction of the velocity vector of the camera, giving a sense of speed and enhancing the perception of motion through space. This technique is inspired by relativistic visualizations and classic star streak effects, but it is grounded in angular motion rather than raw velocity. Vertex-based stretching of stars in supraluminal travel in Gaia Sky. In this post, I describe the technical details that made implementing a performant, vertex-based solution into Gaia Sky possible. The typical way to render such an effect is through a post-processing motion blur pass , especially common in game engines and cinematic visualizations. The process is roughly the following: Separate Render Targets — stars, particles, or other emissive elements are rendered to dedicated framebuffers (with color and velocity). Velocity Buffers — each rendered object encodes its screen-space motion vector based on differences between current and previous frame transforms. Post-Processing Blur Shader — a fullscreen pixel shader smears pixels along their velocity vector, creating blur or streaks. This can be implemented with linear sampling or more advanced temporal accumulation techniques. This method applies uniformly to all rendered elements, works in screen space, typically using a pixel shader, and reuses the pre-existing motion blur infrastructure in engines. However, it requires additional render targets and history buffers, it is almost impossible to control and isolate per-object, and it does not handle transparencies or additive blending correctly. What can we do about it? In Gaia Sky, the effect is instead implemented directly in the vertex shader . Each object is a quad billboard oriented toward the camera. Trails are computed procedurally based on the screen-space velocity of the objects (difference in projected position over time steps), the stretch direction based on camera motion, and a distance-based fade-out. This technique is simpler and has some nice bonuses: In short, this vertex-based approach integrates the effect tightly into Gaia Sky’s real-time astronomical renderer, improving performance, visual clarity, and configurability. The code lives in the snippet that computes the billboard rotation, i.e., the part of the code that orients the quad to the camera to make it a billboard. This snippet uses the following inputs: The shader chunk computes the final vertex position and puts it into . In the next subsections I break down the shader parts and explain each one separately. Here we just compute the orientation base of the quad/billboard, taking as reference the vector from the camera to the center of the object ( ). The quad is oriented toward the camera using a rotation quaternion. We then rotate the camera velocity into the quad’s local space to determine stretch direction. We simulate where the object would be next frame by offsetting it by the camera velocity using the frame time . This gives a screen-space motion estimate in NDC (Normalized Device Coordinates). Note the division by the \(w\) component of clip coordinates, needed to convert to NDC. The stretch magnitude is driven by the screen-space velocity , shaped using a nonlinear power function and clamped. The actual parameters need to be tuned to every scene. To avoid unnecessary GPU load from distant galaxies (which barely move visually), the effect fades out smoothly between 30 and 50 Mpc. This only ever affects very distant, high-z galaxies due to the distances involved in the thresholds. Directionally stretch the quad along the motion vector. Brightness is adjusted to maintain consistent visual intensity. Otherwise, the stars appear brighter as they are stretched, which is bad. We recenter, scale, rotate back to world space, and project to clip space. You have already seen the image at the top of this post. But an image without motion is frankly unimpressive. You can see a video of the effect in this Bluesky post: #GaiaSky will get a star streaks shader in the next release. This effect elongates stars in the direction of motion to create a sort of Star Trek travel effect. Of course, this can be turned off in the settings. [image or embed] This vertex-based trail method avoids the cost and complexity of traditional post-processing while remaining physically intuitive and performant. This represents an ideal fit for real-time astronomical rendering, and for Gaia Sky in particular. Some improvements to consider for the future could be: Separate Render Targets — stars, particles, or other emissive elements are rendered to dedicated framebuffers (with color and velocity). Velocity Buffers — each rendered object encodes its screen-space motion vector based on differences between current and previous frame transforms. Post-Processing Blur Shader — a fullscreen pixel shader smears pixels along their velocity vector, creating blur or streaks. This can be implemented with linear sampling or more advanced temporal accumulation techniques. It avoids history buffers or post-processing passes. It is fully compatible with additive blending and transparency. It scales well with hundreds of thousands of objects. It is tunable based on distance, screen velocity, and apparent motion. — vertex position. — object position w.r.t. camera. — camera view-projection matrix. — quad size. — world-space camera up vector. — world-space camera velocity. — delta time in seconds between frames. — conversion factor from internal units to Mpc. Potential support for color or spectral shifts (red/blue) based on velocity and stretch. Adaptive fall-off curves could optimize performance. A GPU culling pass could eliminate distant objects entirely.

0 views
Langur Monkey 8 months ago

Quadruple joins the fight!

A few days ago I wrote about benchmarking arbitrary precision floating-point libraries in Java . I found out that is not as slow as it is said to be, beating at the same precision level by a long margin in most operations. However, for Gaia Sky , I don’t need hundreds of significant digits at all. It turns out 27 significant digits are enough to represent the whole universe with a precision of 1 meter. The observable universe has a radius of about \(4.4 \times 10^{26}\) meters. To express the entire range down to 1 meter, we need to calculate the number of significant digits \(d\) as follows: $$ \begin{align} d &= \log_{10} \left(\frac{R}{\text{precision}}\right) \\ \\ &= \log_{10} \left(\frac{4.4 \times 10^{26}}{1}\right) \\ \\ &= \log_{10}(4.4 \times 10^{26}) \\ \\ &= \log_{10}(4.4) + \log_{10}(10^{26}) \\ \\ \approx 0.643 + 26 &= 26.643 \end{align} $$ In terms of bits, IEEE 754 double precision (64-bit) provides around 15–17 decimal digits of precision, which is enough for the Solar System, but insufficient for the whole universe. In contrast, IEEE 754 quadruple precision (128-bit) provides around 34 decimal digits of precision, which is in fact more than enough. It uses 113 bits of significand precision, \(log_{10}(2^{113}) \approx 34\) digits. The range of values we can precisely represent in the universe is \(\approx \frac{4.4 \times 10^{26}}{10^{34}} = 4.4 \times 10^{-8}\) meters. This is 4.4 nanometers! As said, this is more than sufficient for our purposes. Browsing through GitHub I found the library , which provides an implementation of 128-bit floating point numbers in Java. The implementation is very compact, and includes addition , subtraction , multiplication , division , and square root . I decided to put it to the test using my JMH benchmark. I created a new benchmark called “ThreeWay”, which tests these operations (plus allocation) for , , and . In the arbitrary precision library I’m using only 32 significant digits of precision instead of 34. I do 1 set of 1 second as warm-up, and 5 iterations of 5 seconds for the measurement (see source ). Below are the specs of the system I used to run the tests, and the specific software versions used. This time around I ran the benchmarks in my laptop while it was plugged in. Only the CPU and the memory should play a significant role. And here are the benchmark results. Three-way Addition results – Interactive view Of course, is compact and only needs to care about 128 bits, while and are generic to any precision, so we can expect to be faster. And it is. Three-way Subtraction results – Interactive view Same with subtraction. Three-way Multiplication results – Interactive view And multiplication. Three-way Division results – Interactive view Division is also faster with the newcomer. Finally, the allocation test. First, we test allocation from a string representation of a floating point number. Three-way Allocation results (from string) – Interactive view Surprising. Let’s analyze this. We use JOL to find out the instance size of each object. It is unlikely that the issue is the instance size. It most definitely comes down to the code to convert the string into the internal representation of each type. This code seems to be much slower for than it is for the others. Let’s see how it fares allocating from a . Three-way Allocation results (from double) – Interactive view The story is reversed. is much faster than the others when allocating an object from a . I never allocate from strings, so this is not that bad actually. There’s not much to say. is obviously much faster in a very significant way than the others. This is, of course, to be expected if we consider that only deals with float-128 types and does not have to care about higher precisions. It may be enough for your purposes, like it is for mine. If this is the case, it may make sense to use it. There are a couple of important caveats to consider if you want to use as it is now: has an instance size of 40 bytes (2 longs, 1 int, 1 boolean, plus header). has an instance size of also 40 bytes (2 ints, 1 long, 2 references to and , plus header). has an instance size of 24 (3 references plus the object header). Only the basic operations are implemented (add, sub, div, mul, sqrt). If you need anything else, you are on your own. instances are mutable . This is a bad design decision in my opinion, and would bar it from adopting further improvements that will land soon to Java like value types ( project Valhalla ). Instantiation from is very slow.

0 views
Langur Monkey 8 months ago

Benchmarking arbitrary precision libraries in Java

Edit (2025-05-08): Changed some test parameters and re-run the tests. Also added bar plots. Note: I have since written a new blog which includes Quadruple to the benchmarks, beating both and consistently. I recently set out to compare the performance of and for arbitrary precision arithmetic in Java. I use arbitrary precision floating point numbers in key places of the update cycle in Gaia Sky, so it made sense to explore this. My initial approach was a naive benchmark: a simple method running arithmetic operations in a loop and measuring the time taken. The results were strongly in favor of , even for large precision values. This was unexpected, as the general consensus I found online suggested that is more performant, especially for higher precision operations (hundreds of digits). To get more accurate and reliable measurements, I decided to implement a proper JMH benchmark. The benchmark project source is available in this repository . The benchmarks test addition, subtraction, multiplication, division, power, natural logarithm, and sine for both and at different precision levels. JMH is a benchmarking framework specifically designed for measuring performance in Java applications. It is developed by the OpenJDK team and provides a robust methodology for generating reliable and reproducible benchmark results by accounting for JVM warm-up, runtime optimizations, and other factors that can skew measurements. Given the surprising results in the naive implementation, using JMH allowed me to get more accurate measurements and mitigate potential inaccuracies caused by JVM behavior. The JMH benchmark project is structured to measure the average time taken for each arithmetic operation over several iterations and precision levels. Here’s the structure: Here is an example for the benchmark: Below are the specs of the system I used to run the tests and the specific software versions used. Only the CPU and the memory should play a significant role. And here are the benchmark results. Addition results – Interactive view We already see that is much faster in all precisions. It is not even close. Subtraction results – Interactive view In the subtraction benchmark comes out on top as well. Multiplication results – Interactive view The same story repeats for multiplication. Division results – Interactive view Again. Division is a notoriously costly operation, but still comes out comfortably on top. Now, let’s test some more involved arithmetic operations, like the natural logarithm, the sine, and the power function. In , those are directly implemented in the library. For , we use the project . Log results – Interactive view The logarithm is faster with at the higher precision settings, but it still wins in the lower precisions. Sin results – Interactive view The sine is much faster in in all precision settings. Pow results – Interactive view And finally, the power repeats the same story, with sitting comfortably on the throne again. For science, I thought it would be cool to test the allocation overhead, so I prepared the Allocation test, which allocates two instances of either or and consumes them. Allocation results – Interactive view We see that allocation is very costly in both libraries. However, while seems to be roughly constant with the precision, shows a higher cost with 25 digits, the lowest precision setting. I though this was very sus, so I re-ran the test a bunch of times and with more iterations and longer times, and got back the same result. I’m not sure what’s the root cause for this, but it is surprising and intriguing. Since both and are immutable, allocation costs need to be factored in. New objects need to be allocated every time new operands are needed. Contrary to expectations, consistently outperformed across all operations and precision levels, including the higher precisions (500 and 1000 digits) where was expected to excel. There is a single case when is faster, and that is in the high precision natural logarithm benchmark. I think it’s safe to say that this is due to the particular implementation or algorithm being used. Otherwise, the disparity is particularly noticeable in division and sine operations, where is significantly slower than . Specifically, was several times faster than in most operations and precisions. Those are, in my opinion, significant results. Finally, allocation seems to be faster with , and there’s a weird dependency on the precision for which I found strange. I was genuinely surprised by the outcome of these benchmarks, as it contradicts the general consensus regarding ’s supposed performance advantage in high-precision arithmetic. I am reaching out to the community to validate my methodology and results. Are these findings trustworthy, or did I overlook something crucial in my benchmarking approach? Feedback and insights are very much welcome. Separate benchmarks for addition , subtraction , multiplication , division , natural logarithm , power , and sine , additionally to an allocation test. Each benchmark tests and . Create the actual objects at benchmark level to factor out allocation costs. Specific benchmark to test allocation overhead. Settled on four precision levels, on a scale ranging from low and high precision settings, represented as the number of digits. They are 25 , 50 , 500 , and 1000 digits. Average time mode. Every benchmark function only runs one operation once. The allocation test creates a couple of objects and consumes them. One warm-up iterations of one second each to minimize JVM effects ( ). Three main iterations of five seconds each for the measurement ( ). Send results into to prevent JIT optimizations.

0 views
Langur Monkey 9 months ago

Oh Garmin, not you!

I’ve been a Garmin user for many years, shelling out non-trivial amounts of monies for their sports watches. My first Garmin watch was a Forerunner 10 (black/red). Battery life was abysmal, but back then this was the norm. Today, I’m sporting a Forerunner 255 , which I love. It’s not top-of-the-line, but it is more than enough for my modest purposes. These devices have been my trusty companions on countless runs, football matches, and hikes, providing invaluable data without any hidden costs. But now, Garmin has decided to introduce Garmin Connect+, a subscription service priced at €8.99 per month or €89.99 annually. Really, Garmin?​ My Garmin Forerunner 255. This past Thursday, March 27th, Garmin announced a premium plan called Garmin Connect+ in a press release titled “Elevate your health and fitness goals with Garmin Connect+” . This new service promises AI-powered insights through their “Active Intelligence” feature, offering personalized suggestions based on health and activity data. Additionally, it includes a performance dashboard with custom charts, enhanced LiveTrack features, and exclusive app badges. I don’t care for any of that, but these additions may sound appealing to some. I can’t help but feel a sense of déjà vu . Initially, companies introduce new features behind a paywall, assuring users that existing functionalities remain free. But as revenue targets loom, there’s a creeping tendency to lock previously free features behind that same paywall, degrading the user experience. This gradual decline, often referred to as enshittification , has plagued many platforms. Garmin’s move mirrors trends seen with other fitness platforms like Strava, which have also introduced subscription models for premium features. While it’s understandable that publicly traded companies seek additional revenue streams, it’s disheartening when it comes at the expense of loyal users who have already invested heavily in their products. The general feeling of their customers is clear. You just need to look around the Garmin subreddit , or the Garmin Subscription Model megathread specifically, to know what I’m talking about. Of course, as these things usually go, Garmin assures us that all existing features will remain free and that Connect+ merely adds new functionalities. I don’t buy it. Forgive me for being skeptical. We’ve seen this play out before, and it often doesn’t end well for the consumer.​ Garmin watches are already a significant investment. Introducing a subscription model feels like a cash grab, especially when the features offered seem like they could be standard inclusions. It’s a slippery slope, and I fear we’re at the top, ready to slide down. I hope Garmin proves me wrong and maintains the integrity of its free offerings. But history suggests otherwise, and I can’t help but brace for the inevitable enshittification of a platform I once held in high regard. One thing is clear to me. My next sports watch will not be a Garmin watch. This move has annihilated my trust in the company. I’ve started researching other options, and I have found Polar and Suunto , both based in Europe, to be excellent alternatives.

0 views
Langur Monkey 9 months ago

Building a local AI assistant with user context

In my last post , I explored the concept of Retrieval-Augmented Generation (RAG) to enable a locally running generative AI model to access and incorporate new information. To achieve this, I used hardcoded documents as context, which were then embedded as vectors and persisted into Chroma DB. These vectors are used during inference to use as context for a local LLM chatbot. But using a few hardcoded sentences is hardly elegant or particularly exciting. It’s alright for educational purposes, but that’s as far as it goes. However, if we need to build a minimally useful system , we need to be more sophisticated than this. In this new post, I set out to create a local Gaia Sky assistant by using the Gaia Sky documentation site and the Gaia Sky homepage as supplementary information, and leveraging Ollama to generate context-aware responses. So, let’s dive into the topic and explain how it all works. The source code used in this post is available in this repository . The first thing we need to do is extract useful content from our sources. Since we use websites, this is where web scraping comes into play: We’re using the library to fetch the HTML content from a given URL. First, we get all the internal links for every URL by recursively scraping the content (with ) and looking for all tags (anchors) that don’t point to internal anchors. This happens in . Then, we extract the text from every page by extracting the tags , , , , , , , , and . This step required a little bit of trial and error. Once we have the text for each page, we concatenate everything and return it. This is implemented mostly in . At first, I wrote my own code to retrieve content from a URL and convert it to text. Of course, this code was less than impressive, and even though it worked, the results were not optimal. Since then I experimented with other methods to extract text from websites, like the libraries , , and . I settled for the latter, as it produces Markdown-formatted documents which are particularly well suited for LLMs. You can see the very different results from the previous method by inspecting the history of this post . The initial scraping behavior can still be accessed in the application with the flag . As we mentioned earlier, we make sure to avoid non-HTML files and internal page anchors (those pesky # URLs that only point to a specific section). This ensures that we only scrape actual webpages that contain relevant data. With this method, we capture all the relevant documentation—every corner of the site. Since the process is recursive, we don’t miss any pages (unless they’re blocked or have weird redirect loops, but that’s a discussion for another time). The code box below shows the link gathering, scraping, and text extraction. Once we’ve scraped the content, it’s time to turn that raw text into something that the machine can understand. This is where embeddings come into play. We use the library to split the scraped text into manageable chunks. This is done using the , which splits the content into chunks of a predefined size (to avoid overloading the model). I tuned this by trial and error, and ended up with a chunk size of 1000, and a chunk overlap of 200. The text is then passed through an embedding model, specifically the “sentence-transformers/all-MiniLM-L6-v2” model from HuggingFace , which transforms it into a high-dimensional vector representation. It is super compact and performs well. I have tried other embed models (see commented out lines at the top of this code block ), but in my experience they did not perform better than . Finally, we store these vectors in a vector database, Chroma DB. The vectors are indexed so that when we query the database, it can efficiently retrieve the most relevant pieces of text. For the retrieval, I’m using a default of 6 results ( in method). I tried with 2 and 3, but it seemed that most models didn’t get enough context. This is where the magic happens. By converting text into vectors, we enable the chatbot to compare and understand the semantic meaning of different pieces of text. The advantage here is that embeddings can capture the relationships between words and concepts, even if they’re not an exact match — meaning the chatbot can intelligently pull out relevant context to answer your questions. The relevant code consists of the concatenated texts we produced in the previous step (we called it ), and the location for our disk database: Now that we’ve got our indexed data, it’s time to query it. But instead of just returning raw search results, we want to use an LLM (Large Language Model) to generate smart, context-aware answers. Enter Ollama. We use ’s and to first create a chain to retrieve documents from our vector store and pass them on to the model. The LLM we’re using is locally hosted through Ollama. The model takes the relevant text retrieved from Chroma and uses that context to generate responses to user queries. The actual model is user-selected at the beginning of the program. We use a simple interface where the user can ask questions, and the chatbot pulls from the stored content to generate and stream an answer. If it doesn’t have enough context to answer, it will graciously let you know, but more often than not, it’s ready to impress with its knowledge. By combining Chroma with an LLM, we ensure that the chatbot not only returns raw text but generates contextually appropriate answers. The power of an LLM with RAG lies in its ability to take a set of relevant documents and provide a coherent, informative response. So, when you ask, “What’s the internal reference system of Gaia Sky?” the chatbot can retrieve the most relevant content and synthesize it into a helpful response. One of the key elements in ensuring a Retrieval-Augmented Generation (RAG) system behaves as expected is the system prompt —a set of initial instructions that guide how the language model responds to queries. In our application, the system prompt is designed to enforce a structured and helpful interaction: The prompt explicitly states that the LLM is a customer support agent to steer its behavior towards professional responses. It also includes a sentence about context awareness so that it actually uses information given in the context. Then, we ask it to not invent something it does not find in the context, and we demand short and concise answers. By fine-tuning this system prompt, we set clear boundaries for how the model should behave, ensuring it delivers accurate, relevant, and trustworthy responses based on the retrieved knowledge. The relevant code that does the actual querying lives in the method. The code below includes the relevant parameters at the top. This method contains the context retrieval code and the question-answer loop. The actual LLM model to use, stored in , is user-defined (code not shown here). After starting, the program presents the available models, and the user needs to select one of them. This happens in the function, which is the final piece of the puzzle. It gets the vector store object—either we have just ingested it from scraping (if is used) or we have retrieved it from the database, if the program is called with no arguments. Then, it prompts the user for the model to use, and calls . The scraping function is hidden behind a flag ( ). When it is used, the websites are scraped, their information is extracted, embedded into vectors and ingested in the database. When no arguments are used, the program starts the chatbot. Of course, you need to run at least once with before using the chatbot . But if you’ve already scraped the site and generated embeddings, the system will load the existing embeddings from disk, saving you time and resources. It’s efficient, and you don’t have to redo the work every time you launch the chatbot. If you change the embed model, remember to nuke the database ( ) and re-do the scraping! Finally, by using in the creation of , and calling instead of , we can stream the response as it is generated, token-by-token. This is the style most web chatbots use. In this section we test the system with , a small instruct LLM with capabilities comparable to larger models, and with , a much smaller but better quantized model(Q5-K-M vs Q4-K-M), also specifically trained for instruct. I ask each model 3 questions: Here are links to the relevant sections. You can install these models directly with Ollama: Remember, if you want to try out the chatbot yourself, download it from this repository . Here we use the general Mistral Small 3 instruct (22B, Q4-K-M) model. It uses Q4_K_M quantization and features 22.2B parameters. I think this is around the limit of what my computer can handle (see conclusion ). On a first pass, we run the program with the flag to gather the information from the websites and create the embeddings. This corresponds to the output shown below, where all the URLs are fetched and scraped. Then, we run without the flag to access the chatbot directly and start asking questions. These are documented in the quoted blocks below. First, I asked about what can Gaia Sky be used for. This is a general type of question, so let’s see how it fares. Q: State the possible usages of Gaia Sky. Gaia Sky is a powerful and versatile software tool that can be used for a variety of purposes, especially in the fields of astronomy, education, and scientific research. Here are some of its potential usages: Educational Tool : Gaia Sky can be used to teach students about astronomy, celestial mechanics, and the universe. It provides an interactive and visually engaging way to learn about different astronomical objects and phenomena. Research in Astronomy : Scientists and researchers can use Gaia Sky to visualize and analyze complex datasets from space missions like Gaia, allowing them to gain insights into stellar evolution, galactic structure, and other astrophysical processes. Data Visualization : The software offers advanced data visualization capabilities, enabling users to create detailed 3D models of celestial objects and phenomena. This is particularly useful for presenting research findings or educational content in a compelling manner. Virtual Tours : Gaia Sky can be used to create virtual tours of the universe, allowing users to explore different regions of space, from our solar system to distant galaxies. Public Outreach : Astronomy enthusiasts and science communicators can use Gaia Sky to create engaging presentations and demonstrations for public events, such as star parties or science festivals. Simulations : Researchers can use the software to run simulations of various astronomical scenarios, helping them understand how different factors influence celestial phenomena. Planetarium Software : Gaia Sky can be used in planetariums to provide immersive and interactive experiences for visitors, enhancing their understanding of astronomy through visual storytelling. Scientific Collaboration : The software facilitates collaboration among researchers by providing a common platform to share and discuss data, simulations, and findings. Artistic Visualizations : Artists can use Gaia Sky to create stunning visual representations of astronomical objects and scenes for artistic purposes or multimedia projects. Space Mission Support : Gaia Sky can be used by teams involved in space missions to plan observations, analyze data, and support mission operations. These uses highlight the flexibility and power of Gaia Sky as a tool for both educational and professional applications in astronomy and related fields. This answer is not concise, but it is quite alright. It captures all important features of Gaia Sky and gives a short description for each. However, I think it might be overselling Gaia Sky a tiny bit, but hey. Also, this model is quite slow on my computer, more on that later. Next, I ask how to install Gaia Sky on Linux. We have several options to install the software on Linux, including APT, DEB and AUR packages, Flatpak, AppImage, and tarballs. Let’s see if the model captures these well. Q: How do I install Gaia Sky on Linux? To install Gaia Sky on a Linux system, you have several options depending on your distribution and preferences. Here are the most common methods: Using Flatpak Install Flatpak : If you don’t already have Flatpak installed, you can install it using your package manager. For example, on Debian-based systems (like Ubuntu), you would use: Add the Flathub repository : Install Gaia Sky : Run Gaia Sky : Using AppImage Download the AppImage : Go to the Gaia Sky downloads page and download the latest file for Gaia Sky. Make the AppImage executable : Open a terminal, navigate to the directory where you downloaded the AppImage, and run: Run the AppImage : Using Unix Installer Download the installer : Go to the Gaia Sky downloads page and download the file for the Unix installer. Make the installer executable : Open a terminal, navigate to the directory where you downloaded the installer, and run: Run the installer : Follow the on-screen instructions to complete the installation. Using TAR.GZ Package Download the TAR.GZ package : Go to the Gaia Sky downloads page and download the file for Gaia Sky. Extract the package : Open a terminal, navigate to the directory where you downloaded the TAR.GZ file, and run: Move the extracted folder to your desired installation location (e.g., ): Create a symbolic link in : Run Gaia Sky : Using Distro-Specific Packages (DEB, RPM, AUR) For distro-specific packages, you can usually install them directly using your package manager: Debian/Ubuntu : RedHat/Fedora : Arch Linux : Choose the method that best suits your needs and preferences. Wow, lots of awesome info and formatting here. It gets most of the information right, but it misses the mark on the installation for Debian and RedHat (we have no packages in their repositories), and Arch Linux (the package is actually called , not ). Other than that, I think this is a good answer. To be fair, this was a rather open question, so it would be surprising that such a small model were able to capture all the info perfectly. Finally, I asked for something much more technical and specific—I asked it to explain how cubemaps are used in Gaia Sky. Q: Explain how cubemap textures are used in Gaia Sky. Cubemaps are a type of texture that maps an entire environment onto a cube’s six faces, providing a full 360-degree view. In Gaia Sky, cubemaps are primarily used to create realistic and immersive environments for planets and other celestial bodies. Here’s how they are utilized: Environment Mapping: Cubemaps are employed for environment mapping, which involves projecting the surrounding scene onto a sphere or cube. This technique is particularly useful for rendering reflections on surfaces, such as water or metallic objects, giving them a more realistic appearance. Skyboxes: One common use of cubemaps in Gaia Sky is to create skyboxes. A skybox is a cube that surrounds the scene and displays an environment map on its faces. This technique is used to simulate distant scenery, such as the sky or outer space, without needing to render far-off objects in detail. Planetary Surfaces: Cubemaps can also be applied to planetary surfaces to create detailed and varied textures. By mapping different environments (like oceans, mountains, or forests) onto the faces of a cube, Gaia Sky can generate diverse and realistic planetary landscapes. Lighting and Shadows: Cubemaps are used in lighting calculations to simulate how light interacts with the environment. This includes creating realistic shadows and highlights based on the surrounding scene, enhancing the overall visual quality of the rendered objects. Implementation Steps: Creation of Cubemap Textures: Loading Cubemaps in Gaia Sky: Application in Shaders: In summary, cubemap textures play a crucial role in Gaia Sky by enabling the creation of immersive, realistic environments through techniques like environment mapping and skybox rendering. This is perhaps too detailed, and sources information from outside of the context, but it is mostly correct, if a little verbose. I’m happy with this answer. All in all, I think this Mistral model is a little too verbose. You can see specks of larger models, like the good formatting, or the effort to produce complete and exhaustive answers. However, the accuracy is not always on point. I wonder if using a model with better quantization (Q6?) would solve this. Now we test the Llama 3.1 instruct (8B, Q5-K-M) model, an instruct-specific model that is much smaller than Mistral Small 3 (24B). However, this time around I stepped up the quantization a bit by using Q5_K_M, which should improve accuracy over the basic Q4_0. First, let’s ask about the usages: Q: State the possible usages of Gaia Sky. Gaia Sky is a free, open-source planetarium software that can be used for various purposes, including: These are just a few examples of the many possible usages of Gaia Sky. Its versatility and flexibility make it an excellent tool for various applications across different domains. This is a good response. Not too long, and very well organized. Everything in it is correct, too. Also, Llama 3.1 was very fast in generating it. The next question is about the Linux installation. Q: How do I install Gaia Sky on Linux? To install Gaia Sky on Linux, you can follow these steps for the different package types: Unix Installer (tarball) DEB (Debian and derivatives) RPM (RedHat and derivatives) AUR (Arch Linux and derivatives) This answer is fracking excellent . It nails everything. It includes all Linux packages we offer, and the instructions for each are on point. I’m super impressed. The quality of this response is much better than the one generated by the (much larger) Mistral Small 3 22B. Finally, we ask it what cubemaps are used for: Q: How are cubemap textures used in Gaia Sky? In Gaia Sky, cubemap textures are used to display planetary surfaces and other 3D models with high detail and realism. Here’s an overview of how they work: What is a cubemap? A cubemap is a type of texture that represents the surface of a 3D object from all six sides (top, bottom, left, right, front, and back). It’s called a “cubemap” because it’s essentially a cube with six faces, each containing a different view of the object. How are cubemaps used in Gaia Sky? In Gaia Sky, cubemaps are used to display planetary surfaces, such as Earth or other planets. When you load a planet into the game, Gaia Sky generates a cubemap texture that represents the surface of the planet from all six sides. This allows for more detailed and realistic rendering of the planet’s surface. How do cubemaps improve graphics in Gaia Sky? Cubemaps offer several benefits over traditional textures: How to use cubemap textures in Gaia Sky To use cubemap textures in Gaia Sky, you’ll need to: By using cubemap textures, Gaia Sky can create stunningly realistic planetary surfaces that draw you in and make you feel like you’re exploring the cosmos itself! The format and wording of this answer look like it comes from a larger model. However, I think the content misses the point a bit. It does not look like it sources this information from the Cubemaps section in our documentation, which may be an issue with the embedding, the context size, or the context retrieval. I tried reducing the number of results from 10 to 5 before running the query again: How are cubemaps used in Gaia Sky? Cubemaps are used in Gaia Sky to texture planets, moons, and other spherical or semi-spherical objects. They help eliminate artifacts that happen at the poles with UV sphere models, providing a more seamless appearance. Cubemaps can be used instead of regular equirectangular (spherically projected) images. This time around, the answer is much more concise and correct. In the global picture, I like the responses of Llama better than those provided by Mistral. Also, Mistral is almost 3 times larger than Llama (22B vs 8B), so it is much slower on my machine. In my case, I’m pretty limited by my hardware, and responses tend to be slow with the larger model (Mistral). The speed is quite good with Llama (8B) though. True, I’m running this a very old computer for today’s standards (Intel(R) Core(TM) i7-7700 (8) @ 4.20 GHz, NVIDIA GeForce GTX 1070 8 GB, 32 GB RAM), so it is what it is. Here are the performance stats for both models. I asked them both to generate a short text (~100 words) about the meaning of life. I found the responses provided by smaller models (1B to 5B) to be mostly garbage. On larger instruct models with decent quantization, accuracy is greatly improved at the expense of your precious time. In this post, we’ve expanded on our previous post and developed an AI chatbot able to answer questions on Gaia Sky. The information is sourced from the Gaia Sky homepage and documentation. With the power of web scraping, embeddings, and Ollama, we’ve brought the Gaia Sky website and docs to life with smart, context-aware interactions. No more endless searches for answers. Instead, we now have a chatbot that knows exactly where to look and can provide concise, relevant responses. So go ahead and try it out—who knew scraping could be this useful? It’s pretty obvious to me that such small models are able to get most of the questions almost right, but they are most definitely not suited for professional settings. In this case, larger models (at least 70B, I reckon) would be required. For our purposes here, Mistral Small 3 instruct (22B) and Llama 3.1 instruct (8B) are enough. I don’t want to finish without mentioning off-the-shelf options that accomplish the same goals. There exist no-code software packages like AnythingLLM and Open WebUI that support local models via Ollama and integrate RAG. I actually recommend using these if you are not too interested in the nitty-gritty details. We’re using the library to fetch the HTML content from a given URL. First, we get all the internal links for every URL by recursively scraping the content (with ) and looking for all tags (anchors) that don’t point to internal anchors. This happens in . Then, we extract the text from every page by extracting the tags , , , , , , , , and . This step required a little bit of trial and error. Once we have the text for each page, we concatenate everything and return it. This is implemented mostly in . At first, I wrote my own code to retrieve content from a URL and convert it to text. Of course, this code was less than impressive, and even though it worked, the results were not optimal. Since then I experimented with other methods to extract text from websites, like the libraries , , and . I settled for the latter, as it produces Markdown-formatted documents which are particularly well suited for LLMs. You can see the very different results from the previous method by inspecting the history of this post . The initial scraping behavior can still be accessed in the application with the flag . As we mentioned earlier, we make sure to avoid non-HTML files and internal page anchors (those pesky # URLs that only point to a specific section). This ensures that we only scrape actual webpages that contain relevant data. We use the library to split the scraped text into manageable chunks. This is done using the , which splits the content into chunks of a predefined size (to avoid overloading the model). I tuned this by trial and error, and ended up with a chunk size of 1000, and a chunk overlap of 200. The text is then passed through an embedding model, specifically the “sentence-transformers/all-MiniLM-L6-v2” model from HuggingFace , which transforms it into a high-dimensional vector representation. It is super compact and performs well. I have tried other embed models (see commented out lines at the top of this code block ), but in my experience they did not perform better than . Finally, we store these vectors in a vector database, Chroma DB. The vectors are indexed so that when we query the database, it can efficiently retrieve the most relevant pieces of text. For the retrieval, I’m using a default of 6 results ( in method). I tried with 2 and 3, but it seemed that most models didn’t get enough context. We use ’s and to first create a chain to retrieve documents from our vector store and pass them on to the model. The LLM we’re using is locally hosted through Ollama. The model takes the relevant text retrieved from Chroma and uses that context to generate responses to user queries. The actual model is user-selected at the beginning of the program. We use a simple interface where the user can ask questions, and the chatbot pulls from the stored content to generate and stream an answer. If it doesn’t have enough context to answer, it will graciously let you know, but more often than not, it’s ready to impress with its knowledge. Temperature – The temperature controls the randomness of the model’s responses, with lower values making answers more deterministic and factual, while higher values increase creativity and variability. We use a default low temperature value of 0.3 to get more correct and less creative responses. The temperature can be set with the argument . Number of results – The number of results parameter ( or ) determines how many relevant documents are retrieved from the vector database to provide context for the model’s response, with higher values increasing the available information but potentially adding noise. We use the default of 6 results, but this can be changed with the argument. The usages of Gaia Sky. How to install Gaia Sky on Linux. How are cubemap textures used in Gaia Sky. Mistral Small 3 Usages Linux installation Llama 3.1 Usages Linux installation Educational Tool : Gaia Sky can be used to teach students about astronomy, celestial mechanics, and the universe. It provides an interactive and visually engaging way to learn about different astronomical objects and phenomena. Research in Astronomy : Scientists and researchers can use Gaia Sky to visualize and analyze complex datasets from space missions like Gaia, allowing them to gain insights into stellar evolution, galactic structure, and other astrophysical processes. Data Visualization : The software offers advanced data visualization capabilities, enabling users to create detailed 3D models of celestial objects and phenomena. This is particularly useful for presenting research findings or educational content in a compelling manner. Virtual Tours : Gaia Sky can be used to create virtual tours of the universe, allowing users to explore different regions of space, from our solar system to distant galaxies. Public Outreach : Astronomy enthusiasts and science communicators can use Gaia Sky to create engaging presentations and demonstrations for public events, such as star parties or science festivals. Simulations : Researchers can use the software to run simulations of various astronomical scenarios, helping them understand how different factors influence celestial phenomena. Planetarium Software : Gaia Sky can be used in planetariums to provide immersive and interactive experiences for visitors, enhancing their understanding of astronomy through visual storytelling. Scientific Collaboration : The software facilitates collaboration among researchers by providing a common platform to share and discuss data, simulations, and findings. Artistic Visualizations : Artists can use Gaia Sky to create stunning visual representations of astronomical objects and scenes for artistic purposes or multimedia projects. Space Mission Support : Gaia Sky can be used by teams involved in space missions to plan observations, analyze data, and support mission operations. Install Flatpak : If you don’t already have Flatpak installed, you can install it using your package manager. For example, on Debian-based systems (like Ubuntu), you would use: Add the Flathub repository : Install Gaia Sky : Run Gaia Sky : Download the AppImage : Go to the Gaia Sky downloads page and download the latest file for Gaia Sky. Make the AppImage executable : Open a terminal, navigate to the directory where you downloaded the AppImage, and run: Run the AppImage : Download the installer : Go to the Gaia Sky downloads page and download the file for the Unix installer. Make the installer executable : Open a terminal, navigate to the directory where you downloaded the installer, and run: Run the installer : Follow the on-screen instructions to complete the installation. Download the TAR.GZ package : Go to the Gaia Sky downloads page and download the file for Gaia Sky. Extract the package : Open a terminal, navigate to the directory where you downloaded the TAR.GZ file, and run: Move the extracted folder to your desired installation location (e.g., ): Create a symbolic link in : Run Gaia Sky : Debian/Ubuntu : RedHat/Fedora : Arch Linux : Environment Mapping: Cubemaps are employed for environment mapping, which involves projecting the surrounding scene onto a sphere or cube. This technique is particularly useful for rendering reflections on surfaces, such as water or metallic objects, giving them a more realistic appearance. Skyboxes: One common use of cubemaps in Gaia Sky is to create skyboxes. A skybox is a cube that surrounds the scene and displays an environment map on its faces. This technique is used to simulate distant scenery, such as the sky or outer space, without needing to render far-off objects in detail. Planetary Surfaces: Cubemaps can also be applied to planetary surfaces to create detailed and varied textures. By mapping different environments (like oceans, mountains, or forests) onto the faces of a cube, Gaia Sky can generate diverse and realistic planetary landscapes. Lighting and Shadows: Cubemaps are used in lighting calculations to simulate how light interacts with the environment. This includes creating realistic shadows and highlights based on the surrounding scene, enhancing the overall visual quality of the rendered objects. Creation of Cubemap Textures: Developers create cubemap textures using specialized software that can render 360-degree environments. These tools often allow for the creation of high-quality, seamless textures. Loading Cubemaps in Gaia Sky: The created cubemaps are then loaded into Gaia Sky. This process involves specifying which texture should be applied to which face of the cube (positive X, negative X, positive Y, negative Y, positive Z, and negative Z). Application in Shaders: The shaders in Gaia Sky are programmed to use these cubemaps for various effects, such as reflections, environment lighting, and skybox rendering. This is done by sampling the cubemap texture within the shader code based on the viewing direction or other parameters. Realism: Cubemaps help create highly realistic environments by providing detailed textures and reflections. Performance: Instead of rendering distant objects, which can be computationally expensive, cubemaps allow for quick and efficient simulation of the environment. Flexibility: They can be easily swapped out to change the appearance of an environment without altering the underlying geometry. Educational purposes : Gaia Sky can be used in educational settings to teach astronomy and astrophysics concepts to students. Research and scientific visualization : The software’s advanced features make it suitable for researchers who need to visualize complex astronomical data, such as star catalogs, galaxy distributions, or planetary systems. Public outreach and engagement : Gaia Sky can be used in public events, exhibitions, or planetariums to showcase the wonders of the universe and promote interest in astronomy among the general public. Personal entertainment : Users can simply enjoy exploring the night sky, viewing celestial objects, and experiencing the thrill of space travel within the software’s virtual environment. Artistic and creative projects : Gaia Sky’s capabilities make it an attractive tool for artists, designers, and filmmakers who need to create realistic astronomical environments or simulate complex astrophysical phenomena. Professional training and development : The software can be used by professionals in fields like astronomy, space exploration, or related industries to improve their knowledge and skills through interactive simulations and visualizations. Install Flatpak if you haven’t already: Add the Flathub repository: Install Gaia Sky from Flathub: Run Gaia Sky: Download the AppImage file from the official website: https://gaiasky.space/downloads/ Make the AppImage executable: Run Gaia Sky: Download the tarball from the official website: https://gaiasky.space/downloads/ Extract the contents of the tarball to a directory of your choice: Run Gaia Sky: Download the DEB package from the official website: https://gaiasky.space/downloads/ Install the package using dpkg: Run Gaia Sky: Download the RPM package from the official website: https://gaiasky.space/downloads/ Install the package using rpm: Run Gaia Sky: Install the AUR helper of your choice (e.g., yay, yay-bin). Clone the Gaia Sky AUR package repository: Navigate to the cloned repository directory: Build and install the package: Run Gaia Sky: Higher detail : Cubemaps can display much higher levels of detail than traditional textures, making planetary surfaces look more realistic. Realistic lighting : Cubemaps take into account the way light interacts with the surface of the planet, creating a more realistic and immersive experience. Improved performance : Because cubemaps are pre-computed and stored in memory, they can be rendered much faster than traditional textures, reducing the load on your graphics card. Load a planet or other 3D model into the game. Adjust the graphics settings to enable cubemaps (this may require updating to a newer version of Gaia Sky). Select the desired level of detail and lighting for the cubemap texture.

0 views
Langur Monkey 9 months ago

Local LLM with Retrieval-Augmented Generation

Edit (2025-03-26): Added some words about next steps in conclusion. Edit (2025-03-25): I re-ran the example with a clean database and the results are better. I also cleaned up the code a bit. Over the past few months I have been running local LLMs on my computer with various results, ranging from ‘unusable’ to ‘pretty good’. Local LLMs are becoming more powerful, but they don’t inherently “know” everything. They’re trained on massive datasets, but those are typically static. To make LLMs truly useful for specific tasks, you often need to augment them with your own data–data that’s constantly changing, specific to your domain, or not included in the LLM’s original training. The technique known as RAG aims to bridge this problem by embedding context information into a vector database that is later used to provide context to the LLM, so that it can expand its knowledge beyond the original training dataset. In this short article, we’ll see how to build a very primitive local AI chatbot powered by Ollama with RAG capabilities. The source code used in this post is available here . In order to build our AI chatbot, we need the following ingredients: First, we need to install Ollama. On Arch Linux, it is in the repository. If you happen to have an NVIDIA GPU that supports CUDA, you’re in luck. You can speed-up your inference but a significant factor! Otherwise, you can still just use the CPU. I have a (rather old) NVIDIA GPU, so I’m installing as well. Next, we need some LLM models to run locally. First, start the Ollama service. You can check whether the service is running with curl: Then, you need to get some LLM models. I have some installed that I use regularly: I would recommend using ( link ) for this experiment, especially if you run a machine with little RAM. It is quite compact and works reasonably well 1 . Additionally, some models like do not support embeddings, so you need to choose carefully. Get with: That’s it for our LLM. Now, let’s create a new directory for our RAG chatbot project, and let’s install the dependencies. This should install , Langchain together with the hook, and Chroma DB. The code we display below is available in this repository . Now, let’s create a file, . First, our chatbot will list all the available models and will ask for the model we want to use. This part is simple: Chroma DB is a vector database designed and optimized for storing and searching vector embeddings , which are crucial in RAG. Vector embeddings are numerical representations of data (text, images, audio, etc.). They capture the meaning or semantic information of the data. They are generated by machine learning models (often transformer models like those used in LLMs). These models are trained to map similar data points to nearby points in a high-dimensional vector space. That’s precisely what we need to do with our context data for it to be useful to the LLM. And to that purpose we use Chroma DB. So, we need to initialize the Chroma DB client with a persistent storage, and get it ready to embed our context data. We decide to persist our storage to disk, in the directory . Once we have Chroma DB set up, with our collection and embedding function, we need to populate it. Here, we’ll just use an array with text, but you could fetch this information from files easily. In my example, I use a totally made up text (1), the abstract to a paper about characterizing atmospheres with JWST (2), and some information about the new Gaia Sky website. The first is made up, so the model can’t know it. The second is a paper which came out much later than the model was published. The third is about the new Gaia Sky website, which was also created after the model. Now we have our vector storage set up. Let’s build the chat logic! That’s it! In this part, we ask the user to provide a query. Then, the script uses this query to fetch some context from our local Chroma DB, adds the context to the prompt (in ), and sends the query to Ollama. Then, we wait for the response and print it out. Here is an example output. I ask it about JWST and exoplanets, the imaginary object ‘Mittius’, and the new Gaia Sky website. I have highlighted the response lines: As you can see, even this small 8B parameter model pretty much nails all three answer. In the third question (radius of the Mittius), it even provides the exact sentence that it sourced the subsequent answer from. Good work, Llama3.1! As we’ve seen, with very little effort we can build a rudimentary RAG system on top of Ollama. This enables us to use context information in our queries in an automated manner, with the help of Chroma DB. This post only highlights the very basic notions required to get RAG to work in a very bare-bones manner. Further concepts to explore include different embedding models, quantization, system prompts, and more. In our small test, we’ve used the Llama3.1 8B model, which is rather small. Using a larger model, like Gemma3 (12B), DeepSeek-R1 (14B), or even Mistral-small (24B), should improve the results at the expense of performance. The code in this post is partially based on this medium article . https://artificialanalysis.ai/models/llama-3-1-instruct-8b   ↩︎ Ollama, to facilitate access to different LLMs. Chroma DB, our vector storage for our context. Langchain, to facilitate the integration of our RAG application with the LLM. https://artificialanalysis.ai/models/llama-3-1-instruct-8b   ↩︎

0 views
Langur Monkey 11 months ago

Gaia Sky gets new website

Over the past few weeks I’ve been working on a new standalone website for Gaia Sky. It uses the same technology stack as my personal website (essentially Hugo ), so it is a static website generated from templates and content. This is enough for Gaia Sky. I am no graphic designer or UX person, but I tried my best to make it look potable . The new Gaia Sky website, gaiasky.space Why did I create a standalone website instead of keeping the old section in the ZAH site? A few reasons: As an extra perk, the new site allowed us to integrate things that were previously scattered in several places. I have written scripts to generate pages for: The site is responsive and should render fine on PC and mobile. If you are interested, give it a visit: gaiasky.space . It is much easier to update and maintain. We now have a news section to make announcements of new releases, datasets, and whatnot. People can follow it with RSS . We can use our own domain , . This was in the back of my mind for a long time. We can verify our Flathub package . The impossibility to get Gaia Sky verified in Flathub is what prompted it all, as we obviously do not control the domain associated with the old app ID . The new ID is already verified . gaiasky.space/downloads/releases – old releases, together with their packages and release notes. gaiasky.space/resources/datasets – a full listing of the datasets available in Gaia Sky, with their descriptions and additional metadata.

0 views
Langur Monkey 1 years ago

Rendering volume aurorae and nebulae

A few weeks ago someone created an issue in the Gaia Sky Codeberg repository requesting the addition of aurorae to the Earth. They used as an example the aurora add-on in Cosmographia 1 , which, at the time, looked to me like it was using some kind of billboard particle system to generate the effect. I never thought it looked particularly good for an aurora; I thought Gaia Sky could do better. So I set on a quest to implement a better looking aurora object in Gaia Sky. This wee quest would involve implementing three very different approaches to aurora rendering over the span of a few weeks, plus more than half a dozen 3D volume nebulae. In this post, I present the three different methods I implemented to render aurorae. But before anything, I need to take a small detour and talk about nebulae. If every story should start at the beginning and be told in full, I must first relate how this little quest took a little detour, at the beginning, to explore the rendering of volume nebulae. The OP mentioned in the Codeberg issue that he also gets requests to fly into nebulae during his tours, and Gaia Sky only contained billboards so far. Since Gaia Sky already had the necessary infrastructure to render ray-marched volumes using a screen quad in a post-processing effect , I decided that addressing this issue first made sense, as it seemed to require a much lower effort and friction. Typically, volumes are rendered using an in-scene model (normally a box) that encapsulates the volume fully. The fragments that hit the model faces are sent to through the pipeline, and the volume ray-marching or ray-tracing is implemented in the respective fragment shader. In our approach, we’ll use a post-processing stage, which means that we create a full screen quad and we run a fragment shader for every pixel in our screen or window. The main challenges of this approach are: To get started, I browsed through shadertoy in search for a good implementation of a ray-marched shader that I could use as a base for my nebulae. I found several, like the Dusty Nebula 4 by Duke, and the Supernova Remnant , also by Duke. This Duke guy seems to be the most prolific shadertoy author when it comes to large-scale dusty objects, and by a large margin. It’s not even close, so props to him and thank you very much for your work. All of his shaders seem to be licensed under CC-BY-NC-SA 3.0 , which is perfect for modification and reuse in Gaia Sky. So, taking some of these shaders as a baseline, I created new ones for the Cat’s Eye nebula, the Hourglass nebula, the Trifid nebula, the Butterfly nebula, and the Crab nebula, among others. You can see some of them here . My implementation of the Crab nebula as a volume. See it live on Shadertoy . Once the Shadertoy shaders were ready, I did a little more work on the post-processing ray-marching infrastructure to accommodate the new members. So far, I had only ever used it for the black holes. The final step was translating the shaders from Shadertoy to Gaia Sky. This was not too difficult, as they were already created with that step in mind. As we mentioned, currently the nebulae are implemented in the post-processor. This means that they are rendered in image space after the main rendering stage, which renders the geometry, has finished.Post-processors get as input data the current frame buffer, the depth buffer, and whatever additional textures are needed for the particular effect (such as one or more noise textures). Additionally, they get positions and orientations as uniforms or varyings computed in the vertex stage. All nebula shaders have the same main parts: Set up – here we prepare the data for the raymarching step. The ray direction, the camera position and the object position are all computed/fetched here. We also recover the depth for the current fragment from the depth buffer. The set-up is typically done in the function. In the following code we have an example of how the set-up stage is done. Code: set-up example Ray marching – here is where the actual ray-marching happens. In ray marching, we shoot a ray for every pixel, divide it into samples, and gather information at every step. This information is usually the density and color of the medium we are rendering. Intersection check – since we implement this in image space (we shoot a ray for each pixel in our window), we first need to check whether our ray intersects the bounding volume containing the object or not. In nebulae, this volume is typically a sphere. Simply enough, this is done with a straightforward ray-sphere intersection function, which returns the intersection near and far distances if there is an intersection at all, or nothing if the ray does not hit the sphere. The code below shows how to do the ray-sphere intersection. Code: ray-sphere intersection example Main loop – here we loop over a maximum number of iterations and accumulate the density. Every cycle advances our position through the ray by a given step distance. The code below illustrates a rather typical main loop. The following code completes the function to include the call to the main loop ( ) and the alpha blending at the very bottom. Code: main loop example Blending – finally, we have the blending. Most nebulae are blended using regular alpha blending, but implementing additive blending is trivial. Code: alpha blending example This covers the basics of our nebulae shaders. The video below shows some of the nebulae that I have adapted for Gaia Sky and that will be available in the NGC2000 catalog when the next version is out, soon™. Now, back to the aurora. Aurorae are natural light displays caused by interactions between charged particles from the Sun (solar wind) and the Earth’s magnetosphere and atmosphere. At the poles, magnetic field lines converge and funnel these particles into the upper atmosphere (thermosphere), where they collide with oxygen and nitrogen molecules. The collisions excite the molecules, causing electrons to jump to higher energy states. The excited atoms are unstable, so as the electrons return to their normal states, they release packets of energy in the form of photons. The color (frequency) of these photons depends on the type of gas and altitude: green from oxygen (~100-150 km), red from oxygen higher up, where lower density causes slower de-excitation (~200+ km), and blue or purple from nitrogen. The shifting, curtain-like shapes result from changes in the solar wind and Earth’s magnetic field. A picture of an aurora borealis in Greenland. The green, red and blue colors can be seen in different parts of the curtains. Photo by Visit Greenland on Pixnio , CC0. Aurorare are, then, purely volumetric and perfectly emissive phenomena. This fact strongly points to a volume ray-caster being the best method for rendering aurorare, but implementing volume rendering shaders is hard, so I explored some alternative options first. I have very creatively labeled them 1, 2 and 3. I like to simplify problems to their very basics. Even though aurora curtains have volume (width), their extent is limited when compared to their height or their footprint length. So, obviously, my first thought was to implement aurorae as a curved mesh with a special shader to render the curtain elements. I knew it wouldn’t look super good, but maybe it was good enough . I set up the infrastructure and created the object. At first, I used an uncapped cylinder mesh. The faces are unculled, since they need to be visible from both sides. Once the mesh was in place, I created the fragment shader which would render the actual emissive elements. This shader is available on Shadertoy: Base Aurora Shader . I loaded the shader and applied it to the mesh. The results were… underwhelming. The shader itself is ok, but when applied to the flat surface of the cylinder, it just does not cut it. When the camera moves the effect is totally shattered and you can tell right away that this is a flat object. The aurora shader on a cylindrical mesh, seen from afar. A close-up. Meh. It was clear to me that this wasn’t good enough. So I went browsing on shadertoy again. I’ll start by saying that this second effort went nowhere, but had the pleasant side effect of having implemented the true in-scene, bounding box-based volume infrastructure. The base idea was to adapt a pre-existing ray-marching shader to be rendered as a volume in Gaia Sky. The shader in question is here . The isolated aurora shader ray-marches an aurora using fractal brownian motion noise as footprints. Looks good in shadertoy, not so much in Gaia Sky. See it live on Shadertoy . That shader contains some code to to intersect the ray with an axis-aligned bounding box (AABB), so naturally I implemented the necessary infrastructure to be able to ship external volume rendering shaders on bounding meshes . And it kind of worked, but I could never make it behave exactly the way I wanted. I had several problems with the integration in Gaia Sky, so instead of spending lots of time and effort into solving them only to end up with a half-assed, bad-looking aurora, I decided to try the next approach. This is the good one. It is based on Lawlor and Genetti’s 2011 paper “Interactive Volume Rendering Aurora on the GPU” 2 . Key innovations include a height-based aurora model, a GPU-friendly atmosphere model, efficient ray marching with distance fields, and interactive frame rates (20–80 FPS). The aurora footprints are initially represented as splines, which are then fed into a stem-like fluid simulator to generate the true footprint textures. Then, they sample the rays and stretch the footprints in the vertical direction using a physically-based vertical electron deposition function. They also accelerate the ray-marching step using a 2D signed distance field (SDF) texture generated from the footprint. I implemented all of this and the results were quite good. I drew the footprints myself using Gimp. I use cubemaps for the footprints and the signed distance function texture, as they eliminate the artifacts at the poles typical of regular UV mapping on spheres. The aurora volume itself is modeled with a flipped sphere that wraps around the Earth. I generate it programmatically. I generated the SDF textures with , a small CLI utility that generates distance fields from raster images. Left: the footprint texture, created with Gimp. Center: the signed distance function texture. Right: the final render. By far, the largest problem I faced was in the UV coordinate mapping. In order to sample the 2D textures from the 3D samples in the ray marching steps, I implemented a projection function that maps 3D points to 2D UV coordinates. However, when the camera was inside the volume, this gave me mirrored ghosting aurorae in their opposite direction relative to the camera. I struggled to solve this issue for a while, until I realized that I just had to enforce that the sampling point had to be on the right side of the ray: This solved the artifacts. I then implemented some additional suggestions by O. Lawlor (the paper’s author), and I got it to a state where I really liked it. See the video below: A few things can be improved in the current aurora: In this post, I have shown how the new volume nebulae and aurorae in Gaia Sky came about. I have given some implementation details, and discussed their pros and cons. In the end, I think the time spent on this was well spent, as the results were quite satisfactory. SPICE-Enhanced Comosgraphia Mission Visualization Tool, NAIF : https://naif.jpl.nasa.gov/naif/cosmographia.html   ↩︎ Interactive Volume Rendering Aurora on the GPU, Journal of WSCG : https://www.researchgate.net/publication/220200705_Interactive_Volume_Rendering_Aurora_on_the_GPU   ↩︎ Ground the model in the scene world. Since we are essentially painting on an empty, flat canvas, we need to ground our 3D volume in our 3D world. This step is taken care of by carefully selecting the coordinate system and constructing the ray direction for each fragment and camera and object positions correctly. Interaction with the depth buffer. We need our volume to be occluded by objects closer to the camera, and the opaque portions of our volume need to occlude further away parts of the scene. To fix this, we pass in the current depth buffer as a texture so that we can sample it at every pixel. Additionally, we also pass in the current scene frame buffer so that we have full control over the blending of our volume with the rest of the scene. Set up – here we prepare the data for the raymarching step. The ray direction, the camera position and the object position are all computed/fetched here. We also recover the depth for the current fragment from the depth buffer. The set-up is typically done in the function. In the following code we have an example of how the set-up stage is done. Code: set-up example Ray marching – here is where the actual ray-marching happens. In ray marching, we shoot a ray for every pixel, divide it into samples, and gather information at every step. This information is usually the density and color of the medium we are rendering. Intersection check – since we implement this in image space (we shoot a ray for each pixel in our window), we first need to check whether our ray intersects the bounding volume containing the object or not. In nebulae, this volume is typically a sphere. Simply enough, this is done with a straightforward ray-sphere intersection function, which returns the intersection near and far distances if there is an intersection at all, or nothing if the ray does not hit the sphere. The code below shows how to do the ray-sphere intersection. Code: ray-sphere intersection example Main loop – here we loop over a maximum number of iterations and accumulate the density. Every cycle advances our position through the ray by a given step distance. The code below illustrates a rather typical main loop. The following code completes the function to include the call to the main loop ( ) and the alpha blending at the very bottom. Code: main loop example Blending – finally, we have the blending. Most nebulae are blended using regular alpha blending, but implementing additive blending is trivial. Code: alpha blending example Render the aurorae to an off-screen buffer. This would unlock a series of benefits: Render at lower resolution to increase performance. Apply blur and glow to mitigate jittering artificts. Full control over blending with scene. Proper color modeling. The base and high colors are now passed in from a JSON descriptor file. This could be improved by adopting a physical model of the upper atmosphere and generating the colors accordingly. SPICE-Enhanced Comosgraphia Mission Visualization Tool, NAIF : https://naif.jpl.nasa.gov/naif/cosmographia.html   ↩︎ Interactive Volume Rendering Aurora on the GPU, Journal of WSCG : https://www.researchgate.net/publication/220200705_Interactive_Volume_Rendering_Aurora_on_the_GPU   ↩︎

0 views
Langur Monkey 1 years ago

Jujutsu, a modern version control system

I first discovered Jujutsu a few weeks ago, and I was immediately intrigued. At first glance, it looked like a simple wrapper around Git, but the deeper I looked, the more impressed I became with its design. Jujutsu, I discovered, offers a new and fresh spin to DVCS es that leads to cleaner and simpler workflows. In this post, I have a look at what Jujutsu has to offer, and I dive into its command line interface and workflow. My goal is that, by the end of this post, you can understand a little bit why I find this tool so cool. I haven’t found a whole lot of content on Jujutsu so far. To be frank, I wasn’t able to find almost anything. Even the official repository of the project contains a getting started tutorial that is outdated (as per their own notice). However, there is an excellent guide written by Steve Klabnik, the author behind no starch press’ The Rust Programming Language 1 book, which I read and enjoyed a few years ago. Anyway, it seems like Steve’s guide on Jujutsu is the go-to tutorial to read if you want get started, so here it is: If you are the kind of person that would rather watch a video than read a text, there are a couple of good ones: Jujutsu is a simple, modern, easy to use, and change-centric DVCS that is compatible with Git. This means that it currently uses Git as a backend, and you can start using it right now with your pre-existing Git repositories, if you are so inclined. This is similar to what Git did in its early days with the bridge. This drove initial adoption because it was easy to communicate changesets between Subversion and Git. However, it is also different, because Git never used SVN as a backend, like Jujutsu does with Git. This last part is probably driven by the immense adoption of GitHub and similar services, and by Git being the de facto standard among developers worldwide. More interesting is Jujutsu’s design, which can be summarized by the following items: Also, before you ask, yes, the name ‘Jujutsu’ is weird. My understanding is that this is purely anecdotical. The binary was first, and it was named because it is easy to type and remember. They then assigned the name Jujutsu simply because it matches . In the next few sections we’ll be playing around with Jujutu’s CLI, and illustrating how things are done in this new world. The rest of this post uses the terms ‘Jujutsu’ and ‘ ’ interchangeably. Let’s get our hands dirty. In this section we’ll be creating a repository and performing some operations to illustrate a typical workflow. Before starting, install and setup . More info on how to do so for your operating system can be found here . First, we need to create and initialize a repository. We create a directory, let’s call it , and then into it. There exists a native backend, but it is disallowed by default, as it is still being worked on. The command itself tells you so if you try to use . We’ll create a new repository backed by Git instead: Looks good. If we want to use with a pre-existing Git repository, we’d use with the flag . For instance, I can do this in my Gaia Sky repository : As you can see, detects the existing remotes ( at Codeberg and at GitLab), and informs us that we need to track the master branch. But since we’re starting from scratch, this is not necessary. We have now a new empty repository. We can see its current status with . The output shows that our current working copy is clean. Below, we see it is empty. In the same line, we see the current change ID ( ) and the current commit ID ( ). The change ID won’t change until we create a new change with , but the commit ID changes every time we modify something in the working copy. As we mentioned earlier, the working copy is committed by default . In the second line we see the parent of our current change. Every repository starts with a root commit with the same change ID ( ) and commit ID ( ). This root commit is present in every repository. We also see that none of the changes have a description. In , descriptions are super important because they are (almost) the only way we have to identify our changes. We can add a description to our current change with , or its alias : Interesting. Now, our change is still empty, but it lists the description we just entered. This makes sense. Also, note that since we added a description, which requires amending a commit, we get a new commit ID. We went from to . In the last command, we could have used without the argument. Then, a new editor would pop up to edit the description. In my case, this editor is Helix (as configured in ), and this is what it would have shown: Write the description in the first line, save, and you are done. Let’s now actually do the work and create , and then check the status again. Right. Our working copy (current change) is not empty anymore. We got yet another commit ID, and now we see that we dded the file , below . At this point we are done with our first change. We can start a new one with . There is also a command, but internally it only updates the description and then creates a new change, so it does followed by . I like the / workflow better than the one, as it enables us to first describe what we’re doing and then do the actual work. We’ll stick with that. At any time, we can see the repository log by using . Woah, this is nice. This is the default output log format, and it is so much nicer than Git’s default output. Wee see three changes: Something else to note is that the current change is always annotated with . Moreover, the highlight script in this website does not show this, but have a look at the real output as shown in a terminal and you may notice something cool: A screenshot of a terminal showing the output of . See the purple letters at the beginning of each change ID? These highlight the minimum unique prefix that can be used to identify this particular change. In this case, identifies the first change, the second and the third. Once the repository grows, they won’t be one-letter prefixes anymore, but they will keep reasonably short for an average number of changes. What if we want to go and edit the change where we created ? Easy, we just use , followed by the change identifier. In this case, the minimum prefix for that change is , so we use that. The output suggests that our working copy now is the change . If we now show the log, we see that the has changed, but the top change is no more: Of course, we had an empty change with no description, so it was discarded. We effectively reverted the we ran before. Let’s re-run it, and add a description. Now we have an empty change with a description that reads . Let’s actually create the file. Our current working change contains a new file (which was automatically committed) and a description. Let’s try to redo the . If we check the log now, we see that the is in the middle change. Notice how there are no “detached HEAD” messages here, as is fine with modifying history. Even more, it will automatically rebase changes we make in the middle of our graph. If we do , we’ll see that our file is not there. This makes sense, as we are back at the previous change. We can now even edit this change and get away with it! Let’s do it. And we check the status. Wild! The output suggests that the descendant commit, where we created , was automatically rebased. Of course, we modified its parent, so the commits that follow need to be rebased, but did this automatically for us. Just like that, what a champ. Maybe, now that we’re at it, we can also change the description of this change to reflect a little bit better what happended. Again, a rebase was needed, because we amended a description. The log reflects the new description. Awesome. We can now go back to our previous change, where we created , and continue from there. To do so, we move our current change (marked by ) in the graph, to . We can just use the minimum unique prefix, which is in this case. Let’s check the log for completion. This time around we’ll use the flag, for ‘summary’. This shows a listing of the actions in each change, with for additions and for edits. Ok. Our repository now has two files, and , and two changes (plus the root one). As we mentioned already, has anonymous branches by default. We create branches by creating new changes with non-leaf commits as parents. First of all, we need an empty change, so we do . Now, we’ll create a new change with as parent. This is our first change, where we created . We use the minimum unique prefix . Seems like it went through. I wonder what the change graph looks like now… detected that the parent is not a leaf, so it created the new change in a branch. Now has two children. Let’s describe the new work on this branch. And now we could create the file with some content in it. Now we have our working copy, where we created , and the other branch, where we created . If now, we should see only and . To switch branches we need to move our working copy . We already know how to do it, with ! Just as we predicted, this branch has and , but no . Let’s say that we are finished with our work in the branch and need to merge it. There is no (actually, there is, but it is deprecated), but we can use instead! You see, creates a new change, and by default it acts on . However, you can give it a different change, or even more than one change. And what is a merge commit, but a commit with two or more parents? Let’s remember where we are. We need to merge ( ) and ( ), so we just create a new change with these two as parents: Awesome, totally painless. We created a new change on top of two parents, effectively creating a merge commit. This merge commit has no description, so we can add it to make it explicit. Now, my merge is an empty change with a description, but no actual file changes. I like to leave it like this, so that my graph is nice and clean. Let’s create a new change on top of it. I hope you are beginning to see how powerful and simple this is. To finish this little tutorial, I’ll cover how manages conflicts. Conflicts are a common pain point in most DVCS. In they are still annoying, but much more pleasant to work with. Let’s create some conflicts in our test repository. First, let’s add a description to our current change, edit , and add some text to it. Now, let’s add a new change below ( ) that also modifies . And now let’s have a look at our graph. Now, we’ll create a new change on top of Sparta ( , or just ) and Athens ( , or just ). promptly informs us that we have a conflict, since we modified from both and . Our log also shows the conflict, and marks it read (not visible with this web’s highlighting). Now we could just describe this, and keep doing work. won’t complain, it will simply take note that we have a conflict at that change. When we are ready to resolve the conflict, we need to go back and edit the merge change, ( ). And then just edit the conflicted file . It looks like this: This is a bit different from Git. Here and indicate the start and end of the conflict, and and indicate the changes from either side. In order to resolve this, we edit the file to look like this: When we save, we can check the status of the repository: No more conflicts! automatically resolved the conflict when it detected that we edited it in-file. This is so nice. Let’s make sure our conflict is gone by looking at the log. Right, gone for good. In the next few sections I will touch on other aspects of , but without entering in much detail. First, we’ll have a look at the revsets feature, then we’ll see how to create named branches with bookmarks, followed by some quick tips as to how to push/pull from remotes, and then we’ll touch on the operations log feature. We can pass change or commit IDs to most commands. In addition, has “revsets”, short for “revision sets”. Revsets can be expressed with symbols, operators or functions. For instance, the symbol describes the current working change. Operators describe relationships between changes. We can use logical operators like or , but also things like to describe the ancestors of . Functions are where most of the strength resides. We have to get the root change, or to get all of them. But also to get all ancestors of a change, or , to get all changes with in their description. You can use revsets with any command. For instance, we can use this in our test repo: Bookmarks are a way to attach named tags to changes. They are most typically used to mimic named branches. We can , , , and bookmarks (and more). If we want to create a bookmark on the current change, we just do this. The log also displays the bookmark. We may add the bookmark to the current change as well. Most probably, you host your code on one of the various git hosting services like GitHub or GitLab. With , you can keep doing it. But before starting, let’s make sure that our repository is fine: Notice that there is no empty change at the tip of our current . First, we add our remote to the repository. I myself use Codeberg , so that’s what we’ll use here too. You need to create a new empty repository through the web interface of your hosting service of choice (or the provided CLI), and then add it as a remote to your project. And now we can just push. Good. It pushed the two named bookmarks, and , which right now point to the same commit ( ). To get the changes from a remote, we need to use . After a fetch, remember to check where the our working copy is, and to create a new change below your desired parent to start new work. You can find this project at https://codeberg.org/langurmonkey/jjtest . I created a file to make the landing page a bit more informative. Before pushing the new change that adds the readme file, I needed to move the bookmark to the current change with: If you don’t do that, the remote detects that the tracked branch has not changed, so nothing is pushed. Finally, I want to show off a very cool feature. This is the operation log. records every operation performed in a repository (commits, pulls, statuses, etc.) in a log that can be listed. Here is the full operation log of the repository. How cool is that! It tracks all the operations that we’ve run on the repository, even the s and the atuses. This allows us to to undo and redo operations to go back and forth in the repository action history, with the help of . Whenever you messed up, but don’t remember exactly what you did, use . Super handy. With this I conclude this short tutorial. Hopefully, this helped illustrate the power and simplicity of Jujutsu. I was surprised when I could find almost no content on Jujutsu in the web, especially given how good this tool already is. This may be due to the project still being worked on, and/or its adoption being super low, almost non-existent. I myself have only used it in testing or personal projects, where I’m the only committer. I think the native workflow is super clean, and I would absolutely recommend everyone interested to, at least, try it. That said, I’m waiting for the native backend to be ready for prime time, as I would like to give it a spin then, when its power bar is full. All in all, I think either Jujutsu, or something very similar, most probably will eventually end up replacing Git as the de facto DVCS . Its compatibility with the latter will certainly help in that regard. Of course, I’ll keep using Git in my production repositories, but going back to it after playing around with Jujutsu for some days feels clunky . I hope you give it a try, and maybe you’ll also become a convinced Jujutser . https://doc.rust-lang.org/book   ↩︎ Steve’s Jujutsu Tutorial Jujutsu: A Git-Compatible VCS – by Martin von Zweigbergk, the creator of Jujutsu himself. What is version control was AWESOME? – a tutorial series with (so far) only one part. Awesome CLI – the command line interface is so damn comfy to use. If using the Git CLI is bumping into a wall with your shoulder at full speed, using (which is what Jujutsu’s binary is called) is like getting a gentle and pleasant back massage. Change-centric – we have changes in addition to our usual revisions . The change model is borrowed from Mercurial, where changes and identify units of work that may span several revisions, or commits. Always-committed working copy – your working copy is committed automatically, so you don’t actually need to explicitly run a counterpart to . The repository is automatically and continuously amended whenever a command runs, so you never actually lose anything. Anonymous branches – also known as branchless design , branches are identified by the descriptions in their commits, but they are anonymous by default. However, since the backend is Git, there exist named bookmarks that can act as branch names. Happy conflicts – conflicts are not scary in Jujutsu’s world. In Git, conflicts are something that you need to get rid of before going on with your life. In Jujutsu, they are accepted and even committed to the repository. You get an annotation label reminding you that the conflict is there, and needs to be dealt with at some point. But in your own time, no need to panic. No staging – there is no index or staging area. The working copy itself is committed as it evolves, and amended on every change. You can still get the same functionality, but in an easier, more organic manner. The first line shows the current (empty) change ( ) The second, the one we just made, where we added ( ) The last line belongs to the root commit ( ) https://doc.rust-lang.org/book   ↩︎

0 views
Langur Monkey 1 years ago

On Neovim and Helix

Today I have updated my variable to point to Helix instead of Neovim, and also configured Yazi to use it as the first option for text. I have used [Neo]vim since forever, so what made me switch? In this post, I discuss some of the ups and downs of both editors and what ultimately made me decide for Helix. Neovim is a modal text editor that is notoriously barebones by default. This is a good thing. It includes the bare minimum to act as capalbe, general-purpose text editor. Any additional functionality is provided by plugins . Whie Neovim can be configured in vimscript to make it compatible with Vim, the native way of doing it is with Lua. When starting with Neovim you are expected to copy someone else’s configuration, or even better, to just use one of the multiple pre-packaged distributions like LunarVim, AstroNVim, or CosmicNVim. These projects exist only to mitigate the complexity of creating a functional and modern Neovim configuration, and to ease its maintenance. My own neovim configuration was initially copied from one of those projects (can’t remember which) and I’ve been tailoring it to my needs ever since. This approach tends to work well though, but it is time-consuming. If you start with a clean slate, things can get complex quickly. Need cool syntax highlighting? Use the treesitter plugin ( ). Need to use LSP? It is included by default in Neovim (not in Vim), but configuring it is hell, so you may want to use a plugin ( ) just for its configuration. Wait, I have so many plugins, this is getting difficult to maintain! Don’t fret, use a plugin manager plugin, like , to simplify the process. And so on and so forth. Sometimes this whole thing feels like an amalgamation of pieces duct taped together that barely work. Here’s a non exhaustive list of things I dislike about plugins: At this point we must ask ourselves, what else is there? Modal text editors are typically used inside a terminal window, and are operated (almost) entirely using the keyboard. The two most well-known candidates are Vim and Neovim , together with their ancestor . Some more recent additions to the roster include Kakoune and Helix , which vary the editing model slightly. I’m sure there are more that I’m unaware of. Out of all of these, I picked Helix as a possible replacement of Neovim. You see, Helix has a different approach to functionality, as it ships with LSP, autocompletion, treesitter, fuzzy search, surround, multiple selections and much more by default. This means that it strives to offer a much more complete experience off-the-shelf, when compared to the others, with minimal configuration. In Neovim , the editing mode is , or . This means that we first specify the action to perform, like ank, elete, hange, etc., and then we specify the object[s] to which the action applies, also called selection, like ord, end of line , etc. This model of action first, and selection last has its problems. For instance, you can’t see your objects until the action has already been performed. If there were errors, you are forced to undo and then try again. In [Neo]vim, you can do to delete the current word. This is action, for delete, and selection , for “in word”. In contrast, borrowing from Kakoune’s model, Helix inverts the paradigm. Instead of action followed by object, we first specify the object and then the action, , or . This enables seeing what will be changed by the action before actually executing the action. This model is much more interactive and, in my opinion, intuitive. In Helix you’d do ( for end-beginning selection, as in Helix movements imply selections, and to delete). In both Neovim and Helix we have , , and modes, with the command mode on top. Additionally, Helix has some minor modes, or sub-modes, that are accessible from normal mode and revert back to it after one command. These are mode, mode, mode or mode. For instance, space mode, accessed by typing Space , offers a set of mappings to different actions, shown in a popup, such as a fuzzy file picker (akin to ), a buffer picker, or a jumplist picker. It is similar to what you achieve in Neovim by using . But in Helix, all of this is built in. Helix has a very discoverable UI, which is quite rare for a terminal application. It does a very good job of communicating the key bindings to the user. When entering space, match or goto modes a popup appears, with the further bindings and a small explanation. This helps a lot with the on-ramp. Goto mode popup in Helix. Configuration We have already established that configuring Neovim is not simple . What about Helix? Well, it uses TOML and the configuration tends to be much shorter than your regular Neovim configuration, thanks to the sane defaults and built-in functionality. My Helix configuration file currently looks like this: As far as I know, Helix does not still have a plugin infrastructure in place. This limits a lot what can be done with the editor, but it also keeps it simple. Arguably, a lot of the functionality expected from a text editor is included in the base package. The core of Neovim is written in C, with a lot of Lua and Vimscript thrown in for additional features. Helix, in contrast, is written in Rust, which is a more modern language that emphasizes safety and produces more correct and memory-safe programs by default. I’m not saying that Rust is better than C, I’m just stating that Rust forces you to write code that tends to be safer. Here are some cool and handy tricks you can do with helix. Helix includes LSP (language server) support by default, but the actual language servers for each particular language need to be installed separately in your OS. you can do to get a listing of all the supported languages, and whether their language server is available or not. Below is an excerpt of its output: You can do and then use Tab eo cycle the list of themes that appears, while the selected theme is applied instantly as a preview. Moreover, Helix comes with a wide selection of actually usable themes. Things like acme, github, dracula, onedark, catppuccin, monokay, and all their variants are in that selection. ( Space ) enters Space mode , which is super handy. It includes a fuzzy search ( ), buffer ( ), jumplist ( ), and symbol pickers ( ), code actions ( ), a rename action ( ), and much more. Space mode in Helix. enters Match mode , which is also quite intuitive. Enter it with . It contains functions to navigate to the matching bracket ( ), add surrounding characters to the selection ( ), replace the closest surrounding characters ( ), or delete the closest surrounding characters ( ). These last functions mimic what is provided by a plugin like in Neovim. More on match mode and surround . and select around and in objects. Objects can be words and paragraphs, but also tree-sitter objects like functions, types, arguments, comments, data structures, etc. and let you navigate through tree-sitter objects. Use the brackets followed by the object type (functions , type definition , argument , comment , etc.). duplicates the cursor. Yes, helix supports multiple cursors by default to operate on multiple lines. You can also align your cursors (and text) with . Super useful. Collapse the cursors with . lets you jump instantly to a two-character label, similar to what qutebrowser does with links. Write and then type in the label of the location where you want to jump to. Jump labels appear at almost every word in view. Of course, there are many more functions and features, but these are the ones I like the most. There are a couple of issues with Helix. As of today, I’m comfortable enough with Helix so that I’m now using it as my default editor. I also configured Yazi to open text files with Helix by default. Still, I’m much more comfortable with vim keybindings, but I hope that in a few days/weeks I can be as productive with Helix as I am now in Neovim. In this post, I have visited some of the most interesting features of Helix, and touched on what ultimately motivated me to switch. That said, I’m not yet sure I’ll stick with Helix. They usually break with new upstream versions. Their documentation is often spotty at best. It’s difficult to discover them. Reddit discussions? Github search? Sometimes their cross-interactions are unexpected and/or weird. Helix includes LSP (language server) support by default, but the actual language servers for each particular language need to be installed separately in your OS. you can do to get a listing of all the supported languages, and whether their language server is available or not. Below is an excerpt of its output: You can do and then use Tab eo cycle the list of themes that appears, while the selected theme is applied instantly as a preview. Moreover, Helix comes with a wide selection of actually usable themes. Things like acme, github, dracula, onedark, catppuccin, monokay, and all their variants are in that selection. ( Space ) enters Space mode , which is super handy. It includes a fuzzy search ( ), buffer ( ), jumplist ( ), and symbol pickers ( ), code actions ( ), a rename action ( ), and much more. Space mode in Helix. enters Match mode , which is also quite intuitive. Enter it with . It contains functions to navigate to the matching bracket ( ), add surrounding characters to the selection ( ), replace the closest surrounding characters ( ), or delete the closest surrounding characters ( ). These last functions mimic what is provided by a plugin like in Neovim. More on match mode and surround . and select around and in objects. Objects can be words and paragraphs, but also tree-sitter objects like functions, types, arguments, comments, data structures, etc. and let you navigate through tree-sitter objects. Use the brackets followed by the object type (functions , type definition , argument , comment , etc.). duplicates the cursor. Yes, helix supports multiple cursors by default to operate on multiple lines. You can also align your cursors (and text) with . Super useful. Collapse the cursors with . lets you jump instantly to a two-character label, similar to what qutebrowser does with links. Write and then type in the label of the location where you want to jump to. Jump labels appear at almost every word in view. To my knowledge, there is no integrated spell checker yet, so if you want this functionality, you need to configure a spell checker LSP, like , in your file for each language. Clearly a pain point. A lot of editors nowadays offer a vim mode, either built-in or as a plugin. I myself use it in IntelliJ IDEA when I need to do some heavy refactoring of Java. If I ever get as comfortable using the Helix editing mode as I am with vim’s, this could become a drawback. I have the impression that some actions take more key strokes in Helix than they do in Neovim. I’m still not sure about that though.

0 views
Langur Monkey 1 years ago

Just ``make`` use of ``just``

Nowadays, makefiles are ubiquitous in software. Most C and C++ projects have used them historically, and still use them as a build system. Nowadays, lots of projects written in other languages which have their own build tools also use them, not to make files, but to store and run commands in an organized manner. If this is you, you are doing it wrong. However, there is a tool designed to do just that: . is a GNU utility mainly used to make files. It automatically determines which parts of a build are outdated and need remaking (recompiling), and runs the commands to make them. uses a makefile, which is a user-written file that specifies the targets (files) and the rules to make them. Usually, the makefile informs make as to how to compile and link a program written in C or C++, even though it is also extensively used in other well-suited languages like LaTeX. However, targets in a makefile are by default files. This means that they build other files. Let’s see a very simple example. In this makefile, we have one file target, . To make the file , we need a file . What appears after the colon in the first line are the requirements to make that file. The command appears below, and it just concatenates 2 times and outputs the result to . Let’s try it. First, let’s create a file named . Then, make sure you have a file named with the contents shown above. Then, run . If you , you will see that now you have a new file named , with the contents of repeated twice. Now, try again. Indeed, nothing was made because determined that is already up to date. This is essentially the main purpose of . However, is often used to store and run arbitrary commands that do not really make any file. For example, in a C codebase you often find a target which removes all object (*.o) files. In our example, we can add a target that removes . To do that, you can explicitly tell that the target is not linked to a file. We do so by annotating the target with : After that, with run even if you do not have a file with the name . However, this target will not work if a file named is in the same directory. To overcome this, we need . A phony target is, in essence, a target that is always out of date. This is why, conceptually, is not very well-suited to only organize and run arbitrary commands. To that purpose, there exists another tool, . ( github ) is a command runner written in Rust. It is not a build system. It steps in in those occasions when you have a project and need to keep your commands (that do not build the project!) nicely organized. stores the commands (called recipes ) in a file named , with a syntax very similar to makefiles. For example, the for this website’s project is the following: It has three targets and none of them build files. They do random stuff like minifying and the CSS and the JavaScript files, deploying the website to the server by running the script, or starting a local server. See that targets can still have dependencies ( depends on ), so that they are run in order. has many more features though. The next time you need to store and organize commands for your project, think again before using . might just be what you need! It is a command runner, not a build system, so there’s no need for recipes. Multi-platform. Can be invoked from any subdirectory. Recipes can be listed with . Specific and informative errors. Errors are resolved statically. Recipes can be written in any arbitrary language. Much more .

0 views
Langur Monkey 1 years ago

Shader canvas Hugo shortcode

Do you want to add a canvas with a shader running in real time to your Hugo site? In this post I show how to create a Hugo shortcode to display a shader. You need two ingredients for this recipe: The glslCanvas Javascript library (single file) originally written for The Book of Shaders website. Save it in your directory. Download it with: Then, add to the header of the post/page. The Hugo shortcode. Paste this in a file named in : Once that’s set up, embed your shader in your post, like so: That’s it. Run your site and open your browser to admire your shader in all its WebGL splendor. My original implementation for the shader below is in Shadertoy . The actual code used in this website is shown below the canvas. The glslCanvas Javascript library (single file) originally written for The Book of Shaders website. Save it in your directory. Download it with: Then, add to the header of the post/page. The Hugo shortcode. Paste this in a file named in : Once that’s set up, embed your shader in your post, like so:

0 views
Langur Monkey 1 years ago

Supercharging exoplanets

A couple of years ago I wrote about the procedurally generated planets in Gaia Sky. In this post, I provided a more or less detailed technical overview of the process used to procedurally generate planetary surfaces and cloud layers. Since then, we have used the system to spice up the planets in the planetary systems for which the Gaia satellite could determine reasonable orbits (see the data here , and some Gaia Sky datasets for some of those systems here , including HD81040, Gl876, and more). However, with the upcoming Gaia DR4 , the number of candidate exoplanets is expected to increase significantly , rendering the “one dataset per system” approach unmaintainable. In this post I describe some of the improvements made with regards to exoplanets in Gaia Sky, in both the handling of large numbers of extrasolar systems seamlessly, and in the brand new, improved procedural generation of planetary surfaces and clouds. A screenshot from the surface of a procedurally generated planet, in Gaia Sky. In the picture we can see elevation, clouds, atmosphere and fog. This uses the new system. Representing Exoplanets So far, Gaia Sky was able to represent extrasolar systems via additional datasets containing the system objects and metadata (barycentre, stars, planets, orbits, etc.). These datasets were and are distributed as standalone downloads via the integrated download manager, which serves data from our data repository . This approach was only good as far as the number of systems was kept low, which was true until now. However, with the advent of DR4 , this number is expected to reach four digits, so a new solution is in order. The representation of exoplanet locations has been in Gaia Sky since 3.6.0, released in March 2024. It is based on the NASA Exoplanet Archive , which contains some 5600 confirmed planets. Up to now, this was implemented as a glorified point cloud, where the glyph for each system is chosen according to the number of candidate planets in said system. Broman et al. developed a more extensive use of glyphs for representing exoplanets in Open Space in ExoplanetExplorer 1 , and we certainly were inspired by this work. The textures used for exoplanets, sorted from 1 to 8 candidates (1-4 top, 5-8 bottom). Below is a view of the NASA Exoplanet Archive from the outside. The elongated arm to the left is the Kepler field of view . The NASA Exoplanet Archive in Gaia Sky, represented as a point cloud with glyphs. This representation is useful to indicate the position and number of planets of each system. It works using the particle set object, which is essentially a point cloud. An extension was necessary, in order to select a texture for each system according to the value of one of the table columns. In this case, the texture is selected according to the number of planets in the system. This is done by means of the new textureAttribute attribute in particle sets. The first objective in this little project was to achieve a seamless navigation from the “global” view to the “local” view in a seamless way without having to preload all systems’ descriptors at startup. The global view represents the full dataset observed from afar, while the local view represents a rendering of each star system with the exoplanets orbiting around. Additionally, the mechanism used for this should be, if possible, generic for all particle sets. The base data file is a VOTable as downloaded from the NASA Exoplanet Archive website. This VOTable contains all the available information for each of the planets: name, host name, position, physical characteristics, orbital information, etc. In order to achieve this seamless navigation, we considered two different options, taking into account that new objects representing the systems need to be loaded at some point: Option one has the advantage that the dataset to distribute is much smaller. It only contains the metadata and the VOTable data file. However, this one solution is, by design, very ad-hoc to this dataset in particular. In other words, it would not be easily extensible to other exoplanet datasets (e.g. Gaia DR4) without major code changes. Option two is much more general, in the sense that it can be applied to all exoplanet catalogs, provided all the Gaia Sky descriptor files are generated. However, the distributed package is a bit heavier, as it needs to include an extra JSON descriptor file for each system. But those are usually small, and compress well. In the end, the positives of the second option vastly outweigh the negatives, so that’s what we went with. We generated the descriptor files with a Python script that aggregates all planets in the same system and produces a series of JSON files. Then, we added a couple of attributes to particle sets : In principle, any particle set can define these two attributes to enable proximity loading . In it, when the solid angle of a particle overcomes the proximity threshold, the load operation is triggered, where the JSON descriptor file corresponding to the particle gets loaded, if it exists (in the location given by “proximityDescriptorsLocation”). The matching is done using the particle’s name, which must be the same as the file name plus extension. There are a few requirements for it to work: The loading itself happens in a background thread. When it is done, the scene graph and the indices are updated. Then, if the camera is in focus mode, and the focus object is the trigger particle, the camera automatically switches focus to the first object in the dataset. Ideally, the first object in the dataset should have the same position as the particle in order to prevent a sudden seeking camera pan. The video above shows the concept of proximity loading in motion. As the camera gets near an object, the proximity loading is triggered and new objects are loaded. When they become available, the camera switches focus to the new object. This results in a seamless exploration from the global dataset to the individual planets of each system. As mentioned in the beginning, back in 2021 we had a first look at the procedural generation of planetary surfaces. This first approach was based on CPU code that ran rather slowly, so we had to add some progress bars at the bottom of the screen for each of the channels to provide some feedback to the user about the status of the generation. This was less than ideal, so we set up a project to revisit this procedural generation and improve it, if possible. This project had the following aims: The first step is straightforward to explain. Essentially, we moved the code from the CPU to the GPU. But the devil is in the details, so let’s dive in. Previously, we were basing on the Joise library , a pure Java implementation of some common noise functions and layering strategies. From that, we created matrices in the main RAM for the elevation and the moisture, and we filled them up in the CPU using this library. From that, we generated the diffuse, specular and normal bitmaps, created the textures and set up the material. Easy enough. Now, in the GPU we have two options: On the one hand, pixel shaders are ubiquitous and supported everywhere, but they are a bit difficult to use for compute operations, and typically require encoding and decoding information using textures. On the other hand, compute shaders are perfect for this task, as they accept data inputs directly, but they are only in OpenGL since version 4.3. This leaves out, for example, macOS (only supports 4.1) and many systems with older graphics cards. For the sake of compatibility, we decided to use pixel shaders in favor of compute shaders. They are more difficult to work with, but they should be universally compatible. Moreover, we can embed them directly in a website, like this curl noise, which is pretty neat: But back to the topic, we based our implementation on the gl-Noise library. We fixed some issues and modified it a bit to better suit our need. We ended up implementing fBm for all noise types. fBm is a way to recursively add finer detail to our noise by increasing its frequency and decreasing its amplitude each cycle or octave . The code below shows how to add fBm to any noise function. Adding a couple of fBm octaves to the previous Curl noise shader, to get a total of 3 cycles, we get something with much finer detail and more convincing: We also changed the noise types from gradval, perlin, simplex, value and white to perlin, simplex, curl, voronoi and white. The types of noise supported in the new version. Since we are creating the noise using shaders, we need to render to an off-screen buffer. We do the process in two passes. The first pass generates two or three noise channels in a texture. We call it the biome map . The channels are: In this pass, we can additionally add another render target to create an emissive map . We’ll cover this later. The second step uses a frame buffer with multiple render targets, gets the biome map generated in step 1 as input, together with some parameters like the look-up table , and outputs the diffuse, specular, normal and emissive maps. Those are then used to texture the object. An example using simplex noise is shown below. Note the biome map only has the red and green channels active (for elevation and moisture) in this example. Generated maps for a random planet. From left to right and top to bottom: biome (elevation and moisture) map, diffuse textrue, specular texture and normal texture. Normal map – Note that the normal texture is only generated if needed, which is when ’elevation representation’ is set to ’none’ in the Gaia Sky settings. If elevation representation is set to either tessellation or vertex displacement, the normals are computed from the orientation of the surface itself, and the normal map is redundant. Emissive map – Additionally, we may choose to create an emissive map in an additional render target in the biome buffer using a combination of the base noise and white noise. This is used to add ‘civilization’ to planets by means of lights that are visible during the night, on the dark side. When the emissive map is active, the surface generation step (step 2) renders the regions with lights with black and gray colors, simulating cities or artificial structures. The maps above correspond to the following planet: A view of the whole planet corresponding to the maps above. This render also contains a cloud layer, which is generated with the same process and by the same shader as the biome map. Noise Parametrization The noise parametrization described in the old post has also changed a bit since then. Essentially, we have now only one fractal type (fBM), the noise types are different, and we have introduced some missing parameters which are important, like the initial amplitude. Finally, I want to write a few words about the way the procedural generation UI, which exposes the functionality directly to the user, has changed in Gaia Sky as a result of this migration. Parameter Presets First, we have added a series of presets that make it very straightforward to play with the surface generation. These are: Surface generation tab, wit the preset buttons at the top, in blue. Each of these is composed of a specific range or value set for each parameter (noise or otherwise), which get automatically applied when the user clicks on the button. So we may have a subset of look-up tables for Earth-like planets, together with a very low initial frequency, and high number of octaves and lacunarity. This is repeated for each of the parameter presets. Hidden Noise We have also hidden the noise parameters in a collapsible pane, which produces a cleaner, less cluttered UI. The clouds generation tab shows all noise parameters. Results Here are some results produced with the new procedural generation system in Gaia Sky: Some procedurally generate planets using the parameter presets, with Gaia Sky. The improvements described in this post will be released shortly with Gaia Sky 3.6.3 for everyone to enjoy. E. Broman et al., “ExoplanetExplorer: Contextual Visualization of Exoplanet Systems,” 2023 IEEE Visualization and Visual Analytics (VIS), Melbourne, Australia, 2023, pp. 81-85, doi: 10.1109/VIS54172.2023.00025 .  ↩︎ https://en.wikipedia.org/wiki/Perlin_noise   ↩︎ https://en.wikipedia.org/wiki/Simplex_noise   ↩︎ https://al-ro.github.io/projects/curl/   ↩︎ https://en.wikipedia.org/wiki/Worley_noise   ↩︎ https://en.wikipedia.org/wiki/White_noise   ↩︎ Generate the system objects transparently directly in Gaia Sky whenever the camera gets close enough. Pre-compute the system descriptor files beforehand and order Gaia Sky to load them whenever the camera nears the object. “proximityDescriptorsLocation” – the location of the descriptor files for the objects of this particle set. The descriptor files must have the same name as the objects. “proximityThreshold” – solid angle, in radians, above which the proximity descriptor loading is triggered. Particles in the particle set must have a name. The proximity descriptors location, and optionally the proximity threshold angle, must be defined for the particle set. The JSON files to load must be prepared and available at the given location. Replace the CPU -based code with a GPU implementation that runs much faster. Improve the procedural generation to produce higher fidelity, more believable planets. Enhance and simplify the procedural generation module and window so that it is easier to use and understand. Pixel shaders. Compute shaders. The first pass generates two or three noise channels in a texture. We call it the biome map . The channels are: Red: Elevation. Green: Moisture. Blue (optional): Temperature. The second step uses a frame buffer with multiple render targets, gets the biome map generated in step 1 as input, together with some parameters like the look-up table , and outputs the diffuse, specular, normal and emissive maps. Those are then used to texture the object. seed – a number which is used as a seed for the noise RNG . type – the base noise type. One of perlin 2 , simplex 3 , curl 4 , voronoi 5 or white 6 . scale – determines the scale of the sampling volume. The noise is sampled on the 2D surface of a sphere embedded in a 3D volume to make it seamless. The scale stretches each of the dimensions of this sampling volume. amplitude – the initial noise amplitude. persistence – factor by which the amplitude is reduced in each octave. frequency – the initial noise frequency. lacunarity – determines how much detail is added or removed at each octave by modifying the frequency. octaves – the number of fBm cycles. Each octave reduces the amplitude and increases the frequency of the noise by using the lacunarity parameter. number of terraces – the number of terraces (steps) to use in the elevation profile. Set to 0 to not use terraces. terrace coarseness – controls the steepness of the terrain in the transition between different terrace levels. range – the output of the noise generation stage is in \([0,1]\) and gets map to the range specified in this parameter. Water gets mapped to negative values, so adding a range of \([-1,1]\) will get roughly half of the surface submerged in water. power – power function exponent to apply to the output of the range stage. turbulence – if active, we use the absolute value of the noise, so that deep valleys are formed. ridge – only available when turbulence is active, it inverts the value of the noise, transforming the deep valleys into high mountain ridges. Earth-like planet. Rocky planet. Water world. E. Broman et al., “ExoplanetExplorer: Contextual Visualization of Exoplanet Systems,” 2023 IEEE Visualization and Visual Analytics (VIS), Melbourne, Australia, 2023, pp. 81-85, doi: 10.1109/VIS54172.2023.00025 .  ↩︎ https://en.wikipedia.org/wiki/Perlin_noise   ↩︎ https://en.wikipedia.org/wiki/Simplex_noise   ↩︎ https://al-ro.github.io/projects/curl/   ↩︎ https://en.wikipedia.org/wiki/Worley_noise   ↩︎ https://en.wikipedia.org/wiki/White_noise   ↩︎

0 views
Langur Monkey 1 years ago

Ph.D. thesis defended successfully

Edit (2024-07-03): The thesis has now been published and is available here (Open Access). After almost 2 years since the submission of my Ph.D. thesis , I finally could defend it successfully last Friday (May 3, 2024). It has been a long journey, exactly 9 years since the initial acceptance on May 3, 2015. The road has been bumpy, especially since I had to juggle my job with the work I was doing on the side for the thesis, but in the end I think it was well worth it. I learned a ton, especially in the field of scientific visualization, which was completely new to me. I will share the author’s copy of my thesis shortly. I even got two Ph.D. hats, one from each research group (Gaia and Visual Computing) I’m part of. I accepted them with great honor and happiness. The Ph.D. hats I was gifted by my colleagues from ARI (Gaia, Jasmine and Digital Twins groups), left, and from the IWR (Visual Computing group), right. The dissertation has been published at the document server of Heidelberg University Library, and is Open Access: http://www.ub.uni-heidelberg.de/archiv/34797

0 views