Skip to content

Commit

Permalink
added poetry generator to petri
Browse files Browse the repository at this point in the history
  • Loading branch information
eyaler committed Apr 14, 2024
1 parent dd4f389 commit 224f10b
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 22 deletions.
12 changes: 8 additions & 4 deletions resen/petri/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,25 @@
<body class="todark" onload="document.body.style.visibility = 'visible'">
<script>make_header()</script>
<p>
<a href="https://he.wikipedia.org/wiki/%D7%A8%D7%A9%D7%AA_%D7%A4%D7%98%D7%A8%D7%99">רשת פטרי</a> היא מודל מתמטי־גרפי המייצג תהליכים מורכבים הכוללים תנאי קדם שתלויים במצב הדינמי של המערכת. ואפשר שמערכת זו תתנהג באופן סטוכסטי (אקראי).
<a href="https://he.wikipedia.org/wiki/%D7%A8%D7%A9%D7%AA_%D7%A4%D7%98%D7%A8%D7%99">רשת פטרי</a> היא מודל מתמטי־גרפי שמייצג תהליכים מורכבים וסטוכסטיים, הכוללים תנאי קדם התלויים במצב הדינמי של המערכת.
<a href="https://he.wikipedia.org/wiki/%D7%9E%D7%A9%D7%95%D7%95%D7%90%D7%95%D7%AA_%D7%9C%D7%95%D7%98%D7%A7%D7%94-%D7%95%D7%95%D7%9C%D7%98%D7%A8%D7%94">משוואות לוטקה-וולטרה</a> הן משוואות דיפרנציאליות המתארות את יחסי הגומלין הכמותניים בין שתי אוכלוסיות: טורפים ונטרפים.
תהליכים אלו הכוללים רבייה, טרף ומוות, ניתנים למידול באופן איכותני באמצעות רשת פטרי.
כאשר מדובר ביחסי גומלין בין עמים, יש גם לקחת בחשבון מצבים ואינטראקציות מורכבים יותר כגון זעם ונקמה.
רשת הפטרי הפטריוטית מייצגת תהליכי רבייה, חיול, לחימה, מוות ונקמה בין שתי אוכלוסיות: ישראל ועזה, ומדמה את מעגל השכול האכזב שהעימות ביניהן מייצר.
רצף המצבים והמעברים המתחלפים מייצרים מעין פואטיקה של שירה מצבית.
</p>
<p>
ברשת פטרי, המעגלים הגדולים מתארים מצבים והמלבנים מתארים מעברים. חיצים מחברים בין מצבים ומעברים, והמצבים יכולים להכיל אסימונים.
ברשת פטרי, המעגלים הגדולים מתארים מצבים והמלבנים מתארים מעברים. חיצים מחברים בין מצבים ומעברים, והמצבים יכולים להכיל אסימונים, שבמקרה שלנו אפשר לחשוב עליהם כעל מנות קטנות של חוסן.
מעבר הוא במצב משופעל (צהוב) אם עבור כל חץ נכנס קיים אסימון במצב הכניסה המתאים.
באופן אקראי, אחד מן המעברים המשופעלים יורה (ירוק), ואז עבור כל חץ נכנס נגרע אסימון במצב הכניסה המתאים, ועבור כל חץ יוצא מתווסף אסימון במצב המוצא המתאים.
המערכת רצה באופן חי בדפדפן. האם תרצה לדעת עוד?
</p>
<p dir="ltr">
A <a href="https://he.wikipedia.org/wiki/%D7%A8%D7%A9%D7%AA_%D7%A4%D7%98%D7%A8%D7%99">Petri net</a> is a mathematical-graphical model representing complex stochastic processes. For example, predator-prey population dynamics which can include behaviors such as breeding, predation and death, as described by the <a href="https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations">Lotka-Volterra equations</a>. When it comes to Human populations, more complex interactions such as wrath or vengeance might need to be taken into account. The Patriotic Petri Poetry (PPP) net simulates processes of reproduction, enlistment, fighting, death and revenge between two populations: Israel and Gaza, simulating the vicious cycle of bereavement induced by the conflict.
The sequence of alternating states and transitions produce a kind poetics of situational poetry.
</p>
<p dir="ltr">
In the Petri net, the large circles describe states and the rectangles describe transitions. Arrows connect states and transitions, and the states can contain tokens.
In the Petri net, the large circles describe states and the rectangles describe transitions. Arrows connect states and transitions, and the states can contain tokens, which in our case can be considered as small rations of resilience.
A transition is enabled (yellow) if for each incoming arrow there is a token in the corresponding entry state.
At random, one of the activated transitions fires (green), then for each incoming arrow a token is deducted from the corresponding entry state, and for each outgoing arrow a token is added to the corresponding exit state.
The system runs live in the browser. Would you like to know more?
Expand Down Expand Up @@ -133,8 +134,11 @@

