#' Template: log PMF for a mark kernel (one event)
#'
#' This is a minimal skeleton for implementing a custom mark kernel. A mark
#' kernel assigns a probability to the observed edge set for a single event at
#' time `t_k`, conditional on the pre-event network `net` and the event mark
#' (`new_nodes`, `new_edges`).
#'
#' @param net network object, the current network at time t_k
#' @param new_nodes data.frame with at least column "id". Any additional
#' columns will be treated as vertex attributes.
#' @param new_edges data.frame with at least columns "i" and "j". Any additional
#' columns will be treated as edge attributes.
#' @param t_k numeric scalar, the event timestamp
#' @param params Named list of model parameters (kernel-specific, any others will be
#' ignored).
#' @param ... Optional kernel-specific arguments (e.g. tuning constants, caches).
#'
#' @return A list with:
#' \describe{
#' \item{logp}{Scalar log probability of observing `new_edges` (and any other
#' mark components) at this event.}
#' \item{edge_probs}{Named numeric vector (or matrix) of attachment
#' probabilities for eligible targets. This is returned for debugging /
#' diagnostics and may be `NULL` if not meaningful for the kernel.}
#' \item{model_cache}{Optional updated cache object (useful for expensive mark
#' models). Include only if you accept/use a cache via `...`.}
#' }
#'
#' @examples
#' # See log_pmf_ba() for a working implementation.
custom_pmf <- function(
net,
new_nodes,
new_edges,
t_k,
params,
...
) {
stopifnot(inherits(net, "network"))
stopifnot(length(t_k) == 1L, is.numeric(t_k), is.finite(t_k))
# Validate your input here, eg:
# - check parameter values are appropriate1
# - check new_nodes and new_edges are possible under the kernel specs
# I think it would be hygiene to create a separate "validate_custom...()"
# but you could just throw it all in here.
# Alter these to include your expected columns
stopifnot(is.data.frame(new_edges), all(c("i", "j") %in% names(new_edges)))
stopifnot(is.data.frame(new_nodes), all(c("id") %in% names(new_nodes)))
# Next I would cover your edge cases & compute edge-candidates, e.g
# - There are no existing nodes in the network
# - There are existing nodes, but no elgible edge-candidates
# And then finally, compute the actual edge probabilities
edge_probs <- calculate_custom_edge_probs(net, t_k, params)
# Placeholder
logp <- NA_real_
return(
list(
logp = as.numeric(logp),
edge_probs = edge_probs
)
)
}10 Custom Kernel PMF
If you want to add your own custom kernel PMF, I put together a rough “skeleton”.
I will eventually add some custom-kernel tests to validate whether a new kernel is truly a PMF.
Once you’ve written it, you just need to update the likelihood function, see below.
And updating the likelihood function:
#...
event_times <- events$times$t
if (length(event_times) == 0L) return(0)
if (is.null(T_end)) T_end <- max(event_times)
if (T_end < max(event_times)) stop("T_end must be >= max event time")
# Mark kernel dispatch
if (mark_type == "ba") {
mark_logpmf <- log_pmf_ba
} else if (mark_type == "ba_bip") {
mark_logpmf <- log_pmf_ba_bip
} else if (mark_type == "cs") {
mark_logpmf <- log_pmf_cs
} else if (mark_type == "YOUR CUSTOM PMF GOES HERE") { # ADD THESE LINES
mark_logpmf <- YOUR_CUSTOM_PMF_FUNCTION # ADD THESE LINES
} else {
stop("mark_type must be one of 'ba', 'cs', 'ba_bip', 'YOUR_CUSTOM_PMF'")
}
# ...
Note
Yes definitely a better way to do this, just a temporary solution for now.