Packages

This module provides a lookup interface into the VerbNet semantic mapping dataset for natural language processing (NLP) solutions.

Current section

Files

Jump to
verbnet lib verbnet.ex
Raw

lib/verbnet.ex

defmodule VerbNet do
@moduledoc ~S"""
This module provides a lookup interface into the [VerbNet](https://verbs.colorado.edu/~mpalmer/projects/verbnet.html)
semantic mapping dataset.
"""
# Some handy module attributes for locating assets.
@external_resource verbnet_xml_path = Path.join([__DIR__, "..", "assets", "verbnet"])
# Start timer
IO.puts("Compiling VerbNet XML to static lookups takes a while...")
start = System.monotonic_time()
# Load and parse each VerbNet class XML.
Code.ensure_compiled(VerbNet.XML)
classes = Path.join([verbnet_xml_path, "*.xml"])
|> Path.wildcard()
|> Task.async_stream(VerbNet.XML, :process_xml, [], timeout: 60_000)
|> Enum.to_list()
|> Enum.map(fn({:ok, class_list}) -> class_list end)
|> List.flatten()
# Process each class extracted from the XML, generate basic class info lookups and collect all frame-member mappings.
IO.puts("Processing #{Enum.count(classes)} parsed VerbNet classes")
all_frames = for {class_id, sections} <- classes do
# Define basic lookup functions for this VerbNet class.
def class(unquote(class_id)), do: unquote(Macro.escape(sections))
# Aggregate frame-member mappings for return.
members = sections |> Map.get(:members, %{})
frames = sections |> Map.get(:frames, %{})
for {primary, _frame} <- frames, member <- members |> Map.keys() do
{primary, member, class_id}
end
end
|> List.flatten()
|> Enum.reduce(%{}, fn({primary, member, class_id}, acc) -> Map.update(acc, {primary, member}, [class_id], fn(class_ids) -> [class_id] ++ class_ids end) end)
# Now generate our function heads for frame lookups
IO.puts("Processing #{Enum.count(all_frames)} VerbNet frames")
def find_frames(primary, member) when is_binary(primary) do
Map.get(unquote(Macro.escape(all_frames)), {primary, member}, []) |> aggregate_class_frames(primary)
end
# End timer
elapsed = (System.monotonic_time() - start) |> System.convert_time_unit(:native, :millisecond)
IO.puts("Codified VerbNet classes in #{elapsed}ms")
@doc ~S"""
Return semantic frame from VerbNet that matches the provided primary POS pattern and class member.
On failed lookup, returns an empty list.
## Examples
iex> VerbNet.find_frames("NP V NP", "wish")
[%{class_id: "wish-62",
description: %{descriptionnumber: "8.1", primary: "NP V NP", secondary: "NP",
xtag: "0.2"}, examples: [["I wished it."]],
semantics: [{:pred, %{value: "desire"},
[{:args, %{},
[{:arg, %{type: "Event", value: "E"}, []},
{:arg, %{type: "ThemRole", value: "Experiencer"}, []},
{:arg, %{type: "ThemRole", value: "Stimulus"}, []}]}]}],
syntax: [{:np, %{value: "Experiencer"}, [{:synrestrs, %{}, []}]},
{:verb, %{}, []},
{:np, %{value: "Stimulus"},
[{:synrestrs, %{},
[{:synrestr, %{type: "sentential", value: "-"}, []}]}]}]}]
iex> VerbNet.find_frames(["NP", "V", "NP"], "wish")
[%{class_id: "wish-62",
description: %{descriptionnumber: "8.1", primary: "NP V NP", secondary: "NP",
xtag: "0.2"}, examples: [["I wished it."]],
semantics: [{:pred, %{value: "desire"},
[{:args, %{},
[{:arg, %{type: "Event", value: "E"}, []},
{:arg, %{type: "ThemRole", value: "Experiencer"}, []},
{:arg, %{type: "ThemRole", value: "Stimulus"}, []}]}]}],
syntax: [{:np, %{value: "Experiencer"}, [{:synrestrs, %{}, []}]},
{:verb, %{}, []},
{:np, %{value: "Stimulus"},
[{:synrestrs, %{},
[{:synrestr, %{type: "sentential", value: "-"}, []}]}]}]}]
iex> VerbNet.find_frames("NP V NP", "foo")
[]
"""
@spec find_frames(primary :: binary, member :: binary) :: list
@spec find_frames(primary :: list, member :: binary) :: list
def find_frames(primary, member) when is_list(primary) do
find_frames(Enum.join(primary, " "), member)
end
@doc ~S"""
Return complete raw map of an entire VerbNet class.
On failed lookup, returns :invalid_class.
"""
@spec class(class_id :: binary) :: map
def class(class_id) do
# To shut up the compiler and make docs generate properly...
invalid_class(class_id)
end
@doc ~S"""
Return list of member maps for VerbNet class, keyed by member name.
On failed lookup, returns :invalid_class.
## Examples
iex> VerbNet.members("wish-62")
%{"aim" => %{grouping: "aim.02", wn: "aim%2:31:01"},
"dream" => %{grouping: "dream.01", wn: "dream%2:36:00"},
"expect" => %{grouping: "expect.01", wn: "expect%2:32:00"},
"imagine" => %{grouping: "imagine.02", wn: "imagine%2:31:01"},
"intend" => %{grouping: "intend.01", wn: "intend%2:31:00"},
"mean" => %{grouping: "mean.02", wn: "mean%2:31:00"},
"plan" => %{grouping: "plan.01", wn: "plan%2:31:01"},
"propose" => %{grouping: "propose.02", wn: "propose%2:31:01"},
"think" => %{grouping: "think.05", wn: "think%2:31:03"},
"wish" => %{grouping: "wish.02", wn: "wish%2:37:02"},
"yen" => %{grouping: "", wn: "yen%2:37:00"}}
iex> VerbNet.members("foo")
:invalid_class
"""
@spec members(class_id :: binary) :: map
def members(class_id) do
case class(class_id) do
%{members: retval} -> retval
_ -> :invalid_class
end
end
@doc ~S"""
Return list of thematic role maps for VerbNet class, keyed by role type.
On failed lookup, returns :invalid_class.
## Examples
iex> VerbNet.roles("wish-62")
%{"Experiencer" => [{:selrestrs, %{logic: "or"},
[{:selrestr, %{type: "animate", value: "+"}, []},
{:selrestr, %{type: "organization", value: "+"}, []}]}],
"Stimulus" => [{:selrestrs, %{}, []}]}
iex> VerbNet.roles("foo")
:invalid_class
"""
@spec roles(class_id :: binary) :: map
def roles(class_id) do
case class(class_id) do
%{themroles: retval} -> retval
_ -> :invalid_class
end
end
@doc ~S"""
Return list of semantic frames for VerbNet class, keyed by primary POS pattern.
On failed lookup, returns :invalid_class.
## Examples
iex> VerbNet.frames("wish-62") |> Map.keys()
["NP V NP", "NP V NP ADJ", "NP V NP to be ADJ", "NP V S_INF",
"NP V for NP S_INF", "NP V that S"]
iex> VerbNet.frames("foo")
:invalid_class
"""
@spec frames(class_id :: binary) :: list
def frames(class_id) do
case class(class_id) do
%{frames: retval} -> retval
_ -> :invalid_class
end
end
# Util method to remove compiler warnings re unused params AND allow ExDoc to generate properly.
defp invalid_class(_class_id) do
:invalid_class
end
# Internal lookup method called by pattern-matched function heads.
defp aggregate_class_frames(class_ids, primary) do
for class_id <- class_ids do
frames(class_id) |> Map.get(primary) |> Map.put(:class_id, class_id)
end
end
end