# -*- org-adapt-indentation: nil; org-edit-src-content-indentation: 0; orgstrap-cypher: sha256; orgstrap-norm-func-name: orgstrap-norm-func--dprp-1-0; orgstrap-block-checksum: 214759152ee9a1e8ea8744b82f85d874f4bed3417e487f180d2bad711cde135b; -*-
# [[orgstrap][jump to the orgstrap block for this file]]
# [[file:queries.pdf]] [[file:queries.html]]
#+name: orgstrap-shebang
#+begin_src bash :eval never :results none :exports none
set -e "-C" "-e" "-e"
{ null=/dev/null;} > "${null:=/dev/null}"
{ args=;file=;MyInvocation=;__p=$(mktemp -d);touch ${__p}/=;chmod +x ${__p}/=;__op=$PATH;PATH=${__p}:$PATH;} > "${null}"
$file = $MyInvocation.MyCommand.Source
{ file=$0;PATH=$__op;rm ${__p}/=;rmdir ${__p};} > "${null}"
emacs -batch -no-site-file -eval "(let (vc-follow-symlinks) (defun orgstrap--confirm-eval (l _) (not (memq (intern l) '(elisp emacs-lisp)))) (let ((file (pop argv)) enable-local-variables) (find-file-literally file) (end-of-line) (when (eq (char-before) ?\^m) (let ((coding-system-for-read 'utf-8)) (revert-buffer nil t t)))) (let ((enable-local-eval t) (enable-local-variables :all) (major-mode 'org-mode)) (require 'org) (org-set-regexps-and-options) (hack-local-variables)))" "${file}" -- ${args} "${@}"
exit
<# powershell open
#+end_src

#+title: SPARC queries
#+startup: showall
#+category: SPARC
#+options: ^:nil
#+latex_header: \usepackage[inkscapelatex=false]{svg}
#+begin: properties
#+property: header-args :eval no-export

# header-args:sparql :url http://localhost:10035/repositories/test
<<sparql-settings>>
#+property: header-args:sparql :url http://localhost:9999/blazegraph/sparql
#+property: header-args:sparql+ :format text/csv
#+property: header-args:sparql+ :results table
#+property: header-args:sparql+ :exports both
#+property: header-args:sparql+ :var limit="10"
# TODO figure out how to allow missing limit to not set epilogue, likely requires modifications to core
# XXX of course that would require epilogue to actually be implemented :/
# #+property: header-args:sparql+ :epilogue "LIMIT ?limit"

<<cypher-settings>>
# #+property: header-args:cypher :scigraph https://scicrunch.org/api/1/sparc-scigraph :api-key (and (fboundp 'get-scicrunch-api-key) (get-scicrunch-api-key))
#+property: header-args:cypher :scigraph http://localhost:9000/scigraph
#+property: header-args:cypher+ :limit 10
#+end:
#+begin: prefixes
# TODO consider a setup file for these maybe?
# or populate additional local curies via orgstrap
# FIXME doi collides which is bad
#+link: doi https://doi.org/
#+link: PMID http://www.ncbi.nlm.nih.gov/pubmed/
#+link: dataset https://api.pennsieve.io/datasets/N:dataset:
#+link: datasetbf https://api.blackfynn.io/datasets/N:dataset:
#+link: TEMP http://uri.interlex.org/temp/uris/
#+link: TEMPRAW http://uri.interlex.org/temp/uris/raw/
#+link: awards http://uri.interlex.org/temp/uris/awards/
#+link: rdf http://www.w3.org/1999/02/22-rdf-syntax-ns#
#+link: fma http://purl.org/sig/ont/fma/
#+link: FMA http://purl.org/sig/ont/fma/fma
#+link: obo http://purl.obolibrary.org/obo/
#+link: IAO http://purl.obolibrary.org/obo/IAO_
#+link: UBERON http://purl.obolibrary.org/obo/UBERON_
#+link: NCBITaxon http://purl.obolibrary.org/obo/NCBITaxon_
#+link: PATO http://purl.obolibrary.org/obo/PATO_
#+link: CHEBI http://purl.obolibrary.org/obo/CHEBI_
#+link: GO http://purl.obolibrary.org/obo/GO_
#+link: CL http://purl.obolibrary.org/obo/CL_
#+link: EMAPA http://purl.obolibrary.org/obo/EMAPA_
#+link: oboInOwl http://www.geneontology.org/formats/oboInOwl#
#+link: BIRNLEX http://uri.neuinfo.org/nif/nifstd/birnlex_
#+link: owl http://www.w3.org/2002/07/owl#
#+link: rdfs http://www.w3.org/2000/01/rdf-schema#
#+link: dc http://purl.org/dc/elements/1.1/
#+link: dcterms http://purl.org/dc/terms/
#+link: ILX http://uri.interlex.org/base/ilx_
#+link: ilxr http://uri.interlex.org/base/readable/
#+link: ilxtr http://uri.interlex.org/tgbugs/uris/readable/
#+link: sparc http://uri.interlex.org/tgbugs/uris/readable/sparc/
#+link: tech http://uri.interlex.org/tgbugs/uris/readable/technique/
#+link: asp http://uri.interlex.org/tgbugs/uris/readable/aspect/
#+link: unit http://uri.interlex.org/tgbugs/uris/readable/aspect/unit/
#+link: build http://uri.interlex.org/tgbugs/uris/readable/build/
#+link: buildid http://uri.interlex.org/tgbugs/uris/readable/build/id/
#+link: ilxtio http://uri.interlex.org/tgbugs/uris/indexes/ontologies/
#+link: nposimtest http://uri.interlex.org/tgbugs/uris/readable/npo-simple-test-1/
#+link: hyp-protcur https://uilx.org/tgbugs/u/hypothesis/protcur/
#+link: NLXINV http://uri.neuinfo.org/nif/nifstd/nlx_inv_
#+link: SAO http://uri.neuinfo.org/nif/nifstd/sao
#+link: NLX http://uri.neuinfo.org/nif/nifstd/nlx_
#+link: pio.api https://www.protocols.io/api/v3/protocols/
#+link: pio.view https://www.protocols.io/view/
#+link: ror https://ror.org/
#+link: SCR http://scicrunch.org/resolver/SCR_
#+link: apinatomy https://apinatomy.org/uris/readable/
#+link: elements https://apinatomy.org/uris/elements/
#+link: aacar https://apinatomy.org/uris/models/ard-arm-cardiac/ids/
#+link: bolew https://apinatomy.org/uris/models/bolser-lewis/ids/
#+link: kblad https://apinatomy.org/uris/models/keast-bladder/ids/
#+link: bromo https://apinatomy.org/uris/models/bronchomotor/ids/
#+link: scaft https://apinatomy.org/uris/models/scaffold-test/ids/
#+link: vagnr https://apinatomy.org/uris/models/vagus-nerve/ids/
#+link: sdcol https://apinatomy.org/uris/models/sawg-distal-colon/ids/
#+link: sstom https://apinatomy.org/uris/models/sawg-stomach/ids/
#+link: dlcon https://apinatomy.org/uris/models/dev-layout-conn/ids/
#+link: splen https://apinatomy.org/uris/models/spleen/ids/
#+link: pancr https://apinatomy.org/uris/models/pancreas/ids/
#+link: wbrcm https://apinatomy.org/uris/models/wbrcm/ids/
# FIXME definition is not expanding as expected, probably because my
# implementation is wrong?? but only occasionally? other times it works ??
#+link: definition http://purl.obolibrary.org/obo/IAO_0000115
#+link: rawmethods https://raw.githubusercontent.com/SciCrunch/NIF-Ontology/methods/ttl/
#+link: rawneurons https://raw.githubusercontent.com/SciCrunch/NIF-Ontology/neurons/ttl/
#+link: cassont https://cassava.ucsd.edu/sparc/ontologies/
#+link: sparc-olymp-apinat https://sparc.olympiangods.org/ApiNATOMY/ontologies/
#+link: prov http://www.w3.org/ns/prov#
#+link: foaf http://xmlns.com/foaf/0.1/
#+link: NIFRID http://uri.neuinfo.org/nif/nifstd/readable/
#+link: swrl http://www.w3.org/2003/11/swrl#
#+link: prism http://prismstandard.org/namespaces/basic/2.1/
#+link: BFO http://purl.obolibrary.org/obo/BFO_
#+link: RO http://purl.obolibrary.org/obo/RO_
#+link: HP http://purl.obolibrary.org/obo/HP_
#+link: UBPROP http://purl.obolibrary.org/obo/UBPROP_
#+link: chebi http://purl.obolibrary.org/obo/chebi/
#+link: ncbitaxon http://purl.obolibrary.org/obo/ncbitaxon#
#+link: cl http://purl.obolibrary.org/obo/cl#
#+link: MIRO http://uri.interlex.org/MIRO/uris/readable/
#+link: bibo http://purl.org/ontology/bibo/
#+link: cito http://purl.org/spar/cito/
#+link: swrl http://swrl.stanford.edu/ontologies/3.3/swrla.owl#
#+link: doap http://usefulinc.com/ns/doap#
#+link: xsd http://www.w3.org/2001/XMLSchema#
#+link: skos http://www.w3.org/2004/02/skos/core#
#+link: mba http://api.brain-map.org/api/v2/data/Structure/
#+link: jax http://jaxmice.jax.org/strain/
#+link: npokb http://uri.interlex.org/npo/uris/neurons/
#+link: PAXRAT http://uri.interlex.org/paxinos/uris/rat/labels/
#+link: NIFEXT http://uri.neuinfo.org/nif/nifstd/nifext_
#+end:

* Data queries
** Datasets
*** Dataset Predicates
#+begin_src sparql
SELECT DISTINCT
?p
# ?pp
# ?po
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset ?p ?o .
  # ?p ?pp ?po .
} ORDER BY ?p
#+end_src

*** Electrophysiology datasets
#+begin_src sparql
SELECT DISTINCT
?d
?label
?approach
WHERE {
  VALUES ?match { "electrophysiology" }
  ?d a sparc:Dataset ;
  rdfs:label ?l ;
  TEMP:hasExperimentalApproach ?approach .
  FILTER (str(?approach) = str(?match))
  BIND(substr(str(?l), 0, 40) AS ?label)
  # TODO lift and pull filetypes
}
#+end_src

*** Datasets by contents updated time
#+begin_src sparql
SELECT DISTINCT
?dataset
?ut
(substr(str(?l), 0, 40) as ?label)
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:contentsWereUpdatedAtTime ?ut .
  ?dataset rdfs:label ?l .
} ORDER BY DESC(?ut)
#+end_src

*** Human dataset queries
#+name: human-datasets-queries
#+begin_src python :results output drawer :exports both :eval no-export
import rdflib
from pyontutils.core import OntResIri
from pyontutils.namespaces import sparc, TEMP, dc, rdfs

ori = OntResIri('https://cassava.ucsd.edu/sparc/preview/exports/curation-export.ttl')
g = ori.graph
gns = g.namespace_manager

def fmt(s, u):
    return f'[[{u}][{s.n3(gns)}]]'

species = set([fmt(do, urih) for s, p, o in g
              if isinstance(o, rdflib.Literal) and
              ('human' in o.lower() or 'homo' in o.lower()) and
              p == sparc.animalSubjectIsOfSpecies
              for do in g[s:TEMP.hasDerivedInformationAsParticipant]
              for urih in g[do:TEMP.hasUriHuman]])

hlabel = set([fmt(s, urih) for s, p, o in g
             if isinstance(o, rdflib.Literal) and
             ('human' in o.lower() or 'homo' in o.lower()) and
             p == rdfs.label
             for urih in g[s:TEMP.hasUriHuman]])

htitle = set([fmt(s, urih) for s, p, o in g
              if isinstance(o, rdflib.Literal) and
              ('human' in o.lower() or 'homo' in o.lower()) and
              p == dc.title
              for urih in g[s:TEMP.hasUriHuman]])

htd = set([fmt(s, urih) for s, p, o in g
           if isinstance(o, rdflib.Literal) and
           ('human' in o.lower() or 'homo' in o.lower()) and
           (p == dc.title or p == dc.description)
           for urih in g[s:TEMP.hasUriHuman]])

counts = dict(species=len(human),
              label=len(hlabel),
              title=len(htitle),
              title_and_desc=len(htd))

[print(_ + r' \\') for _ in ['species n= ' + str(counts['species'])] +
sorted(species) +
['label n= ' + str(counts['label'])] +
sorted(hlabel) +
['title n= ' + str(counts['title'])] +
sorted(htitle) +
['td n= ' + str(counts['title_and_desc'])] +
sorted(htd)]
#+end_src
*** Test
Originally from =sparcur.reports.SparqlQueries=.
**** Dataset about
#+begin_src sparql
SELECT DISTINCT
?dataset
?about
WHERE {
  ?type rdfs:subClassOf* sparc:Resource .
  ?dataset rdf:type ?type .
  ?dataset isAbout: ?about .
}
LIMIT ?limit
#+end_src

**** Dataset subjects
#+name: dataset-subjects
#+begin_src sparql
SELECT DISTINCT
?dataset
?subj
WHERE {
    ?startsubj a sparc:Subject .
    ?startsubj TEMP:hasDerivedInformationAsParticipant ?dataset .
    ?subj a sparc:Subject .
    ?subj  TEMP:hasDerivedInformationAsParticipant ?dataset .
}
LIMIT ?limit
#+end_src

#+call: dataset-subjects(startsubj="<https://api.pennsieve.io/datasets/N:dataset:07c31ef4-cc72-4348-813a-f074675643ed/subjects/sub-STxF-19>", limit="100")

**** Dataset groups
#+name: dataset-groups
#+begin_src sparql
SELECT DISTINCT
?dataset
?group
?subj
WHERE {
    ?startsubj TEMP:hasDerivedInformationAsParticipant ?dataset .
    ?subj  TEMP:hasDerivedInformationAsParticipant ?dataset .
    ?subj  TEMP:hasAssignedGroup ?group .
} LIMIT ?limit
#+end_src

#+call: dataset-groups(startsubj="<https://api.pennsieve.io/datasets/N:dataset:07c31ef4-cc72-4348-813a-f074675643ed/subjects/sub-STxF-19>", limit="100")

**** Dataset bundle
#+name: dataset-bundle
#+begin_src sparql
SELECT DISTINCT
?dataset
WHERE {
    ?startdataset TEMP:collectionTitle ?string .
    ?dataset  TEMP:collectionTitle ?string .
}
LIMIT ?limit
#+end_src

#+call: dataset-bundle(startdataset="dataset:bec4d335-9377-4863-9017-ecd01170f354")

**** Dataset subject species
#+begin_src sparql :var limit="100"
SELECT DISTINCT
?dataset
?species
WHERE {
    ?dataset TEMP:isAboutParticipant ?subject .
    ?subject sparc:animalSubjectIsOfSpecies ?species .
    FILTER ( CONTAINS(str(?species), "human")
          || CONTAINS(str(?species), "homo sapiens")
          || ?species = NCBITaxon:9606 )
}
LIMIT ?limit
#+end_src

**** Dataset human subjects and samples
#+begin_src sparql :var limit="10"
SELECT DISTINCT
?dataset
?subject
?subject_meta
?sample
?sample_meta
?species
WHERE {
    ?dataset TEMP:isAboutParticipant ?subject .
    ?subject sparc:animalSubjectIsOfSpecies ?species .
    ?subject ?sup ?subject_meta .
    FILTER ( CONTAINS(str(?species), "human") || CONTAINS(str(?species), "homo sapiens") || ?species = NCBITaxon:9606 )
    ?sample TEMP:wasDerivedFromSubject ?subject .
    ?sample ?sap ?sample_meta .
}
LIMIT ?limit
#+end_src

**** Dataset milestone completion date
#+begin_src sparql
SELECT DISTINCT
?dataset
?date
WHERE {
    ?dataset TEMP:milestoneCompletionDate ?date .
}
ORDER BY ASC(?date)
LIMIT ?limit
#+end_src

**** Award affiliations
Can we see the multi-institutional nature of SPARC collaborations?
#+begin_src sparql
SELECT DISTINCT
?award
?affiliation
(str(?affil_l) as ?al)
WHERE {
    ?dataset TEMP:hasAwardNumber ?award .
    ?contributor TEMP:contributorTo ?dataset .
    ?contributor TEMP:hasAffiliation ?affiliation .
    ?affiliation rdfs:label ?affil_l .
    # FILTER isUri(?affiliation)
}
ORDER BY ASC(?award) ASC(?al)
LIMIT ?limit
#+end_src

** Protocols
*** Protocol general report
# TODO (make-directory "./reports/")

How to interpret this report.

The rows are ordered by
1. whether there is a dataset directly associated with the protocol id
2. whether the protocol has a human readable uri.
3. by the number of protcur annotations that have been made on the protocol

If there are zero protcur annotations it usually means that only the minimal protocol curation
workflow was completed. If there the number of protocol annotations is null it means that no
annotations of any kind have been made on that protocol. This can only happen for protocols that
come from the dataset description file.

If the dataset is null and there are annotations it usually means that the protocol is transitively
related to a dataset. There are annotations on the protocols that link them directly to the dataset
in question but we are not currently pulling them into the knowledge graph. It also means that the
protocol url in question was not listed in the dataset description file but may have been listed in
another protocol or in a collection of protocols.

If =hasUri= is false and dataset is not null then it usually means that the protocol has been
deleted or that something else has gone wrong. If dataset is null and =hasUri= is null it just means
that the protocol is present only in =protcur.ttl= and the additional identifiers have not been
processed.

# FIXME mkdir reports somehow
#+header: :results file :file ./reports/protocol-report.csv :exports code
#+begin_src sparql
select distinct
?protocol
?n
(sample(?dataset) AS ?datasetx)
?doi_protocol
(sample(bound(?urih_protocol)) as ?hasUri)
(sample(bound(?some_child)) as ?hasComplexAnnos)
where {
  ?protocol a sparc:Protocol .
  optional { ?protocol TEMP:hasNumberOfProtcurAnnotations ?n }
  optional { ?dataset TEMP:hasProtocol ?protocol }
  optional { ?protocol TEMP:hasDoi ?doi_protocol }
  optional { ?protocol TEMP:hasUriHuman ?urih_protocol }
  optional { ?protocol ?some_predicate ?annotation .
             ?annotation TEMP:protcurChildren ?some_child }
}
group by ?protocol ?n ?doi_protocol ?hasUri
order by desc(bound(?datasetx)) asc(?hasUri) desc(?n)
#+end_src

#+RESULTS:
[[file:./reports/protocol-report.csv]]

*** Total protocols
#+name: sparql-count-protocols
#+begin_src sparql
SELECT (COUNT(DISTINCT ?protocol) AS ?count)
WHERE { ?protocol a sparc:Protocol . }
#+end_src
*** Total protocols with at least one protcur annotation
#+name: sparql-count-protocols-at-least-one-protc
#+begin_src sparql :noweb no-export
SELECT (COUNT(*) as ?count) {
  <<sparql-protocols-at-least-one-protc>>
}}
#+end_src

#+name: sparql-count-protocols-pio-api-at-least-one-protc
#+begin_src sparql :noweb no-export
SELECT (COUNT(*) as ?count) {
  <<sparql-protocols-pio-api-at-least-one-protc>>
}
#+end_src

#+name: sparql-protocols-pio-api-at-least-one-protc
#+begin_src sparql :noweb yes :epilogue " order by desc(?anno_count)"
<<sparql-protocols-at-least-one-protc>>
  FILTER CONTAINS(str(?protocol), "protocols.io/api")
  #FILTER CONTAINS(str(?protocol), "protocols.io")
}
#+end_src

#+begin_src bash :eval never
grep -oE 'TEMP:protocol[^\ ]+' protcur.ttl | sort -u
#+end_src

#+name: sparql-protocols-at-least-one-protc
#+begin_src sparql :epilogue "} order by desc(?anno_count)"
select distinct
?protocol ?anno_count
where {
  values ?some_predicate {
    TEMP:protocolEmploysTechnique
    TEMP:protocolInvolvesAction
    TEMP:protocolInvolvesAspect
    TEMP:protocolInvolvesBlackBox
    TEMP:protocolInvolvesBlackBoxComponent
    TEMP:protocolInvolvesInput
    TEMP:protocolInvolvesInvariant
  }
  ?protocol a sparc:Protocol ;
    ?some_predicate ?annotation ;  # this line an the filter should wind up with the same number TODO add a test to check
    TEMP:hasNumberOfProtcurAnnotations ?anno_count .
  FILTER (?anno_count > 0 )
#+end_src

*** Published protocols with complex protcur annotations
#+name: sparql-complex-protocols-count
#+begin_src sparql :noweb no-export
SELECT (COUNT(*) as ?count) {
  <<sparql-complex-protocols>>
}
#+end_src

#+name: sparql-complex-protocols-published-count
#+begin_src sparql :noweb no-export
SELECT (COUNT(*) as ?count) {
  <<sparql-complex-protocols-published>>
}
#+end_src

#+name: sparql-complex-protocols-published
#+begin_src sparql :noweb no-export :epilogue "order by desc(?anno_count)"
<<sparql-complex-protocols>>
    ?protocol TEMP:hasDoi ?doi .
}
#+end_src

#+name: sparql-complex-protocols
#+begin_src sparql :epilogue "} order by desc(?anno_count)"
select distinct
?protocol ?anno_count
where {
    ?annotation TEMP:protcurChildren ?some_child .
    ?protocol a sparc:Protocol ;
              ?some_predicate ?annotation ;
              TEMP:hasNumberOfProtcurAnnotations ?anno_count .
#+end_src

# FIXME one failure of noweb is that you cannot remix and recombine in
# case where there is nesting, e.g. if I wanted the where clause to be
# the same except for one line, I couldn't reuse a block because you
# can make a noweb ref something that can be passed as an argument

*** Conditions studied
*** Experimental groups
#+name: experimental-groups-core
#+begin_src sparql :var limit="10" :eval never
WHERE {
    ?startsubj TEMP:hasDerivedInformationAsParticipant ?dataset .
    ?subj  TEMP:hasDerivedInformationAsParticipant ?dataset .
    ?subj  TEMP:hasAssignedGroup ?group .
}
#+end_src

#+name: experimental-groups
#+begin_src sparql :var limit="10" :noweb yes
SELECT DISTINCT
?dataset
?group
?subj
<<experimental-groups-core>>
ORDER BY ASC(?dataset) ASC(?group) LIMIT ?limit
#+end_src

#+name: experimental-groups-by-dataset
#+begin_src sparql :noweb yes :var limit="10"
SELECT DISTINCT
?group
?subj
<<experimental-groups-core>>
ORDER BY ASC(?group) LIMIT ?limit
#+end_src

# #+call: experimental-groups-by-dataset(limit="20",dataset="dataset:01b5cc2d-b321-4fed-8767-52771c4d680f")
# FIXME this is broken
#+call: experimental-groups-by-dataset(limit="20", dataset="dataset:257a485a-89b3-4187-888a-9843b277e6cb")

*** Techniques
# #+header: :var dataset=(or)
#+begin_src sparql
SELECT DISTINCT
?dataset
?technique
# ?protocol
# ?technique_p
WHERE {
  { ?dataset a sparc:Dataset .
    ?dataset TEMP:protocolEmploysTechnique ?technique .
    # ?technique rdfs:label ?tl
  }
  UNION
  { ?dataset TEMP:hasProtocol ?protocol .
    ?protocol TEMP:protocolEmploysTechnique ?technique .
    # ?technique rdfs:label ?tl
  }
}
LIMIT ?limit
#+end_src

*** Test
Originally from =sparcur.reports.SparqlQueries=.
**** Protocol techniques
#+begin_src sparql
SELECT DISTINCT
?protocol
?technique
WHERE {
    ?protocol rdf:type sparc:Protocol .
    ?protocol TEMP:protocolEmploysTechnique ?technique .
}
LIMIT ?limit
#+end_src

**** Protocol aspects
#+begin_src sparql
SELECT DISTINCT
?protocol
?aspect
WHERE {
    ?protocol rdf:type sparc:Protocol .
    ?protocol TEMP:protocolInvolvesAspect ?ast .
    ?ast rdf:type protcur:aspect .
    ?ast TEMP:hasValue ?aspect .
}
LIMIT ?limit
#+end_src

**** Protocol inputs
# Note that the ontology alignment here is often incorrect
#+begin_src sparql
SELECT DISTINCT
?protocol
?ast_in
?input
WHERE {
    ?protocol rdf:type sparc:Protocol .
    ?protocol TEMP:protocolInvolvesInput ?ast_in .
    ?ast_in rdf:type protcur:input .
    ?ast_in TEMP:hasValue ?input .
}
LIMIT ?limit
#+end_src

**** Protocol species dose
#+begin_src sparql
SELECT DISTINCT
?dataset
?protocol

(str(?label_drug) as ?l_drug)
?value_lt
WHERE {
    VALUES ?t {protcur:invariant protcur:parameter} .
    ?ast_inv a ?t .
    ?ast_inv TEMP:hasValue ?bnode .
    ?bnode rdf:value ?value_lt .
    #?bnode TEMP:hasUnit unit:milligram%20%2F%20kilogram . # seems like the %20 etc. break curies?
    ?bnode TEMP:hasUnit <http://uri.interlex.org/tgbugs/uris/readable/aspect/unit/milligram%20%2F%20kilogram> .
    FILTER (?value_lt < ?limit)

    ?ast_drug a protcur:input .
    ?ast_drug TEMP:hasValue ?drug .
                            ?drug rdfs:label ?label_drug .
    ?ast_drug TEMP:protcurChildren+ ?ast_child .
    ?ast_child TEMP:hasValue ?bnode .

    ?protocol a sparc:Protocol .
    ?protocol TEMP:protocolInvolvesInput ?ast_drug .

    ?protocol TEMP:protocolInvolvesInput ?ast_in_sp .
    ?ast_in_sp rdf:type protcur:input .
    ?ast_in_sp TEMP:hasValue ?species .

    OPTIONAL { ?dataset TEMP:hasProtocol ?protocol } .

}
ORDER BY ?label_input ?value_lt
#+end_src

**** Protocols with known anaesthetic or analgesic roles
#+begin_src sparql
select distinct
?pro
?ana
?l
?u
?v
where {
  ?pro TEMP:protocolInvolvesInput ?anno .
  ?anno TEMP:hasValue ?ana .
  ?anno TEMP:protcurChildren ?ad .
  ?ad TEMP:hasValue ?asp .
  ?ad TEMP:protcurChildren ?ai .
  ?ai TEMP:hasValue ?bn .
  ?bn TEMP:hasUnit ?u .
  ?bn rdf:value ?v .
  ?ana rdfs:label ?l .
  ?ana ?p ?b .
  ?b owl:onProperty RO:0000087 .
  ?b owl:someValuesFrom ?o .
  ?o rdfs:subClassOf* ?role .
  values ?asp {asp:dose asp:percent-volume} .
  values ?role {CHEBI:38867 CHEBI:35480} .
} order by ?l
#+end_src

** Datasets following same protocol with different subjects
*** Completeness
#+begin_src sparql
SELECT DISTINCT
?dataset
?completeness
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:completenessOfDataset ?completeness .
}
LIMIT ?limit
#+end_src