<br>
<div class="petri" oncontextmenu="toggle_fullscreen(event)"></div>

<textarea id="poem" autocomplete="off" readonly rows="15" spellcheck="false"></textarea>

<p>
העבודה נוצרה עבור <a href="https://oulipoh.com/resen#0">רֶסֶן אפס</a>.
העבודה נוצרה עבור <a href="https://oulipoh.com/resen/#0">רֶסֶן אפס</a>.
</p>
<div class="refs">
<blockquote>Susan Stepney, <a href="https://susan-stepney.blogspot.com/2012/12/ode-to-petri-net.html">ODE to a Petri net</a> (2012).</blockquote>
Expand Down
14 changes: 7 additions & 7 deletions resen/petri/petri.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
["עזה", "עזה", "עזה", "עזה"]
],
"הסתה": [
["איבה עזה", "עזה"],
["איבה בעזה", "עזה"],
["ארגון טרור"]
],

Expand All @@ -28,17 +28,17 @@
],

"מבצע": [
["איבה ישראל", "צבא", "ארגון טרור", "עזה", "עזה"],
["צבא", "בית עלמין עזה", "בית עלמין עזה", "בית עלמין עזה", "איבה עזה"]
["איבה בישראל", "צבא", "ארגון טרור", "עזה", "עזה"],
["צבא", "בית עלמין עזה", "בית עלמין עזה", "בית עלמין עזה", "איבה בעזה"]
],
"פיגוע": [
["ישראל", "ארגון טרור"],
["כלא", "בית עלמין ישראל", "איבה ישראל", "איבה עזה"]
["כלא", "בית עלמין ישראל", "איבה בישראל", "איבה בעזה"]
],

