{ "cells": [ { "cell_type": "markdown", "id": "222a62e1c4e059c0", "metadata": {}, "source": [ "# Walkthrough Tutorial: Grammar Formalisation and Testing\n", "\n", "
\n", "\n", "*This walkthrough tutorial accompanies Section 4.1 of the following paper:*\n", "\n", "Authors. (submitted). PyFCG: Fluid Construction Grammar in Python.\n", "\n", "
\n", "\n", "A common use of FCG revolves around the formalisation and computational operationalisation of construction grammar theories and analyses. Not only can computational operationalisations help validate their preciseness and internal consistency, they also facilitate the comparison, exchange and integration of insights from different researchers [(van Trijp, Beuls and Van Eecke 2022)](https://doi.org/10.1371/journal.pone.0269708).\n", "\n", "This tutorial exemplifies how PyFCG can be used to equip an agent with a designed grammar, how new constructions can be added to or removed from the agent's grammar on the fly, how the agent can use its grammar to comprehend and formulate utterances, and how all these processes can be visually inspected through FCG's graphical web interface. For more information on aspects of the syntax and semantics of FCG that are not particular to the PyFCG module, we refer the interested reader to [the syntax and semantics of FCG](https://emergent-languages.org/wiki/docs/recipes/fcg/syntax-and-semantics).\n", "\n" ] }, { "cell_type": "markdown", "id": "d3f8993f9afa90bf", "metadata": {}, "source": [ "## Installing PyFCG\n", "If you haven't pip-installed PyFCG, you can run the following cell to do so:\n" ] }, { "cell_type": "code", "execution_count": null, "id": "64d79e70678dc6d", "metadata": {}, "outputs": [], "source": [ "! pip install pyfcg" ] }, { "cell_type": "markdown", "id": "2a47f6d0-ad9a-4f1c-ba1c-64cdcd0d7034", "metadata": {}, "source": [ "## An agent named Sue" ] }, { "cell_type": "markdown", "id": "a1fe69404d17e836", "metadata": {}, "source": [ "After importing and initialising PyFCG, we create a new agent named Sue. Sue, as an instance of the ```fcg.Agent``` class, is automatically initialised with an empty grammar. Sue is also assigned a unique identifier." ] }, { "cell_type": "code", "execution_count": 1, "id": "6f4b0cfb6bc7de73", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:42.018109Z", "start_time": "2025-05-19T14:07:39.953585Z" } }, "outputs": [], "source": [ "import pyfcg as fcg\n", "fcg.init()" ] }, { "cell_type": "code", "execution_count": 2, "id": "ef593054", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:42.150446Z", "start_time": "2025-05-19T14:07:42.098497Z" } }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sue = fcg.Agent(name='Sue')\n", "sue" ] }, { "cell_type": "markdown", "id": "071aa23b-c540-44fd-8f34-cbc1b10f234e", "metadata": {}, "source": [ "## Sue reads a grammar" ] }, { "cell_type": "markdown", "id": "352ea140b0b483d2", "metadata": {}, "source": [ "Sue can read in a predefined grammar, specified in the Open FCG Exchange Format (OFEF). In this case, we use PyFCG's ```load_resource``` function to download a human-designed demo grammar fragment that specifies six constructions for processing the English resultative sentence \"Firefighters cut the child free.\". This grammar fragment uses the Abstract Meaning Representation format (AMR) to represent constructional meaning. We instruct Sue to load the grammar specified in the file by calling their ```load_grammar_from_file``` method and then list the names of the constructions that were loaded." ] }, { "cell_type": "code", "execution_count": 3, "id": "859082f11a707cf", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:42.185029Z", "start_time": "2025-05-19T14:07:42.182779Z" } }, "outputs": [], "source": [ "# Download a human-designed demo grammar fragment that specifies six constructions\n", "demo_grammar_spec_file = fcg.load_resource('demo-resultative.json')" ] }, { "cell_type": "code", "execution_count": 4, "id": "74c1d1ba", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:42.264312Z", "start_time": "2025-05-19T14:07:42.222239Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sue:\n" ] }, { "data": { "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Sue's constructions:\n" ] }, { "data": { "text/plain": [ "{'firefighters-cxn': ,\n", " 'child-cxn': ,\n", " 'cut-cxn': ,\n", " 'free-cxn': ,\n", " 'the-x-cxn': ,\n", " 'resultative-cxn': }" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Sue reads the grammar from the downloaded file\n", "sue.load_grammar_from_file(demo_grammar_spec_file)\n", "\n", "# Printing some information about sue after reading the grammar\n", "print('Sue:')\n", "display(sue)\n", "print()\n", "print(\"Sue's constructions:\")\n", "display(sue.grammar.cxns)" ] }, { "cell_type": "markdown", "id": "ddf31bed7783655a", "metadata": {}, "source": [ "Let us have a closer look at one of Sue's constructions, for example the ```firefighters-cxn```. Let us first take the perspective of a machine and look at the raw data structure. We can see the name, entrenchment score, contributing and conditional poles of the construction." ] }, { "cell_type": "code", "execution_count": 5, "id": "4fbec632802ad3a3", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:42.291918Z", "start_time": "2025-05-19T14:07:42.287204Z" } }, "outputs": [ { "data": { "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "\n", "Name: firefighters-cxn\n", "Score: 0.5\n", "Contributing pole: \n" ] }, { "data": { "text/plain": [ "[['?firefighters-unit',\n", " {'referent': '?p',\n", " 'category': 'firefighters-cxn',\n", " 'boundaries': ['?left', '?right']}]]" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Conditional pole: \n" ] }, { "data": { "text/plain": [ "[['?firefighters-unit',\n", " {'#meaning': [['person', '?p'],\n", " ['fight-01', '?f'],\n", " ['fire', '?f2'],\n", " [':arg0-of', '?p', '?f'],\n", " [':arg1', '?f', '?f2']]},\n", " {'#form': [['sequence', '\"Firefighters\"', '?left', '?right']]}]]" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "firefighters_cxn = sue.grammar.find_cxn_by_name('firefighters-cxn')\n", "\n", "display(firefighters_cxn)\n", "print()\n", "\n", "print('Name: ' + firefighters_cxn.name)\n", "print('Score: ' + str(firefighters_cxn.get_score()))\n", "print('Contributing pole: ')\n", "display(firefighters_cxn.contributing_pole)\n", "print('Conditional pole: ')\n", "display(firefighters_cxn.conditional_pole)" ] }, { "cell_type": "markdown", "id": "84d5d2eaac2be45b", "metadata": {}, "source": [ "Humans typically prefer to use FCG's graphical web interface. We launch the web interface and add the ```firefighters-cxn```. You should now be able to inspect the construction in a separate browser window (http://localhost:8010). You can click on the circle that is displayed above the arrow separating the conditional from the contributing pole to inspect the construction." ] }, { "cell_type": "code", "execution_count": 6, "id": "63f681224e93c278", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:47.392950Z", "start_time": "2025-05-19T14:07:42.332369Z" } }, "outputs": [], "source": [ "fcg.start_web_interface()\n", "firefighters_cxn.show_in_web_interface()" ] }, { "cell_type": "markdown", "id": "d4f5ee9f0a396792", "metadata": {}, "source": [ "We can also inspect Sue's entire grammar in the web interface." ] }, { "cell_type": "code", "execution_count": 7, "id": "a681ad49a9cacd97", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:47.444372Z", "start_time": "2025-05-19T14:07:47.426690Z" } }, "outputs": [], "source": [ "sue.grammar.show_in_web_interface()" ] }, { "cell_type": "markdown", "id": "db055956-b668-49fb-830c-1c913e464fae", "metadata": {}, "source": [ "## Sue comprehends and formulates utterances" ] }, { "cell_type": "markdown", "id": "ee41c644159e080b", "metadata": {}, "source": [ "We now ask Sue to comprehend the utterance \"Firefighters cut the child free.\". In FCG, comprehension and formulation respectively refer to the processes of mapping utterances to their meaning representation and vice versa. In order to be able to visually inspect Sue's comprehension process, we need to activate the standard ```trace-fcg``` monitor." ] }, { "cell_type": "code", "execution_count": 8, "id": "1cb3016d2efdccaf", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:48.005384Z", "start_time": "2025-05-19T14:07:47.454160Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[['child', '?c-100'], ['cause-01', '?c-115'], ['arg0', '?c-106', '?p-5'], ['arg0-of', '?c-106', '?c-115'], ['arg1', '?c-115', '?f-11'], ['arg1', '?f-11', '?c-100'], ['free-04', '?f-11'], ['cut-01', '?c-106'], ['person', '?p-5'], ['fight-01', '?f-9'], ['fire', '?f2-5'], ['arg0-of', '?p-5', '?f-9'], ['arg1', '?f-9', '?f2-5']]\n" ] } ], "source": [ "fcg.deactivate_all_monitors()\n", "fcg.activate_monitor('trace-fcg')\n", "\n", "amr = sue.comprehend('Firefighters cut the child free.')\n", "print(amr)" ] }, { "cell_type": "markdown", "id": "7f68f88c-9997-44cf-bfb3-4995e0ca0bfb", "metadata": {}, "source": [ "The resulting AMR meaning representation can be challenging to read for humans in this format. Let's use the Penman library to print the meaning in a more human-friendly way. We can see that Sue understood that a cutting action in the sense of *slice, injure* (denoted by PropBank's ```cut-01``` roleset) was performed by an *intentional cutter* (```arg0``` in ```cut-01```), more in particular a person who habitually *fights* (```fight-01```) fire (```arg1``` in ```fight-01```), and that the cutting action itself led to (```arg0-of``` in ```cause-01```) the *unconstrained, unrestricted* state (```free-04```) of a child (```arg1``` in ```free-04```)." ] }, { "cell_type": "code", "execution_count": 9, "id": "71fa3c67-d5a3-4c14-80c2-2f526d341123", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:48.017652Z", "start_time": "2025-05-19T14:07:48.015083Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(c-106 / cut-01\n", " :arg0 (p-5 / person\n", " :arg0-of (f-9 / fight-01\n", " :arg1 (f2-5 / fire)))\n", " :arg0-of (c-115 / cause-01\n", " :arg1 (f-11 / free-04\n", " :arg1 (c-100 / child))))\n" ] } ], "source": [ "print(fcg.predicate_network_to_penman(amr))" ] }, { "cell_type": "markdown", "id": "dadb90f9-22a1-4b5b-b71e-1064fccecbce", "metadata": {}, "source": [ "Sue can also use their grammar to formulate utterances given a meaning representation. We will reuse the meaning representation from above." ] }, { "cell_type": "code", "execution_count": 10, "id": "a0939373d3304d99", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:48.528323Z", "start_time": "2025-05-19T14:07:48.034488Z" } }, "outputs": [ { "data": { "text/plain": [ "'Firefighters cut the child free.'" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "utterance = sue.formulate(fcg.instantiate_variables(amr))\n", "''.join(token for token in utterance)" ] }, { "cell_type": "markdown", "id": "b1efd3747039e039", "metadata": {}, "source": [ "Alternatively, we can try to comprehend an utterance and immediately formulate the resulting meaning representation in one go:" ] }, { "cell_type": "code", "execution_count": 11, "id": "3b49d47e56482c08", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:49.490691Z", "start_time": "2025-05-19T14:07:48.533380Z" } }, "outputs": [ { "data": { "text/plain": [ "'Firefighters cut the child free.'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "utterance = sue.comprehend_and_formulate(\"Firefighters cut the child free.\")\n", "''.join(token for token in utterance)" ] }, { "cell_type": "markdown", "id": "b5140350-b180-4b25-b336-e69a1d93afff", "metadata": {}, "source": [ "## Sue adds a new construction to their grammar" ] }, { "cell_type": "markdown", "id": "98ba3b4ef108f7f3", "metadata": {}, "source": [ "Let us now add a new construction to Sue’s grammar: the ```dog-cxn``` that in essence pairs the form \"dog\" with its AMR meaning of instantiating the dog concept. Along with its name, we specify its contributing and conditional poles, and load it into Sue. We also add a categorial link between the category proper to the ```dog-cxn``` and the category of the noun slot in the ```the-x-cxn```, following the design choices made in the demo grammar fragment and very much in the spirit of Radical Construction Grammar (Croft, 2001)." ] }, { "cell_type": "code", "execution_count": 12, "id": "9aab3b444cd7fc79", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:49.508765Z", "start_time": "2025-05-19T14:07:49.496175Z" } }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dog_cxn = fcg.Construction(name= 'dog-cxn',\n", " contributing_pole= [('?dog-unit',\n", " {'referent': '?d',\n", " 'category': 'dog-cxn',\n", " 'boundaries': ('?left', '?right')})],\n", " conditional_pole= [('?dog-unit',\n", " {'#meaning': [('dog', '?d')]},\n", " {'#form': [('sequence', '\"dog\"', '?left', '?right')]})])\n", "\n", "sue.add_cxn(dog_cxn)\n", "sue.add_category('dog-cxn')\n", "sue.add_link('dog-cxn', 'the-x-cxn-x')\n", "sue" ] }, { "cell_type": "markdown", "id": "95e5ace000cc8246", "metadata": {}, "source": [ "We now ask Sue to use their grammar to ```formulate``` an utterance that expresses that a cutting action in the sense of *slice, injure* was performed by an *intentional cutter*, more in particular a person who habitually *fights* fire, and that the cutting action itself led to the *unconstrained, unrestricted* state of a dog:" ] }, { "cell_type": "code", "execution_count": 13, "id": "c0ecfe8785fa7cb7", "metadata": { "ExecuteTime": { "end_time": "2025-05-19T14:07:49.982998Z", "start_time": "2025-05-19T14:07:49.518395Z" } }, "outputs": [ { "data": { "text/plain": [ "'Firefighters cut the dog free.'" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "amr_penman = '(c / cut-01 :arg0 (p / person :arg0-of (f / fight-01 :arg1 (f2 / fire))) :arg0-of (c2 / cause-01 :arg1 (f3 / free-04 :arg1 (d / dog))))'\n", "amr_pnw = fcg.penman_to_predicate_network(amr_penman)\n", "form = sue.formulate(amr_pnw)\n", "''.join(token for token in form)" ] }, { "cell_type": "markdown", "id": "0b8f35c7-6098-4429-8681-b2ddf028eef1", "metadata": {}, "source": [ "We can see that Sue produces the utterance \"Firefighters cut the dog free.\". Again, the construction application process can be traced in detail in the web interface." ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.10" } }, "nbformat": 4, "nbformat_minor": 5 }