*** Dataset collections
#+begin_src sparql
SELECT DISTINCT
?title
?dataset
WHERE {
    ?startdataset TEMP:collectionTitle ?title .
    ?dataset  TEMP:collectionTitle ?title .
}
ORDER BY ASC(?title)
LIMIT ?limit
#+end_src

** Subjects
*** Members
Show me all of the experimental subjects that a dataset contains information about.
#+begin_src sparql
SELECT DISTINCT
?dataset
?subject
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:isAboutParticipant ?subject .
  ?subject a sparc:Subject .
}
LIMIT ?limit
#+end_src

*** Total
Show me the total number of subjects in the knowledge graph.
#+begin_src sparql
SELECT DISTINCT
(COUNT(DISTINCT ?subject) as ?count_subject)
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:isAboutParticipant ?subject .
  ?subject a sparc:Subject .
}
#+end_src

*** Subject Metadata
Show me a regularized set of metadata for all subjects that includes the following.
- Identifier
- Group
- Species
- Strain
- Sex
- Age Category
- Age
- Mass

# #+header: :var species="NCBITaxon:10116"
# #+header: :var species="NCBITaxon:9685"
#+name: subject-metadata
#+begin_src sparql
SELECT DISTINCT
?local_id

?assigned_group

?l_species
?strain
?l_sex

?age_category
?age_value
?age_unit

?mass_value
?mass_unit

WHERE {
  ?subject a sparc:Subject .
  ?subject TEMP:localId ?local_id .
  ?subject sparc:animalSubjectIsOfSpecies ?species . OPTIONAL { ?species rdfs:label ?l_species . }
  OPTIONAL { ?subject sparc:animalSubjectIsOfStrain ?strain . } # ?strain rdfs:label ?l_strain .
  OPTIONAL { ?subject TEMP:hasBiologicalSex ?sex . ?sex rdfs:label ?l_sex . }
  OPTIONAL { ?subject TEMP:hasAgeCategory ?age_category . }
  OPTIONAL { ?subject TEMP:hasAssignedGroup ?assigned_group . }
  # OPTIONAL { ?subject TEMP:participantInPerformanceOf ?protocol . }

  OPTIONAL {
  # mass
  ?subject sparc:animalSubjectHasWeight ?bn_mass .
  ?bn_mass a sparc:Measurement .
  ?bn_mass TEMP:hasUnit ?mass_unit .
  ?bn_mass rdf:value ?mass_value .
}
  OPTIONAL {
  # age
  ?subject TEMP:hasAge ?bn_age .
  ?bn_age a sparc:Measurement .
  ?bn_age TEMP:hasUnit ?age_unit .
  ?bn_age rdf:value ?age_value .
}
#  VALUES ?l_s {?species ?sex}  # doesn't work, if it did it would duplicate rows
#  ?l_s rdfs:label ?label
} LIMIT ?limit
#+end_src

#+call: subject-metadata() :var species="NCBITaxon:10090"

*** TODO By sex
#+begin_src sparql
SELECT DISTINCT
(COUNT(DISTINCT ?subject) as ?count_subject)
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:isAboutParticipant ?subject .
  ?subject a sparc:Subject .
}
#+end_src
** Samples
*** Members
Show me all of the experimental samples that a dataset contains information about.
#+begin_src sparql
SELECT DISTINCT
?dataset
?sample
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:isAboutParticipant ?sample .
  ?sample a sparc:Sample .
}
LIMIT ?limit
#+end_src

*** Total
Show me the total number of samples in the knowledge graph.
#+begin_src sparql
SELECT DISTINCT
(COUNT(DISTINCT ?sample) as ?count_sample)
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:isAboutParticipant ?sample .
  ?sample a sparc:Sample .
}
#+end_src

*** Sample predicates
#+begin_src sparql
SELECT DISTINCT
?p
WHERE {
  ?sample a sparc:Sample .
  ?sample ?p ?o .
}
#+end_src

*** Sample Metadata
Show me a regularized set of metadata for all subjects that includes the following.
- Identifier
- Group
- Anatomical entity
#+name: sample-metadata
#+begin_src sparql
SELECT DISTINCT
?subject_lid

?local_id

?assigned_group

(str(?anat_ent_src) as ?aess)

WHERE {
  ?sample a sparc:Sample .
  ?sample TEMP:localId ?local_id .

  ?sample TEMP:wasDerivedFromSubject ?subject .
  ?subject TEMP:localId ?subject_lid .
  ?subject a sparc:Subject .

  OPTIONAL { ?sample TEMP:hasAssignedGroup ?assigned_group . }
  OPTIONAL { ?sample TEMPRAW:wasExtractedFromAnatomicalRegion ?anat_ent_src . }
  # OPTIONAL { ?sample TEMP:hasAge ?abe . }
  # OPTIONAL { ?sample TEMP:participantInPerformanceOf ?protocol . }
}
LIMIT ?limit
#+end_src

** Anatomical entities
*** Dataset
**** Involves
# FIXME unused at the moment
#+begin_src sparql :var limit="30"
SELECT DISTINCT
?dataset
?protocol
#?ae
(str(?aes) as ?entity)
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:hasProtocol ?protocol .
  ?protocol TEMPRAW:involvesAnatomicalRegion ?aes .
  # ?ae rdfs:label ?l_ae .
}
ORDER BY ASC(?entity)
LIMIT ?limit
#+end_src

**** About
#+begin_src sparql
SELECT DISTINCT
?dataset
# ?ae
(str(?l_ae) as ?entity)
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset isAbout: ?ae .  # TODO not 100% on the modelling here
  ?ae rdfs:label ?l_ae .
  ?ae rdfs:subClassOf* UBERON:0001062 .  # have to load uberon for this
}
ORDER BY ASC(?l_ae)
LIMIT ?limit
#+end_src

**** Sample Source
#+name: dataset-sample-source
#+begin_src sparql :var limit="20"
SELECT DISTINCT
?dataset
(str(?ae) as ?aes)
# ?slid
WHERE {
  ?sample TEMP:hasDerivedInformationAsParticipant ?dataset .
  ?sample TEMPRAW:wasExtractedFromAnatomicalRegion ?ae .
  ?sample a sparc:Sample .
  # ?sample TEMP:localId ?slid .
  ?dataset a sparc:Dataset .
}
ORDER BY
DESC(?ae)
# ASC(?slid)
LIMIT ?limit
#+end_src

*** Protocol
**** Simple
#+name: sparql-count-protocols-pio-api
#+begin_src sparql :noweb yes
SELECT (COUNT(*) as ?count) {
  <<sparql-protocols-pio-api>>
}
#+end_src

#+name: sparql-protocols-pio-api
#+begin_src sparql :var limit="10" :epilogue "LIMIT ?limit"
SELECT DISTINCT
?protocol
WHERE {
  ?protocol a sparc:Protocol .
  FILTER CONTAINS(str(?protocol), "protocols.io/api")
  #FILTER CONTAINS(str(?protocol), "protocols.io")
}
#+end_src

**** Protocol triples
#+begin_src sparql :var limit="20"
SELECT DISTINCT
?protocol
?p
?o
WHERE {
  ?protocol a sparc:Protocol .
  ?protocol ?p ?o
  FILTER CONTAINS(str(?protocol), "protocols.io")
} LIMIT ?limit
#+end_src

**** Involves
NOTE In order for these to work with subClassOf =UBERON= uberon must be loaded.
#+begin_src sparql :var limit="20"
SELECT DISTINCT
?protocol
?bb_value
(str(?l_bb) as ?ls)
WHERE {
  ?protocol a sparc:Protocol .
  ?protocol TEMP:protocolInvolvesBlackBox ?ast_bb .

  ?ast_bb rdf:type protcur:black-box .  # TODO need to refine on organ an ingest the new alignment
  ?ast_bb TEMP:hasValue ?bb_value .

  ?bb_value rdfs:subClassOf+ UBERON:0001062 .
  ?bb_value rdfs:label ?l_bb .
} LIMIT ?limit
#+end_src

# seems like there are some issues with labels https://jira.blazegraph.com/browse/BLZG-4476
# :format application/sparql-results+xml
#+begin_src sparql :var limit="20"
SELECT DISTINCT
?protocol
?bbc_value
(str(?l_bbc) as ?bbl)
WHERE {
  ?protocol a sparc:Protocol .
  ?protocol TEMP:protocolInvolvesBlackBoxComponent ?ast_bbc .

  ?ast_bbc rdf:type protcur:black-box-component .  # TODO need to refine on organ an ingest the new alignment
  ?ast_bbc TEMP:hasValue ?bbc_value .

  ?bbc_value rdfs:subClassOf+ UBERON:0001062 .
  ?bbc_value rdfs:label ?l_bbc .
} ORDER BY ?bbl LIMIT ?limit
#+end_src

**** Protc annotations
#+begin_src bash :eval never
grep -oE 'a\ protcur:[^\ ]+' protcur.ttl | sort -u | cut -d' ' -f2
#+end_src

#+name: sparql-count-annotations-protc
#+begin_src sparql :noweb yes
select (count(*) as ?count) {
  <<sparql-annotations-protc>>
}}
#+end_src

#+name: sparql-count-annotations-pio-api-protc
#+begin_src sparql :noweb yes
select (count(*) as ?count) {
  <<sparql-annotations-pio-api-protc>>
}
#+end_src

#+name: sparql-annotations-pio-api-protc
#+begin_src sparql :noweb yes
<<sparql-annotations-protc>>
  ?protocol a sparc:Protocol .
  ?protocol ?p ?banno .
  ?banno TEMP:protcurChildren* ?anno .
  FILTER CONTAINS(str(?protocol), "protocols.io/api")
  #FILTER CONTAINS(str(?protocol), "protocols.io")
}
#+end_src

#+name: sparql-annotations-protc
#+begin_src sparql :epilogue "}"
select distinct ?anno where {
  # TODO subPropertyOf
  #values ?anno_predicate {}  # this is a bad way to do this
  # ?s ?anno_predicate ?anno
  values ?anno_type {
    protcur:aspect
    protcur:black-box
    protcur:black-box-component
    protcur:executor-verb
    protcur:input
    protcur:invariant
    protcur:output
    protcur:parameter
  }
  ?anno a ?anno_type .
#+end_src

** Uberon tests
In order to use these you need to load the latest version of uberon into blazegraph.
#+begin_src sparql :var limit="15"
SELECT DISTINCT ?sc (str(?l) as ?label) WHERE {
  # BIND("brain" AS ?label) # Well would you look at that!
  VALUES (?root) { ( UBERON:0001062 ) }
  ?sc rdfs:subClassOf+ ?root .
  ?sc rdfs:label ?l .
} ORDER BY ASC(?label) LIMIT ?limit
#+end_src

#+begin_src sparql
select ?p ?o where {
    VALUES (?s) { ( UBERON:0001062 ) }
    ?s ?p ?o .
}
#+end_src

** Associated scaffolds
** Manifest files
A SODA development use case. Find me datasets that have top level manifests with no errors.
#+begin_src sparql
SELECT DISTINCT
?dataset
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:hasPart+ ?file .
  ?file a sparc:File .
  ?file a sparc:Manifest .
  ?file TEMP:hasParent ?dataset . # this is the tricky bit, because we conflate the identifier for dataset and the folder
  ?file TEMP:hasErrors 0 .
}
LIMIT ?limit
#+end_src

** Contributors
*** Count
#+begin_src sparql
SELECT DISTINCT
(COUNT(DISTINCT ?person) as ?cp)
WHERE {
  ?person a sparc:Person .
}
LIMIT ?limit
#+end_src

*** ORCiD
Count the number of contributors that have ORCiDs.
#+begin_src sparql
SELECT DISTINCT
(COUNT(DISTINCT ?person) as ?cp)
#?fn
#?ln
WHERE {
  ?person a sparc:Person .
  ?person sparc:firstName ?fn .
  ?person sparc:lastName ?ln .
  FILTER CONTAINS(str(?person), "orcid.org")
}
LIMIT ?limit
#+end_src

** Other
NOTE These are not yet in the ttl file, the queries written here will not work yet.
*** File types
In all likelihood we are not going to include the names of each of the
individual files in the standard ttl export. We may put it in a named
graph and then update the journal, possibly only for released
datasets. The use cases for having individual files in the graph is
not at all clear, we might have individual folders, or run it in the
other direction where a subject could list the folders that contain
data about it.  Trying to keep the graph in sync with Blackfynn would
be quite a pain, essentially we would store/append every single file
that ever appears and then mark the deleted ones as deleted or something.
#+begin_src sparql
SELECT DISTINCT
?dataset
?file_type
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:containsFileWithType ?file_type .  # TODO not 100% on the modelling here
}
LIMIT ?limit
#+end_src

In the =path-metadata= folder of =curation-export=.

#+begin_src bash
grep -rh 'mimetype":' | sort | uniq -c | sort -h
#+end_src

This doesn't get coverage for file extensions so in addition we run the following.
#+begin_src bash
grep -rh 'basename":.\+\.\([a-z]\+\)' | grep -o '\.[a-z]\+"' | sort | uniq -c | sort -h
#+end_src

*** Has Raw Data
#+begin_src sparql
SELECT DISTINCT
?dataset
?raw
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:containsFolderForRawData ?raw .  # TODO not 100% on the modelling here
}
LIMIT ?limit
#+end_src
*** Has Derived Data
#+begin_src sparql
SELECT DISTINCT
?dataset
?derived
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:containsFolderForDerivedData ?derived .  # TODO not 100% on the modelling here
}
LIMIT ?limit
#+end_src
*** Has Code
#+begin_src sparql
SELECT DISTINCT
?dataset
?code
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset TEMP:containsFolderForCode ?code .  # TODO not 100% on the modelling here
}
LIMIT ?limit
#+end_src
*** Used in simulation
This modeling is extremely preliminary.
#+begin_src sparql
SELECT DISTINCT
?dataset
?dataset_sim
WHERE {
  ?dataset a sparc:Dataset .
  ?dataset_sim a sparc:Dataset .  # TODO not 100% on this
  # the dataset holds the simulation, and is also what references the other datasets
  # whether we need an explicit type for simulation datasets is not clear, I suspect
  # that we do not, since datasets are just data, the aboutness or typeness probably
  # should come from the fact that the dataset specifies or houses a simulation ...
  # ?dataset_sim a sparc:SimulationDataset . # also not good
  # ?dataset_sim TEMP:isSubstrateForSomeComputationalSimulation true .  # FIXME this is bad bad bad
  VALUES ?p {TEMP:derivesParametersFrom TEMP:derivesValidationDataFrom ilxtr:hasInformationInput} .
  ?dataset_sim ?p ?dataset .
}
LIMIT ?limit
#+end_src
** Subject reconciliation
first pass requires that the subject_id be identical
*** v2
#+name: pds-v2-count
#+begin_src sparql :noweb no-export
SELECT (COUNT(*) as ?count) {
  <<pds-v2>>
}
#+end_src

#+name: pds-v2
#+begin_src sparql :epilogue "\nLIMIT ?limit\n" :var limit="9"
SELECT DISTINCT ?s ?dataset (str(?id) as ?local_id) ?award ?species
WHERE {

  ?s a sparc:Subject;
     TEMP:localId ?id;
     TEMP:hasDerivedInformationAsParticipant ?dataset ;
     sparc:animalSubjectIsOfSpecies ?species .
  ?dataset a sparc:Dataset ;
           TEMP:hasAwardNumber ?award .

 { SELECT DISTINCT ?id
    WHERE {
	  ?s a sparc:Subject ;
	      TEMP:localId ?id ;
	      TEMP:hasDerivedInformationAsParticipant ?dataset .
    } GROUP BY ?id HAVING (count(?id) > 1) }

}
ORDER BY ?id
#+end_src

# #+header: :results drawer output
# #+header: :var by_id=(and)
#+header: :epilogue "return table" :results value table
#+name: pds-v2-python
#+begin_src python :var data=pds-v2(limit="99999") :exports results
import sys
from pprint import pprint
from collections import Counter
counts = Counter([(id, species, award) for _, _, id, species, award in data])
#print(len([c for c in counts.values() if c > 1]))
#pprint(counts)
multi = [row for row in data if counts[tuple(row[2:])] > 1]

for index in (0, 1, 3, 4):
    for row in multi:
        try:
            row[index] = OntId(row[index]).curie  # shorten iris
        except Exception as e:
            sys.stderr.write(f'not a curie! {row[index]}')  # FIXME how to stderr

hardcoded_header =  [
    f'individuals (n={len(multi)})',
    'dataset',
    f'id (n={len(set([r[2] for r in multi]))})',
    'awards',
    'species']

key_id = lambda row:(row[2], row[3], row[4], row[1])
key_award_species_dataset_id = lambda row:(row[3], row[1], row[2], row[4])
table = [hardcoded_header] + [None] + sorted(set(tuple(_) for _ in multi))#, key=(key_id if by_id else key_award_species_dataset_id)
#+end_src

#+begin_src elisp :results none :exports none
(let ((org-table-convert-region-max-lines most-positive-fixnum))
  (ow-babel-eval "pds-v2-python"))
#+end_src

*** v1
# #+header: :cache yes
#+name: potential-duplicate-subjects
#+begin_src sparql :cache yes
# :results table
# RUN:block
SELECT distinct ?s1 ?s2 ?id ?dataset1 ?dataset2 ?species1 ?species2 ?award1 ?award2
WHERE {

  # Give me all subjects with duplicate IDs with unique datasets
  { SELECT ?id
    WHERE {
	  ?s1 a owl:NamedIndividual ;
	  TEMP:localId ?id ;
	  TEMP:hasDerivedInformationAsParticipant ?datasetx .
	  ?s2 a owl:NamedIndividual ;
	  TEMP:localId ?id ;
	  TEMP:hasDerivedInformationAsParticipant ?datasety .
	  filter(?datasetx != ?datasety)
    } GROUP BY ?id having (count(?id) > 1)}

  # filter out the cases where there can't be a match because species doesn't match
  ?s1 TEMP:localId ?id ;
  sparc:animalSubjectIsOfSpecies ?species1;
  TEMP:hasDerivedInformationAsParticipant ?dataset1 .
  ?s2 TEMP:localId ?id ;
  sparc:animalSubjectIsOfSpecies ?species2;
  TEMP:hasDerivedInformationAsParticipant ?dataset2 .
  filter(?s1 != ?s2)
  filter(STR(IRI(?s1)) < STR(IRI(?s2))) # this is a hack to filter out duplicate triples

  # filter to find non-matching awards
  # using contact person doesn't make sense as they are 1:1 with award right now
  ?dataset1 a owl:NamedIndividual ;
  TEMP:hasAwardNumber ?award1 .
  ?dataset2 a owl:NamedIndividual ;
  TEMP:hasAwardNumber ?award2 .
} order by ?id limit ?limit
#+end_src

# *[[RUN:][Run Query ▶]]*
# #+header: :results drawer output
#+header: :epilogue "return result"
#+name: process-duplicate-subjects
#+begin_src python :var data=potential-duplicate-subjects(limit="99999") :exports results
from pprint import pprint
import sys
import itertools
from collections import defaultdict, OrderedDict, Counter
from sparcur.core import OntId

def func(data):
    idmap = defaultdict(lambda: defaultdict(list)) # change to list; order for local_id
    for row in data:
        subj1, subj2, local_id = row[0], row[1], row[2]
        if not idmap[subj1]['local_id']:
            idmap[subj1]['local_id'].append(local_id)
        idmap[subj1]['cluster'].append(subj2)
        idmap[subj1]['local_id'].append(local_id)
        value_pairs = [(row[i], row[i+1]) for i in range(3, len(row), 2)]
        value_pairs_len = len(value_pairs)
        for i, value_pair in enumerate(value_pairs):
            v1, v2 = value_pair
            if v1 not in idmap[subj1][i]:
                idmap[subj1][i].append(v1)
            idmap[subj1][i].append(v2)
    header = ['cluster', 'local_id'] + [i for i in range(value_pairs_len)] # todo: replace this with sparql headers if possible
    columns = defaultdict(list)
    subjs = sorted([(v, v.split('/')[-1]) for v in idmap], key=lambda x: x[1])
    for subj, _ in subjs:
        padding_max = max([len(v) for v in idmap[subj].values()])
        for colname, values in idmap[subj].items():
            values = list(values)
            values = [subj] + values if colname == 'cluster' else values
            padding = padding_max - len(values) if len(values) < padding_max else 0
            columns[colname] += values # + ['-'] * padding + ['']
    __thing = [list(_) for _ in itertools.zip_longest(*[columns[k] for k in header])]
    _thing = sorted(list(_) for _ in set(tuple(_) for _ in __thing))
    counts = Counter([(id, species, award) for _, id, _, species, award in _thing])
    thing = [row for row in _thing if counts[tuple(row[x] for x in [1, 3, 4])] > 1]  # remove unique rows since there is no chance they will match
    for index in (0, 2, 3, 4):
        for row in thing:
            try:
                row[index] = OntId(row[index]).curie  # shorten iris
            except Exception as e:
                sys.stderr.write(f'not a curie! {row[index]}')  # FIXME how to stderr

    hardcoded_header =  [f'individuals (n={len(thing)})',
                         f'id (n={len(set([r[1] for r in thing]))})',
                         'datasets', 'species', 'awards']
    table = [hardcoded_header] + [None] + sorted(set(tuple(_) for _ in thing), key=lambda row:(row[1], row[3], row[4], row[2]))
    return table


result = func(data)
#+end_src

#+RESULTS: process-duplicate-subjects
* Python helper
** call

# Use this block to run the next block an all dependencies without being asked again.
#+begin_src elisp :results none :exports none
(ow-babel-eval "neru-simplified")
#+end_src

#+header: :var json-string=python-neru-helper(result=cypher-neru-6-helper(neupop_id="ilxtr:neuron-type-aacar-12"))
#+header: :var path="./images/neru-simplified-aacar-12.svg" :results file
#+name: neru-simplified
#+begin_src elisp :exports none
(json->svg-file json-string path)
#+end_src

#+RESULTS: neru-simplified
[[file:./images/neru-simplified-aacar-12.svg]]

** simple
Python code to simplify =neru= and supporting functions to extract terminal regions.
# FIXME emacs-jupyter doesn't convert t -> True !??!
# FIXME this doesn't highlight correctly in init-simple.el
# FIXME the python will run the epilogue for in a babel call but jupyter-python will not, fun times
# #+header: :epilogue "\nimport json\nb, ed, s, ex, o = main(blob_string=result)\n#return json.dumps(b, indent=1, sort_keys=False)"
#+header: :epilogue "\nimport json\nb, ed, s, ex, o = main(neupop_id=\"ilxtr:neuron-type-keast-6\")\nprint(json.dumps(b, indent=1, sort_keys=False))"
#+header: :results drawer
#+header: :var result=(or) remove=(or)
#+name: python-process-neru
#+begin_src jupyter-python :session pys
from pyontutils.config import auth
from nifstd_tools.simplify import apinat_deblob, sub, pred, obj, ematch
from nifstd_tools.simplify import axon, dend, bag, top, ie, ies, iot, iots, ext, fasIn, endIn, layerIn, onts

ext = onts  # XXX schema change


def query(neupop_id):
    import requests
    url = ('https://scicrunch.org/api/1/sparc-scigraph/scigraph/dynamic/'
           f'demos/apinat/neru-7/{neupop_id}?limit=9999999&key={auth.get("scigraph-api-key")}')
    resp = requests.get(url, headers={'Accept': 'application/json'})
    blob = resp.json()
    return blob


def find_terminals(blob, type):
    return [es for es in blob['edges']
            if
            pred(es, iots) and
            obj(es, type) and
            ematch(blob,
                   (lambda e, m:
                    sub(e, m) and
                    pred(e, top) and
                    obj(e, bag)),
                   sub(es))]


def find_region(blob, edge):
    collect = []
    def select(e, m, collect=collect):
        if sub(e, m):
            #print(e)
            if pred(e, layerIn) or pred(e, fasIn) or pred(e, endIn):
                return ematch(blob, select, obj(e))
            elif pred(e, onts):
                region = obj(e)
                collect.extend([b for b in blob['nodes'] if b['id'] == region])
                return region

    ematch(blob, select, sub(edge))
    return collect


def find_region_layer(blob, edge, bindex):
    collect = []
    layers = []
    layers_iots = []
    donel = set()
    doner = set()
    def select_term(e, m, layers=layers):
        if sub(e, m):
            pprint(e)
            if (pred(e, onts)
                or pred(e, iots)):
                layer = obj(e)
                if layer not in donel:
                    donel.add(layer)
                    if pred(e, iots):
                        layers_iots.append(bindex[layer])
                    else:
                        layers.append(bindex[layer])
                return layer

    def select(e, m, collect=collect):
        if sub(e, m):
            if pred(e, layerIn):
                # we're at a layer
                ematch(blob, select_term, sub(e))
                return ematch(blob, select, obj(e))
            elif (pred(e, fasIn)
                  or pred(e, endIn)):
                return ematch(blob, select, obj(e))
            elif pred(e, onts):
                region = obj(e)
                if region not in doner:
                    doner.add(region)
                    collect.append(bindex[region])
                return region
    ematch(blob, select, sub(edge))
    if collect and not layers and not layers_iots:
        layers = [None]
    elif layers_iots and not layers:
        layers = layers_iots

    if len(collect) != len(layers):
        raise ValueError(f'len not matched {len(collect)} {len(layers)}')
    return list(zip(collect, layers))


def find_terminal_regions(blob, type):
    return [region for es in find_terminals(blob, type) for region in find_region(blob, es)]


def find_terminal_region_layers(blob, type, bindex):
    return [(region, layer) for es in find_terminals(blob, type)
            for region, layer in find_region_layer(blob, es, bindex)]


def main(blob=None, blob_string=None, neupop_id=None, remove_converge=remove):
    # do not call this function unless it is from an org babel block, there are free variables
    if blob is None and blob_string is not None:
        import json
        blob = json.loads(blob_string)

    neupop_id = neupop_id if neupop_id else 'ilxtr:neuron-type-keast-6'

    if blob is None:
        blob = query(neupop_id)

    blob, edges, somas, externals, ordering_edges = v = apinat_deblob(
        blob, remove_converge=remove_converge)

    test_call = False  # change to True to test the #+call: expression
    if test_call:
        pprint(v)
    return v
