mirror of
https://github.com/osmarks/website
synced 2025-01-30 19:04:45 +00:00
copyedits, heavenbanning post
This commit is contained in:
parent
9d9a78a950
commit
fe281b95ca
BIN
assets/images/facebook-arpu.png
Normal file
BIN
assets/images/facebook-arpu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
BIN
assets/images/heaven.png.original
Normal file
BIN
assets/images/heaven.png.original
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
BIN
assets/images/reddit-arpu.png
Normal file
BIN
assets/images/reddit-arpu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 189 KiB |
BIN
assets/images/snap-arpu.png
Normal file
BIN
assets/images/snap-arpu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
112
blog/heavenbanning-economics.md
Normal file
112
blog/heavenbanning-economics.md
Normal file
@ -0,0 +1,112 @@
|
||||
---
|
||||
title: The economics of heavenbanning
|
||||
description: Predicting the post-social world.
|
||||
created: 26/01/2025
|
||||
slug: heaven
|
||||
squiggle: true
|
||||
---
|
||||
::: epigraph attribution=Architects link="https://www.youtube.com/watch?v=nT9NUjeBocw"
|
||||
Delete another day <br />
|
||||
Doomscrolling, sat beneath my shadow <br />
|
||||
Keep loading, nothing here is hallowed <br />
|
||||
Come see the world as it oughta be
|
||||
:::
|
||||
|
||||
[Heavenbanning](https://x.com/nearcyan/status/1532076277947330561) - [first proposed](https://news.ycombinator.com/item?id=25522518) as far back as late 2020, but popularized in 2022[^1] - is an alternative to shadowbanning (hiding users' social media comments from other people without telling them) in which users see only encouraging, sycophantic LLM-generated replies to their posts (which remain hidden from other humans). This, and similar technologies like the [Automated Persuasion Network](https://osmarks.net/stuff/apn.pdf), raise concerning questions about ethics, the role of stated preferences versus revealed preferences and the relevance of humans in the future. But for predicting adoption, the relevant question is not whether it's ethical - it's whether it's profitable.
|
||||
|
||||
The purpose of heavenbanning, to the platform implementing it, is to mollify users who would otherwise be banned from the platform and leave, keeping their engagement up without disrupting other users' experience. An obvious question is whether shadowbanning - or slightly softer but functionally similar forms like downweighting in algorithmic recommenders - is common enough for this to matter. Most of the available academic research is qualitative surveys of how users feel about (thinking they are) being shadowbanned, and the quantitative data is still primarily self-reports, since social media platforms are opaque about their moderation. According to [a PDF](https://files.osf.io/v1/resources/xcz2t/providers/osfstorage/628662af52d1723f1080bc21?action=download&direct&version=1) describing a survey of 1000 users, the perceived shadowbanning rate is about 10%. [This paper](https://arxiv.org/abs/2012.05101) claims a shadowban rate of ~2% on Twitter (and a variety of slightly different shadowban mechanisms), based on scraping.
|
||||
|
||||
Either is high enough that an alternative which doesn't drive away the shadowbanned users is worth thinking about. But why limit heavenbanning to them at all? Many users may not be banned but nevertheless receive less of a response than they would like, or than is optimal to keep them using the platform. A [recent study](https://karthikecon.github.io/karthiksrinivasan.org/paying_attention.pdf) - which did *causal* testing, giving some posts extra LLM-generated replies - shows that receiving extra interaction on a Reddit post increases content producer output significantly, which I think is a good indicator of increased general engagement. The paper also suggests an additional benefit of heavenbanning: modelling users as split between "content consumer" and "content producer", a profit-maximizing platform should choose to show consumers "bad" content so that producers - who value consumer attention - produce more "good" content. Heavenbanning decouples this, allowing consumers to be served more "good" content (by discarding more of producers' output) without producer alienation. This is only relevant in the regime where heavenbanning can substitute for consumers' output but not producers', but this is the current situation and I think this will hold for a few more years[^3].
|
||||
|
||||
## Quantitative costs and benefits
|
||||
|
||||
Let's make up some numbers and see what happens.
|
||||
|
||||
Meta is a publicly traded company, so they have to disclose some financials. The [Q4 2023 earnings presentation](https://investor.atmeta.com/financials/default.aspx) has somewhat more information than later ones - specifically, advertising revenue by user geography and active users by geography. Since Reddit's IPO, they also release [results](https://investor.redditinc.com/news-events/news-releases/news-details/2024/Reddit-Announces-Third-Quarter-2024-Results/default.aspx). [Snap](https://investor.snap.com/overview/default.aspx) disclosures are also available. I am not aware of other large, relevant social media companies with useful releases like this at this time.
|
||||
|
||||
::: captioned src=/assets/images/facebook-arpu.png
|
||||
Facebook average revenue per user, Q4 2023.
|
||||
:::
|
||||
|
||||
::: captioned src=/assets/images/reddit-arpu.png
|
||||
Reddit's figures, from Q3 2024.
|
||||
:::
|
||||
|
||||
::: captioned src=/assets/images/snap-arpu.png
|
||||
Snap's figures, also from Q3 2024.
|
||||
:::
|
||||
|
||||
Facebook apparently makes ~10 times the revenue per user of Reddit and Snap in the US and internationally[^2]. I don't have good enough information to know whether this is due to more effective monetization of each ad, demographics, or higher per-user use of Facebook platforms resulting in more ads served, but this provides very rough bounds and shows that choices somewhere can substantially affect revenue.
|
||||
|
||||
The intended effect of heavenbanning is to increase users' use-hours, by making them feel more valued and included in the platform's community, and thus to increase the quantity of ads they see and interact with. We probably can't model ad revenue as scaling linearly with use time, however: ads are useful insofar as they direct spending or other actions off the platform, so doubling ads served on a platform will less-than-double their value and so less-than-double the spending, assuming reasonable market efficiency.
|
||||
|
||||
Modelling complex second-order effects such as more high-quality output from producers specifically is tricky, so for the purposes of my very simplified model I will instead assume that the heavenbanning system operates by giving a constant fraction of users LLM-generated responses to their comments and posts, which increases their engagement by a fixed factor. Alternatives include a somewhat heavenbanning-like system in which each user has a fully generated feed, or in which every user's post sometimes "goes viral" and receives large amounts of synthetic traffic.
|
||||
|
||||
With some other figures plugged in or imagined, here's the full model:
|
||||
|
||||
```squiggle
|
||||
// Guessed: most people are probably not commenting/posting once per minute and expecting substantive responses.
|
||||
posts_per_active_hour = 1 to 60
|
||||
|
||||
heavenban_replies_per_post = 1 to 6
|
||||
|
||||
// ~directly generate comment to fairly complex RAG/etc system.
|
||||
tokens_per_generation = 100 to 5000
|
||||
|
||||
// ~7B models on commercial inference to LLaMA-3.1-405B.
|
||||
// With in-house inference lower costs may be possible.
|
||||
model_cost_per_million_tokens = 0.02 to 0.8
|
||||
|
||||
heavenban_cost_per_post = tokens_per_generation * model_cost_per_million_tokens / 1e6 * heavenban_replies_per_post
|
||||
heavenban_cost_per_hour = posts_per_active_hour * heavenban_cost_per_post
|
||||
|
||||
// https://www.statista.com/statistics/270229/usage-duration-of-social-networks-by-country/ says ~2 hours globally, but this is across all social media services rather than any particular one and I don't know its methodology.
|
||||
social_media_hours_per_day = 1
|
||||
social_media_hours_per_quarter = social_media_hours_per_day * 30 * 3
|
||||
|
||||
// 50% increased use as an upper bound seems high given the already quite large numbers, but people presumably have other entertainment activities (also other social media platforms) they can trade off against.
|
||||
heavenban_increased_engagement = triangular(1.0, 1.15, 1.5)
|
||||
|
||||
social_media_heavenbanned_hours_per_quarter = heavenban_increased_engagement * social_media_hours_per_quarter
|
||||
|
||||
heavenban_cost_per_quarter = social_media_heavenbanned_hours_per_quarter * heavenban_cost_per_hour
|
||||
|
||||
// Ad revenue per marginal hour decreases with total hours.
|
||||
diminishing_returns_on_advertising_exponent = beta(2, 2)
|
||||
|
||||
ad_revenue_increase = (social_media_heavenbanned_hours_per_quarter / social_media_hours_per_quarter) ^ diminishing_returns_on_advertising_exponent - 1
|
||||
|
||||
necessary_ad_revenue_per_quarter = heavenban_cost_per_quarter / ad_revenue_increase
|
||||
|
||||
necessary_ad_revenue_per_quarter
|
||||
```
|
||||
|
||||
The exact values used in this are quite arbitrary and the error bars are extremely wide, but this structure is still useful. I consider first-order heavenbanning effects only, i.e. users engaging more with the platform as a result of heavenban responses, and estimate them at a fairly low level based on handwaved extrapolation from the earlier Reddit study. In this case, heavenbanning profitability is insensitive to how many users are heavenbanned. Given the (assumed) diminishing returns on advertising, it's better for a platform with relatively little average daily use to implement heavenbanning than a more popular-with-individual-users one, though the shape of this effect would vary significantly with a more rigorous model for diminishing advertising returns. In the median case, initial quarterly ad revenue per user has to be ~$2.50 to pay for heavenbanning - this makes it theoretically profitable for Meta internationally and Snap/Reddit in the US.
|
||||
|
||||
This surprised me when I finished modelling it (try editing the Squiggle code above if you want to play with it), but social media runs on fairly small costs and revenues per user, and this use of LLMs is in the worse scenarios heavier than services like free ChatGPT. It must be noted that even if it's unprofitable now at many operating points, the cost of inference will only go down. It has already dropped about five orders of magnitude since GPT-3.
|
||||
|
||||
Another way to think about this is that it is profitable (on average) to heavenban-or-similar users with quarterly ad revenues above some threshold, and whether users are above that threshold is partly but not wholly determined by geography. User behaviour is sufficiently heterogeneous that it might be viable to e.g. heavenban only users who you predict will be most affected by it, or who are most valuable to advertisers, who have a low enough posts-per-hour rate to keep costs down.
|
||||
|
||||
## The equilibrium
|
||||
|
||||
The above is, however, a marginal analysis of adding heavenbanning to a human-dominated platform. If a platform is believed to be mostly human, users will interpret increased attention as human and react positively, but perceived bot comments are neutral or negative - indeed, the [Reddit study](https://karthikecon.github.io/karthiksrinivasan.org/paying_attention.pdf) finds that larger quantities of generated replies (6 vs 3) result in suspicion and nullify the positive effect. At a small scale, it's probably possible to improve on the study's bots' credibility with better models and interfaces, particularly with platform cooperation, but with enough use, users might default to assuming all interactions are bots[^4]. [SocialAI](https://x.com/michaelsayman/status/1835841675584811239), which explicitly has users interact with only bots, has not proved particularly popular.
|
||||
|
||||
Research into AI-generated creative works has generally shown that while people claim to favour human works, and favour works they're told are human, they actually prefer AI-generated works. A [paper](https://www.nature.com/articles/s41598-024-76900-1) evaluating poetry (from an extremely outdated model, even) finds that humans prefer AI-generated poetry in a blind test - as [remarked upon by Gwern](https://gwern.net/creative-benchmark#caring-is-the-hard-part), they find that this is because the output of RLHF/aesthetic-optimized models is simpler and easier to understand[^5] despite being worse to connoisseurs. Similarly, [ACX](https://www.astralcodexten.com/p/how-did-you-do-on-the-ai-art-turing) finds AI art to be preferred over human art (when selected for stylistic diversity and quality), and [another study on art](https://cognitiveresearchjournal.springeropen.com/articles/10.1186/s41235-023-00499-6) agrees with this.
|
||||
|
||||
Taken together, this suggests that scalable heavenbanning might only require *plausible deniability*. It breaks if it becomes common knowledge that a platform is using it extensively, because interacting with a system which only provides positivity and validation feels low-status, as it suggests that you can't "make it" with real humans. But given a choice which isn't directly labelled that way, I expect many will prefer the sycophantic machines, as they already favour sycophancy in chatbots, and motivated reasoning will stop the connections being made[^6]. It's easy to remain oblivious - see for instance the Facebook [AI slop](https://en.wikipedia.org/wiki/Dead_Internet_theory#Facebook) phenomenon.
|
||||
|
||||
I don't think this is a good outcome, for various reasons. But I aim to describe the world as it is, not as I want it to be. As DeepSeek-R1 phrases it:
|
||||
|
||||
> In the absence of cultural backlash or regulatory intervention, heavenbanning may prove tragically optimal: cheaper than moderation, more addictive than bans, and eerily compatible with humanity’s growing comfort in algorithmic companionship. The economics, as always, favor the void.
|
||||
|
||||
[^1]: The "dead internet theory" emerged at around the same time, but is more general and somewhat underspecified.
|
||||
|
||||
[^2]: Note that this is total revenue in that location divided by Facebook users in that location, not Facebook-specific revenue, which they do not appear to quote. This makes it an overestimate, but not by much.
|
||||
|
||||
[^3]: This is mostly blocked on video generation and LLM agent capability.
|
||||
|
||||
[^4]: Also, heavenbanning relies on showing different users different things, and people can detect this like they can detect shadowbanning. Social media becoming increasingly closed makes this less of a problem, however.
|
||||
|
||||
[^5]: "Low culture", in [this essay's](https://cameronharwick.com/writing/high-culture-and-hyperstimulus/) terms.
|
||||
|
||||
[^6]: Some people may also simply not care, if the alternatives are bad enough.
|
@ -38,7 +38,7 @@ Both models use a prefix to indicate whether an input is a query or a passage to
|
||||
|
||||
## The quantitative data is not all that helpful
|
||||
|
||||
While I could in principle get interesting results out of analyzing things like web browsing activity by day and whether my step count is correlated with anything interesting, I have not had any compelling reason to do this yet, and this would likely require complex dedicated analysis scripts and frontends to do well. Datasette's ability to run and nicely render custom SQL queries is cool, but not very relevant to this - I've only ever used it about five times in total.
|
||||
While I could in principle get interesting results out of analyzing things like web browsing activity by day and whether my step count is correlated with anything of note, I have not had any compelling reason to do this yet, and this would likely require complex dedicated analysis scripts and frontends to do well. Datasette's ability to run and nicely render custom SQL queries is cool, but not very relevant to this - I've only ever used it about five times in total.
|
||||
|
||||
## Sharded vector indices
|
||||
|
||||
@ -78,4 +78,4 @@ Search results even get colors (by indexer) and will link to the source where av
|
||||
|
||||
[^5]: I think this is because the overly long instruction for queries contains "passage".
|
||||
|
||||
[^6]: There do exist ways to fix this but they're either bad or tricky mathematics.
|
||||
[^6]: There do exist ways to fix this but they're either bad or tricky mathematics.
|
||||
|
@ -103,7 +103,7 @@ No mention of equipment power creep, however, is complete without mentioning Dra
|
||||
|
||||
You can reasonably argue that it's not an actual problem because multiplayer games usually aren't sufficiently competitive to force use of the optimal strategy, so players can pick their preferred option, and because [CraftTweaker](https://www.curseforge.com/minecraft/mc-mods/crafttweaker)-like tools allow customization to bring strategies' power into alignment. I don't think this is right, since defaults are very powerful: recipe tweaking is slow and annoying enough to be rarely done extensively except by specialized pack designers, people are likely still biased towards "better" options, and it affects the design of new mechanics in mods, as they will usually be built against the perceived defaults rather than people's idiosyncratic preferences.
|
||||
|
||||
Something interesting to note here is that we don't see a smooth monotonic progression towards more power. There generally instead seems to be convergence toward a "preferred" level of simplicity/abstraction/power within each domain, progressing rapidly at first as people discover-or-invent new mechanics and occasionally reversing (e.g. Create, AE2[^9]). There's also lots of sensitivity to the actions and design goals of individual developers, making jumps discontinuous and somewhat unpredictable. It also appears that power creep is avoidable by individual mod authors by deliberately reducing interconnectivity with other mods, such that users cannot pick-and-choose optimal solutions finely, and by having unusual features which can't clearly be compared in power[^7].
|
||||
Something to note here is that we don't see a smooth monotonic progression towards more power. There generally instead seems to be convergence toward a "preferred" level of simplicity/abstraction/power within each domain, progressing rapidly at first as people discover-or-invent new mechanics and occasionally reversing (e.g. Create, AE2[^9]). There's also lots of sensitivity to the actions and design goals of individual developers, making jumps discontinuous and somewhat unpredictable. It also appears that power creep is avoidable by individual mod authors by deliberately reducing interconnectivity with other mods, such that users cannot pick-and-choose optimal solutions finely, and by having unusual features which can't clearly be compared in power[^7].
|
||||
|
||||
What can we learn from this outside of Minecraft mod balance? Probably not that much. While we see that the dynamics involved have been quite sensitive to individual developers, I think this is because there's not very strong selection pressure on mods and because "generations" are slow rather than this being a more general pattern. One interesting angle here is that the (apparent) nonexistence of a dominant strategy often comes from the advantages and disadvantages being merely hard to compare rather than actually equal. I think this holds up in other contexts, and growing ability to make end-to-end comparisons might remove illusions that many things are equally matched.
|
||||
|
||||
|
@ -4,7 +4,7 @@ description: A slightly odd pattern I've observed.
|
||||
created: 16/10/2024
|
||||
slug: pricecog
|
||||
---
|
||||
Price discrimination is a practice where sellers of a product try and sell materially the same product to different customers at different prices closer to their willingness to pay. Economists probably have opinions on whether this is good or bad, but I'm going to focus on a specific mechanism for it here. Price discrimination requires some way to show different customers different prices: this might be through timing, very slightly different variants of the product, location or directly selling to customers without a public price.
|
||||
Price discrimination is a practice where sellers of a product try to sell materially the same product to different customers at different prices closer to their willingness to pay. Economists probably have opinions on whether this is good or bad, but I'm going to focus on a specific mechanism for it here. Price discrimination requires some way to show different customers different prices: this might be through timing, very slightly different variants of the product, location or directly selling to customers without a public price.
|
||||
|
||||
One perhaps more modern variant is to discriminate through making prices extremely confusing. I initially thought of this while discussing airline ticketing with one of my friends: the pricing of air fares is [famously](http://www.demarcken.org/carl/papers//ITA-software-travel-complexity/ITA-software-travel-complexity.html) so complex that it is literally impossible[^1] to determine the best price for a journey in some possible cases. One possible explanation is that the airlines want to price-discriminate by offering fares with strange restrictions to more price-sensitive buyers while charging more for convenient journeys. However, in some cases it's possible to pay less for exactly the same journey with the same features through a more complicated purchasing structure!
|
||||
|
||||
@ -16,4 +16,4 @@ This is mostly relevant where resale is impractical or the price difference is l
|
||||
|
||||
This may not be an especially important pattern, but I think it clarifies some things, it seems novel enough that I wasn't easily able to find preexisting work on it (though maybe I don't know the right terms, or it's buried in other things), and it permits me to feel vaguely annoyed that the world is not run by a perfect machine god executing zero-waste central planning.
|
||||
|
||||
[^1]: Uncomputable - apparently, there's some construction which lets you reduce diophantine equations to fare search problems.
|
||||
[^1]: Uncomputable - apparently, there's a construction which lets you reduce diophantine equations to fare search problems.
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: Scaling meme search to 230 million images
|
||||
description: Downloading and indexing everything* on Reddit on one computer.
|
||||
created: 24/01/2025
|
||||
# series: meme_search
|
||||
series: meme_search
|
||||
series_index: 3
|
||||
slug: memescale
|
||||
---
|
||||
@ -50,7 +50,7 @@ There are better solutions available. [DiskANN](https://proceedings.neurips.cc/p
|
||||
|
||||
Benchmarking on a small dataset showed that the product quantization was a significant limit on OOD query performance (and ID performance, but this starts from a much higher point and seems to vary less). DiskANN's algorithm can compensate for this with more disk reads - since it retrieves full-precision vectors as it traverses the graph, it asymptotically approaches perfect recall - but disk reads are costly. I adopted a slight variant of [OOD-DiskANN](https://arxiv.org/abs/2211.12850)'s AOPQ algorithm, which adjusts quantization to adapt to the distribution of queries. I investigated [RaBitQ](https://github.com/gaoj0017/RaBitQ) briefly due to its better claims, but it did not seem good in practice, and the basic idea was considered and rejected by [some other papers](https://arxiv.org/abs/1806.03198) anyway. In principle, there are much more efficient ways to pack vectors into short codes - including, rather recently, some work on [using a neural network to produce an implicit codebook](https://arxiv.org/abs/2501.03078) - but the ~1μs/PQ comparison time budget at query time makes almost all of them impractical even with GPU offload[^14]. The one thing which can work is *additive* quantization, which is as cheap at query time as product quantization but much slower at indexing time. The slowdown is enough that it's not practical for DiskANN, unless GPU acceleration is used. Most additive quantizers rely on beam search, which parallelizes poorly, but [LSQ++](https://openaccess.thecvf.com/content_ECCV_2018/papers/Julieta_Martinez_LSQ_lower_runtime_ECCV_2018_paper.pdf) should be practical. This may be integrated later.
|
||||
|
||||
An interesting problem I also hadn't considered beforehand was sharding: DiskANN handles larger-than-memory datasets by clustering them using k-means or a similar algorithm, processing each shard separately and stitching the resulting graphs together. K-means itself, however, doesn't produce balanced clusters, which is important since it's necessary to bound the maximum cluster size to fit it in RAM. I fixed this in a moderately cursed way by using [simulated annealing](https://github.com/osmarks/meme-search-engine/blob/master/kmeans.py) to generate approximately-right clustering centroids on a subset of the data and adding a fudge factor at full dataset shard time to balance better than that. I tried several other things which did not work.
|
||||
An unexpected problem was sharding: DiskANN handles larger-than-memory datasets by clustering them using k-means or a similar algorithm, processing each shard separately and stitching the resulting graphs together. K-means itself, however, doesn't produce balanced clusters, which is important since it's necessary to bound the maximum cluster size to fit it in RAM. I fixed this in a moderately cursed way by using [simulated annealing](https://github.com/osmarks/meme-search-engine/blob/master/kmeans.py) to generate approximately-right clustering centroids on a subset of the data and adding a fudge factor at full dataset shard time to balance better than that. I tried several other things which did not work.
|
||||
|
||||
Full graph assembly took slightly over six days with 42 shards with about 13 million vectors each (each vector is spilled to two shards so that the shards are connected, and there are more vectors in the index than are shown in results because I did some filtering too late). There were some auxiliary processes like splitting and merging them and running the rating models and quantizers, but these were much quicker.
|
||||
|
||||
|
1356
package-lock.json
generated
1356
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,8 +6,10 @@
|
||||
"dependencies": {
|
||||
"@extractus/feed-extractor": "^7.1.3",
|
||||
"@msgpack/msgpack": "^3.0.0-beta2",
|
||||
"@quri/squiggle-lang": "^0.10.0",
|
||||
"@vscode/markdown-it-katex": "^1.1.0",
|
||||
"axios": "^1.5.0",
|
||||
"base64-js": "^1.5.1",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"binary-fuse-filter": "^1.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
@ -30,6 +32,7 @@
|
||||
"msgpackr": "^1.11.0",
|
||||
"mustache": "^4.0.1",
|
||||
"nanoid": "^2.1.11",
|
||||
"pako": "^2.1.0",
|
||||
"porter2": "^1.0.1",
|
||||
"pug": "^3.0.2",
|
||||
"ramda": "^0.26.1",
|
||||
|
17
src/index.js
17
src/index.js
@ -153,7 +153,15 @@ const renderContainer = (tokens, idx) => {
|
||||
const readFile = path => fsp.readFile(path, { encoding: "utf8" })
|
||||
const anchor = require("markdown-it-anchor")
|
||||
|
||||
const md = new MarkdownIt({ html: true })
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
highlight: (code, language) => {
|
||||
if (language === "squiggle") {
|
||||
return `<textarea class="squiggle" rows=${code.split("\n").length}>${md.utils.escapeHtml(code.trim())}</textarea>`
|
||||
}
|
||||
return "" // default escaping
|
||||
}
|
||||
})
|
||||
.use(require("markdown-it-container"), "", { render: renderContainer, validate: params => true })
|
||||
.use(require("markdown-it-footnote"))
|
||||
.use(anchor, {
|
||||
@ -512,6 +520,13 @@ const compilePageJSTask = async () => {
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
format: "esm"
|
||||
}),
|
||||
esbuild.build({
|
||||
entryPoints: [ path.join(srcDir, "squiggle_rt.mjs") ],
|
||||
bundle: true,
|
||||
outfile: path.join(outAssets, "js/squiggle_rt.js"),
|
||||
minify: true,
|
||||
sourcemap: true
|
||||
})
|
||||
])
|
||||
}
|
||||
|
53
src/squiggle_rt.mjs
Normal file
53
src/squiggle_rt.mjs
Normal file
@ -0,0 +1,53 @@
|
||||
import { run, defaultEnvironment } from "@quri/squiggle-lang"
|
||||
import { deflate } from "pako" // ... really?
|
||||
import { fromByteArray } from "base64-js"
|
||||
|
||||
window.run = run
|
||||
window.defaultEnvironment = defaultEnvironment
|
||||
|
||||
const e = (cls, parent, content, type="div") => {
|
||||
const element = document.createElement(type)
|
||||
element.classList.add(cls)
|
||||
if (content) { element.appendChild(document.createTextNode(content)) }
|
||||
if (parent) { parent.appendChild(element) }
|
||||
return element
|
||||
}
|
||||
|
||||
const blocks = document.querySelectorAll("textarea.squiggle")
|
||||
for (const block of blocks) {
|
||||
const resultsDiv = e("squiggle-control", null, null)
|
||||
const b64code = encodeURIComponent(fromByteArray(deflate(JSON.stringify({
|
||||
defaultCode: block.value
|
||||
}))))
|
||||
resultsDiv.innerHTML += `<div>Interactive embedded Squiggle instance. Go to <a href="https://www.squiggle-language.com/playground?v=0.10.0#code=${b64code}">the Squiggle playground</a> for complex edits.</div>`
|
||||
const runButton = e("large-button", resultsDiv, "Run", "button")
|
||||
const resultsCode = e("squiggle-results", resultsDiv, "", "div")
|
||||
const execute = async () => {
|
||||
try {
|
||||
const code = block.value
|
||||
const result = await run(code, defaultEnvironment)
|
||||
if (!result.result.ok) {
|
||||
throw new Error(result.result.value.errors.map(x => x.toString()).join("\n"))
|
||||
}
|
||||
const samples = result.result.value.result.value.getSamples()
|
||||
samples.sort((a, b) => a - b) // numerically sort (JS...)
|
||||
const percentiles = [0.05, 0.2, 0.3, 0.5, 0.7, 0.8, 0.95]
|
||||
resultsCode.innerText = "Distribution:"
|
||||
for (const percentile of percentiles) {
|
||||
const index = Math.floor(samples.length * percentile)
|
||||
const sample = samples[index]
|
||||
e(null, resultsCode, `${(percentile * 100).toFixed(0)}%ile: ${sample.toFixed(3)}`, "div")
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
resultsCode.innerText = e.message
|
||||
}
|
||||
// sorry.
|
||||
if (window.relayout) {
|
||||
setTimeout(() => window.relayout(true), 0)
|
||||
}
|
||||
}
|
||||
runButton.addEventListener("click", execute)
|
||||
block.parentElement.insertBefore(resultsDiv, block.nextSibling)
|
||||
setTimeout(execute, 0)
|
||||
}
|
@ -191,7 +191,7 @@ button, select, input, textarea, .textarea
|
||||
margin: 0
|
||||
|
||||
blockquote
|
||||
padding-left: 0.4rem
|
||||
padding-left: 0.8rem
|
||||
border-left: 0.4rem solid black
|
||||
margin-left: 0.2rem
|
||||
|
||||
@ -306,7 +306,7 @@ $hl-border: 3px
|
||||
color: white
|
||||
|
||||
blockquote
|
||||
border-left: 0.4rem solid white
|
||||
border-left: 0.8rem solid white
|
||||
|
||||
button, select, input, textarea, .textarea
|
||||
background: #333
|
||||
@ -412,9 +412,36 @@ table
|
||||
#citebox
|
||||
width: 100%
|
||||
|
||||
textarea.squiggle
|
||||
width: 100%
|
||||
|
||||
.emphasis
|
||||
margin-top: 16px
|
||||
margin-bottom: 16px
|
||||
padding: 16px
|
||||
p
|
||||
margin: 0
|
||||
|
||||
@media print
|
||||
.isso
|
||||
display: none
|
||||
|
||||
.sidenotes
|
||||
display: block
|
||||
li
|
||||
position: static !important
|
||||
|
||||
.cite-this-post
|
||||
display: none
|
||||
|
||||
img
|
||||
max-width: 60% !important
|
||||
|
||||
nav
|
||||
display: none
|
||||
|
||||
.large-button
|
||||
padding: 0.5em
|
||||
|
||||
.squiggle-control
|
||||
text-wrap: wrap
|
||||
|
@ -54,7 +54,7 @@ block content
|
||||
each entry in openring
|
||||
!= entry
|
||||
|
||||
iframe(src="https://george.gh0.pw/embed.cgi?gollark", style="border:none;width:100%;height:50px", title="Acquiesce to GEORGE.")
|
||||
//iframe(src="https://george.gh0.pw/embed.cgi?gollark", style="border:none;width:100%;height:50px", title="Acquiesce to GEORGE.")
|
||||
|
||||
block under-title
|
||||
h2= name
|
||||
|
@ -86,3 +86,6 @@ html(lang="en")
|
||||
main.isso
|
||||
h2 Comments
|
||||
section(id="comments-wrapper")
|
||||
|
||||
if squiggle
|
||||
script(src="/assets/js/squiggle_rt.js", async=true)
|
||||
|
Loading…
Reference in New Issue
Block a user