"חטיפה": [
["ישראל", "ארגון טרור", "כלא"],
["כלא", "ארגון טרור", "שבי", "איבה ישראל"]
["כלא", "ארגון טרור", "שבי", "איבה בישראל"]
],
"חילוף": [
["שבי", "כלא", "כלא", "כלא"],
Expand All @@ -49,10 +49,10 @@
"marking": {
"ישראל": 20,
"עזה": 10,
"איבה עזה": 1
"איבה בעזה": 1
},

"labels": {"בית עלמין ישראל": "ISRAEL CEMETERY", "פיגוע": "TERROR ATTACK", "כלא": "PRISON", "חילוף": "SWAP", "שבי": "CAPTIVITY", "פטירה_ישראל": "PASSING", "ישראל": "ISRAEL", "חטיפה": "ABDUCTION|ABDUCT", "ארגון טרור": "TERROR GROUP", "רבייה_עזה": "BREEDING", "איבה ישראל": "ISRAEL ENMITY", "רבייה_ישראל": "BREEDING", "איבה עזה": "GAZA ENMITY", "הסתה": "INCITEMENT|INCITE", "עזה": "GAZA", "שחרור": "DISCHARGE", "צבא": "MILITARY", "מבצע": "OPERATION|MIL OP", "בית עלמין עזה": "GAZA CEMETERY", "פטירה_עזה": "PASSING"},
"labels": {"בית עלמין ישראל": "ISRAEL CEMETERY", "פיגוע": "TERROR ATTACK", "כלא": "PRISON", "חילוף": "SWAP", "שבי": "CAPTIVITY", "פטירה_ישראל": "PASSING_ISRAEL", "ישראל": "ISRAEL", "חטיפה": "ABDUCTION|ABDUCT", "ארגון טרור": "TERROR GROUP", "רבייה_עזה": "BREEDING_GAZA", "איבה בישראל": "ENMITY IN ISRAEL", "רבייה_ישראל": "BREEDING_ISRAEL", "איבה בעזה": "ENMITY IN GAZA", "הסתה": "INCITEMENT|INCITE", "עזה": "GAZA", "שחרור": "DISCHARGE", "צבא": "MILITARY", "מבצע": "OPERATION|MIL OP", "בית עלמין עזה": "GAZA CEMETERY", "פטירה_עזה": "PASSING_GAZA"},

"require": [
["ישראל", "צבא"],
Expand Down
60 changes: 52 additions & 8 deletions resen/petri/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ leaderline_comp_bottom = 90
const comp_marking = 5

const lang = get_lang()
if (poem && lang)
poem.dir = 'ltr'
let global_reset_counter = 0
document.addEventListener('keydown', e => global_reset_counter += is_shortcut(e, 'Backspace'))

Expand Down Expand Up @@ -110,13 +112,48 @@ function is_enabled(places, tokens) {
return true
}

function fire(grid, json, steps, max_tokens, result_counter, reset_counter, tokens, enabled) {
const trans = enabled[Math.random() * enabled.length | 0]
function poem_generator(json, trans, place) {
let article, verbs, verb, obj
if (lang) {
trans = json.full_labels[trans] || trans
place = json.labels[place] || place
article = 'THE '
verbs = ['BOOSTED', 'ENHANCED', 'FORTIFIED', 'INCREASED', 'STRENGTHENED']
verb = verbs[Math.random() * verbs.length | 0]
obj = 'THE RESILIENCE ' + (place.startsWith('ENMITY') ? 'OF' : 'IN') + ' '
if (!['ISRAEL', 'GAZA'].includes(place))
obj += 'THE '
} else {
article = 'ה'
verbs = ['ביצר', 'הגביר', 'הגדיל', 'העצים', 'חיזק']
verb = verbs[Math.random() * verbs.length | 0]
if (trans.match(/ה($| |_)/))
verb = verb.replace(/ם$/, 'מ') + 'ה'
obj = 'את החוסן ' + (place.startsWith('איבה') ? 'של ה' : 'ב')
}
trans = trans.replace('_', lang ? ' IN ' : ' ב').split('_')[0]
place = place?.split('_')[0]
return article + trans + ' ' + verb + ' ' + obj + place
}
function fire(grid, json, steps, max_tokens, result_counter, reset_counter, tokens, enabled, comp) {
let trans = enabled[Math.random() * enabled.length | 0]
const elem = grid.querySelector(`[data-id="${trans}"]`)
if (elem)
elem.style.color = 'var(--firing)'
json.transitions[trans][0].forEach(p => tokens[p]--)
json.transitions[trans][1].forEach(p => tokens[p] = (tokens[p] || 0) + 1)
const out = json.transitions[trans][1]
out.forEach(p => tokens[p] = (tokens[p] || 0) + 1)
if (!comp && poem) {
const selection_start = poem.selectionStart
const selection_end = poem.selectionEnd
const should_scroll = poem.scrollTop + 1 >= poem.scrollHeight - poem.clientHeight && selection_start == selection_end
poem.value += poem_generator(json, trans, out[Math.random() * out.length | 0]) + '\n'
if (should_scroll)
poem.scrollTop = poem.scrollHeight
else
poem.setSelectionRange(selection_start, selection_end)
}

setTimeout(step, halfstep_secs * 1000 * !fast, grid, json, steps, max_tokens, result_counter, reset_counter, tokens)
}

Expand Down Expand Up @@ -288,9 +325,11 @@ function step(grid, json, steps=0, max_tokens={}, result_counter={}, reset_count
max_tokens = Object.fromEntries(Object.entries(max_tokens).map(([p, counts]) => ([p, counts.slice(0, -1)])))
step(grid, json, 0, max_tokens, result_counter, reset_counter)
} else if (!enabled.length || comp && Object.values(tokens).some(n => n > place_max_tokens) || !comp && !json.require?.every(require => require.some(t => tokens[t]))) {
const result = json?.require.map(side => side.some(p => tokens[p]) | 0)
const result = json.require?.map(side => side.some(p => tokens[p]) | 0)
result_counter[result] = (result_counter[result] || []).concat(steps).sort((a, b) => a - b)
if (!comp) {
if (poem)
poem.value += '\n'
const all_steps = Object.values(result_counter).flat()
const sides = result.map((_, i) => Object.entries(result_counter).filter(x => x[0].split(',')[i] == 1).map(x => x[1]).flat().length)
const avg_tokens = Object.fromEntries(Object.entries(max_tokens).map(([p, counts]) => [p, counts.reduce((a, b) => a + b, 0) / counts.length]).sort((a, b) => a[1] - b[1] || a[0].localeCompare(b[0])))
Expand All @@ -299,11 +338,13 @@ function step(grid, json, steps=0, max_tokens={}, result_counter={}, reset_count
}
setTimeout(step, restart_secs * 1000 * !fast, grid, json, 0, max_tokens, result_counter, reset_counter)
} else
setTimeout(fire, halfstep_secs * 1000 * !fast, grid, json, steps + 1, max_tokens, result_counter, reset_counter, tokens, enabled)
setTimeout(fire, halfstep_secs * 1000 * !fast, grid, json, steps + 1, max_tokens, result_counter, reset_counter, tokens, enabled, comp)
}

fetch(json_file).then(response => response.json()).then(json => {
const all_labels = Object.keys(json.labels || {})
json.labels ??= {}
json.full_labels = {...json.labels}
const all_labels = Object.keys(json.labels)
const all_transitions = Object.keys(json.transitions)
Object.values(json.transitions).map(v => v.slice(0, 2)).flat(2).forEach(place => {
if (!all_labels.includes(place)) {
Expand Down Expand Up @@ -345,8 +386,11 @@ fetch(json_file).then(response => response.json()).then(json => {
if (hor > ver)
pre.className = 'vertical'
}
if (json.labels[label].includes('|'))
json.labels[label] = json.labels[label].split('|')[pre.classList.contains('vertical') | 0]
if (json.labels[label]?.includes('|')) {
const forms = json.labels[label].split('|')
json.full_labels[label] = forms[0]
json.labels[label] = forms[pre.classList.contains('vertical') | 0]
}
} else {
pre.className = 'place'
pre.addEventListener('click', () => pre.dataset.clicks = (pre.dataset.clicks | 0) + 1)
Expand Down
18 changes: 15 additions & 3 deletions resen/petri/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@
--firing: #64ff64;
color: var(--fg);
display: grid;
font-size: initial;
overflow-x: auto;
outline-style: none;
padding-block: 1em 3em;
user-select: none;
-webkit-user-select: none;
}

.petri, #poem {
font-family: 'IBM Plex Mono', 'Courier New', var(--mono_font);
font-size: initial;
outline-style: none;
}

.petri > div {
display: grid;
grid-template-columns: repeat(var(--cols), 25ch);
font-family: 'IBM Plex Mono', 'Courier New', var(--mono_font);
justify-self: stretch;
letter-spacing: initial;
line-height: 1.5;
Expand Down Expand Up @@ -95,4 +98,13 @@

.petri pre[data-after].place::after {
bottom: var(--ver_offset);
}

#poem {
box-sizing: border-box;
line-height: 2;
margin-block: 3em;
padding-inline: 1em;
resize: none;
width: 100%;
}

0 comments on commit 224f10b

Please sign in to comment.