#+end_src

#+call: python-process-neru(result=cypher-neru-6-helper(neupop_id="ilxtr:neuron-type-aacar-12"))

#+begin_src jupyter-python :session pys
pprint(blob)
#+end_src

# we need this block because jupyter-python doesn't include the epilogue when being called by another block wew
# #+header: :epilogue "\nimport pprint\nreturn pprint.pformat(ed)"
# #+header: :epilogue "\nimport json\nb, ed, s, ex, o = main(neupop_id=\"ilxtr:neuron-type-keast-6\")\nreturn json.dumps(b, indent=1, sort_keys=False)"
# #+header: :var result=cypher-neru-3-helper(neupop_id="ilxtr:neuron-type-keast-6")
#+header: :results drawer
#+header: :var result=(or) remove=(and "sigh")
#+header: :epilogue "\nimport json\nb, ed, s, ex, o = main(blob_string=result)\nreturn json.dumps(b, indent=1, sort_keys=False)"
#+name: python-neru-helper
#+begin_src python :noweb yes :exports none
<<python-process-neru>>
#+end_src

** terminal regions
Find the ontology identifiers for terminal regions.

# run this to populate/update the kernel (sigh)
#+call: python-process-neru(result=cypher-neru-6-helper(neupop_id="ilxtr:neuron-type-aacar-5"))

#+header: :var remove=(or) result=cypher-neru-6-helper(neupop_id="ilxtr:neuron-type-keast-6")
#+name: neru-find-terminal-regions
#+begin_src jupyter-python :session pys :exports both :var neupop_id="" :noweb yes
<<python-process-neru>>
from pprint import pprint

blob, *_ = main(blob_string=result)
bindex = {n['id']:n for n in blob['nodes']}

# find terminal regions
axon_terminal_regions = find_terminal_regions(blob, axon)
dend_terminal_regions = find_terminal_regions(blob, dend)
#pprint(axon_terminal_regions)
#pprint(dend_terminal_regions)

axon_terminal_rl = find_terminal_region_layers(blob, axon, bindex)
dend_terminal_rl = find_terminal_region_layers(blob, dend, bindex)
pprint([(r['id'], (l if l is None else l['id'])) for r, l in axon_terminal_rl])
pprint([(r['id'], (l if l is None else l['id'])) for r, l in dend_terminal_rl])
#+end_src

** connectivity pairs

This code produces the basic connectivity of a neuron population.
The result is a list of pairs, the pairs are the subject and object
of the triple =subject (or apinatomy:next apinatomy:next*) object=.
Thus they represent the full connectivity of the population.

The contents of each element of the pair tuples with three elements.
1. The id for the neuronal process involved in the connection.
2. The ontology id for the layer in which the neuronal process is
   located. This may be None if no layer is specified in the model.
3. The ontology id for the region in which the neuronal process is located.

In the example presented here for keast neuron 5 the final entries all
have the same start and end location because they are the soma, and
the roots of the axon and the dendrite.

#+name: py-conn-pairs
#+begin_src jupyter-python :session pys
intIn = 'apinatomy:internalIn'

def isLayer(blob, s):
    return ematch(blob, (lambda e, m: sub(e, m) and pred(e, layerIn)), s)

def reclr(blob, start_link):
    # recurse up the hierarchy until fasIn endIn intIn terminates
    #print('starting reclr')
    collect = []
    layer = []
    col = True
    def select_ext(e, m, collect=collect):
        nonlocal col
        nonlocal layer
        if sub(e, m):
            if pred(e, 'apinatomy:cloneOf'):  # should be zapped during simplify
                return ematch(blob, select_ext, obj(e))
            if pred(e, onts) or pred(e, iot) or pred(e, iots):
                #print('select_ext', e)
                external = obj(e)
                #print('se', col, layer)
                if col:
                    if layer:
                        if len(layer) > 1:
                            l, *layer = layer
                        else:
                            l = layer.pop()
                    else:
                        l = None
                    r = [b for b in blob['nodes'] if b['id'] == external][0]['id']  # if this is empty we are in big trouble
                    collect.append((l, r))
                else:
                    l = [b for b in blob['nodes'] if b['id'] == external][0]['id']
                    layer.append(l)
                #print(layer)
                return external

    def select(e, m):
        nonlocal col
        if sub(e, m):
            #print(e)
            if pred(e, layerIn) or pred(e, fasIn) or pred(e, endIn) or pred(e, intIn):
                #print('select', e)
                col = not isLayer(blob, obj(e))
                #print('s', col)
                ematch(blob, select_ext, obj(e))
                ematch(blob, select, obj(e))

    ematch(blob, select, start_link)

    return collect

def lay_reg(blob, start):
    direct = [obj(t) for t in
              ematch(blob, (lambda e, m: sub(e, m)
                            and (pred(e, intIn) or  # XXX why isn't this getting pulled in correctly for somas? answer: missing somas in starts? surely not, ARGH a lyph internalIn a layer
                                 pred(e, endIn) or
                                 pred(e, fasIn))),
                     start)]

    layers = [obj(t) for d in direct for t in
              ematch(blob, (lambda e, m: sub(e, m)
                            and isLayer(blob, m)
                            and (pred(e, iot) or
                                 pred(e, iots) or
                                 pred(e, onts))),
                     d)]

    lregs = []
    if layers:
        ldir = [obj(t) for d in direct for t in
                ematch(blob, (lambda e, m: sub(e, m)
                              and pred(e, layerIn)),
                       d)]

        lregs = [obj(t) for d in ldir for t in
                 ematch(blob, (lambda e, m: sub(e, m)
                               and not isLayer(blob, m)
                               and (pred(e, iot) or
                                    pred(e, iots) or  # XXX ie seems to have no hits now
                                    pred(e, onts))),
                        d)]

    regions = [obj(t) for d in direct for t in
               ematch(blob, (lambda e, m: sub(e, m)
                             and not isLayer(blob, m)
                             and (pred(e, iot) or
                                  pred(e, iots) or
                                  pred(e, onts))),
                      d)]

    #if start == 'kblad:snl16':
        #print(start, direct, layers, regions)

    # FIXME TODO nested regions and layers
    lrs = reclr(blob, start)  # FIXME doesn't pair regions and layers
    # FIXME SIGH SIGH SIGH start can be either part of a next >_<
    #print(lrs)

    assert not (lregs and regions), (lregs, regions)  # not both
    regions = lregs if lregs else regions
    #out = start, layers[0] if layers else None, regions[0] if regions else None, tuple([l for l in lrs])
    out = start, tuple(lrs)
    if out:
      return out

def render_blob(blob):
    starts = [obj(e) for e in blob['edges'] if pred(e, 'apinatomy:lyphs')]

    nxt = 'apinatomy:next'
    nxts = 'apinatomy:next*'

    nexts = [(sub(t), obj(t)) for start in starts for t in
             ematch(blob, (lambda e, m: pred(e, nxt) or pred(e, nxts)), None)]

    #pprint(nexts)

    connected_pairs = sorted(set([tuple([lay_reg(blob, e) for e in p]) for p in nexts]))
    #pprint(connected_pairs)
    return connected_pairs
#+end_src

#+call: python-process-neru(result=cypher-neru-6-helper(neupop_id="ilxtr:neuron-type-keast-1"))

#+begin_src jupyter-python :session pys
blob, *_ = main(blob_string=result)
render_blob(blob)
#+end_src

** neru
#+name: py-neupop-helper
#+begin_src jupyter-python :noweb yes :session pys :var remove=(and "sigh") :results none
from pprint import pprint
from pyontutils.scigraph import Cypher, Vocabulary
from pyontutils.scigraph_codegen import moduleDirect
from pyontutils.config import auth
scigraphd = moduleDirect(auth.get('scigraph-api'), 'scigraphd')
<<python-process-neru>>
<<py-conn-pairs>>
sgv = Vocabulary()
sgd = scigraphd.Dynamic(cache=True, verbose=True, do_error=True)
sgc = scigraphd.Cypher(cache=True, verbose=True)
q_neru_6 = """
<<cypher-neru-6>>
"""
def neupop_blob(neupop_id):
    # XXX requires a patch to Cypher and CypherBase to support arbitrary kwargs
    blob = sgc.execute(query=q_neru_6, limit=None, neupop_id=neupop_id, output='application/json')
    return blob

def sigh():
    blobs = [neupop_blob(f"ilxtr:neuron-type-keast-{npid}") for npid in range(1, 21)]
    debs = [main(blob=blob)[0] for blob in blobs]
    simples = [render_blob(deb) for deb in debs]
    #[pprint(simple) for simple in simples[:1]]

def fbid(id):
    if id:
        result = sgv.findById(id)
        if result['labels']:
            return result['labels'][0]

def fbids(ids):
    return [(id, fbid(id)) for id in ids]

def rendlab(pairs):
    for pair in pairs:
        yield tuple((start, *(tuple(fbids(hp)) for hp in housings)) for start, housings in pair)
    #for (*tstart, sid, sasdf), (*tend, eid, easdf) in pairs:
        #yield ((*tstart, sid, fbid(sid), fbids(sasdf)) if sid else (*tstart, sid, fbids(sasdf)),
               #(*tend, eid, fbid(eid), fbids(easdf)) if eid else (*tend, eid, fbids(easdf)))
#+end_src

#+begin_src jupyter-python :session pys
from copy import deepcopy

blobs = [neupop_blob(f"ilxtr:neuron-type-keast-{npid}") for npid in range(1, 21)]

debs = [main(blob=deepcopy(blob))[0] for blob in blobs]

simples = [render_blob(deb) for deb in debs]

sigh = [s for n, si in enumerate(simples)  for s in ((f'--------- {n + 1}','---------'), *sorted(rendlab(si)))]

#+end_src

#+begin_src jupyter-python :session pys
#sigh
#pprint(blobs[0])
#pprint(debs[0])
#pprint(simples[0])
#sigh[:200]
sigh
#+end_src

** asdf
#+begin_src sparql
select distinct ?s ?p where {
  ?s ?p ilxtr:NeuronCUT . }
#+end_src

#+begin_src cypher :limit 10
// MATCH path = (a)-[t]-(:Class) RETURN a
//MATCH path = ()-[:apinatomy:conveyingLyph]-() RETURN path
// MATCH path = ()-[:TEMPRAW:protocolInvolvesInputInstance]-() RETURN path
#+end_src

** heatmaps
*** TODO get the numbers along the axis, so n populations
*** TODO color code by process type
*** TODO costimulation by location
*** neupop by region
# FIXME if missing :session breaks on evil =o=
#+name: py-nbr-1
#+begin_src jupyter-python :session pys :noweb yes :results none :var remove=(and "sigh")
<<py-neupop-helper>>
from collections import defaultdict
q_cnap = """
<<cy-neru-all-pop>>
"""

def get_label(region):
    return id_label[region]

def get_labels(regions):
    return [get_label(region) for region in regions]

d = sgc.execute(query=q_cnap, limit=99999, output='application/json')
ids = [n['id'] for n in d['nodes']]
blobs = [neupop_blob(id) for id in ids]
id_label = {n['id']:n['lbl'] if 'lbl' in n and n['lbl'] else n['id'] for blob in blobs for n in blob['nodes']}
#+end_src

#+name: py-nbr-2
#+begin_src jupyter-python :session pys
_ext_all = sorted(set([obj(e) for b in blobs for e in b['edges'] if pred(e, 'apinatomy:ontologyTerms')]))
ext_pop = {i:set([obj(e) for e in blob['edges'] if pred(e, 'apinatomy:ontologyTerms')]) for i, blob in zip(ids, blobs)}

# TODO popuation, region, process type, terminal
pop_by_reg_ads_trm = []

pop_by_reg = defaultdict(list)
for i, regs in ext_pop.items():
    for reg in regs:
        pop_by_reg[reg].append(i)

def key_allc(region):
    count = 0
    for i, id in enumerate(ids):
        if region in ext_pop[id]:
            count += 1
    return count

_ext_allc = sorted(_ext_all, key=key_allc)

def key_pop(pop):
    return -len(ext_pop[pop])

ids_ord = sorted(ids, key=key_pop)

def key_all(region):
    first = None
    count = 0
    regs = []
    iths = []
    for i, id in enumerate(ids_ord):
        if region in ext_pop[id]:
            regs.append(region)
            iths.append(i)
            #count += 1  # wow =+ 1 no error wat
            #if first is None:
            #first = i

    return iths[0], *[-i for i in iths[1:]]

ext_all = sorted(_ext_all, key=key_all)
n_region = len(ext_all)
n_pop = len(ext_pop)
header = [['neupop', *ext_all]]  # TODO -> label
xl = get_labels(header[0][1:])
rows = header + [[id, *[region in ext_pop[id] for region in ext_all]] for id in ids_ord]

def jac(a, b):
    anb, aub = zip(*[(ar and br, ar or br) for ar, br in zip(a, b)])
    sanb, saub = sum(anb) , sum(aub)
    if not sanb and not saub:
        return sanb  # 0
    elif not sanb:
        return saub
    elif not saub:
        return sanb
    else:
        return sanb / saub

def sumjac(rows, norm=False):
    # actually want distance from .5 ?
    mat = jac_mat(rows, switch=True)
    #pprint(mat)
    return [(sum(r[:-1]) * sum(rows[r[-1] + 1][1:]) if norm else 1, r[-1]) for i, r in enumerate(mat)]

def sumjac_norm(rows):
    return sumjac(rows, norm=True)

def jac_mat(rows, switch=False, reverse=False):
    asdf = []
    for i, (_, *rowa) in enumerate(rows[1:]):
        jacr = []
        for j, (_, *rowb) in enumerate(rows[1:]):
            jci = jac(rowa, rowb)
            if switch:
                jacr.append(jci)
            else:
                jacr.append((jci, j))
        if switch:
            jacr.append(i)
            asdf.append(jacr)
        else:
            minimized = [i for j, i in sorted(jacr)][1:]  # 1: to skip self
            minimized.append(i)
            asdf.append(minimized)

    return asdf

def jmc(rows):
    return jac_mat(rows, switch=True)

def jmcr(rows):  # XXX broken
    return jac_mat(rows, switch=True, reverse=True)

def jac_sigh(rows):
    sigh = jmc(rows)
    return [(np.std(s[:-1]), np.mean(s[:-1]), s[-1]) for s in sigh]

def jac_sigh(rows):
    sigh = jmc(rows)
    #return [(np.std(s[:-1]), np.mean(s[:-1]), s[-1]) for s in sigh]
    return [(*[((x - .5) ** 2) ** .5 for x in s[:-1]], s[-1]) for s in sigh]

def resort(rows, sortf, reverse=False):
    asdf_ord = sorted(sortf(rows), reverse=not reverse) # don't think too hard about it
    ind_rank = {j:i for i, (*_, j) in enumerate(asdf_ord)}

    def srt(row):
        ind = rows.index(row)
        if ind == 0:
            return 0
        else:
            return ind_rank[ind - 1]

    return sorted(rows, key=srt)

def sumthm(rows):
    return [[-sum(row[1:]), i] for i, row in enumerate(rows[1:])]

def sumth(rows):
    return [[sum(row[1:]), i] for i, row in enumerate(rows[1:])]

def blank(rows):
    return [[*row[1:], i] for i, row in enumerate(rows[1:])]

def sumoth(rows):
    crank = [sum(col) for col in list(zip(*rows[1:]))[1:]]
    return [[*
             #[crank[j] if c and crank[j] == max([crank[x] for x, v in enumerate(row[1:]) if v]) else 0
             [crank[j] if c else 0
             for j, c in enumerate(row[1:])], i] for i, row in enumerate(rows[1:])]

def sumothm(rows):
    crank = [sum(col) for col in list(zip(*rows[1:]))[1:]]
    return [[*
             #[crank[j] if c and crank[j] == max([crank[x] for x, v in enumerate(row[1:]) if v]) else 0
             [-crank[j] if c else 0
             for j, c in enumerate(row[1:])], i] for i, row in enumerate(rows[1:])]

def maxp(rows):
    crank = [sum(col) for col in list(zip(*rows[1:]))[1:]]
    return [[
             sorted([crank[j] if c else 0 for j, c in enumerate(row[1:])])[0],
             ,*[-_ for _ in sorted([crank[j] if c else 0 for j, c in enumerate(row[1:])])[1:]]
        , i]
            for i, row in enumerate(rows[1:])]

def jacb(rows):
    a = [row[1:] for row in rows[1:]]
    aT = list(zip(*a))
    # FIXME symmetry
    rd = [[jac(r1, r2) for r2 in a] for r1 in a]
    cd = [[jac(c1, c2) for c2 in aT] for c1 in aT]
    #pprint(rd)
    #pprint(cd)
    # output is a 4d matrix, from rc -> to rc
    dist = np.zeros((len(rd), len(cd), len(rd), len(cd)))
    for i, row_dists in enumerate(rd):
        for k, row_dist in enumerate(row_dists):
            for j, col_dists in enumerate(cd):
                for l, col_dist in enumerate(col_dists):
                    dist[i, j, k, l] = (row_dist ** 2 + col_dist ** 2) ** .5
    #pprint(dist)
    return dist

rows = resort(rows, sumth)
rows = list(zip(*resort(list(zip(*rows)), blank, reverse=True)))
rows = resort(rows, jmc)

if False:
    import seaborn
    seaborn.set(rc={'figure.figsize':(50, 50)})
    #seaborn.set(rc={'figure.figsize':(5, 5)})
    axes = seaborn.heatmap(
        [r[1:] for r in rows[1:]],
        xticklabels=xl,
        yticklabels=[r[0] for r in rows[1:]],
        cbar=False,
        square=True,
    )
    # axes.get_figure().savefig('heatmap-pop-reg-test.svg')
#+end_src

#+begin_src jupyter-python :session pys
import csv
csv_path = 'heatmap-pop-reg.csv'
with open(csv_path, 'wt') as f:
    csv.writer(f).writerows(rows)
#+end_src

**** generate npo representation of apinatomy population location phenotypes
undifferentiated NPO representation that matches the heatmap above

run the blocks in the section above first
#+begin_src jupyter-python :session pys
#len(blobs)
#pprint(id_label)
#pprint(blobs[0])
sorted([[a, *sorted(b)] for a, b in ext_pop.items()])
#+end_src

#+name: py-nbr-3
#+begin_src jupyter-python :session pys
from neurondm import Neuron, Phenotype, EntailedPhenotype, Config
from pyontutils.namespaces import ilxtr, owl, skos, rdfs
config = Config('apinatomy-locations')
neurons = [Neuron(Phenotype(id), *(EntailedPhenotype(loc, ilxtr.hasLocationPhenotype) for loc in sorted(locs)), id_=id) for id, locs in ext_pop.items()]
config.write()
to_mod = [(s, skos.altLabel, o) for s, p, o in config._written_graph if p == rdfs.label]
to_remove = [t for t in config._written_graph if t[1] in (owl.equivalentClass, ilxtr.genLabel, ilxtr.localLabel, ilxtr.simpleLabel, ilxtr.simpleLocalLabel, rdfs.label, skos.prefLabel)]
[config._written_graph.remove(t) for t in to_remove]
[config._written_graph.add(t) for t in to_mod]
config._written_graph.write()
config.write_python()
#+end_src

#+begin_src jupyter-python :noweb yes :session pys :var remove=(and "sigh")
<<py-nbr-1>>
<<py-nbr-2>>
<<py-nbr-3>>
#+end_src

*** region by region
* SciGraph cypher queries
These can be tested against the execute endpoint as well (if the
SciGraph server is running the patch to align the behavior).
** neru
*** neru-7
Switch over to =apinat:inheritedOntologyTerms=.

#+name: cypher-neru-7-helper
#+begin_src cypher :noweb yes :results drawer :limit 99999999 :exports none
<<cypher-neru-7>>
#+end_src

#+header: :limit 999999 :file ./images/neru-7-keast-6.graphml :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-6"
#+name: cypher-neru-7
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK

, (neugrp)
-[:apinatomy:links]->(link)
-[c:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[d:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph

WITH neugrp, link, lyph, a, c, d
OPTIONAL MATCH layer_ext = (lyph)
<-[d*1]-(layer)-[:apinatomy:cloneOf*0..1]->()-[:apinatomy:inheritedOntologyTerms]->()

WITH neugrp, link, lyph, a, c, d, layer_ext
OPTIONAL MATCH more = (lyph)
-[:apinatomy:layerIn|apinatomy:endsIn|apinatomy:fasciculatesIn|apinatomy:internalIn|apinatomy:cloneOf*1..]->()
-[:apinatomy:ontologyTerms|apinatomy:inheritedOntologyTerms]->()

WITH neugrp, link, lyph, a, c, d, layer_ext, more // there is a difference here because the previous match does not require lyphs to have external ids
MATCH (lyph)
-[e:apinatomy:ontologyTerms]->(region)

, p2 = (link)
-[:apinatomy:conveyingLyph]->(cl)
-[:apinatomy:topology]->()

, (cl)
-[x:apinatomy:inheritedOntologyTerms*0..1]->()

// use apinatomy:next to extract ordering information
, (link)
-[f:apinatomy:next|apinatomy:nextChainStartLevels*0..]->()
-[g:apinatomy:target*0..1]->()
-[h:apinatomy:rootOf*0..1]->()
-[i:apinatomy:levels*0..1]->()
<-[:apinatomy:links]-(neugrp)

// publications
WITH neugrp, a, c, d, e, f, g,h,i, p2, x, layer_ext, more
OPTIONAL MATCH path = (neugrp)
-[:apinatomy:references]->(pub)
-[:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Reference"}) // cannot be curied, dynamic endpoints will not expand it

RETURN a, null as b, c, d, e, f, g,h,i, path, p2, x, layer_ext, more

UNION

// this part usually only returns the soma housing lyph
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
-[b:apinatomy:lyphs]->(lyph)
-[c:apinatomy:internalIn]->()
-[d:apinatomy:ontologyTerms*0..1]->(region)
, p2 = (lyph)
-[:apinatomy:conveys]->(soma_link)
-[:apinatomy:source|apinatomy:target]->(soma_node)
-[:apinatomy:sourceOf]->(chain_link)
, (chain_link)
-[:apinatomy:levelIn]->(chain)
, (soma_node)
-[:apinatomy:rootOf]->(chain)

WITH lyph, a, b, c, d, p2
OPTIONAL MATCH more = (lyph)
-[:apinatomy:layerIn|apinatomy:endsIn|apinatomy:fasciculatesIn|apinatomy:internalIn|apinatomy:cloneOf*1..]->()
-[:apinatomy:ontologyTerms|apinatomy:inheritedOntologyTerms]->()

RETURN a, b, c, d, null AS e, null AS f, null AS g, null AS h, null AS i, null AS path, p2, null as x, null as layer_ext, more
#+end_src

*** neru-6
The only difference between 5 and 6 is that 6 also traverses apinat:nextChainStartLevels
# #+header: :var neupop_id="ilxtr:neuron-type-aacar-12"
#+name: cypher-neru-6-helper
#+begin_src cypher :noweb yes :results drawer :limit 99999999 :exports none
<<cypher-neru-6>>
#+end_src

#+header: :limit 999999 :file ./images/neru-6-aacar-12.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-aacar-12"
#+name: cypher-neru-6
#+begin_src cypher :noweb yes
<<cypher-neru-5/6-common-1>>
-[f:apinatomy:next|apinatomy:nextChainStartLevels*0..]->()
<<cypher-neru-5/6-common-2>>
#+end_src

#+RESULTS: cypher-neru-6
[[file:./images/neru-6-aacar-12.svg]]

*** neru-5
**** query
# #+header: :var neupop_id="ilxtr:neuron-type-keast-6"
#+name: cypher-neru-5-helper
#+begin_src cypher :noweb yes :results drawer :limit 99999999 :exports none
<<cypher-neru-5>>
#+end_src

# #+header: :results drawer :limit 99999999 :exports none
#+header: :limit 999999 :file ./images/neru-5-keast-6.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-6"
#+name: cypher-neru-5
#+begin_src cypher :noweb yes
<<cypher-neru-5/6-common-1>>
-[f:apinatomy:next*0..]->()
<<cypher-neru-5/6-common-2>>
#+end_src

#+RESULTS: cypher-neru-5
[[file:./images/neru-5-keast-6.svg]]

#+name: cypher-neru-5/6-common-1
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK

, (neugrp)
-[:apinatomy:links]->(link)
-[c:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[d:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph

WITH neugrp, link, lyph, a, c, d
OPTIONAL MATCH layer_ext = (lyph)
<-[d*1]-(layer)-[:apinatomy:cloneOf*0..1]->()-[:apinatomy:inheritedExternal]->()

WITH neugrp, link, lyph, a, c, d, layer_ext
OPTIONAL MATCH more = (lyph)
-[:apinatomy:layerIn|apinatomy:endsIn|apinatomy:fasciculatesIn|apinatomy:internalIn|apinatomy:cloneOf*1..]->()
-[:apinatomy:ontologyTerms|apinatomy:inheritedExternal]->()

WITH neugrp, link, lyph, a, c, d, layer_ext, more // there is a difference here because the previous match does not require lyphs to have external ids
MATCH (lyph)
-[e:apinatomy:ontologyTerms]->(region)

, p2 = (link)
-[:apinatomy:conveyingLyph]->(cl)
-[:apinatomy:topology]->()

, (cl)
-[x:apinatomy:inheritedExternal*0..1]->()

// use apinatomy:next to extract ordering information
, (link)
#+end_src

#+name: cypher-neru-5/6-common-2
#+begin_src cypher
-[g:apinatomy:target*0..1]->()
-[h:apinatomy:rootOf*0..1]->()
-[i:apinatomy:levels*0..1]->()
<-[:apinatomy:links]-(neugrp)

// publications
WITH neugrp, a, c, d, e, f, g,h,i, p2, x, layer_ext, more
OPTIONAL MATCH path = (neugrp)
-[:apinatomy:references]->(pub)
-[:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Reference"}) // cannot be curied, dynamic endpoints will not expand it

RETURN a, null as b, c, d, e, f, g,h,i, path, p2, x, layer_ext, more

UNION

// this part usually only returns the soma housing lyph
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
-[b:apinatomy:lyphs]->(lyph)
-[c:apinatomy:internalIn]->()
-[d:apinatomy:ontologyTerms*0..1]->(region)
, p2 = (lyph)
-[:apinatomy:conveys]->(soma_link)
-[:apinatomy:source|apinatomy:target]->(soma_node)
-[:apinatomy:sourceOf]->(chain_link)
, (chain_link)
-[:apinatomy:levelIn]->(chain)
, (soma_node)
-[:apinatomy:rootOf]->(chain)

WITH lyph, a, b, c, d, p2
OPTIONAL MATCH more = (lyph)
-[:apinatomy:layerIn|apinatomy:endsIn|apinatomy:fasciculatesIn|apinatomy:internalIn|apinatomy:cloneOf*1..]->()
-[:apinatomy:ontologyTerms|apinatomy:inheritedExternal]->()

RETURN a, b, c, d, null AS e, null AS f, null AS g, null AS h, null AS i, null AS path, p2, null as x, null as layer_ext, more
#+end_src

*** neru-4
:PROPERTIES:
:CUSTOM_ID:  neru-4
:END:
#+name: cypher-neru-4-helper
#+begin_src cypher :noweb yes :results drawer :limit 99999999 :exports none
<<cypher-neru-4>>
#+end_src

# #+header: :results drawer :limit 99999999 :exports none
#+header: :limit 999999 :file ./images/neru-4.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-5"
#+name: cypher-neru-4
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK

, (neugrp)
-[:apinatomy:links]->(link)
-[c:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[d:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph

WITH neugrp, link, lyph, a, c, d
OPTIONAL MATCH layer_ext = (lyph)
<-[d*1]-(layer)-[:apinatomy:cloneOf]->()-[:apinatomy:inheritedOntologyTerms]->()

WITH neugrp, link, lyph, a, c, d, layer_ext // there is a difference here because the previous match does not require lyphs to have external ids
MATCH (lyph)
-[e:apinatomy:ontologyTerms]->(region)

, p2 = (link)
-[:apinatomy:conveyingLyph]->(cl)
-[:apinatomy:topology]->()

, (cl)
-[x:apinatomy:inheritedOntologyTerms*0..1]->()

// use apinatomy:next to extract ordering information
, (link)
-[f:apinatomy:next*0..]->()
-[g:apinatomy:target*0..1]->()
-[h:apinatomy:rootOf*0..1]->()
-[i:apinatomy:levels*0..1]->()
<-[:apinatomy:links]-(neugrp)

// publications
WITH neugrp, a, c, d, e, f, g,h,i, p2, x, layer_ext
OPTIONAL MATCH path = (neugrp)
-[:apinatomy:references]->(pub)
-[:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Publication"}) // cannot be curied, dynamic endpoints will not expand it

RETURN a, null as b, c, d, e, f, g,h,i, path, p2, x, layer_ext

UNION

// this part usually only returns the soma housing lyph
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
-[b:apinatomy:lyphs]->(lyph)
-[c:apinatomy:internalIn]->()
-[d:apinatomy:ontologyTerms*0..1]->(region)
, p2 = (lyph)
-[:apinatomy:conveys]->(soma_link)
-[:apinatomy:source|apinatomy:target]->(soma_node)
-[:apinatomy:sourceOf]->(chain_link)
, (chain_link)
-[:apinatomy:levelIn]->(chain)
, (soma_node)
-[:apinatomy:rootOf]->(chain)

RETURN a, b, c, d, null AS e, null AS f, null AS g, null AS h, null AS i, null AS path, p2, null as x, null as layer_ext
#+end_src

[[file:./images/neru-4.svg]]

*** neru-3

#+name: cypher-neru-3-helper
#+begin_src cypher :noweb yes :results drawer :limit 99999999 :exports none
<<cypher-neru-3>>
#+end_src

# #+header: :results table :limit 1
#+header: :limit 999999 :file ./images/neru-3.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-5"
#+name: cypher-neru-3
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK

// publications
WITH neugrp, a
OPTIONAL MATCH path = (neugrp)
-[:apinatomy:references]->(pub)
-[:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Publication"}) // cannot be curied, dynamic endpoints will not expand it

WITH neugrp, a, path
MATCH (neugrp)
-[b:apinatomy:links]->(link)
-[c:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[d:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph

WITH neugrp, link, lyph, a, b, c, d, path // MATCH vs , not all things that match as lyphs have externals
MATCH (lyph)
-[e:apinatomy:ontologyTerms]->(region)

// use apinatomy:next to extract ordering information
WITH neugrp, link, a, b, c, d, e, path
MATCH p2 = (link)
-[:apinatomy:conveyingLyph]->(cl)
-[:apinatomy:topology]->()

WITH neugrp, link, a, b, c, d, e, path, p2, cl
MATCH (cl)
-[x:apinatomy:inheritedOntologyTerms*0..1]->()

WITH neugrp, link, a, b, c, d, e, path, p2, x
MATCH (link)
-[f:apinatomy:next*0..]->()
//-[f:apinatomy:next|apinatomy:nextChainStartLevels*0..]->()
// FIXME these should be collapsing into a single relationship
-[g:apinatomy:target*0..1]->()
-[h:apinatomy:rootOf*0..1]->()
-[i:apinatomy:levels*0..1]->()
<-[:apinatomy:links]-(neugrp)

RETURN a, b, c, d, e, f, g,h,i, path, p2, x

UNION

// this part usually only returns the soma housing lyph
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
-[b:apinatomy:lyphs]->(lyph)
-[c:apinatomy:internalIn]->()
-[d:apinatomy:ontologyTerms*0..1]->(region)
// this variant shows the dead end lyphs that correspond to the fasciculatesIn links above
//-[c:apinatomy:internalIn*0..1]->()
//-[d:apinatomy:ontologyTerms*0..1]->(region)
RETURN a, b, c, d, null AS e, null AS f, null AS g, null AS h, null AS i, null AS path, null as p2, null as x
#+end_src

[[file:./images/neru-3.svg]]

*** neru-2
# #+header: :results drawer :limit 1
#+header: :limit 999999 :file ./images/neru-2.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-6"
#+name: cypher-neru-2
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK

// publications
WITH neugrp, a
OPTIONAL MATCH path = (neugrp)
-[:apinatomy:references]->(pub)
-[:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Publication"}) // cannot be curied, dynamic endpoints will not expand it

WITH neugrp, a, path
MATCH (neugrp)
-[b:apinatomy:links]->(link)
-[c:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[d:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph

WITH neugrp, link, lyph, a, b, c, d, path
MATCH (lyph)
-[e:apinatomy:ontologyTerms]->(region)

// use apinatomy:next to extract ordering information
WITH neugrp, link, a, b, c, d, e, path
MATCH (link)
-[f:apinatomy:next*0..]->()
//-[f:apinatomy:next|apinatomy:nextChainStartLevels*0..]->()
// FIXME these should be collapsing into a single relationship
-[g:apinatomy:target*0..1]->()
-[h:apinatomy:rootOf*0..1]->()
-[i:apinatomy:levels*0..1]->()
<-[:apinatomy:links]-(neugrp)

RETURN a, b, c, d, e, f, g,h,i, path

UNION

// this part usually only returns the soma housing lyph
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
-[b:apinatomy:lyphs]->(lyph)
-[c:apinatomy:internalIn]->(e) // e is a hack to get columns to match
-[d:apinatomy:ontologyTerms*0..1]->(region)
// this variant shows the dead end lyphs that correspond to the fasciculatesIn links above
//-[c:apinatomy:internalIn*0..1]->(e)
//-[d:apinatomy:ontologyTerms*0..1]->(region)
RETURN a, b, c, d, null AS e, null AS f, null AS g, null AS h, null AS i, null AS path
#+end_src

#+RESULTS: cypher-neru-2
[[file:./images/neru-2.svg]]

# #+header: :limit 999 :results drawer
# #+header: :limit 9999 :results table
#+header: :limit 999999 :file ./images/neru-debug.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-6"
#+name: cypher-neru-debug
#+begin_src cypher
MATCH path = (neupop:Class{iri: $neupop_id})
-[:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"})
-[:apinatomy:links]->()
-[:apinatomy:next*0..]->()
// -[:apinatomy:nextChainStartLevels*0..]-()
//-[:apinatomy:prevChainEndLevels*0..]-()
-[:apinatomy:levelIn*0..]->()
RETURN path
#+end_src

#+RESULTS: cypher-neru-debug
[[file:./images/neru-debug.svg]]

*** neru-1
# #+header: :results drawer :limit 1
#+header: :limit 999999 :file ./images/neru-1.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-6"
#+name: cypher-neru-1
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK

// publications
WITH neugrp, a
OPTIONAL MATCH path = (neugrp)
-[:apinatomy:references]->(pub)
-[:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Publication"}) // cannot be curied, dynamic endpoints will not expand it

WITH neugrp, a, path
MATCH (neugrp)
-[b:apinatomy:links]->(link)
-[c:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[d:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph

WITH lyph, a, b, c, d, path
MATCH (lyph)
-[e:apinatomy:ontologyTerms]->(region)
RETURN a, b, c, d, e, path

UNION

// this part usually only returns the soma housing lyph
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
-[b:apinatomy:lyphs]->(lyph)
-[c:apinatomy:internalIn]->(e) // e is a hack to get columns to match
-[d:apinatomy:ontologyTerms*0..1]->(region)
// this variant shows the dead end lyphs that correspond to the fasciculatesIn links above
//-[c:apinatomy:internalIn*0..1]->(e)
//-[d:apinatomy:ontologyTerms*0..1]->(region)
return a, b, c, d, null AS e, null AS path
#+end_src

#+RESULTS: cypher-neru-1
[[file:./images/neru-1.svg]]

*** all populations
#+name: cy-neru-all-pop
#+header: :limit 9999
#+begin_src cypher
OPTIONAL MATCH (start:Ontology)
<-[:isDefinedBy]-(graph:NamedIndividual)
-[:type]->({iri: "https://apinatomy.org/uris/elements/Graph"})
, (start)
<-[:isDefinedBy]-(external:Class)
-[:subClassOf*]->(:Class {iri: "http://uri.interlex.org/tgbugs/uris/readable/NeuronEBM"})
return external
#+end_src

*** neru populations
#+name: desc-cypher-apinat-model-populations
List all neuron populations that appear in some ApiNATOMY model.

#+header: :limit 99999
#+name: cypher-apinat-model-populations
#+begin_src cypher
MATCH (start:Ontology)
<-[:isDefinedBy]-(external:Class)
-[:subClassOf*]->(:Class {iri: "http://uri.interlex.org/tgbugs/uris/readable/NeuronEBM"}) // FIXME
, (start:Ontology)
<-[:apinat:hasGraph]-(graph:Class {iri: ""})
RETURN external
#+end_src

*** neru model populations
Given an ApiNATOMY model id return the identifiers for the neuron
populations that are present in the model.
#+header: :var model_id="https://apinatomy.org/uris/models/keast-bladder" :limit 99
#+begin_src cypher
MATCH (start:Ontology {iri: $model_id})
<-[:isDefinedBy]-(external:Class)
-[:subClassOf*]->(:Class {iri: "http://uri.interlex.org/tgbugs/uris/readable/NeuronEBM"}) // FIXME
RETURN external
#+end_src

*** neru model populations and references
#+name: desc-cypher-apinat-model-pops-and-refs
Given an ApiNATOMY model id return the identifiers for the neuron
populations that are present in the model and the identifiers for
references that provide supporting evidence. Publications and
populations can be distingished by checking whether their meta type
field is NamedIndividual or Class.

# #+header: :results drawer
#+header: :var model_id="https://apinatomy.org/uris/models/keast-bladder" :limit 99 :results drawer
#+name: cypher-apinat-model-pops-and-refs
#+begin_src cypher
MATCH (start:Ontology {iri: $model_id})
<-[:isDefinedBy]-(external:Class)
-[:subClassOf*]->(:Class {iri: "http://uri.interlex.org/tgbugs/uris/readable/NeuronEBM"}) // FIXME
,
(external)
-[e:type]->()
RETURN e
UNION
OPTIONAL MATCH (start:Ontology {iri: $model_id})
<-[:isDefinedBy]-(graph:NamedIndividual)
-[:type]->({iri: "https://apinatomy.org/uris/elements/Graph"}) // elements don't have a superclass right now
,
(graph)
-[:apinatomy:references]->(pub)
-[e:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Reference"})
RETURN e
#+end_src

*** TODO neru regions projected to
#+begin_src cypher
#+end_src
*** TODO neru regions dendrited to
*** TODO neru regions with processes from
region contains some process part of some neuron population
#+begin_src cypher
MATCH (region)
-[:apinatomy:annotates]->(lyph)
<-[:apinatomy:lyphs]-
(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"})
RETURN region
#+end_src
*** neru projects to
*** neru neurites to
# FIXME this seems not quite right?
# TODO dot ignore directionality in layout for certain edges e.g. apinat:topology
# #+header: :results drawer :limit 1
#+header: :limit 999999 :file ./images/neru-projects.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-11"
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"})
-[:apinatomy:lyphs]->(end_lyph)
-[t:apinatomy:topology]->(top)
WHERE top.iri IN ["https://apinatomy.org/uris/readable/BAG", "https://apinatomy.org/uris/readable/BAG2"]
WITH neugrp, end_lyph, t
MATCH (type) // FIXME we need more information in the models
<-[x:apinatomy:inheritedOntologyTerms*0..1]-(end_lyph)
-[a:apinatomy:conveys]->(link)
-[b:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[c:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph
WITH neugrp, lyph, a,b,c,t,x
MATCH (lyph)
-[e:apinatomy:ontologyTerms]->(region)
RETURN e, a,b,c,t,x
#+end_src

*** neru population projects to
*** neru populations projecting to
Given an anatomical region return a list of neuron types that project to that region.

# FIXME TODO need to also be able to query layers i.e. rexed lamina vii
#+header: :limit 999999 :file ./images/neru-axons-bag.svg :results file :exports both
#+header: :var region_id="UBERON:0001989" :note vagus nerve
#+header: :var type_id="SAO:280355188" :note regional part of axon
#+name: cypher-neru-axons-bag
#+begin_src cypher :noweb yes
<<cypher-neru-terminal-type>>
#+end_src

*** neru populations with axons in
Given an anatomical region return a list of neuron types with axons that are located in that region.

# TODO compare with an NPO based query
#+header: :limit 999999 :file ./images/neru-axons.svg :results file :exports both
#+header: :var region_id="UBERON:0001759" :note vagus nerve
#+header: :var type_id="SAO:280355188" :note regional part of axon
#+name: cypher-neru-axons
#+begin_src cypher :noweb yes
<<cypher-neru-process-type>>
RETURN e, neupop, neugrp
#+end_src

#+RESULTS: cypher-neru-axons
[[file:./images/neru-axons.svg]]

*** neru populations dendriting to
Given an anatomical region return a list of neuron types that have dendrite terminals in that region.
#+header: :limit 999999 :file ./images/neru-dendrites-bag.svg :results file :exports both
#+header: :var region_id="UBERON:0001989" :note vagus nerve
#+header: :var type_id="SAO:420754792" :note regional part of dendrite
#+name: cypher-neru-dendrites-bag
#+begin_src cypher :noweb yes
<<cypher-neru-terminal-type>>
#+end_src

#+RESULTS: cypher-neru-dendrites-bag
[[file:./images/neru-dendrites-bag.svg]]

*** neru populations with dendrites in
Given an anatomical region return a list of neuron types with dendrites that are located in that region.

# TODO compare with an NPO based query
#+header: :limit 999999 :file ./images/neru-dendrites.svg :results file :exports both
#+header: :var region_id="UBERON:0001759" :note vagus nerve
#+header: :var type_id="SAO:420754792" :note regional part of dendrite
#+name: cypher-neru-dendrites
#+begin_src cypher :noweb yes
<<cypher-neru-process-type>>
RETURN e, neupop, neugrp
#+end_src

# #+call: does not work here because we can't override a bunch of the other settings

#+RESULTS: cypher-neru-dendrites
[[file:./images/neru-dendrites.svg]]

*** TODO neru populations with neurites in
*** neru populations with processes in
:PROPERTIES:
:CUSTOM_ID:  neru-populations-with-processes-in
:END:

Given an anatomical region return a list of neuron types with processes that are located in that region.

# NOTE have to include :Class otherwise neupop-[annoates*0..1]->neugrp
# will match neugrp which we do not want
# FIXME still not quite right because now we neupop is no longer optional
#+name: cypher-neru-processes-base
#+begin_src cypher :eval never
MATCH
(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"})
-[:apinatomy:links]->(link)
-[:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer)
-[:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:ontologyTerms*0..1]->(region:Class{iri: $region_id})
WITH neugrp
OPTIONAL MATCH (neupop:Class)
-[e:apinatomy:annotates*0..1]->(neugrp)
#+end_src

#+name: cypher-neru-processes-somas
#+begin_src cypher :eval never
// somas
MATCH
(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"})
-[:apinatomy:lyphs]->(lyph)
-[:apinatomy:internalIn]->()
-[:apinatomy:ontologyTerms*0..1]->(region:Class{iri: $region_id})
WITH neugrp
OPTIONAL MATCH (neupop:Class)
-[e:apinatomy:annotates*0..1]->(neugrp)
#+end_src

TODO Need to expand =region_id= using the Uberon partonomy to answer more generic queries.
# #+header: :var region_id="UBERON:0018683" :note splanchnic nerve
# #+header: :var region_id="UBERON:0035769" :note mesenteric ganglion
# #+header: :results drawer :limit 10
# #+header: :results table :limit 10
#+header: :limit 999999 :file ./images/neru-processes.svg :results file :exports both
#+header: :var region_id="UBERON:0001759" :note vagus nerve
#+name: cypher-neru-processes
#+begin_src cypher :noweb yes
// dendrites and axons
<<cypher-neru-processes-base>>
RETURN e, neupop, neugrp

UNION

<<cypher-neru-processes-somas>>
RETURN e, neupop, neugrp
#+end_src

#+RESULTS: cypher-neru-processes
[[file:./images/neru-processes.svg]]

#+call: cypher-neru-processes(region_id="UBERON:0005453") :results table
#+call: cypher-neru-processes(region_id="UBERON:0006490") :results table

#+name: cypher-neru-processes-pop-only
#+begin_src cypher :noweb yes
<<cypher-neru-processes-base>>
RETURN neupop

UNION

<<cypher-neru-processes-somas>>
RETURN neupop
#+end_src

#+call: cypher-neru-processes-pop-only(region_id="UBERON:0005453") :results table
#+call: cypher-neru-processes-pop-only(region_id="UBERON:0006490") :results table

Helper blocks for more specialized queries.

Filter by process =type_id=, where =type_id= could be the ontology
identifier for axon or dendrite.  The =type_id= has to match the
conventions used in ApiNATOMY models.
#+caption: process type helper
#+name: cypher-neru-process-type
#+begin_src cypher :noweb yes :eval never
<<cypher-neru-processes-base>>
WITH e, neupop, neugrp, link
MATCH (link)
-[:apinatomy:conveyingLyph]->(cl)
-[:apinatomy:inheritedOntologyTerms]->(:Class {iri: $type_id})
#+end_src

Filter processes based on their topology. For example, this is used to
select processes with bag topology that correspond to axon and
dendrite terminals.
#+caption: terminal type helper
#+name: cypher-neru-terminal-type
#+begin_src cypher :noweb yes :eval never
<<cypher-neru-process-type>>
WITH e, neupop, neugrp, cl
MATCH (cl)
-[:apinatomy:topology]-(top)
WHERE top.iri IN ["https://apinatomy.org/uris/readable/BAG", "https://apinatomy.org/uris/readable/BAG2"]
RETURN e, neupop, neugrp
#+end_src
*** TODO neru populations by phenotype
*** TODO neru phenotype by population
** existing
*** sparc
**** organParts
#+name: desc-organ-parts
Get the parts list for an organ including nerves and blood vessels
#+header: :var id=(or)
#+name: cyres-organ-parts
#+begin_src cypher
// depth of 6 captures everything, 5 is too shallow, 40 is WAY too deep
MATCH path = (start:Class{iri: "${id}"})
<-[:subClassOf|ilxtr:includedForSPARCUnder|fma:regional_part_of|fma:constitutional_part_of|fma:related_part_of*0..6]-(part)
<-[:fma:arterial_supply_of|fma:nerve_supply_of|fma:venous_drainage_of|fma:continuous_with*0..1]-(sup)
<-[:subClassOf|fma:constitutional_part_of|fma:branch|fma:tributary|fma:branch_of*0..1]-(a_bit_more)
RETURN path
UNION  // for this query UNION seems to be MUCH faster than using WITH
MATCH path = (start:Class{iri: "${id}"})
// this one does not need to be inverted ? except for the INCOMING flag
<-[:fma:arterial_supply_of|fma:venous_drainage_of]-(vessel)
<-[:fma:branch|fma:tributary]-(more_vessel)
<-[:fma:branch|fma:tributary|fma:regional_part]-(even_more_vessel)
RETURN path
#+end_src

# heart test query
#+call: cyres-organ-parts(id="FMA:7088", limit=1) :results table

**** parcellationArtifacts
#+name: cyres-parcellation-artifacts
#+begin_src cypher
MATCH path = (artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species)
WHERE artifact.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
#+end_src
**** parcellationArtifacts/{species-id}
#+header: :var species-id=(or)
#+name: cyres-parcellation-artifacts-species-id
#+begin_src cypher
MATCH path = (artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species:Class{iri: "${species-id}"})
WHERE artifact.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
#+end_src

#+call: cyres-parcellation-artifacts-species-id(species-id="NCBITaxon:9606")

**** artifactRoots/{artifact-id}
#+header: :var artifact-id=(or)
#+name: cyres-artifact-roots-artifact-id
#+begin_src cypher
MATCH path = (root)
-[:ilxtr:isDefinedBy]->(a)<-[:subClassOf*0..2]
-(artifact:Class{iri: "${artifact-id}"})
RETURN path
#+end_src
**** artifactLabels/{artifact-id}
#+header: :var artifact-id=(or)
#+name: cyres-artifact-labels-artifact-id
#+begin_src cypher
MATCH path = (label)
-[:subClassOf]->(root)
-[:ilxtr:isDefinedBy]->(a)<-[:subClassOf*0..2]
-(artifact:Class{iri: "${artifact-id}"})
WHERE label.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
#+end_src
**** rootLabels/{root-id}
#+header: :var root-id=(or)
#+name: cyres-root-labels-root-id
#+begin_src cypher
MATCH (label)-[:subClassOf]->(root:Class{iri: "${root-id}"})
, path = (label)-[relation*0..1]-(maybe)
WHERE NONE (r in relation WHERE type(r) IN ["isDefinedBy", "subClassOf", "filler"])
      AND NOT (label.iri =~ ".*_:.*") AND NOT (maybe.iri =~ ".*_:.*")
      AND label.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
#+end_src
**** parcellationRoots
#+name: cyres-parcellation-roots
#+begin_src cypher
MATCH path = (artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species)
WHERE artifact.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
UNION
MATCH path = (root)
-[:ilxtr:isDefinedBy]->(artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species)
RETURN path
#+end_src
**** parcellationRoots/{species-id}
#+header: :var species-id=(or)
#+name: cyres-parcellation-roots-species-id
#+begin_src cypher
MATCH path = (artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species:Class{iri: "${species-id}"})
WHERE artifact.iri <> "http://www.w3.org/2002/07/owl#Nothing"
return path
UNION
MATCH path = (root)
-[:ilxtr:isDefinedBy]->(artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species:Class{iri: "${species-id}"})
RETURN path
#+end_src

#+call: cyres-parcellation-roots-species-id(species-id="NCBITaxon:9606")

**** parcellationRoots/{species-id}/{region-id}
#+header: :var species-id=(or)
#+header: :var region-id=(or)
#+name: cyres-parcellation-roots-species-id-region-id
#+begin_src cypher
MATCH
(region:Class{iri: "${region-id}"})
<-[:ilxtr:isDefinedInRegion]-
(parent)
-[:ilxtr:isDefinedInTaxon]->
(species:Class{iri: "${species-id}"})
WITH parent
MATCH path = (artifact)
-[:subClassOf*0..2]->(parent)
WHERE artifact.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
UNION
MATCH
(region:Class{iri: "${region-id}"})
<-[:ilxtr:isDefinedInRegion]-
(parent)
-[:ilxtr:isDefinedInTaxon]->
(species:Class{iri: "${species-id}"})
WITH parent
MATCH path = (root)
-[:ilxtr:isDefinedBy]->(artifact)
-[:subClassOf*0..2]->(parent)
RETURN path
#+end_src
**** parcellationRootsFMA/{species-id}/{fma-id}
#+header: :var species-id=(or)
#+header: :var region-id=(or)
#+name: cyres-parcellation-roots-fma-species-id-region-id
#+begin_src cypher
MATCH (fma:Class{iri: "${fma-id}"})
WITH "FMA:" + toString(fma.`http://purl.org/sig/ont/fma/FMAID`) AS curie
MATCH (region)
-[:subClassOf*]->(start:Class{iri: "http://purl.obolibrary.org/obo/UBERON_0001062"})
WHERE any(x IN
          region.`http://www.geneontology.org/formats/oboInOwl#hasDbXref`
          WHERE x =~ curie)
WITH region
MATCH
(region)
<-[:ilxtr:isDefinedInRegion]-
(parent)
-[:ilxtr:isDefinedInTaxon]->
(species:Class{iri: "${species-id}"})
WITH parent
MATCH path = (artifact)
-[:subClassOf*0..2]->(parent)
WHERE artifact.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
UNION
MATCH (fma:Class{iri: "${fma-id}"})
WITH "FMA:" + toString(fma.`http://purl.org/sig/ont/fma/FMAID`) AS curie
MATCH (region)
-[:subClassOf*]->(start:Class{iri: "http://purl.obolibrary.org/obo/UBERON_0001062"})
WHERE any(x IN
          region.`http://www.geneontology.org/formats/oboInOwl#hasDbXref`
          WHERE x =~ curie)
WITH region
MATCH
(region)
<-[:ilxtr:isDefinedInRegion]-
(parent)
-[:ilxtr:isDefinedInTaxon]->
(species:Class{iri: "${species-id}"})
WITH parent
MATCH path = (root)
-[:ilxtr:isDefinedBy]->(artifact)
-[:subClassOf*0..2]->(parent)
RETURN path
#+end_src
**** parcellationGraph
#+name: cyres-parcellation-graph
#+begin_src cypher
MATCH path = (artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species)
WHERE artifact.iri <> "http://www.w3.org/2002/07/owl#Nothing"
return path
UNION
MATCH path = (maybe)
-[relation*0..1]-(label)
-[:subClassOf]->(root)
-[:ilxtr:isDefinedBy]->(artifact)
-[:subClassOf*0..2]->(parent)
-[:ilxtr:isDefinedInTaxon]->(species)
WHERE NONE (r in relation WHERE type(r) IN ["isDefinedBy", "subClassOf", "filler"])
      AND NOT (label.iri =~ ".*_:.*") AND NOT (maybe.iri =~ ".*_:.*")
      AND label.iri <> "http://www.w3.org/2002/07/owl#Nothing"
RETURN path
#+end_src
**** organList
# TODO generate from the canonical organ list above
#+name: cyres-organ-list
#+begin_src cypher
MATCH (n)
WHERE n.iri IN [
        "http://purl.org/sig/ont/fma/fma7195",  // lung
        "http://purl.org/sig/ont/fma/fma7088",  // heart
        "http://purl.org/sig/ont/fma/fma7197",  // liver
        "http://purl.org/sig/ont/fma/fma7198",  // pancreas
        "http://purl.org/sig/ont/fma/fma7203",  // kidney
        "http://purl.org/sig/ont/fma/fma7148",  // stomach
        "http://purl.org/sig/ont/fma/fma7196",  // spleen
        "http://purl.org/sig/ont/fma/fma14543", // colon
        "http://purl.org/sig/ont/fma/fma7201",  // large intestine
        "http://purl.org/sig/ont/fma/fma7200",  // small intestine
        "http://purl.org/sig/ont/fma/fma7199",  // intestine
        "http://purl.org/sig/ont/fma/fma15900", // urinary bladder
        "http://purl.org/sig/ont/fma/fma45659", // lower urinary tract
        "http://purl.org/sig/ont/fma/fma7157",  // nervous system
        "http://purl.org/sig/ont/fma/fma9903",  // peripheral nervous system
        "http://purl.org/sig/ont/fma/fma9906",  // sympathetic nervous system
        "http://purl.org/sig/ont/fma/fma7647",  // spinal cord
        "http://purl.org/sig/ont/fma/fma50801", // brain
        "http://purl.org/sig/ont/fma/fma5889",  // autonomic ganglion
        "http://purl.org/sig/ont/fma/fma19667", // urethra
        "http://purl.org/sig/ont/fma/fma7131"   // esophagus
        ]
RETURN n
#+end_src
**** speciesList
#+name: cyres-species-list
#+begin_src cypher
MATCH (n)
WHERE n.iri IN [
        "http://purl.obolibrary.org/obo/NCBITaxon_9378",   // Suncus murinus
        "http://purl.obolibrary.org/obo/NCBITaxon_9606",   // Homo sapiens
        "http://purl.obolibrary.org/obo/NCBITaxon_9685",   // Felis catus
        "http://purl.obolibrary.org/obo/NCBITaxon_9823",   // Sus scrofa
        "http://purl.obolibrary.org/obo/NCBITaxon_10090",  // Mus musculus
        "http://purl.obolibrary.org/obo/NCBITaxon_10116"   // Rattus norvegicus
        ]
RETURN n
#+end_src
**** sparc community terms
#+name: cyres-sparc-community-terms
#+begin_src cypher :limit 10
MATCH
(t)-[:isDefinedBy]->(graph:Ontology{iri: "http://uri.interlex.org/sparc/ontologies/community-terms"})
RETURN t
#+end_src
**** anatomyPhenotypes
#+name: desc-anatomy-phenotypes
Return phenotypes that inhere in some part of an anatomical entity.
#+header: :var id="UBERON:0001556"
#+name: cyres-anatomy-phenotypes
#+begin_src cypher :limit 100
MATCH (anatomical_entity:Class{iri: $id})
<-[part_of:subClassOf|BFO:0000050|RO:0002433*0..40]-(subclass_part_ctmo)
<-[inheres_in:RO:0000052|RO:0002314!]-(intersection_of_quality_and_anatomy) // inheres in (anon intersection of)
<-[has_process_part:BFO:0000051]-() // has part (process)
<-[:subClassOf*0..]-(phenotype) // phenotype and all subclasses

WHERE
// filter out partOf/hasPart cases that induce circularity
ALL(e IN part_of WHERE NOT EXISTS(e.owlType) OR e.owlType = "subClassOf" OR e.owlType = "operand")
RETURN phenotype
#+end_src
*** shortestSimple
# FIXME TODO make sure this actuall works as expected, I think it will?
#+header: :var start_id=(or)
#+header: :var end_id=(or)
#+header: :var max_depth=(or)
#+header: :var relationship=(or)
#+name: cyres-shortest-simple
#+begin_src cypher
MATCH (start:Class{iri: '${start_id}'})
WITH start
MATCH (end:Class{iri: '${end_id}'})
WITH start, end
MATCH path = shortestPath((start)-[:${relationship}*..${max_depth}]->(end))
RETURN path
#+end_src
*** neurons
**** connectivity
#+header: :var start_id=(or)
#+name: cyres-neurons-connectivity
#+begin_src cypher
MATCH (blank)-
[entrytype:ilxtr:hasSomaLocatedIn|ilxtr:hasAxonLocatedIn|ilxtr:hasDendriteLocatedIn|ilxtr:hasPresynapticTerminalsIn]
->(location:Class{iri: '${start_id}'})
WITH location, entrytype, blank
MATCH (phenotype)<-[predicate]-(blank)<-[:equivalentClass]-(neuron)
WHERE NOT (phenotype.iri =~ ".*_:.*")
// RETURN phenotype, (phenotype)-[predicate]-(neuron) as e
// WITH location, predicate, phenotype, neuron
RETURN location, entrytype, neuron, predicate, phenotype
#+end_src
**** connectedRegions
#+header: :var start_id=(or)
#+name: cyres-neurons-connected-regions
#+begin_src cypher
MATCH (blank)-
[entrytype:ilxtr:hasSomaLocatedIn|ilxtr:hasAxonLocatedIn|ilxtr:hasDendriteLocatedIn|ilxtr:hasPresynapticTerminalsIn]
->(location:Class{iri: '${start_id}'})
WITH entrytype, blank
MATCH (phenotype)<-[:${target_predicate}]-(blank)
// WHERE NOT (phenotype.iri =~ ".*_:.*")
RETURN phenotype
#+end_src
*** apinat
**** bundles/{start-id}
#+header: :var start-id=(or)
#+name: cyres-apinat-bundles-start-id
#+begin_src cypher
MATCH path1 = (start:Class{iri: "${start-id}"})
-[:apinatomy:annotates]->(start_housing)
-[:apinatomy:subtypes*0..1]->()
-[:apinatomy:clones*0..1]->(layer_or_end)
-[:apinatomy:layers*0..1]->()
-[:apinatomy:bundles]->(linkStart)
-[:apinatomy:prevChainEndLevels|apinatomy:prev|apinatomy:source*1..]->(link)
-[:apinatomy:targetOf|apinatomy:sourceOf]->(linkSoma)  // axon or dendrite root
-[:apinatomy:conveyingLyph]->()
-[:apinatomy:supertype*0..1]->(soma:NamedIndividual)
-[:apinatomy:ontologyTerms]->(c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
WITH path1, link
OPTIONAL MATCH path2 = (link)
-[:apinatomy:fasciculatesIn|apinatomy:endsIn]->(layer_or_end)
-[:apinatomy:layerIn*0..1]->(end)
-[:apinatomy:ontologyTerms]->(external)
RETURN path1, path2
#+end_src
**** old-bundles/{start-id}
#+header: :var start-id=(or)
#+name: cyres-apinat-old-bundles-start-id
#+begin_src cypher
MATCH path1 = (start:Class{iri: '${start-id}'})
-[:apinatomy:annotates]->(start_housing)
-[:apinatomy:bundlesChains]->(chain)
-[:apinatomy:root]->(root)
-[:apinatomy:internalIn]->(layer_or_end)  # this hits a cycle back to start_housing
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:ontologyTerms]->(layer_or_end_external)
WITH path1, root, layer_or_end AS layer
OPTIONAL MATCH path2 = (layer)
-[:apinatomy:layerIn]->(end_housing)
-[:apinatomy:ontologyTerms]->(end_housing_external)
WITH path1, path2, root
MATCH path3 = (root) // in the layer case this hits an additional lyph
<-[:apinatomy:target|apinatomy:source]-(link)
<-[:apinatomy:conveys]-(soma)
<-[:apinatomy:annotates]-(soma_NLX)
RETURN path1, path2, path3
#+end_src
**** somas
#+name: cyres-apinat-somas
#+begin_src cypher
MATCH (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)
RETURN soma
#+end_src

#+call: cyres-apinat-somas() :limit 9999

**** housing-lyphs
#+name: cyres-apinat-housing-lyphs
#+begin_src cypher
MATCH path = (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)  // soma lyph
-[:apinatomy:conveys]->(somaLink)                // link connecting soma to axon and dendrite
-[:apinatomy:target|apinatomy:source]->(root)    // axon or dendrite root
-[:apinatomy:controlNodes|apinatomy:rootOf*1..2]->(chain)                    // axon or dendrite tree
-[:apinatomy:housingLyphs]->(housing)            // list of lyphs housing the trees
-[:apinatomy:ontologyTerms*0..1]->(external)          // external ids for the housing lyphs
WHERE soma.`https://apinatomy.org/uris/readable/generated` IS NULL
RETURN path
#+end_src
**** housing-lyphs/{start-id}
#+header: :var start-id=(or)
#+name: cyres-apinat-housing-lyphs-start-id
#+begin_src cypher
MATCH path1 = (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)  // soma lyph
-[:apinatomy:conveys]->(somaLink)                // link connecting soma to axon and dendrite
-[:apinatomy:target|apinatomy:source]->(root)    // axon or dendrite root
-[:apinatomy:internalIn]->(layer_or_end)
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:ontologyTerms]->(layer_or_end_external:Class{iri: '${start-id}'})
WHERE soma.`https://apinatomy.org/uris/readable/generated` IS NULL
WITH path1, root
MATCH path2 = (root)
-[:apinatomy:controlNodes|apinatomy:rootOf*1..2]->(chain)                    // axon or dendrite tree
-[:apinatomy:housingLyphs]->(housing)            // list of lyphs housing the trees
-[:apinatomy:ontologyTerms*0..1]->(external)          // external ids for the housing lyphs
RETURN path1, path2

UNION

MATCH path1 = (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)  // soma lyph
-[:apinatomy:conveys]->(somaLink)                // link connecting soma to axon and dendrite
-[:apinatomy:target|apinatomy:source]->(root)    // axon or dendrite root
-[:apinatomy:internalIn]->(layer)
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:layerIn]->(end_housing)
-[:apinatomy:ontologyTerms]->(end_housing_external:Class{iri: '${start-id}'})
WHERE soma.`https://apinatomy.org/uris/readable/generated` IS NULL
WITH path1, root
MATCH path2 = (root)
-[:apinatomy:rootOf]->(chain)                    // axon or dendrite tree
-[:apinatomy:housingLyphs]->(housing)            // list of lyphs housing the trees
-[:apinatomy:ontologyTerms*0..1]->(external)          // external ids for the housing lyphs
RETURN path1, path2
#+end_src
**** soma-processes
# #+header: :results table :limit 1
#+header: :file ./images/soma-processes.svg :results file :limit 99999
#+name: cyres-apinat-soma-processes
#+begin_src cypher
MATCH path1 = (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)    // soma lyph
-[:apinatomy:conveys]->(linkSoma)                  // link connecting soma to axon and dendrite
-[:apinatomy:target|apinatomy:source]->(nodeRoot)  // axon or dendrite root
-[:apinatomy:sourceOf|apinatomy:nextChainStartLevels|apinatomy:next*1..]->(link)  // sourceOf is first and only once
-[:apinatomy:fasciculatesIn|apinatomy:endsIn]->(layer_or_end)
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:ontologyTerms]->(external)
WHERE soma.`https://apinatomy.org/uris/readable/generated` IS NULL
WITH path1, nodeRoot, layer_or_end AS layer
OPTIONAL MATCH path2 = (layer)  // if we were in a layer, get the containing lyph as well
-[:apinatomy:layerIn]->(end_housing)
-[:apinatomy:ontologyTerms]->(end_housing_external)
WITH path1, path2, nodeRoot
MATCH path3 = (nodeRoot)        // extract chain for axon vs dendrite
-[:apinatomy:rootOf]->(chain)
RETURN path1, path2, path3
#+end_src

#+RESULTS: cyres-apinat-soma-processes
[[file:./images/soma-processes.svg]]

A sparql version of the query (work in progress).
#+name: sparql-soma-processes
#+begin_src sparql :var limit="99"
SELECT DISTINCT
?c
?soma
?linkSoma
?nodeRoot
?link
?layer_or_end
?cloned
?supertype
?external_st
?external_loe
?external_end_housing
WHERE
{
  VALUES ?c { NLX:154731 }
  ?c rdf:type owl:Class .
  ?c apinatomy:annotates ?soma .
  ?soma rdf:type owl:NamedIndividual .
  ?soma apinatomy:conveys ?linkSoma .
  ?linkSoma apinatomy:target | apinatomy:source ?nodeRoot .
  ?nodeRoot apinatomy:rootOf ?chain .
  ?nodeRoot apinatomy:sourceOf | apinatomy:nextChainStartLevels | apinatomy:next+ ?link .
  ?link apinatomy:fasciculatesIn | apinatomy:endsIn ?layer_or_end .
  optional { ?layer_or_end apinatomy:cloneOf* ?cloned . } # optional
  optional { ?cloned apinatomy:supertype* ?supertype . } # optional

  ?supertype apinatomy:ontologyTerms ?external_st .
  ?layer_or_end apinatomy:ontologyTerms ?external_loe .

  optional { ?layer_or_end apinatomy:layerIn ?end_housing . } # optional
  optional { ?end_housing apinatomy:ontologyTerms ?external_end_housing . } # optional
}
limit ?limit
#+end_src
**** soma-processes/{start-id}
#+header: :file ./images/soma-processes-id.svg :results file :limit 99999
#+header: :var start-id=(or)
#+name: cyres-apinat-soma-processes-start-id
#+begin_src cypher
MATCH path1 = (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)    // soma lyph
-[:apinatomy:conveys]->(linkSoma)                  // link connecting soma to axon and dendrite
-[:apinatomy:target|apinatomy:source]->(nodeRoot)  // axon or dendrite root
-[:apinatomy:internalIn]->(layer_or_end)
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:layerIn*0..1]->(layerSoma)  // don't need to see both layer and housing for soma
-[:apinatomy:ontologyTerms]->(externalEndSoma:Class{iri: '${start-id}'})
WHERE soma.`https://apinatomy.org/uris/readable/generated` IS NULL
WITH path1, nodeRoot
MATCH path3 = (chain)
<-[:apinatomy:rootOf]-(nodeRoot)
-[:apinatomy:sourceOf|apinatomy:nextChainStartLevels|apinatomy:next*1..]->(link)
-[:apinatomy:fasciculatesIn|apinatomy:endsIn]->(layer_or_end)
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:ontologyTerms]->(external)
WITH path1, path3, nodeRoot, layer_or_end AS layer
OPTIONAL MATCH path2 = (layer)  // if we were in a layer, get the containing lyph as well
-[:apinatomy:layerIn]->(end_housing)
-[:apinatomy:ontologyTerms]->(end_housing_external)
RETURN path1, path2, path3
#+end_src

#+call: cyres-apinat-soma-processes-start-id(start-id="UBERON:0006448")

**** weird-soma-processes/{process-id}
#+header: :var process-id=(or)
#+name: cyres-apinat-weird-soma-processes-process-id
#+begin_src cypher
MATCH path1 = (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)    // soma lyph
-[:apinatomy:conveys]->(linkSoma)                  // link connecting soma to axon and dendrite
-[:apinatomy:target|apinatomy:source]->(nodeRoot)  // axon or dendrite root
-[:apinatomy:sourceOf|apinatomy:nextChainStartLevels|apinatomy:next*1..]->(link)  // sourceOf is first and only once
-[:apinatomy:fasciculatesIn|apinatomy:endsIn]->(layer_or_end)
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:ontologyTerms]->(external:Class{iri: '${process-id}'})
WHERE soma.`https://apinatomy.org/uris/readable/generated` IS NULL
WITH path1, nodeRoot, layer_or_end AS layer
OPTIONAL MATCH path2 = (layer)  // if we were in a layer, get the containing lyph as well
-[:apinatomy:layerIn]->(end_housing)
-[:apinatomy:ontologyTerms]->(end_housing_external)
WITH path1, path2, nodeRoot
MATCH path3 = (nodeRoot)        // extract chain for axon vs dendrite
-[:apinatomy:rootOf]->(chain)
RETURN path1, path2, path3

UNION

MATCH path1 = (c:Class{iri: "http://uri.neuinfo.org/nif/nifstd/nlx_154731"})
-[:apinatomy:annotates]->(soma:NamedIndividual)    // soma lyph
-[:apinatomy:conveys]->(linkSoma)                  // link connecting soma to axon and dendrite
-[:apinatomy:target|apinatomy:source]->(nodeRoot)  // axon or dendrite root
-[:apinatomy:sourceOf|apinatomy:nextChainStartLevels|apinatomy:next*1..]->(link)  // sourceOf is first and only once
-[:apinatomy:fasciculatesIn|apinatomy:endsIn]->(layer_or_end)
-[:apinatomy:cloneOf*0..1]->()
-[:apinatomy:supertype*0..1]->()
-[:apinatomy:ontologyTerms]->(external)
WHERE soma.`https://apinatomy.org/uris/readable/generated` IS NULL
WITH path1, nodeRoot, layer_or_end AS layer
MATCH path2 = (layer)  // if we were in a layer, get the containing lyph as well
-[:apinatomy:layerIn]->(end_housing)
-[:apinatomy:ontologyTerms]->(end_housing_external:Class{iri: '${process-id}'})
WITH path1, path2, nodeRoot
MATCH path3 = (nodeRoot)        // extract chain for axon vs dendrite
-[:apinatomy:rootOf]->(chain)
RETURN path1, path2, path3
#+end_src
** yaml file
Example template.
#+begin_src yaml
cypherResources:
  - path:
    operations: &name-ops
      - summary: <<nonl(desc-name())>>
        parameters:
          - name: some-id
            description: does something
            paramType:
    query: &name |
      <<block-name>>
#+end_src

#+name: cypher-resources
#+begin_src yaml :noweb yes :tangle ../resources/scigraph/cypher-resources.tangled.yaml
cypherResources:
  - path: /dynamic/prod/sparc/anatomyPhenotypes/{id}
    operations: &anatomyPhenotypes-ops
      - summary: <<nonl(desc-anatomy-phenotypes())>>
        parameters:
          - name: id
            description: uberon id of the anatomical entity to search from
            paramType: path
    query: &anatomyPhenotypes |
      <<cyres-anatomy-phenotypes>>

  - path: /dynamic/test/sparc/anatomyPhenotypes/{id}
    operations: *anatomyPhenotypes-ops
    query: *anatomyPhenotypes

  - path: /dynamic/prod/sparc/organParts/{id}
    operations: &organParts-ops
      - summary: <<nonl(desc-organ-parts())>>
        parameters:
         - name: id
           description: ontology id of the organ
           paramType: path
    query: &organParts |
      <<cyres-organ-parts>>

  - path: /dynamic/test/sparc/organParts/{id}
    operations: *organParts-ops
    query: *organParts

  - path: /dynamic/prod/sparc/parcellationArtifacts
    operations:
      - summary: Get the graph of all parcellation artifacts for all species
    query: |
      <<cyres-parcellation-artifacts>>

  - path: /dynamic/prod/sparc/parcellationArtifacts/{species-id}
    operations:
      - summary: Get the graph of all parcellation artifacts for a single species
        parameters:
         - name: species-id
           description: ontology id of the species
           paramType: path
    query: |
      <<cyres-parcellation-artifacts-species-id>>

  - path: /dynamic/prod/sparc/artifactRoots/{artifact-id}
    operations:
      - summary: Get the graph of all parcellation label roots for a single artifact WARNING this can return no results
        parameters:
         - name: artifact-id
           description: ontology id of the parcellation artifact
           paramType: path
    query: |
      <<cyres-artifact-roots-artifact-id>>

  - path: /dynamic/prod/sparc/artifactLabels/{artifact-id}
    operations:
      - summary: Get the graph of all parcellation labels for a single artifact WARNING this can return no results
        parameters:
         - name: artifact-id
           description: ontology id of the parcellation artifact
           paramType: path
    query: |
      <<cyres-artifact-labels-artifact-id>>

  - path: /dynamic/prod/sparc/rootLabels/{root-id}
    operations: &rootLabes-ops
      - summary: Get the list of all parcellation labels for a single label root
        parameters:
         - name: root-id
           description: ontology id of the parcellation label root
           paramType: path
    query: &rootLabels |
      <<cyres-root-labels-root-id>>

  - path: /dynamic/test/sparc/rootLabels/{root-id}
    operations: *rootLabes-ops
    query: *rootLabels

  - path: /dynamic/prod/sparc/parcellationRoots
    operations:
      - summary: Get the graph of all parcellation label roots for all species
    query: |
      <<cyres-parcellation-roots>>

  - path: /dynamic/prod/sparc/parcellationRoots/{species-id}
    operations:
      - summary: Get the graph of all parcellation label roots for a single species
        parameters:
         - name: species-id
           description: ontology id of the species
           paramType: path
    query: |
      <<cyres-parcellation-roots-species-id>>

  - path: /dynamic/prod/sparc/parcellationRoots/{species-id}/{region-id}
    operations:
      - summary: Get the graph of all parcellation label roots for a single species and anatomical region
        parameters:
         - name: species-id
           description: ontology id of the species
           paramType: path
         - name: region-id
           description: ontology id of the anatomical region
           paramType: path
    query: |
      <<cyres-parcellation-roots-species-id-region-id>>

  - path: /dynamic/prod/sparc/parcellationRootsFMA/{species-id}/{fma-id}
    operations: &parcellationRootsFMA-ops
      - summary: Get the graph of all parcellation label roots for a single species and anatomical region
        parameters:
         - name: species-id
           description: ontology id of the species
           paramType: path
         - name: fma-id
           description: ontology id of the anatomical region
           paramType: path
    query: &parcellationRootsFMA |
      <<cyres-parcellation-roots-fma-species-id-region-id>>

  - path: /dynamic/test/sparc/parcellationRootsFMA/{species-id}/{fma-id}
    operations: *parcellationRootsFMA-ops
    query: *parcellationRootsFMA

  - path: /dynamic/prod/sparc/parcellationGraph
    operations:
      - summary: Get the graph of all parcellation labels for all species
    query: |
      <<cyres-parcellation-graph>>

  - path: /dynamic/prod/sparc/organList
    operations: &organList-ops
      - summary: Get the list of all FMA organ identifiers relevant to SPARC
    query: &organList |
      <<cyres-organ-list>>

  - path: /dynamic/test/sparc/organList
    operations: *organList-ops
    query: *organList

  - path: /dynamic/prod/sparc/speciesList
    operations: &speciesList-ops
      - summary: Get the list of all NCBITaxon species identifiers relevant to SPARC
    query: &speciesList |
      <<cyres-species-list>>

  - path: /dynamic/test/sparc/speciesList
    operations: *speciesList-ops
    query: *speciesList

  - path: /dynamic/shortestSimple
    query: |
      <<cyres-shortest-simple>>
    operations:
      - summary: Get the shortest path between two IDs
        parameters:
          - name: start_id
            description: The starting node (ex UBERON:0005751)
            paramType: query
          - name: end_id
            description: The ending node (ex UBERON:0001255)
            paramType: query
          - name: max_depth
            description: the maximum depth to traverse
            paramType: query
          - name: relationship
            description: The property to traverse (ex subClassOf or subClassOf|partOf|isA)
            paramType: query
            required: false

  - path: /dynamic/neurons/connectivity
    query: |
      <<cyres-neurons-connectivity>>
    operations:
      - summary: Get connected anatomical regions by neuron type
        parameters:
          - name: start_id
            description: The starting location (eg UBERON:0001759)
            paramType: query

  - path: /dynamic/neurons/connectedRegions
    query: |
      <<cyres-neurons-connected-regions>>
    operations:
      - summary: Get connected anatomical regions by starting location and target relationship
        parameters:
          - name: start_id
            description: The starting location (eg UBERON:0001759)
            paramType: query
          - name: target_predicate
            description: The predicate for the type of connectivity (eg ilxtr:hasPresynapticTerminalsIn)
            paramType: query
            required: false

  - path: /dynamic/demos/apinat/bundles/{start-id}
    operations:
      - summary: Return the paths to somas from an anatomical region (aka connected-somas)
        parameters:
         - name: start-id
           description: ontology id of the starting point
           paramType: path
    query: |
      <<cyres-apinat-bundles-start-id>>

  - path: /dynamic/demos/apinat/old-bundles/{start-id}
    operations:
      - summary: Return the paths to somas from an anatomical region (aka connected-somas)
        parameters:
         - name: start-id
           description: ontology id of the starting point
           paramType: path
    query: |
      <<cyres-apinat-old-bundles-start-id>>

  - path: /dynamic/demos/apinat/somas
    operations:
      - summary: List all the somas for a given graph (TODO on the given graph)
    query: |
      <<cyres-apinat-somas>>

  - path: /dynamic/demos/apinat/housing-lyphs
    operations:
      - summary: List all the housing lyphs (neuronal processes) for all starting points.
    query: |
      <<cyres-apinat-housing-lyphs>>

  - path: /dynamic/demos/apinat/housing-lyphs/{start-id}
    operations:
      - summary: List all the housing lyphs for a starting point.
        parameters:
         - name: start-id
           description: ontology id of the starting point
           paramType: path
    query: |
      <<cyres-apinat-housing-lyphs-start-id>>

  - path: /dynamic/demos/apinat/soma-processes
    operations:
      - summary: List all the neuronal processes for all somas.
    query: |
      <<cyres-apinat-soma-processes>>

  - path: /dynamic/demos/apinat/soma-processes/{start-id}
    operations:
      - summary: List all the neuronal processes for somas located in start-id.
        parameters:
         - name: start-id
           description: ontology id of the starting point
           paramType: path
    query: |
      <<cyres-apinat-soma-processes-start-id>>

  - path: /dynamic/demos/apinat/weird-soma-processes/{process-id}
    operations:
      - summary: List all the neuronal processes for somas where some processes is in process-id.
        parameters:
         - name: process-id
           description: ontology id of the starting point
           paramType: path
    query: |
      <<cyres-apinat-weird-soma-processes-process-id>>

  - path: /dynamic/demos/apinat/neru-1/{neupop-id}
    operations: &neru-ops
      - summary: Return the housing regions and publications for neurulated groups.
        parameters:
         - name: neupop-id
           description: neuron population identifier
           paramType: path
    query: |
      <<cypher-neru-1>>

  - path: /dynamic/demos/apinat/neru-2/{neupop_id}
    operations: *neru-ops
    query: |
      <<cypher-neru-2>>

  - path: /dynamic/demos/apinat/neru-3/{neupop_id}
    operations: *neru-ops
    query: |
      <<cypher-neru-3>>

  - path: /dynamic/demos/apinat/neru-4/{neupop_id}
    operations: *neru-ops
    query: |
      <<cypher-neru-4>>

  - path: /dynamic/demos/apinat/neru-5/{neupop_id}
    operations: *neru-ops
    query: |
      <<cypher-neru-5>>

  - path: /dynamic/demos/apinat/neru-6/{neupop_id}
    operations: *neru-ops
    query: |
      <<cypher-neru-6>>

  - path: /dynamic/demos/apinat/neru-7/{neupop_id}
    operations: *neru-ops
    query: |
      <<cypher-neru-7>>

  - path: /dynamic/demos/apinat/modelList
    operations:
      - summary: <<nonl(desc-cypher-apinat-models())>>
    query: |
      <<cypher-apinat-models>>

  - path: /dynamic/demos/apinat/modelPopulationsReferences/{model_id}
    operations:
      - summary: <<nonl(desc-cypher-apinat-model-pops-and-refs())>>
        parameters:
         - name: model_id
           description: the identifier for an ApiNATOMY model
           paramType: path
    query: |
      <<cypher-apinat-model-pops-and-refs>>

#+end_src
* ApiNATOMY competency queries
** expected counts per model
Expected number of somas and neupops.

| model-id          | neupops | somas |
|-------------------+---------+-------|
| ard-arm-cardiac   |      12 |    41 |
| bolser-lewis      |      29 |    29 |
| bronchomotor      |       6 |    19 |
| keast-bladder     |      20 |    34 |
| sawg-distal-colon |      17 |    23 |
| sawg-stomach      |      14 |    14 |

*** somas
somas >= neupops

The number of somas for distinct populations in a given model.

*** populations
the number of neuron populations as defined by the neurulator that have an ontology identifier

*** neuron part per region per model
| model-id          | diag abbrev | external | somas | axons | axon term | dend | dend term |
|-------------------+-------------+----------+-------+-------+-----------+------+-----------|
| bolser-lewis      | SCG         |          |    17 |       |           |      |           |
| keast-bladder     |             |          |       |       |           |      |           |
| bronchomotor      |             |          |       |       |           |      |           |
| ard-arm-cardiac   |             |          |       |       |           |      |           |
| sawg-distal-colon |             |          |       |       |           |      |           |
| sawg-stomach      |             |          |       |       |           |      |           |

** expected locations
use NPO as a top down constraint on the locations of somas etc.
*** somas
could do this by population or generically
*** population process location
soma-processes
can also be checked using NPO but not needed for simple cases
| model-id     | neupop | diag abbrev | external |
|--------------+--------+-------------+----------|
| bolser-lewis | q      | PN          |          |
| bolser-lewis | q      | C4          |          |
| bolser-lewis | q      | PHRNIC      |          |
| bolser-lewis | q      | DIAPHRAGM   |          |
*** location process populations
bundles
NPO here as well
| model-id     | diag abbrev | external | neupop  |
|--------------+-------------+----------+---------|
| bolser-lewis | VAGUS       |          | 9 total |

** cross model
*** expect populations from models to have processes in common location
#+name: region-populations
#+begin_src sparql
select distinct
?external
(str(?el) as ?label)
#?p
?model
#?region
#?link
#?neru
# ?seed
(replace(str(?neru), "^.+-([A-Za-z0-9]+)$", "$1") as ?neuron_number)
#?dyn
?neupop
where {
  values ?external { UBERON:0006457 UBERON:0001894 UBERON:0005453 UBERON:0018683 }

  ?external a owl:Class ;
    apinatomy:annotates ?region ;
    rdfs:label ?el .

  ?region a elements:Lyph .

  ?model a elements:Graph ;
    apinatomy:lyphs ?region .

  ?link ( apinatomy:fasciculatesIn | apinatomy:endsIn | apinatomy:layerIn | apinatomy:internalIn )+ ?region .

  ?neru apinatomy:links ?link ;
    apinatomy:description ?dyn ;
    a elements:Group .
  filter contains(str(?dyn), "dynamic")
  optional { ?neru apinatomy:ontologyTerms ?neupop . }
  optional { ?seed apinatomy:seedIn ?neru . } # seedIn sort of works but misses cases where a dynamic group was created with a seed
}
order by str(?external) str(?model) xsd:integer(?neuron_number)
#+end_src

Expected.
# FIXME the hline breaks with uberon somehow or for some reason? was
# doing (- 8 14) and getting -6 somehow the width was too narrow and
# broke `org-table--align-field'
| external       | diag abbrev  | model-id          | neupop            |
|----------------+--------------+-------------------+-------------------|
| UBERON:0006457 | T1           | bolser-lewis      | 1 2 3 6 7         |
| UBERON:0006457 | T1           | keast-bladder     | 20                |
| UBERON:0001894 | DIENC        | bolser-lewis      | 1 26              |
| UBERON:0001894 | Diencephalon | keast-bladder     | 13 14 15 16 17 18 |
| UBERON:0005453 | IMG          | keast-bladder     | 3 6 7 11          |
| UBERON:0005453 | IMG          | sawg-distal-colon | B D F G H Q       |
| UBERON:0018683 | LumSplN      | keast-bladder     | 6 7 11            |
| UBERON:0018683 | LumSplN      | sawg-distal-colon | B                 |
*** vagus
OWL DL query

#+begin_src owldl
hasLocationPhenotype some ('part of' some 'vagus nerve')
hasLocationPhenotype some ('part of' some 'vagus nerve nucleus')
hasLocationPhenotype some ('part of' some 'vagus nerve' or 'part of' some 'vagus nerve nucleus')
#+end_src

#+begin_src cypher
// filter out things that project to the vagus nerve not that start in the vagus nerve or something like that
(neupop)-[:ilxtr:hasLocationPhenotype]->(:Class{iri: UBERON:0001759})
(neupop)-[:ilxtr:hasAxonPresynapticElementIn]->(axon_locations)
(neupop)-[:ilxtr:hasSensorySubcellularElementIn]->(dendrite_locations)
#+end_src

*** visualize all populations that have some process in the IMG
Images of nerus that have some process in the IMG, from =keast-bladder= and =sawg-distal-colon=.

** kg queries
#+call: cypher-neru-3(neupop_id="ilxtr:neuron-type-keast-5") :results table drawer

#+call: cypher-neru-3(neupop_id="ilxtr:neuron-type-keast-5") :file ./images/neru-3-ntk-5-wat.svg

#+call: cypher-neru-3(neupop_id="ilxtr:neuron-type-keast-11") :file ./images/neru-3-ntk-11.svg

#+call: neru-simplified(path="./images/neru-simplified-ntk-11.svg", json-string=python-neru-helper(result=cypher-neru-helper(neupop_id="ilxtr:neuron-type-keast-11")))

TODO point people to the query that returns a list of all neuron populations and all populations + papers per model
#+call: cypher-neru-3(neupop_id="ilxtr:neuron-type-keast-9") :file ./images/neru-3-ntk-9.svg

#+call: neru-simplified(path="./images/neru-simplified-ntk-9.svg", json-string=python-neru-helper(result=cypher-neru-helper(neupop_id="ilxtr:neuron-type-keast-9")))

#+call: cypher-neru-debug(neupop_id="ilxtr:neuron-type-keast-2") :file ./images/debug-2.svg

#+call: cypher-neru-3(neupop_id="ilxtr:neuron-type-keast-2") :file ./images/neru-3-ntk-2.svg

#+call: neru-simplified(path="./images/neru-simplified-ntk-2.svg", json-string=python-neru-helper(result=cypher-neru-3-helper(neupop_id="ilxtr:neuron-type-keast-2")))

5 is going to be the easiest
#+call: cypher-neru-debug(neupop_id="ilxtr:neuron-type-keast-5") :file ./images/debug-5.svg

#+call: cypher-neru-3(neupop_id="ilxtr:neuron-type-keast-5") :file ./images/neru-3-ntk-5.svg

#+call: neru-simplified(path="./images/neru-simplified-ntk-5.svg", json-string=python-neru-helper(result=cypher-neru-3-helper(neupop_id="ilxtr:neuron-type-keast-5")))

*** neuron type keast 5 projects to
#+begin_src cypher
RETURN region
#+end_src

Expected.
| external       | label           |
|----------------+-----------------|
| UBERON:0016508 | pelvic ganglion |

*** img processes query
Input
| UBERON:0005453 | inferior mesenteric ganglion |

Expect
| ilxtr:neuron-type-keast-3  |
| ilxtr:neuron-type-keast-6  |
| ilxtr:neuron-type-keast-7  |
| ilxtr:neuron-type-keast-11 |
| ilxtr:neuron-type-sdcol-b  |
| ilxtr:neuron-type-sdcol-d  |
| ilxtr:neuron-type-sdcol-f  |
| ilxtr:neuron-type-sdcol-g  |
| ilxtr:neuron-type-sdcol-h  |
| ilxtr:neuron-type-sdcol-q  |

#+call: cypher-neru-processes-pop-only(region_id="UBERON:0005453") :results table :note inferior mesenteric ganglion

* Simple blaze
simple sckan queries over blazegraph

** inserts
:PROPERTIES:
:header-args:sparql: :url http://localhost:9999/blazegraph/namespace/kb/sparql
:header-args: :eval never
:END:

#+begin_src sparql :noweb yes
insert { ?s ?np ?o }
<<simple-sckan-load-1-where>>
#+end_src

** queries
#+begin_src sparql
#select (count(*) as ?c) {
select distinct ?s ?o
where
{
  #?s nposimtest:hasSomaLocatedIn ?o
  ?s nposimtest:hasDendriteLocatedIn ?o
} order by ?s ?o
#}
#+end_src

#+begin_src sparql :noweb yes
PREFIX nposimtest: <http://uri.interlex.org/tgbugs/uris/readable/npo-simple-test-1/>
SELECT (COUNT(*) as ?count) {
  <<simple-sckan-load-1>>
}
#+end_src

#+name: simple-sckan-load-1
#+begin_src sparql :noweb yes
#PREFIX nposimtest: <http://uri.interlex.org/tgbugs/uris/readable/npo-simple-test-1/>
select
distinct ?s ?np ?o
<<simple-sckan-load-1-where>>
order by ?s ?np ?o
limit 5000
#+end_src

# TODO add expansion over equivalentClass

#+name: simple-sckan-load-1-where
#+begin_src sparql
where
{
  {
    values (?p ?np) {
      (ilxtr:hasAxonPresynapticElementIn        nposimtest:hasAxonPresynapticElementIn)
      (ilxtr:hasSomaLocatedIn                   nposimtest:hasSomaLocatedIn)
      #(ilxtr:hasSomaLocatedIn                   nposimtest:hasAxonLocatedIn) # add inferred axon locations (decided to exclude)
      (ilxtr:hasAxonLocatedIn                   nposimtest:hasAxonLocatedIn)
      (ilxtr:hasDendriteLocatedIn               nposimtest:hasDendriteLocatedIn)
      (ilxtr:hasAxonSensorySubcellularElementIn nposimtest:hasAxonSensorySubcellularElementIn)

      #(ilxtr:hasAxonPresynapticElementIn ilxtr:hasAxonTerminalLocation)
      #(ilxtr:hasSomaLocatedIn ilxtr:hasSomaLocation)
      #(ilxtr:hasAxonLocatedIn ilxtr:hasAxonLocation)
      #(ilxtr:hasDendriteLocatedIn ilxtr:hasDendriteLocation)
      #(ilxtr:hasAxonSensorySubcellularElementIn ilxtr:hasAxonSensoryLocation)
    }
    ?s owl:equivalentClass [
                             rdf:type owl:Class ;
                             owl:intersectionOf ?bn0
                           ] .
    ?bn0 rdf:rest*/rdf:first ilxtr:NeuronApinatSimple .
    ?bn0 rdf:rest*/rdf:first [
                               rdf:type owl:Restriction ;
                               owl:onProperty ?p ;
                               owl:someValuesFrom [
                                                    a owl:Restriction ;
                                                    owl:onProperty partOf: ;
                                                    owl:someValuesFrom ?o
                                                  ] ]

  }
  union
  {
    values (?p ?np) {
      (ilxtr:hasForwardConnectionPhenotype     nposimtest:hasForwardConnectionPhenotype)
      (ilxtr:hasFunctionalCircuitRolePhenotype nposimtest:hasFunctionalCircuitRolePhenotype)
    }
    ?s owl:equivalentClass [
                             rdf:type owl:Class ;
                             owl:intersectionOf ?bn0
                           ] .
    ?bn0 rdf:rest*/rdf:first ilxtr:NeuronApinatSimple .
    ?bn0 rdf:rest*/rdf:first [
                               rdf:type owl:Restriction ;
                               owl:onProperty ?p ;
                               owl:someValuesFrom ?o ]
  }}
#+end_src

* Sanity checks
Other queries run against various representations of the data.
** JSON
#+name: &jq-count-datasets
: jq -c '.datasets | length'

#+name: &jq-count-path-metadata
: jq -c '[paths | select(.[-1] == "path_metadata")] | length'

#+begin_src bash :noweb yes
function path-metadata-coverage () {
    local PATH_EXPORT_JSON=$1
    local COUNT_T=$(<<&jq-count-datasets()>> "${PATH_EXPORT_JSON}")
    local COUNT_PM=$(<<&jq-count-path-metadata()>> "${PATH_EXPORT_JSON}")
    awk -v t=${COUNT_T} -v pm=${COUNT_PM} 'BEGIN { print ( pm / t ) }'
}
#+end_src
* General queries
General metadata and housekeeping queries.
** List all loaded ontologies
#+begin_src sparql
SELECT ?s WHERE { ?s a owl:Ontology }
#+end_src

#+begin_src cypher :limit 9999
MATCH (o:Ontology) RETURN o
#+end_src
** predicates
#+name: predicates-all
#+begin_src sparql
select distinct ?p ?l where { ?s ?p ?o . optional { ?p rdfs:label ?l . } } order by ?p
#+end_src

#+name: predicates-version
#+begin_src sparql
select ?s ?p ?o where {
values ?p { prov:startedAtTime prov:wasGeneratedBy dc:date owl:versionInfo TEMP:TimestampExportStart }
?s ?p ?o . } order by desc(str(?o))
#+end_src
** versions
Useful version information is not present in all ontology files at the moment.
*** embedded load provenance record
:PROPERTIES:
:CUSTOM_ID: embedded-load-provenance-record
:END:

#+begin_src sparql
SELECT ?p ?o WHERE {
build:prov ?p ?o .
}
#+end_src

#+begin_src cypher :results drawer
MATCH
(p)-[i:build:id]-(),
(p)-[e]-()
RETURN i, e
#+end_src

*** curation-export.ttl
#+begin_src sparql
select ?s ?tes ?vi where {
  ?s TEMP:TimestampExportStart ?tes ;
     owl:versionInfo ?vi .
}
#+end_src

*** protcur.ttl
#+begin_src sparql
select distinct ?o where {
  <https://cassava.ucsd.edu/sparc/ontologies/protcur.ttl> owl:versionInfo ?o
}
#+end_src

*** apinat models
:PROPERTIES:
:CUSTOM_ID: apinat-models
:END:
From [[cypher-apinat-graphs]]
#+begin_src cypher :results drawer
MATCH ({iri: "https://apinatomy.org/uris/elements/Graph"})<-[:type]-(g) RETURN g
#+end_src

* Use cases
** Connectivity dashboard
*** organs
:PROPERTIES:
:CREATED:  [2021-08-06 Fri 13:10]
:END:
# FIXME looking for the ya' or ya" evil commands, I know I've used them before
# reval maybe? nope `url-unhex-string-at-point' `evil-inner-double-quote'
# "with escape \" yeah? "
# org doesn't have strings but evil works
#+begin_src elisp :results none :exports none
(defun org-babel-execute:ttl (body params)
  "Return the verbatim BODY of the ttl.

We can probably cook up an alternate version of this which would
expand into triples using arrays or something like that."
  body)
#+end_src

#+name: py-ttl-subjects
#+begin_src python :noweb yes :var input=(and (fboundp 'orgstrap--with-block) (orgstrap--with-block "ttl-organs" body)) :results code :wrap src ttl
from pyontutils.core import OntGraph

g = OntGraph().parse(data=input, format='ttl')

#return [[g.namespace_manager._qhrm(_)] for _ in sorted(set(g.subjects()))]
return ' '.join([g.namespace_manager._qhrm(_) for _ in sorted(set(g.subjects()))])
#+end_src

#+name: ttl-organs
#+begin_src ttl
@prefix l: <http://www.w3.org/2000/01/rdf-schema#label> .
@prefix e: <http://uri.interlex.org/tgbugs/uris/readable/hasExistingId> .
@prefix FMA: <http://purl.org/sig/ont/fma/fma> .
@prefix UBERON: <http://purl.obolibrary.org/obo/UBERON_> .

# tissue
UBERON:0001013 l: "adipose tissue"; e: FMA:20110 .

# regions
UBERON:0002298 l: "brainstem"; e: FMA:79876 .
UBERON:0001016 l: "nervous system"; e: FMA:7157 .
UBERON:0000010 l: "peripheral nervous system"; e: FMA:9903 .
UBERON:0000013 l: "sympathetic nervous system"; e: FMA:9906 .
UBERON:0002005 l: "enteric nervous system"; e: FMA:66070 .
# TODO cardiovascular system # e.g. for blood pressure
# TODO cardiopulmonary system

# organs
UBERON:0000955 l: "brain"; e: FMA:50801 .
UBERON:0001155 l: "colon"; e: FMA:14543 .
UBERON:0002110 l: "gallbladder"; e: FMA:7202 .
UBERON:0000948 l: "heart"; e: FMA:7088 .
UBERON:0000160 l: "intestine"; e: FMA:7199 .
UBERON:0002113 l: "kidney"; e: FMA:7203 .
UBERON:0000059 l: "large intestine"; e: FMA:7201 .
UBERON:0002107 l: "liver"; e: FMA:7197 .
UBERON:0001556 l: "lower urinary tract"; e: FMA:45659 .
UBERON:0002048 l: "lung"; e: FMA:68877 .
UBERON:0001630 l: "muscle organ"; e: FMA:5022 .
UBERON:0001264 l: "pancreas"; e: FMA:7198 .
UBERON:0002097 l: "skin of body"; e: FMA:7163 .
UBERON:0002108 l: "small intestine"; e: FMA:7200 .
UBERON:0002240 l: "spinal cord"; e: FMA:7647 .
UBERON:0002106 l: "spleen"; e: FMA:7196 .
UBERON:0000945 l: "stomach"; e: FMA:7148 .
UBERON:0001255 l: "urinary bladder"; e: FMA:15900 .

# granular
UBERON:0000057 l: "urethra"; e: FMA:19667 .
UBERON:0001043 l: "esophagus"; e: FMA:7131 .
#+end_src

*** nerves
#+name: ttl-nerves
#+begin_src ttl
@prefix l: <http://www.w3.org/2000/01/rdf-schema#label> .
@prefix e: <http://uri.interlex.org/tgbugs/uris/readable/hasExistingId> .
@prefix FMA: <http://purl.org/sig/ont/fma/fma> .
@prefix UBERON: <http://purl.obolibrary.org/obo/UBERON_> .

# XXX sciatic nerve is missing from this list

# not actually cranial
UBERON:0002019 l: "accessory XI nerve"; e: FMA:6720 . # missing from the larger list

# nerves missing from the larger list
UBERON:0001646 l: "abducens nerve"; e: FMA:50867 .
UBERON:0001650 l: "hypoglossal nerve"; e: FMA:50871 .
UBERON:0001579 l: "olfactory nerve"; e: FMA:46787 .
UBERON:0001644 l: "trochlear nerve"; e: FMA:50865 .
UBERON:0003723 l: "vestibulocochlear nerve"; e: FMA:50869 .

# the larger list
UBERON:0034728 l: "autonomic nerve" .
UBERON:0009009 l: "carotid sinus nerve" .
UBERON:0009675 l: "chorda tympani branch of facial nerve" .
UBERON:0019198 l: "dorsal nerve of clitoris" .
UBERON:0019197 l: "dorsal nerve of penis" .
UBERON:0010380 l: "enteric nerve" .
UBERON:0010406 l: "cholinergic enteric nerve" .
UBERON:0001647 l: "facial nerve"; e: FMA:50868 .
UBERON:0001649 l: "glossopharyngeal nerve"; e: FMA:50870 .
UBERON:0001643 l: "oculomotor nerve"; e: FMA:50864 .
UBERON:0002924 l: "terminal nerve" .
UBERON:0001645 l: "trigeminal nerve"; e: FMA:50866 .
UBERON:0001759 l: "vagus nerve"; e: FMA:5731 .
# TODO vagus nerve branches
UBERON:0018680 l: "greater splanchnic nerve" .
UBERON:0003438 l: "iris nerve" .
UBERON:0011096 l: "lacrimal nerve" .
UBERON:0035642 l: "laryngeal nerve" .
UBERON:0011766 l: "left recurrent laryngeal nerve" .
UBERON:0011767 l: "right recurrent laryngeal nerve" .
UBERON:0003716 l: "recurrent laryngeal nerve" .
UBERON:0011326 l: "superior laryngeal nerve" .
UBERON:3010764 l: "laryngeus ventralis" .
UBERON:0001964 l: "least splanchnic nerve" .
UBERON:0018681 l: "lesser splanchnic nerve" .
UBERON:0003721 l: "lingual nerve" .
UBERON:0022301 l: "long ciliary nerve" .
UBERON:0018683 l: "lumbar splanchnic nerve" .
UBERON:0000377 l: "maxillary nerve" .
UBERON:0036143 l: "meningeal branch of mandibular nerve" .
UBERON:0017641 l: "meningeal branch of spinal nerve" .
UBERON:0022300 l: "nasociliary nerve" .
UBERON:0008810 l: "nasopalatine nerve" .
UBERON:0035650 l: "nerve of clitoris" .
UBERON:0035649 l: "nerve of penis" .
UBERON:0015162 l: "superior branch of oculomotor nerve" .
UBERON:0018675 l: "pelvic splanchnic nerve" .
UBERON:0011391 l: "perineal nerve" .
UBERON:0001884 l: "phrenic nerve" .
UBERON:0034725 l: "pterygopalatine nerve" .
UBERON:0011390 l: "pudendal nerve" .
UBERON:0009625 l: "sacral nerve" .
UBERON:0018684 l: "sacral splanchnic nerve" .
UBERON:0022302 l: "short ciliary nerve" .
UBERON:0003715 l: "splanchnic nerve" .
UBERON:0018679 l: "thoracic splanchnic nerve" .
UBERON:0001323 l: "tibial nerve" .
UBERON:0036216 l: "tympanic nerve" .
UBERON:0018412 l: "vidian nerve" .

# cervical nerve
UBERON:0000962 l: "nerve of cervical vertebra"; e: FMA:5859 .

#+end_src

*** plexi
#+begin_src ttl
UBERON:0002439 l: "myenteric nerve plexus"; e: FMA:63252 .
#+end_src

distributed ganglion that has not ensheathed
there may be significant variablity between individuals in whether
a plexius is fully ganglionated/ensheathed

the issue is that from the perspective the cell populations, they are
actually the same population regardless of whether they are plexi or ganglia

maybe need to get these to converge onto the neuron populations so that
we can merge the two

**** TODO consult with SAWG about these
:PROPERTIES:
:CREATED:  [2021-09-17 Fri 14:33]
:END:

*** ganglia
:PROPERTIES:
:CREATED:  [2021-09-17 Fri 14:29]
:END:
#+name: ttl-ganglia
#+begin_src ttl
@prefix l: <http://www.w3.org/2000/01/rdf-schema#label> .
@prefix e: <http://uri.interlex.org/tgbugs/uris/readable/hasExistingId> .
@prefix syn: <http://uri.neuinfo.org/nif/nifstd/readable/synonym> .
@prefix FMA: <http://purl.org/sig/ont/fma/fma> .
@prefix UBERON: <http://purl.obolibrary.org/obo/UBERON_> .

UBERON:0001808 l: "parasympathetic ganglion" .
UBERON:0035776 l: "accessory ciliary ganglion" .
UBERON:0001805 l: "autonomic ganglion"; e: FMA:5889 .
UBERON:0014463 l: "cardiac ganglion" .
UBERON:0002262 l: "celiac ganglion" .
UBERON:0035783 l: "ganglion of ciliary nerve" .
UBERON:0001701 l: "glossopharyngeal ganglion" .
UBERON:0005360 l: "inferior glossopharyngeal IX ganglion" .
UBERON:0013500 l: "glossopharyngeal-vagus IX-X ganglion complex" .
UBERON:0035769 l: "mesenteric ganglion" .
UBERON:0005479 l: "superior mesenteric ganglion" .
UBERON:0005453 l: "inferior mesenteric ganglion" .
UBERON:0001989 l: "superior cervical ganglion" .
UBERON:0001990 l: "middle cervical ganglion" .
UBERON:0002440 l: "inferior cervical ganglion" .
UBERON:0003963 l: "otic ganglion" .
UBERON:0001807 l: "paravertebral ganglion" .
UBERON:0016508 l: "pelvic ganglion" .
UBERON:0003964 l: "prevertebral ganglion" .
UBERON:0003962 l: "pterygopalatine ganglion" .
UBERON:0005407 l: "sublingual ganglion" .
UBERON:0002059 l: "submandibular ganglion" .
UBERON:0001806 l: "sympathetic ganglion" .
UBERON:0005362 l: "vagus X ganglion" .
UBERON:0005364 l: "superior vagus X ganglion" .
UBERON:0005363 l: "inferior vagus X ganglion"; syn: "nodose ganglion" .
UBERON:0000408 l: "vertebral ganglion" .
UBERON:0002441 l: "cervicothoracic ganglion"; e: FMA:6469; syn: "stellate ganglion" .
#+end_src

**** TODO add synonyms
:PROPERTIES:
:CREATED:  [2021-09-17 Fri 14:33]
:END:

*** neuron population by organ

#+name: organ-subjects
#+call: py-ttl-subjects(input=(orgstrap--with-block "ttl-organs" body)) :cache yes

#+name: ganglia-subjects
#+call: py-ttl-subjects(input=(orgstrap--with-block "ttl-ganglia" body)) :cache yes

#+name: nerve-subjects
#+call: py-ttl-subjects(input=(orgstrap--with-block "ttl-nerves" body)) :cache yes

# FIXME somehow selecting only predicates here produces numerical results only !?
#+name: ongrep
#+header: :exports code
#+begin_src sparql :noweb yes :var limit="99" :file ./reports/organ-nerve-ganglia.csv :results file
select distinct
?organ_start
?label
(count(distinct(?ele)) as ?elements)
#?elements
?model
#?ele
#?type
# ?sp
#?s
#?op_wat
#?p
#?o
where {
  values ?organ_start {
  <<organ-subjects()>>
  <<ganglia-subjects()>>
  <<nerve-subjects()>>
  }
  ?organ_start rdfs:label ?sl .
  bind(str(?sl) as ?label)
  # FIXME TODO traverse partonomy
  optional {
  values ?type { elements:Lyph elements:Group elements:Material elements:Chain }
  ?organ ( partOf: | rdfs:subClassOf )* ?organ_start .
  #?organ apinatomy:annotates ?ele .
  ?ele apinatomy:ontologyTerms ?organ .
  ?ele a ?type .
  ?model ( apinatomy:lyphs | apinatomy:chains | apinatomy:groups | apinatomy:materials ) ?ele .
  ?model a elements:Graph .
  #?ele apinatomy:inheritedOntologyTerms ?organ .
  #?o ?p ?q .
  }
}
group by ?organ_start ?label ?model
order by ?organ_start
#order by ?type ?organ ?model
#limit ?limit
#+end_src

#+begin_src elisp :results none :exports none
(ow-babel-eval "ongrep")
#+end_src

#+RESULTS: ongrep
[[file:./reports/organ-nerve-ganglia.csv]]

https://docs.google.com/spreadsheets/d/1pgprwTr4bvAaex8cNLeoRLUgt0VU5tilbWi4BCWF1JE/edit#gid=219324374
=organ-nerve-ganglia=
# TODO auto populate results
#+begin_src sparql
select distinct
?part_start
(str(?sl) as ?start_label)
where {
  ?part_start rdfs:subClassOf+/owl:someValuesFrom+ UBERON:0001255 .
  ?part_start rdfs:label ?sl .
}
#+end_src

the nested version here is massively slow
#+begin_src sparql :var limit="200"
select distinct
?part
(str(?l) as ?label)
where {

  values ?part_0 { UBERON:0001255 }

  #?part_1 rdfs:label ?l1 .
  ?part_1 rdfs:subClassOf ?bn0 .
                          ?bn0 a owl:Restriction .
                          ?bn0 owl:onProperty partOf: .
                          ?bn0 owl:someValuesFrom ?part_0 .


  optional {
  #?part_2 rdfs:label ?l2 .
  ?part_2 rdfs:subClassOf ?bn1 .
                          ?bn1 a owl:Restriction .
                          ?bn1 owl:onProperty partOf: .
                          ?bn1 owl:someValuesFrom ?part_1 .

    optional {
    #?part_3 rdfs:label ?l3 .
    ?part_3 rdfs:subClassOf ?bn2 .
                            ?bn2 a owl:Restriction .
                            ?bn2 owl:onProperty partOf: .
                            ?bn2 owl:someValuesFrom ?part_2 .
    }

  }

  #optional {
  #?part_4 rdfs:label ?l4 .
  #?part_4 rdfs:subClassOf ?bn3 .
                          #?bn3 a owl:Restriction .
                          #?bn3 owl:onProperty partOf: .
                          #?bn3 owl:someValuesFrom ?part_3 .
  #}

  ?part a owl:Class .
  ?part rdfs:label ?l .
  filter ( ?part = ?part_0 || ?part = ?part_1 || ?part = ?part_2 || ?part = ?part_3 ) #( || ?part = ?part_4 )

}
limit ?limit
#+end_src

#+begin_src sparql
select distinct
?part_1
where {
values ?part_0 { UBERON:0009958 }

?part_1 rdfs:label ?l .
?part_1 rdfs:subClassOf ?bn0 .
                        ?bn0 a owl:Restriction .
                        ?bn0 owl:onProperty partOf: .
                        ?bn0 owl:someValuesFrom ?part_0 .
}
#+end_src

#+begin_src sparql
select distinct
?sigh
(str(?l) as ?label)
#?sigx
#(str(?lx) as ?labx)
where {
?sigh rdfs:subClassOf+/owl:someValuesFrom+ UBERON:0001255 . # consider also RO:0002433 usually go together
?sigh rdfs:label ?l .
# FIXME somehow male and female urethra are still being pulled in :/
#filter ( ?sigh != UBERON:0000057 )
#filter not exists { ?sigh rdfs:subClassOf/owl:someValuesFrom UBERON:0000057 . }
?sigh rdfs:subClassOf/owl:onProperty partOf: .
#?sigx rdfs:subClassOf ?bn .
                      #?bn a owl:Restriction .
                      #?bn owl:onProperty partOf: .
                      #?bn owl:someValuesFrom ?sigh .
#?sigx rdfs:label ?lx .

filter not exists { ?sigh rdfs:subClassOf/owl:onProperty <http://purl.obolibrary.org/obo/uberon/core#channels_into> . }
filter not exists { ?sigh rdfs:subClassOf/owl:onProperty <http://purl.obolibrary.org/obo/uberon/core#channels_from> . }
filter not exists { ?sigh rdfs:subClassOf/owl:onProperty BFO:0000051 . }
filter not exists { ?sigh rdfs:subClassOf/owl:onProperty RO:0002150 . }
filter not exists { ?sigh rdfs:subClassOf/owl:onProperty RO:0002221 . }
filter not exists { ?sigh rdfs:subClassOf/owl:onProperty RO:0002178 . }
}
order by ?label

#+end_src

#+begin_src sparql
select distinct
?part
?prop
(str(?l) as ?label)
?part_start
(str(?sl) as ?start_label)
where {
  values ?wat { UBERON:0001255 }
  # FIXME this is still completely broken because sparql and
  # transitive queries over over owl restrictions are ... problematic
  ?part_start rdfs:subClassOf+/owl:someValuesFrom+ ?wat .
  filter ( ?prop = partOf: )
  ?part_start rdfs:label ?sl .
  ?part_start rdfs:subClassOf ?bny .
                              ?bny owl:onProperty ?prop .
  ?part rdfs:label ?l .
  ?part rdfs:subClassOf ?bn0 .
                        ?bn0 a owl:Restriction .
                        ?bn0 owl:onProperty partOf: .
                        ?bn0 owl:someValuesFrom ?part_start .

}
order by ?start_label
#+end_src

#+name: ongrep-table
#+call: ongrep() :results table

#+name: ongrep-push
#+begin_src python :var data=ongrep-table() :results drawer
from sparcur.sheets import Reports

class ONGRep:

    _data = data

    class options:
        preview = False
        to_sheets = True

    @Reports.makeReportSheet('organ_start', sheet_name='organ-nerve-ganglia')
    def populate(self, ext=None):
        return self._data, 'organ-nerve-ganglia coverage report'


t = ONGRep()
#t.populate()
return t._data
#+end_src

#+begin_src elisp :results none :exports none
(ow-babel-eval "ongrep-push")
#+end_src

*** neuron population by nerve
*** neuron population by ganglion
*** devel
#+begin_src jupyter-python :session pys3
1 + 2
#+end_src

#+RESULTS:
: 3

# @prefix ILX: <http://uri.interlex.org/base/ilx_> .
#+begin_src jupyter-python :session pys2
from pyontutils.core import OntTerm

asdf = ['ILX:0727875',
'ILX:0727396',
'ILX:0727235',
'ILX:0736783',
'ILX:0725331',
'ILX:0724284',
'ILX:0732483',
'ILX:0725803',
'ILX:0723837',
'ILX:0737036',
'ILX:0725086',
'ILX:0736097',
'ILX:0733375',
'ILX:0730739',
'ILX:0734588',
'ILX:0728741',
'ILX:0728157',
'ILX:0724431',
'ILX:0728322',
'ILX:0486534',
'ILX:0731053',
'ILX:0734339',
'ILX:0731969',
'ILX:0726449',
'ILX:0734582',
'ILX:0731914',
'ILX:0729870',
'ILX:0728987',
'ILX:0736712',
'ILX:0735649',
'ILX:0730231',
'ILX:0725969',
'ILX:0725552',
'ILX:0729045',
'ILX:0732611',
'ILX:0724152',
'ILX:0724140',
'ILX:0736057',
'ILX:0726733',
'ILX:0735212',
'ILX:0726145',
'ILX:0736880',
'ILX:0732740',
'ILX:0730942',
'ILX:0736020',
'ILX:0733193',
'ILX:0733995',
'ILX:0503992',
'ILX:0734328',
'ILX:0724764',
'ILX:0729794',
'ILX:0734607',
'ILX:0727602',
'ILX:0735024',
'ILX:0725520',
'ILX:0728778',
'ILX:0726312',
'ILX:0733021',
'ILX:0732970',
'ILX:0725326',
'ILX:0725956',
'ILX:0726454',
'ILX:0734766',
'ILX:0733700',
'ILX:0733004',
'ILX:0732328',
'ILX:0735011',
'ILX:0734057',
'ILX:0730509',
'ILX:0727725',
'ILX:0730966',
'ILX:0729052',
'ILX:0736358',
'ILX:0727970',
'ILX:0736369']

qq = [OntTerm(a) for a in asdf]
#+end_src

#+begin_src jupyter-python :session pys2
[(repr(_), _('ilxtr:hasIlxPreferredId')[0].curie) for _ in qq]
#+end_src

** ApiNATOMY
*** Coverage of FMA by the TOO map.
Unclaimed regions from FMA/UBERON in the TOO map

Define the TOO map as the covering set of terms.
It might not actually be, but that is how we will treat it.
Probably want to avoid the immaterial entities.

Try to come up with a basis set from FMA as well.

If there are regional hierarchies that have been missed we will see them.
*** Summary statistics
**** debug
# fuck everything about this
#+begin_src sparql
SELECT distinct ?o
WHERE {
  ?o a elements:Graph .
} order by str(?o)
#+end_src

#+begin_src sparql
select distinct
?graph
?lyphs
?label
WHERE {
  # ?model apinatomy:hasGraph ?graph  # TODO broken conversion to literal instead of uri at the moment

  # sadly it seems that no one has managed to write an optimization that converts
  # into this form when querying multiple predicates in the same expression
  {
    ?graph a elements:Graph ;
           rdfs:label ?label .
    { select ?graph (count(distinct(?o)) as ?lyphs       ) where { ?graph apinatomy:lyphs        ?o . } group by ?graph }
  }
  union
  {
    { select (count(distinct(?graph_)) as ?graph  ) where { ?graph_ a elements:Graph } }
    bind("Total unique" as ?label)
    { select (count(distinct(?o)) as ?lyphs       ) where { ?graph_ apinatomy:lyphs        ?o . } }
} }
#+end_src

**** Dashboard
:PROPERTIES:
:CUSTOM_ID: apinat-dashboard
:END:
# TODO configure header args for development vs production
#+begin_src elisp :results none :exports none
(ow-babel-eval "apinat-dashboard-model-summary")
(ow-babel-eval "apinat-dashboard-overview")
(ow-babel-eval "sparql-apinat-prefixes")
#+end_src

#+RESULTS: apinat-dashboard-model-summary
| label                                                                  | graph                   | lyphs | lyphs_input | externals | publications | nerus | neupops | neupops_pat | somas |
|------------------------------------------------------------------------+-------------------------+-------+-------------+-----------+--------------+-------+---------+-------------+-------|
| ApiNATOMY model of the stomach                                         | sstom:sawg-stomach      |   668 |          37 |        77 |            8 |    14 |      14 |          14 |    14 |
| Ardell Armour Cardiac Model                                            | aacar:ard-arm-cardiac   |  1827 |         137 |       161 |            2 |    17 |      17 |          17 |    41 |
| Bolser-Lewis Model of the Physiology of the Superior Cervical Ganglion | bolew:bolser-lewis      |  1151 |         107 |       141 |          239 |    29 |      29 |          29 |    29 |
| Keast ApiNATOMY model of bladder innervation                           | kblad:keast-bladder     |  2394 |         139 |       174 |           30 |    21 |      20 |          20 |    35 |
| SAWG ApiNATOMY model of the descending colon                           | sdcol:sawg-distal-colon |  1066 |          89 |       135 |            5 |    18 |      18 |          18 |    24 |
| SPARC Bronchomotor Flatmap                                             | bromo:bronchomotor      |   623 |         144 |       187 |            6 |     6 |       6 |           6 |    19 |
| pancreas model                                                         | pancr:pancreas          |   452 |          17 |       529 |            1 |     5 |       5 |           5 |    11 |
| ApiNATOMY model of the spleen                                          | splen:spleen            |   580 |          47 |       103 |            5 |     5 |       5 |           5 |     8 |
| Total unique                                                           | 8                       |  8761 |         717 |       823 |          296 |   115 |     114 |         114 |   181 |

#+RESULTS: apinat-dashboard-overview
| element         | count |
|-----------------+-------|
| models          |     8 |
| models scigraph |     8 |
| lyphs           |  8761 |
| lyphs input     |   717 |
| externals       |   583 |
| publications    |   296 |
| nerus           |   115 |
| neupops         |   114 |
| neupops pattern |   114 |

# TODO links to a way to view the terms per ontology if possible
#+RESULTS: sparql-apinat-prefixes
| prefix  | count |
|---------+-------|
| nlx     |     1 |
| CL      |     3 |
| GO      |     5 |
| NCIT    |     5 |
| CHEBI   |    11 |
| sao     |    11 |
| fma     |    15 |
| tgbugs  |   114 |
| ilx     |   179 |
| UBERON  |   239 |
| :SUM:   |   583 |
| :TOTAL: |   583 |

#+header: :var n-sparql-apinat-graphs=n-sparql-apinat-graphs()
#+header: :var cypher-apinat-models=cypher-apinat-models()
#+header: :var n-sparql-apinat-lyphs=n-sparql-apinat-lyphs()
#+header: :var n-sparql-apinat-lyphs-input=n-sparql-apinat-lyphs-input()
#+header: :var n-sparql-apinat-external=n-sparql-apinat-external()
#+header: :var n-sparql-apinat-publications=n-sparql-apinat-publications()
#+header: :var n-apinat-nerus=n-apinat-nerus()
#+header: :var n-apinat-neupops=n-apinat-neupops()
#+header: :var n-apinat-neupops-pat=n-apinat-neupops-pat()
#+name: apinat-dashboard-overview
#+begin_src elisp :exports results
(let ((models (caar n-sparql-apinat-graphs))
      (models-cypher (length cypher-apinat-models))
      (lyphs (caar n-sparql-apinat-lyphs))
      (lyphs-input (caar n-sparql-apinat-lyphs-input))
      (externals (caar n-sparql-apinat-external))
      (publications (caar n-sparql-apinat-publications))
      (nerus (caar n-apinat-nerus))
      (neupops (caar n-apinat-neupops))
      (neupops-pat (caar n-apinat-neupops-pat)))
  (list
   (list "element" "count")
   'hline
   (list "models" models)
   (list "models scigraph" models-cypher)
   (list "lyphs" lyphs)
   (list "lyphs input" lyphs-input)
   (list "externals" externals)
   (list "publications" publications)
   (list "nerus" nerus)
   (list "neupops" neupops)
   (list "neupops pattern" neupops-pat)
))
#+end_src

#+name: apinat-dashboard-model-summary
#+begin_src sparql :noweb no-export
select distinct
?label
?graph
#?g
?lyphs
?lyphs_input
#?links
#?nodes
#?chains
#?coalescences
?externals
?publications
#?groups
?nerus
?neupops
?neupops_pat
?somas
<<apinat-model-summary-where>>
#+end_src

# TODO add the presence of the schematic svg and pdf to the dashboard
# so we need to ingest the state of the repo during export
# TODO dois for the published models

# TODO open the folder in git
# TODO open a linke to the ttl file
# TODO open a link to the list of publications -> zenodo record

# have to union over the aggregated group by results and the total
# separately and then we can get the table to work correctly
#+name: apinat-model-summary-where
#+begin_src sparql :eval never
WHERE {
  # ?model apinatomy:hasGraph ?graph  # TODO broken conversion to literal instead of uri at the moment

  # sadly it seems that no one has managed to write an optimization that converts
  # into this form when querying multiple predicates in the same expression
  {
    ?graph a elements:Graph ;
           rdfs:label ?l .
    bind(str(?l) as ?label)  # avoid issues with graphs loaded multiple times
    { select ?graph (count(distinct(?o)) as ?lyphs       ) where { ?graph apinatomy:lyphs        ?o . } group by ?graph }
    { select ?graph (count(distinct(?o)) as ?lyphs_input ) where
      { ?graph apinatomy:lyphs ?o .
           filter not exists { ?o apinatomy:isTemplate true . }
           filter not exists { ?o apinatomy:generated  true . } }                                       group by ?graph }
    { select ?graph (count(distinct(?o)) as ?links       ) where { ?graph apinatomy:links        ?o . } group by ?graph }
    { select ?graph (count(distinct(?o)) as ?nodes       ) where { ?graph apinatomy:nodes        ?o . } group by ?graph }
    { select ?graph (count(distinct(?o)) as ?chains      ) where { ?graph apinatomy:chains       ?o . } group by ?graph }
    { select ?graph (count(distinct(?o)) as ?coalescences) where { ?graph apinatomy:coalescences ?o . } group by ?graph }
    { select ?graph (count(distinct(?o)) as ?externals   ) where { ?graph apinatomy:ontologyTerms     ?o . } group by ?graph }
    optional
    { select ?graph (count(distinct(?o)) as ?publications) where { ?graph apinatomy:references ?o . } group by ?graph }
    { select ?graph (count(distinct(?o)) as ?groups      ) where { ?graph apinatomy:groups       ?o . } group by ?graph }
    optional
    { select ?graph (count(distinct(?o)) as ?nerus       ) where
      { ?graph apinatomy:groups ?o .
                                ?o apinatomy:description ?d .
                                     filter(contains(str(?d), "dynamic")) }                             group by ?graph }
    optional
    { select ?graph (count(distinct(?o)) as ?neupops     ) where
      { ?graph apinatomy:ontologyTerms ?o .
                                  ?o rdfs:subClassOf+ ilxtr:NeuronEBM . }                               group by ?graph }
    optional
    { select ?graph (count(distinct(?o)) as ?neupops_pat ) where
      { ?graph apinatomy:ontologyTerms ?o .
              filter contains(str(?o), "/neuron-type-") }                                               group by ?graph }
    optional
    { select ?graph (count(distinct(?o)) as ?somas       ) where
      { ?graph apinatomy:lyphs ?o .
                               ?o apinatomy:ontologyTerms NLX:154731 . }                                     group by ?graph }
  }
  union
  {
    { select (count(distinct(?graph_)) as ?graph  ) where { ?graph_ a elements:Graph } }
    bind("Total unique" as ?label)
    { select (count(distinct(?o)) as ?lyphs       ) where { ?graph_ apinatomy:lyphs        ?o . } }
    { select (count(distinct(?o)) as ?lyphs_input ) where
      { ?graph_ apinatomy:lyphs ?o .
           filter not exists { ?o apinatomy:isTemplate true . }
           filter not exists { ?o apinatomy:generated  true . } } }
    { select (count(distinct(?o)) as ?links       ) where { ?graph_ apinatomy:links        ?o . } }
    { select (count(distinct(?o)) as ?nodes       ) where { ?graph_ apinatomy:nodes        ?o . } }
    { select (count(distinct(?o)) as ?chains      ) where { ?graph_ apinatomy:chains       ?o . } }
    { select (count(distinct(?o)) as ?coalescences) where { ?graph_ apinatomy:coalescences ?o . } }
    { select (count(distinct(?o)) as ?externals   ) where { ?graph_ apinatomy:ontologyTerms     ?o . } }
    { select (count(distinct(?o)) as ?publications) where { ?graph_ apinatomy:references ?o . } }
    { select (count(distinct(?o)) as ?groups      ) where { ?graph_ apinatomy:groups       ?o . } }
    { select (count(distinct(?o)) as ?nerus       ) where
      { ?graph_ apinatomy:groups ?o .
                                 ?o apinatomy:description ?d .
                                      filter(contains(str(?d), "dynamic")) } }
    optional
    { select (count(distinct(?o)) as ?neupops     ) where
      { ?graph_ apinatomy:ontologyTerms ?o .
                                   ?o rdfs:subClassOf+ ilxtr:NeuronEBM . } }
    optional
    { select (count(distinct(?o)) as ?neupops_pat ) where
      { ?graph_ apinatomy:ontologyTerms ?o .
               filter contains(str(?o), "/neuron-type-") } }
    optional
    { select (count(distinct(?o)) as ?somas       ) where
      { ?graph_ apinatomy:lyphs ?o .
                                ?o apinatomy:ontologyTerms NLX:154731 . } }
  }
}
#+end_src

**** Queries
# aka dashboard queries
***** ApiNATOMY sourced
****** models
#+name: n-sparql-apinat-graphs
#+begin_src sparql :noweb no-export
SELECT (COUNT(*) as ?count) {
  <<sparql-apinat-models>>
}
#+end_src

#+name: sparql-apinat-graphs
#+begin_src sparql
SELECT DISTINCT
(?s as ?id)
(str(?l) as ?label)
WHERE {
  ?s a elements:Graph .
  ?s rdfs:label ?l.
}
#+end_src

#+name: sparql-apinat-models
#+begin_src sparql
SELECT DISTINCT
?o (str(?l) as ?label) (str(?v) as ?version)
WHERE {
?o a owl:Ontology ;
   rdfs:label ?l ;
   apinatomy:hasGraph ?g .
?g a elements:Graph .
?g apinatomy:version ?v .
}
#+end_src

# FIXME we need a way to issue cypher queries that can return
# something other than the graph results so that we can match
# sparql unfortunately that is a larger engineering undertaking the
# requires modifying scigraph again

#+name: cypher-apinat-graphs
#+begin_src cypher
MATCH ({iri: "https://apinatomy.org/uris/elements/Graph"})<-[:type]-(g) RETURN g
#+end_src

#+name: desc-cypher-apinat-models
Return the list of all ApiNATOMY models in the database.

#+name: cypher-apinat-models
#+begin_src cypher
MATCH ({iri: "https://apinatomy.org/uris/elements/Graph"})
<-[:type]-(g)-[:isDefinedBy]->(o:Ontology)
RETURN o
#+end_src

****** number of lyphs
#+name: n-sparql-apinat-lyphs
#+begin_src sparql
SELECT (COUNT(DISTINCT ?s) as ?total_lyphs)
WHERE { ?s a elements:Lyph . }
#+end_src

#+RESULTS: n-sparql-apinat-lyphs
| total_lyphs |
|-------------|
|        8162 |

generated vs input
s? rdf:type element:Lyph
#+name: n-sparql-apinat-lyphs-input
#+begin_src sparql :var limit="200"
SELECT DISTINCT
(COUNT(DISTINCT ?s) as ?input_lyphs)
#?s
#?p
#?o
WHERE {
  ?s a elements:Lyph .
  #?s ?p ?o .
  FILTER NOT EXISTS { ?s apinatomy:isTemplate true . }
  FILTER NOT EXISTS { ?s apinatomy:generated true . }
  #FILTER ( ! ( ?p = apinatomy:isTemplate && ?o = true ) )
  #FILTER ( ! ( ?p = apinatomy:generated && ?o = true ) )
  #?s apinatomy:generated ?o .
}
LIMIT ?limit
#+end_src

#+RESULTS: n-sparql-apinat-lyphs-input
| input_lyphs |
|-------------|
|         742 |

****** publications referenced
apinatomy:references
average number of publications per unit? (for now no)
will also want the full listing of publications
select count distinct o?
select distinct o?
s? apinat:publication o?
#+name: n-sparql-apinat-publications
#+begin_src sparql
SELECT DISTINCT
(COUNT(DISTINCT ?pub) as ?publications)
WHERE { ?s apinatomy:references ?pub . }
#+end_src

#+RESULTS: n-sparql-apinat-publications
| publications |
|--------------|
|          293 |

****** TODO species
both model level and lyph level
****** terms employed in annotation
count of apinatomy:ontologyTerms by prefix UBERON, GO, CHEBI, etc.
full listing of terms as well

# sadly noweb is way faster for solving this problem than trying to do the right thing >_<
#+name: apinat-search-prefixes
: "UBERON" "EMAPA" "fma" "CHEBI" "GO" "sao" "CL" "nlx" "ilx" "tgbugs" "NCBITaxon" "NCIT" "pubmed" "doi" "ncit"

#+name: sparql-apinat-prefixes
#+begin_src sparql :noweb yes
SELECT
?prefix
(COUNT(DISTINCT ?s) AS ?count)
WHERE {
  {
    VALUES ?prefix { <<apinat-search-prefixes()>> }
    ?s a elements:OntologyTerm .
    FILTER CONTAINS(str(?s), ?prefix)
  } UNION {
    SELECT (":SUM:" AS ?prefix) ?s
    {
      VALUES ?pre { <<apinat-search-prefixes()>> }
      ?s a elements:OntologyTerm .
      FILTER CONTAINS(str(?s), ?pre)
    }
  } UNION {
    BIND(":TOTAL:" AS ?prefix)
    { SELECT ?s WHERE { ?s a elements:OntologyTerm . } }
  }
}
GROUP BY ?prefix
ORDER BY ?count
#+end_src

#+name: n-sparql-apinat-external
#+begin_src sparql
SELECT (COUNT(DISTINCT(?s)) as ?count) WHERE { ?s a elements:OntologyTerm . }
#+end_src

******* external issues
publications appearing as external ids, unmapped prefixes etc.
FIXME where are these coming from?
#+begin_src sparql :var limit="500"
SELECT DISTINCT
?l ?s
WHERE {
    ?s a elements:OntologyTerm .
    ?l ?p ?s .
    FILTER(CONTAINS(str(?s), "doi") || CONTAINS(str(?s), "pubmed") || CONTAINS(str(?s), "ncit"))
}
ORDER BY ?l ?s
LIMIT ?limit
#+end_src

****** Number of neurulated groups
Total neuron populations including those that are unidentified or are in error
# TODO by model
#+name: n-apinat-nerus
#+begin_src sparql :noweb yes
SELECT (COUNT(*) as ?count) {
  <<apinat-nerus>>
}
#+end_src

#+RESULTS: n-apinat-nerus
| count |
|-------|
|   117 |

#+name: apinat-nerus
#+begin_src sparql
SELECT DISTINCT
?group
(str(?l) as ?label)
WHERE {
  ?group a elements:Group .
  ?group apinatomy:description ?d
  filter(contains(str(?d), "dynamic"))
  ?group rdfs:label ?l .
}
order by ?group
#+end_src

****** Number of neuron populations with external ids and correct subClassOf.
#+name: n-apinat-neupops
#+begin_src sparql :noweb yes
SELECT (COUNT(*) as ?count) {
  <<apinat-neupops>>
}
#+end_src

#+RESULTS: n-apinat-neupops
| count |
|-------|
|   117 |

#+name: apinat-neupops
#+begin_src sparql
SELECT DISTINCT
?neupop
(str(?pl) as ?preferred_label)
(str(?l) as ?label)
WHERE {
  ?neupop a elements:OntologyTerm .
  ?neupop rdfs:subClassOf+ ilxtr:NeuronEBM .
  OPTIONAL { ?neupop skos:prefLabel ?pl . }
  OPTIONAL { ?neupop rdfs:label ?l . }
}
#+end_src

****** Number of neuron populations with external ids that match the =neuron-type= pattern.
#+name: n-apinat-neupops-pat
#+begin_src sparql :noweb yes
SELECT (COUNT(*) as ?count) {
  <<apinat-neupops-pat>>
}
#+end_src

#+RESULTS: n-apinat-neupops-pat
| count |
|-------|
|   117 |

#+name: apinat-neupops-pat
#+begin_src sparql
SELECT DISTINCT
?neupop
(str(?pl) as ?preferred_label)
(str(?l) as ?label)
WHERE {
  ?neupop a elements:OntologyTerm .
  FILTER CONTAINS(str(?neupop), "/neuron-type-")
  OPTIONAL { ?neupop skos:prefLabel ?pl . }
  OPTIONAL { ?neupop rdfs:label ?l . }
}
#+end_src

***** NPO sourced queries
Keast
Bolser Lewis
First pass NLP output

****** location
experimental
#+begin_src cypher :limit 99 :results drawer :wrap src json :var neupop_id="ilxtr:neuron-type-keast-2"
MATCH
(neuron:Class{iri: "http://uri.neuinfo.org/nif/nifstd/sao1417703748"})<-[:subClassOf*1..]-
(neupop)
//(neupop:Class{iri: $neupop_id})
-[e:ilxtr:hasLocationPhenotype!]->(blank)
-[q]->(more)
//-[q:BFO:0000050]->(more)
RETURN e, q
#+end_src

****** phenotypes for neuron
#+name: npo-basic-phenotypes
#+begin_src cypher :limit 99 :results drawer :wrap src json :var neupop_id="ilxtr:neuron-type-keast-2"
MATCH (neupop:Class{iri: $neupop_id})-[e:ilxtr:hasPhenotype!]->(phenotype) RETURN e
#+end_src

#+begin_src jupyter-python :session pys :noweb yes
from pprint import pprint
from pyontutils.scigraph import Cypher, Vocabulary

sgc = Cypher()
query = """
<<npo-basic-phenotypes>>
"""


def get_phenotypes(neupop_id):
    d = sgc.execute(query=query, limit=99999, output='application/json', neupop_id=neupop_id)
    good = [e for e in d['edges'] if 'meta' in e and e['meta']['owlType'] == ['subClassOf']
            and not e['obj'].startswith('_:')]
    return good


pprint(get_phenotypes('ilxtr:neuron-type-keast-1'))
#+end_src

****** number of neuron populations aka neuron types
# TODO load NPO, and we have to use robot to reason the NPO graph
#+begin_src sparql
SELECT DISTINCT
?s
WHERE {
    ?s rdfs:subClassOf+ SAO:1813327414 . # NOTE this counts cells
}
#+end_src
****** basic connectivity
******* python
#+begin_src jupyter-python :session pys
from collections import defaultdict
from pprint import pprint
from neurondm.compiled.common_usage_types import *
neurons = config.neurons()
#pprint(neurons)
#pprint(dir(neurons[0]))
# pprint(neurons[0].edges)
#pprint([n for n in neurons if ilxtr.hasSomaLocatedIn in n.edges][0])
#pprint([n for n in neurons if ilxtr.hasAxonLocatedIn in n.edges][0])
#pprint([n for n in neurons if ilxtr.hasAxonPresynapticElementIn in n.edges][0])
#pprint([n for n in neurons if ilxtr.hasDendriteLocatedIn in n.edges][0])

x = defaultdict(list)
for n in neurons:
    for somar in [pe.p for pe in n.pes if pe.e == ilxtr.hasSomaLocatedIn]:
        for axonr in [pe.p for pe in n.pes if pe.e in (ilxtr.hasAxonPresynapticElementIn, ilxtr.hasProjectionPhenotype)]:
            # via may be incorrect here if an axon location applies only to one collateral for a terminal
            # this is why we need the partial order
            x[somar].append(axonr)

pprint(dict(x))

# via axon
pprint([n for n in neurons if ilxtr.hasAxonLocatedIn in n.edges][:1])

# axon terminal in apinatomy
# region A is axonally forward connect to B forward here means soma -> axon terminal
pprint([n for n in neurons if ilxtr.hasProjectionPhenotype in n.edges][:1])
pprint([n for n in neurons if ilxtr.hasAxonPresynapticElementIn in n.edges][:1])

# soma
pprint([n for n in neurons if ilxtr.hasSomaLocatedIn in n.edges][:1])
pprint([n for n in neurons if ilxtr.hasSomaLocatedInLayer in n.edges][:1])

# via dendrite
pprint([n for n in neurons if ilxtr.hasDendriteLocatedIn in n.edges][:1])

# dendrite terminal for sensory neurons in apinatomy
# region A is dendritically forward connect to B forward here means dendrite terminal -> soma
pprint([n for n in neurons if ilxtr.hasSensorySubcellularElementIn in n.edges][:1])

pprint([n for n in neurons if ilxtr.hasForwardConnectionPhenotype in n.edges][:1])
pprint([n for n in neurons if ilxtr.hasReverseConnectionPhenotype in n.edges][:1])

#pprint([n for n in neurons if EntailedPhenotype('UBERON:0002790', 'ilxtr:hasAxonLocatedIn') in n.pes ])
#pprint([n for n in neurons if ilxtr.hasSomaLocatedIn in n.edges])
#pprint([n for n in neurons if Phenotype('UBERON:0002610', 'ilxtr:hasSomaLocatedIn') in n.pes ])
#+end_src

******* cypher
#+begin_src ttl
:x a owl:Class ;
owl:equivalentClass [ a owl:Restriction ;
owl:intersectionOf (
[ a owl:Restriction  ;
owl:onProperty ilxtr:hasSomaLocatedIn ;
owl:someValuesFrom [ a owl:Restriction ;
               owl:onProperty :partOf ;
               owl:someValuesFrom ?my-region ;

] ;
] ;
)
]
#+end_src
#+begin_src cypher :limit 10 :results drawer
MATCH (neuron)-[e]->
// (blank)-[:ilxtr:hasPhenotype]->(phenotype)
(blanka)-[:ilxtr:hasAxonLocatedIn]->(blankb)-[edge]->(region_or_blank)
//(blanka)-[:ilxtr:hasSomaLocatedIn]->(blankb)-[:BFO:0000050]->(region_or_blank)
// or_blank happens if it is a union of regions i.e. a population with somas in more than one region

//,(region_or_blank: Class{iri: "UBERON:0001950"})
//RETURN region_or_blank
//RETURN neuron
RETURN edge
#+end_src

#+begin_src sparql
select distinct ?p ?o
where {
# ilxtr:neuron-type-keast-1 ?p ?o
ilxtr:neuron-type-keast-1 owl:equivalentClass ?b1 .
?b1 owl:intersectionOf ?b2 .
?b2 ?unkp ?b3 .
# lists
?b3 owl:onProperty ?p .
?b3 owl:someValuesFrom ?o .
}
#+end_src

#+begin_src sparql
select distinct ?neuron ?prop ?value ?prop2 ?value2 # ?p ?ox # ?p2 ?o2 # ?p3 # ?o3 ?p4 ?o4 ?p5 ?o5 ?p6 ?o6
where {
#ilxtr:neuron-type-keast-1 owl:equivalentClass ?o .
?neuron a owl:Class .
?neuron ilxtr:simpleLocalLabel ?sll .  # XXX hack to filter for neurons
?neuron owl:equivalentClass ?o .
# ?o ?p2 ?o2 .
?o owl:intersectionOf ?list .
?list rdf:rest*/rdf:first ?elem . # FIXME at least one elem subClassOf neuron or cell
?elem a owl:Restriction .
?elem owl:onProperty ?prop .
?elem owl:someValuesFrom ?value .
OPTIONAL {
?value a owl:Restriction .
?value owl:onProperty ?prop2 .
?value owl:someValuesFrom ?value2 .
# ?value ?p ?ox .
#OPTIONAL {
#?ox ?p2 ?o2 .
#?o2 a owl:Class .
#}
}

# then do optional for location vs other types

#?o3 ?p4 ?o4 .
#?o4 ?p5 ?o5 .  # first on property hits here
#?o5 ?p6 ?o6 .
# TODO list optional ???
}
limit 300
#+end_src

#+begin_src cypher :results drawer
MATCH path = (neuron)
//MATCH path = (neuron:Class{iri: "ilxtr:neuron-type-keast-1"})
-[:equivalentClass|subClassOf]->(hrm)
RETURN path
#+end_src

****** number of distinct phenotypes
****** number of dimensions
****** number of phenotype values per phenotype dimension
******* number of anatomical regions
******* number of species
******* number of x
* Extras :noexport:
** neru
#+begin_src sparql
select * where { ?s rdfs:subClassOf ilxtr:NeuronNerves }
#+end_src
*** neru-reduced for monqiue

#+header: :limit 999999 :file ./images/neru-fm.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-5"
#+name: neru-fm
#+begin_src cypher
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
,
(neugrp)
-[:apinatomy:links]->(link_start)
-[b:apinatomy:endsIn*0..1]->(lyph_or_layer)
//-[:apinatomy:topology*0..1]->(top)
,
(neugrp)
-[:apinatomy:links]->(link)
//<-[c:apinatomy:bundles|apinatomy:endBundles]-
-[c:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->
(lyph_or_layer) // real lyphs convey things, layers do not
-[d:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph
,
(lyph_or_layer) // FIXME WHERE ARE MY DENDRITES MOTHER FUCKER
-[z:apinatomy:cloneOf*0..1]->()
-[y:apinatomy:external|apinatomy:inheritedOntologyTerms*0..1]->(layer)
,
(lyph)
-[e:apinatomy:external]->(region)
,
(link) // use apinatomy:next to extract ordering information
-[f:apinatomy:next*0..]->()
//-[f:apinatomy:next|apinatomy:nextChainStartLevels*0..]->()
// FIXME these should be collapsing into a single relationship
-[g:apinatomy:target*0..1]->()
-[h:apinatomy:rootOf*0..1]->()
-[i:apinatomy:levels*0..1]->()
<-[:apinatomy:links]-(neugrp)
,
p2 = (link)
-[cle:apinatomy:conveyingLyph]->(cl)
-[:apinatomy:topology]->()
,
(cl)
-[x:apinatomy:inheritedOntologyTerms*0..1]->()

//WHERE top.iri IN ["https://apinatomy.org/uris/readable/BAG", "https://apinatomy.org/uris/readable/BAG2"]

// publications
WITH neugrp, a, b, c, d, e, f, g, h, i, x, y, z // p2 //, b 
OPTIONAL MATCH path = (neugrp)
-[:apinatomy:publications]->(pub)
-[:type]->(:Class{iri: "https://apinatomy.org/uris/elements/Publication"}) // cannot be curied, dynamic endpoints will not expand it

RETURN a, b, c, d, e, f, g, h, i, path, x, y, z //, p2 

UNION

// this part usually only returns the soma housing lyph
MATCH (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
-[b:apinatomy:lyphs]->(lyph)
//<-[c:apinatomy:internalLyphs]-()
-[c:apinatomy:internalIn]->()
-[d:apinatomy:external*0..1]->(region)
// this variant shows the dead end lyphs that correspond to the fasciculatesIn links above
//-[c:apinatomy:internalIn*0..1]->()
//-[d:apinatomy:external*0..1]->(region)
RETURN a, b, c, d, null AS e, null AS f, null AS g, null AS h, null AS i, null AS path, null as x, null as y, null as z//, null as p2
#+end_src

*** neru-explore
# #+header: :results table :limit 1
#+header: :limit 999999 :file ./images/neru-4.svg :results file :exports both
#+header: :var neupop_id="ilxtr:neuron-type-keast-5"
#+name: cypher-neru-exp
#+begin_src cypher
MATCH p1 = (neupop:Class{iri: $neupop_id})
-[a:apinatomy:annotates]->(neugrp:NamedIndividual{`https://apinatomy.org/uris/readable/description`: "dynamic"}) // FIXME HACK
,
p2 = (neugrp)
-[:apinatomy:links]->(link)
-[:apinatomy:fasciculatesIn|apinatomy:endsIn*0..1]->(lyph_or_layer) // real lyphs convey things, layers do not
-[:apinatomy:layerIn*0..1]->(lyph)
-[:apinatomy:conveys*0..1]->() // make sure we are at a real lyph
,
p3 = (lyph)
-[e:apinatomy:external]->(region)
,
p4 = (link) // use apinatomy:next to extract ordering information
-[:apinatomy:next*0..]->()
//-[f:apinatomy:next|apinatomy:nextChainStartLevels*0..]->()
// FIXME these should be collapsing into a single relationship
-[:apinatomy:target*0..1]->()
-[:apinatomy:rootOf*0..1]->()
-[:apinatomy:levels*0..1]->()
<-[:apinatomy:links]-(neugrp)
,
p5 = (link)
-[:apinatomy:conveyingLyph]->(cl)
-[:apinatomy:topology]->()
,
p6 = (cl)
-[:apinatomy:inheritedOntologyTerms*0..1]->()
//,
//p7 = (layer)
//-[:apinatomy:layerIn:*0..1]->(lyph) // FIXME this is wrong it will bull in unrelated layers
//,
//p8 = (link)
// TODO need a layer for th elinks too I think

// XXX it turns out that neru-3 already has all the information we need?????

RETURN p1, p2, p3, p4, p5, p6
#+end_src

* Bootstrap :noexport:
:PROPERTIES:
:visibility:  folded
:END:

# TODO consider automatically displaying the edit src buffer
# in another window when the cursor is present and switching ?????
# TODO need to extract my rdf related stuff out of init.el so that the uris compact
# TODO use the context menus branch to make right click context sensitive in buffer

#+name: orgstrap
#+begin_src elisp :results none :exports none :lexical yes
(defvar ow-do-packages-emacs t "Install Emacs packages.")
(defvar ow-do-config-emacs t "Run code that modifies the Emacs configuration.")

(unless (symbol-function 'org-assert-version) ;; sigh
  (defmacro org-assert-version () 't))

(when ow-do-config-emacs
  (setq inhibit-startup-screen t))

;; FIXME somehow need to mkdir images, unfortunately mkdirp doesn't work for files
;; only for tangling ... maybe we should add it so that it works for :file results as well?

;; somehow this gets around lexical scope FIXME no not really have to
;; modify the elvs
(setq-local
 org-confirm-babel-evaluate
 (lambda (lang body)
   ;;(message "%S" body)
   (not
    (or
     (member lang '("cypher" "sparql"))
     (and
      (string= lang "python") ; XXX THIS IS TO MAKE AUDITING EASIER YOU SHOULD AUDIT ALL safe-block declarations like this
      (string-prefix-p "# py-safe-block" body))
     (and
      (string= lang "elisp")
      (string= body "(string-trim (replace-regexp-in-string \"\\n\" \"\" paragraph))"))))))

(setq-local org-confirm-babel-evaluate-cell nil)

;; TODO
;;(defun orgstrap---advise-sparql-execute-query (&rest args)
;;  (compactify-current-buffer default-curies))

;;(advice-add #'sparql-execute-query :after #'orgstrap---advise-sparql-execute-query)

(unless (featurep 'reval)
  (defvar reval-cache-directory (concat user-emacs-directory "reval/cache/"))
  (defun reval-minimal (cypher checksum path-or-url &rest alternates)
    "Simplified and compact implementation of reval."
    (let* (done (o url-handler-mode) (csn (symbol-name checksum))
           (cache-path (concat reval-cache-directory (substring csn 0 2) "/" csn
                               "-" (file-name-nondirectory path-or-url))))
      (url-handler-mode)
      (unwind-protect
          (cl-loop for path-or-url in (cons cache-path (cons path-or-url alternates))
                   do (when (file-exists-p path-or-url)
                        (let* ((buffer (find-file-noselect path-or-url))
                               (buffer-checksum (intern (secure-hash cypher buffer))))
                          (if (eq buffer-checksum checksum)
                              (progn
                                (unless (string= path-or-url cache-path)
                                  (let ((parent-path (file-name-directory cache-path))
                                        make-backup-files)
                                    (unless (file-directory-p parent-path)
                                      (make-directory parent-path t))
                                    (with-current-buffer buffer
                                      (write-file cache-path))))
                                (eval-buffer buffer)
                                (setq done t))
                            (kill-buffer buffer) ; kill so cannot accidentally evaled
                            (error "reval: checksum mismatch! %s" path-or-url))))
                   until done)
        (unless o
          (url-handler-mode 0)))))
  (defalias 'reval #'reval-minimal)
  (reval 'sha256 '3620321396c967395913ff19ce507555acb92335b0545e4bd05ec0e673a0b33b
         "https://raw.githubusercontent.com/tgbugs/orgstrap/300b1d5518af53d76d950097bcbcd7046cfa2285/reval.el"))

(let ((ghost "https://raw.githubusercontent.com/tgbugs/orgstrap/"))
  (unless (featurep 'ow)
    (reval 'sha256 'b3b26172d8e54abd0d3c4503d1b5438e0fc29d05fb6275a6da17d4d1b628a38a
           ;;"~/git/orgstrap/ow.el"
           (concat ghost "f4df67e94926f9d389f4a456a9cbf721c9b22b89" "/ow.el")))
  (unless (featurep 'blazegraph)
    (reval 'sha256 '8f54a319227ee6d9ca457ad77be955ba6b1a98a36564dbda61fa6892192afd51
           ;;"~/git/orgstrap/services/blazegraph.el"
           (concat ghost "abb93f698d3cfeeea607856bec98ae1d6cfe39c9" "/services/blazegraph.el"))))

(when ow-do-packages-emacs
  (ow-enable-use-package)
  (let ((gt26 (>= emacs-major-version 26)))
    (ow-use-packages
     csv-mode
     rainbow-delimiters
     orgstrap
     (when gt26 jupyter)
     (cypher-mode
      :init
      (defface cypher-pattern-face
        '((t :foreground "DeepPink" :background unspecified :bold t))
        "Face for pattern struct." :group 'cypher-faces))
     sparql-mode
     ob-cypher
     (org
      :mode ("\\.org\\'" . org-mode)
      :bind ("<f5>" . ow-babel-eval-closest-block)
      :init
      (org-babel-do-load-languages
       'org-babel-load-languages
       `((cypher . t)
         (sparql . t)
         (python . t)
         ,(when gt26 '(jupyter . t))))))))

(unless (fboundp #'ob-cypher/scigraph/query)
  (reval 'sha256 '30fd7742d7b00bd963310b4af9e505f07f981ff8b26f9b6315baa14574348e88 "https://raw.githubusercontent.com/tgbugs/ob-cypher/7778fa42ae3a49b9370939b14a3e9cefbd884a51/ob-cypher.el"))

(unless (fboundp #'org-babel-sparql--table-replace-curies)
  (reval 'sha256 '62bdbaf3d54f16b5019715f1dd175ecda8b37de420d14bcb8a469ef6820c5386
         "https://raw.githubusercontent.com/tgbugs/sparql-mode/62ee958fe5abb1212a351025abce522e53ef1fae/ob-sparql.el"))

(defun json->svg-file (json-string path) ; FIXME `defun-local' may not work with `ow-babel-eval'
  (let* ((tmp (org-babel-temp-file "cypher-dot-"))
         (dot (ob-cypher/scigraph/json-to-dot json-string))
         (cmd (if (string= (file-name-extension path) "graphml")
                  (format "dot2graphml %s %s" tmp path)
                (format
                 (concat
                  "dot "
                  "-Efontname='Dejavu Sans Mono' "
                  "-Nfontname='Dejavu Sans Mono' "
                  "-Grankdir=LR "
                  "-T%s -o %s %s")
                 (file-name-extension path) path tmp))))
    (with-temp-file tmp
      (insert dot))
    (org-babel-eval cmd "")
    path))

(defun literal (string) ; can't use `defun-local' due to loading via `find-file-noselect'
  "Utility function for sparql :vars that need to be escaped."
  (prin1-to-string string))

;; see orgware.org for a longer note on noweb and empty lines
(defun ow---strip-empty-lines ()
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "^ +$" nil t)
      (replace-match ""))))
(add-hook 'org-babel-tangle-body-hook #'ow---strip-empty-lines)

;; support for passing scigraph api keys via envars/secrets file
;; envars are here for convenience but should never be used in
;; practice mounting the secrets file is way more secure
(unless (fboundp 'get-scicrunch-api-key)
  (defun get-scicrunch-api-key ()
    (or (getenv "SCIGRAPH_API_KEY")
        (getenv "SCICRUNCH_API_KEY")
        (oa-path :scicrunch :api :key))))

;; temporary backport of fixes for ol-man behavior until a new version of org-mode is released
(require 'ol-man)
(defun org-man-open (path _)
  "Visit the manpage on PATH.
PATH should be a topic that can be thrown at the man command.
If PATH contains extra ::STRING which will use `occur' to search
matched strings in man buffer."
  (string-match "\\(.*?\\)\\(?:::\\(.*\\)\\)?$" path)
  (let* ((command (match-string 1 path))
         (search (match-string 2 path))
         (buffer (funcall org-man-command command)))
    (when search
      (with-current-buffer buffer
        (goto-char (point-min))
        (unless (search-forward search nil t)
          (let ((process (get-buffer-process buffer)))
            (while (process-live-p process)
              (accept-process-output process)))
          (goto-char (point-min))
          (search-forward search))
        (forward-line -1)
        (let ((point (point)))
          (let ((window (get-buffer-window buffer)))
            (set-window-point window point)
            (set-window-start window point)))))))

;; command line interface
(when noninteractive
  (ow-cli-gen
      ((:tangle)
       (:visit) ; FIXME better name
       (:report-apinat)
       (:report-organs))
    (when tangle (org-babel-tangle))
    (when report-apinat)
    (when report-organs)))

;; note that we had to remove the call to `org-set-startup-visibility'
;; from the elvs in order to get this to work
(when enable-local-eval ; FIXME HACK how to know when this was called
  ;; by the elvs? we don't want this running on a manual trigger
  (ow-hide-section-0-blocks)
  (when ow-do-config-emacs
    (ow--rainy-day)
    (add-hook 'emacs-lisp-mode-hook #'rainbow-delimiters-mode)
    (ow-enable-config-familiar-1 'global)))

;; if we make it all the way through the block make sure that we don't
;; run the orgstrap elvs again so that that ocbe doesn't get klobbered
(setq-local enable-local-eval nil)
#+end_src

Tangle helper for bare paragraphs.
#+name: nonl
#+begin_src elisp :exports none :results value :var paragraph=""
(string-trim (replace-regexp-in-string "\n" "" paragraph))
#+end_src

** Local Variables :ARCHIVE:
# close powershell comment #>
# Local Variables:
# org-adapt-indentation: nil
# org-edit-src-content-indentation: 0
# org-hide-emphasis-markers: t
# eval: (progn (setq-local orgstrap-min-org-version "8.2.10") (let ((a (org-version)) (n orgstrap-min-org-version)) (or (fboundp #'orgstrap--confirm-eval) (not n) (string< n a) (string= n a) (error "Your Org is too old! %s < %s" a n))) (defun orgstrap-norm-func--dprp-1-0 (body) (let ((p (read (concat "(progn\n" body "\n)"))) (m '(defun defun-local defmacro defvar defvar-local defconst defcustom)) print-quoted print-length print-level) (cl-labels ((f (b) (cl-loop for e in b when (listp e) do (or (and (memq (car e) m) (let ((n (nthcdr 4 e))) (and (stringp (nth 3 e)) (or (cl-subseq m 3) n) (f n) (or (setcdr (cddr e) n) t)))) (f e))) p)) (prin1-to-string (f p))))) (unless (boundp 'orgstrap-norm-func) (defvar-local orgstrap-norm-func orgstrap-norm-func-name)) (defun orgstrap-norm-embd (body) (funcall orgstrap-norm-func body)) (unless (fboundp #'orgstrap-norm) (defalias 'orgstrap-norm #'orgstrap-norm-embd)) (defun orgstrap-org-src-coderef-regexp (_fmt &optional label) (let ((fmt org-coderef-label-format)) (format "\\([:blank:]*\\(%s\\)[:blank:]*\\)$" (replace-regexp-in-string "%s" (if label (regexp-quote label) "\\([-a-zA-Z0-9_][-a-zA-Z0-9_ ]*\\)") (regexp-quote fmt) nil t)))) (unless (fboundp #'org-src-coderef-regexp) (defalias 'org-src-coderef-regexp #'orgstrap-org-src-coderef-regexp)) (defun orgstrap--expand-body (info) (let ((coderef (nth 6 info)) (expand (if (org-babel-noweb-p (nth 2 info) :eval) (org-babel-expand-noweb-references info) (nth 1 info)))) (if (not coderef) expand (replace-regexp-in-string (org-src-coderef-regexp coderef) "" expand nil nil 1)))) (defun orgstrap--confirm-eval-portable (lang _body) (not (and (member lang '("elisp" "emacs-lisp")) (let* ((body (orgstrap--expand-body (org-babel-get-src-block-info))) (body-normalized (orgstrap-norm body)) (content-checksum (intern (secure-hash orgstrap-cypher body-normalized)))) (eq orgstrap-block-checksum content-checksum))))) (unless (fboundp #'orgstrap--confirm-eval) (defalias 'orgstrap--confirm-eval #'orgstrap--confirm-eval-portable)) (let (enable-local-eval) (vc-find-file-hook)) (let ((ocbe org-confirm-babel-evaluate) (obs (org-babel-find-named-block "orgstrap"))) (if obs (unwind-protect (save-excursion (setq-local orgstrap-norm-func orgstrap-norm-func-name) (setq-local org-confirm-babel-evaluate #'orgstrap--confirm-eval) (goto-char obs) (org-babel-execute-src-block)) (when (eq org-confirm-babel-evaluate #'orgstrap--confirm-eval) (setq-local org-confirm-babel-evaluate ocbe)) (org-set-visibility-according-to-property)) (warn "No orgstrap block."))))
# End:
