diff --git a/resen/petri/index.html b/resen/petri/index.html index a025649..4668626 100644 --- a/resen/petri/index.html +++ b/resen/petri/index.html @@ -17,7 +17,7 @@

- רשת פטרי היא מודל מתמטי־גרפי המייצג תהליכים מורכבים הכוללים תנאי קדם שתלויים במצב הדינמי של המערכת. ואפשר שמערכת זו תתנהג באופן סטוכסטי (אקראי). + רשת פטרי היא מודל מתמטי־גרפי שמייצג תהליכים מורכבים וסטוכסטיים, הכוללים תנאי קדם התלויים במצב הדינמי של המערכת. משוואות לוטקה-וולטרה הן משוואות דיפרנציאליות המתארות את יחסי הגומלין הכמותניים בין שתי אוכלוסיות: טורפים ונטרפים. תהליכים אלו הכוללים רבייה, טרף ומוות, ניתנים למידול באופן איכותני באמצעות רשת פטרי. כאשר מדובר ביחסי גומלין בין עמים, יש גם לקחת בחשבון מצבים ואינטראקציות מורכבים יותר כגון זעם ונקמה. @@ -25,16 +25,17 @@ רצף המצבים והמעברים המתחלפים מייצרים מעין פואטיקה של שירה מצבית.

- ברשת פטרי, המעגלים הגדולים מתארים מצבים והמלבנים מתארים מעברים. חיצים מחברים בין מצבים ומעברים, והמצבים יכולים להכיל אסימונים. + ברשת פטרי, המעגלים הגדולים מתארים מצבים והמלבנים מתארים מעברים. חיצים מחברים בין מצבים ומעברים, והמצבים יכולים להכיל אסימונים, שבמקרה שלנו אפשר לחשוב עליהם כעל מנות קטנות של חוסן. מעבר הוא במצב משופעל (צהוב) אם עבור כל חץ נכנס קיים אסימון במצב הכניסה המתאים. באופן אקראי, אחד מן המעברים המשופעלים יורה (ירוק), ואז עבור כל חץ נכנס נגרע אסימון במצב הכניסה המתאים, ועבור כל חץ יוצא מתווסף אסימון במצב המוצא המתאים. המערכת רצה באופן חי בדפדפן. האם תרצה לדעת עוד?

A Petri net 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 Lotka-Volterra equations. 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.

- 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? @@ -133,8 +134,11 @@

+ + +

- העבודה נוצרה עבור רֶסֶן אפס. + העבודה נוצרה עבור רֶסֶן אפס.

Susan Stepney, ODE to a Petri net (2012).
diff --git a/resen/petri/petri.json b/resen/petri/petri.json index 20b2966..05eb033 100644 --- a/resen/petri/petri.json +++ b/resen/petri/petri.json @@ -14,7 +14,7 @@ ["עזה", "עזה", "עזה", "עזה"] ], "הסתה": [ - ["איבה עזה", "עזה"], + ["איבה בעזה", "עזה"], ["ארגון טרור"] ], @@ -28,17 +28,17 @@ ], "מבצע": [ - ["איבה ישראל", "צבא", "ארגון טרור", "עזה", "עזה"], - ["צבא", "בית עלמין עזה", "בית עלמין עזה", "בית עלמין עזה", "איבה עזה"] + ["איבה בישראל", "צבא", "ארגון טרור", "עזה", "עזה"], + ["צבא", "בית עלמין עזה", "בית עלמין עזה", "בית עלמין עזה", "איבה בעזה"] ], "פיגוע": [ ["ישראל", "ארגון טרור"], - ["כלא", "בית עלמין ישראל", "איבה ישראל", "איבה עזה"] + ["כלא", "בית עלמין ישראל", "איבה בישראל", "איבה בעזה"] ], "חטיפה": [ ["ישראל", "ארגון טרור", "כלא"], - ["כלא", "ארגון טרור", "שבי", "איבה ישראל"] + ["כלא", "ארגון טרור", "שבי", "איבה בישראל"] ], "חילוף": [ ["שבי", "כלא", "כלא", "כלא"], @@ -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": [ ["ישראל", "צבא"], diff --git a/resen/petri/script.js b/resen/petri/script.js index 62c2e36..e880fb0 100644 --- a/resen/petri/script.js +++ b/resen/petri/script.js @@ -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')) @@ -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) } @@ -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]))) @@ -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)) { @@ -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) diff --git a/resen/petri/style.css b/resen/petri/style.css index 6421837..78317ef 100644 --- a/resen/petri/style.css +++ b/resen/petri/style.css @@ -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; @@ -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%; } \ No newline at end of file